From 28bc7b380c67fdf24c5d2e50150a09df3481d188 Mon Sep 17 00:00:00 2001 From: beynar Date: Tue, 30 Sep 2025 17:09:18 +0200 Subject: [PATCH 1/2] extract core and create svelte + vanilla package --- core/.gitignore | 34 + core/README.md | 2 + {package/src => core}/lib/constants.ts | 0 core/lib/index.ts | 11 + core/lib/presence.ts | 81 + core/lib/provider.ts | 26 + core/lib/proxys/array.ts | 467 ++++ core/lib/proxys/base.ts | 58 + core/lib/proxys/boolean.ts | 46 + core/lib/proxys/common.ts | 10 + core/lib/proxys/date.ts | 128 + core/lib/proxys/discriminatedUnion.ts | 224 ++ core/lib/proxys/enum.ts | 48 + core/lib/proxys/map.ts | 286 +++ core/lib/proxys/number.ts | 36 + core/lib/proxys/object.ts | 328 +++ core/lib/proxys/set.ts | 274 +++ core/lib/proxys/syncroState.ts | 320 +++ core/lib/proxys/text.ts | 38 + {package/src => core}/lib/schemas/array.ts | 0 {package/src => core}/lib/schemas/base.ts | 0 {package/src => core}/lib/schemas/boolean.ts | 0 core/lib/schemas/date.ts | 96 + .../lib/schemas/discriminatedUnion.ts | 0 {package/src => core}/lib/schemas/enum.ts | 0 {package/src => core}/lib/schemas/literal.ts | 0 {package/src => core}/lib/schemas/map.ts | 0 {package/src => core}/lib/schemas/number.ts | 0 {package/src => core}/lib/schemas/object.ts | 0 {package/src => core}/lib/schemas/richtext.ts | 0 core/lib/schemas/schema.ts | 169 ++ {package/src => core}/lib/schemas/set.ts | 0 {package/src => core}/lib/schemas/string.ts | 0 {package/src => core}/lib/types.ts | 0 core/lib/utils.ts | 224 ++ core/package.json | 36 + core/tests/fixture.ts | 73 + .../discriminatedUnion.integration.test.ts | 199 ++ core/tests/proxys/array.bounds.test.ts | 193 ++ core/tests/proxys/array.proxy.test.ts | 389 +++ .../proxys/array.splice.diagnostic.test.ts | 220 ++ core/tests/proxys/boolean.proxy.test.ts | 346 +++ core/tests/proxys/date.proxy.test.ts | 500 ++++ .../proxys/discriminatedUnion.proxy.test.ts | 359 +++ core/tests/proxys/enum.proxy.test.ts | 346 +++ core/tests/proxys/map.proxy.test.ts | 325 +++ core/tests/proxys/number.proxy.test.ts | 346 +++ core/tests/proxys/object.proxy.test.ts | 462 ++++ core/tests/proxys/set.proxy.test.ts | 250 ++ core/tests/proxys/string.proxy.test.ts | 339 +++ .../src => core}/tests/schemas/array.test.ts | 0 core/tests/schemas/boolean.test.ts | 88 + core/tests/schemas/date.test.ts | 107 + .../tests/schemas/discriminatedUnion.test.ts | 0 core/tests/schemas/enum.test.ts | 96 + .../tests/schemas/literal.test.ts | 0 .../src => core}/tests/schemas/map.test.ts | 0 core/tests/schemas/number.test.ts | 88 + .../src => core}/tests/schemas/object.test.ts | 0 .../src => core}/tests/schemas/set.test.ts | 0 core/tests/schemas/string.test.ts | 88 + core/tests/syncroState.test.ts | 292 +++ core/tsconfig.json | 28 + core/tsdown.config.ts | 11 + core/vite.config.ts | 19 + demo/package.json | 2 +- package/postcss.config.js | 6 - package/src/app.css | 3 - package/src/lib/index.ts | 3 - package/src/lib/presence.svelte.ts | 60 - package/src/lib/proxys/array.svelte.ts | 437 ---- package/src/lib/proxys/base.svelte.ts | 55 - package/src/lib/proxys/boolean.svelte.ts | 46 - package/src/lib/proxys/common.ts | 6 - package/src/lib/proxys/date.svelte.ts | 94 - .../lib/proxys/discriminatedUnion.svelte.ts | 206 -- package/src/lib/proxys/enum.svelte.ts | 46 - package/src/lib/proxys/map.svelte.ts | 272 --- package/src/lib/proxys/number.svelte.ts | 36 - package/src/lib/proxys/object.svelte.ts | 315 --- package/src/lib/proxys/richText.svelte.ts | 142 -- package/src/lib/proxys/set.svelte.ts | 248 -- package/src/lib/proxys/syncroState.svelte.ts | 307 --- package/src/lib/proxys/text.svelte.ts | 38 - package/src/lib/schemas/date.ts | 96 - package/src/lib/schemas/schema.ts | 121 - package/src/lib/utils.ts | 201 -- .../discriminatedUnion.integration.test.ts | 186 -- package/src/tests/proxys/array.bounds.test.ts | 177 -- package/src/tests/proxys/array.proxy.test.ts | 364 --- .../proxys/array.splice.diagnostic.test.ts | 216 -- .../src/tests/proxys/boolean.proxy.test.ts | 341 --- package/src/tests/proxys/date.proxy.test.ts | 431 ---- .../proxys/discriminatedUnion.proxy.test.ts | 355 --- package/src/tests/proxys/enum.proxy.test.ts | 341 --- package/src/tests/proxys/map.proxy.test.ts | 307 --- package/src/tests/proxys/number.proxy.test.ts | 341 --- package/src/tests/proxys/object.proxy.test.ts | 392 --- package/src/tests/proxys/set.proxy.test.ts | 242 -- package/src/tests/proxys/string.proxy.test.ts | 332 --- package/src/tests/schemas/boolean.test.ts | 88 - package/src/tests/schemas/date.test.ts | 104 - package/src/tests/schemas/enum.test.ts | 96 - package/src/tests/schemas/number.test.ts | 85 - package/src/tests/schemas/string.test.ts | 88 - package/src/tests/syncroState.test.ts | 278 --- package/tailwind.config.ts | 14 - pnpm-lock.yaml | 2162 +++++++++++------ pnpm-workspace.yaml | 4 +- {package => svelte}/.gitignore | 0 {package => svelte}/.npmrc | 0 {package => svelte}/.prettierignore | 0 {package => svelte}/.prettierrc | 0 {package => svelte}/debug-variant.test.ts | 0 {package => svelte}/eslint.config.js | 0 {package => svelte}/package.json | 49 +- {package => svelte}/pnpm-lock.yaml | 0 svelte/publish/fixVersionAfterPublication.js | 5 + svelte/publish/fixVersionBeforePublication.js | 5 + svelte/src/app.css | 2 + {package => svelte}/src/app.d.ts | 0 {package => svelte}/src/app.html | 0 svelte/src/lib/index.ts | 22 + svelte/src/lib/provider.svelte.ts | 48 + {package => svelte}/src/routes/+layout.svelte | 0 {package => svelte}/src/routes/+page.svelte | 27 +- .../src/routes/proxy/+page.svelte | 0 .../src/routes/proxy/Test.svelte | 0 .../src/routes/proxy/proxy.svelte.ts | 0 {package => svelte}/static/favicon.png | Bin {package => svelte}/svelte.config.js | 0 {package => svelte}/tsconfig.json | 0 {package => svelte}/vite.config.ts | 1 + vanilla/.gitignore | 34 + vanilla/README.md | 15 + vanilla/lib/index.ts | 18 + vanilla/lib/provider.ts | 56 + vanilla/package.json | 38 + vanilla/publish/fixVersionAfterPublication.js | 5 + .../publish/fixVersionBeforePublication.js | 5 + vanilla/tsconfig.json | 28 + vanilla/tsdown.config.ts | 11 + 142 files changed, 9892 insertions(+), 8267 deletions(-) create mode 100644 core/.gitignore create mode 100644 core/README.md rename {package/src => core}/lib/constants.ts (100%) create mode 100644 core/lib/index.ts create mode 100644 core/lib/presence.ts create mode 100644 core/lib/provider.ts create mode 100644 core/lib/proxys/array.ts create mode 100644 core/lib/proxys/base.ts create mode 100644 core/lib/proxys/boolean.ts create mode 100644 core/lib/proxys/common.ts create mode 100644 core/lib/proxys/date.ts create mode 100644 core/lib/proxys/discriminatedUnion.ts create mode 100644 core/lib/proxys/enum.ts create mode 100644 core/lib/proxys/map.ts create mode 100644 core/lib/proxys/number.ts create mode 100644 core/lib/proxys/object.ts create mode 100644 core/lib/proxys/set.ts create mode 100644 core/lib/proxys/syncroState.ts create mode 100644 core/lib/proxys/text.ts rename {package/src => core}/lib/schemas/array.ts (100%) rename {package/src => core}/lib/schemas/base.ts (100%) rename {package/src => core}/lib/schemas/boolean.ts (100%) create mode 100644 core/lib/schemas/date.ts rename {package/src => core}/lib/schemas/discriminatedUnion.ts (100%) rename {package/src => core}/lib/schemas/enum.ts (100%) rename {package/src => core}/lib/schemas/literal.ts (100%) rename {package/src => core}/lib/schemas/map.ts (100%) rename {package/src => core}/lib/schemas/number.ts (100%) rename {package/src => core}/lib/schemas/object.ts (100%) rename {package/src => core}/lib/schemas/richtext.ts (100%) create mode 100644 core/lib/schemas/schema.ts rename {package/src => core}/lib/schemas/set.ts (100%) rename {package/src => core}/lib/schemas/string.ts (100%) rename {package/src => core}/lib/types.ts (100%) create mode 100644 core/lib/utils.ts create mode 100644 core/package.json create mode 100644 core/tests/fixture.ts create mode 100644 core/tests/integration/discriminatedUnion.integration.test.ts create mode 100644 core/tests/proxys/array.bounds.test.ts create mode 100644 core/tests/proxys/array.proxy.test.ts create mode 100644 core/tests/proxys/array.splice.diagnostic.test.ts create mode 100644 core/tests/proxys/boolean.proxy.test.ts create mode 100644 core/tests/proxys/date.proxy.test.ts create mode 100644 core/tests/proxys/discriminatedUnion.proxy.test.ts create mode 100644 core/tests/proxys/enum.proxy.test.ts create mode 100644 core/tests/proxys/map.proxy.test.ts create mode 100644 core/tests/proxys/number.proxy.test.ts create mode 100644 core/tests/proxys/object.proxy.test.ts create mode 100644 core/tests/proxys/set.proxy.test.ts create mode 100644 core/tests/proxys/string.proxy.test.ts rename {package/src => core}/tests/schemas/array.test.ts (100%) create mode 100644 core/tests/schemas/boolean.test.ts create mode 100644 core/tests/schemas/date.test.ts rename {package/src => core}/tests/schemas/discriminatedUnion.test.ts (100%) create mode 100644 core/tests/schemas/enum.test.ts rename {package/src => core}/tests/schemas/literal.test.ts (100%) rename {package/src => core}/tests/schemas/map.test.ts (100%) create mode 100644 core/tests/schemas/number.test.ts rename {package/src => core}/tests/schemas/object.test.ts (100%) rename {package/src => core}/tests/schemas/set.test.ts (100%) create mode 100644 core/tests/schemas/string.test.ts create mode 100644 core/tests/syncroState.test.ts create mode 100644 core/tsconfig.json create mode 100644 core/tsdown.config.ts create mode 100644 core/vite.config.ts delete mode 100644 package/postcss.config.js delete mode 100644 package/src/app.css delete mode 100644 package/src/lib/index.ts delete mode 100644 package/src/lib/presence.svelte.ts delete mode 100644 package/src/lib/proxys/array.svelte.ts delete mode 100644 package/src/lib/proxys/base.svelte.ts delete mode 100644 package/src/lib/proxys/boolean.svelte.ts delete mode 100644 package/src/lib/proxys/common.ts delete mode 100644 package/src/lib/proxys/date.svelte.ts delete mode 100644 package/src/lib/proxys/discriminatedUnion.svelte.ts delete mode 100644 package/src/lib/proxys/enum.svelte.ts delete mode 100644 package/src/lib/proxys/map.svelte.ts delete mode 100644 package/src/lib/proxys/number.svelte.ts delete mode 100644 package/src/lib/proxys/object.svelte.ts delete mode 100644 package/src/lib/proxys/richText.svelte.ts delete mode 100644 package/src/lib/proxys/set.svelte.ts delete mode 100644 package/src/lib/proxys/syncroState.svelte.ts delete mode 100644 package/src/lib/proxys/text.svelte.ts delete mode 100644 package/src/lib/schemas/date.ts delete mode 100644 package/src/lib/schemas/schema.ts delete mode 100644 package/src/lib/utils.ts delete mode 100644 package/src/tests/integration/discriminatedUnion.integration.test.ts delete mode 100644 package/src/tests/proxys/array.bounds.test.ts delete mode 100644 package/src/tests/proxys/array.proxy.test.ts delete mode 100644 package/src/tests/proxys/array.splice.diagnostic.test.ts delete mode 100644 package/src/tests/proxys/boolean.proxy.test.ts delete mode 100644 package/src/tests/proxys/date.proxy.test.ts delete mode 100644 package/src/tests/proxys/discriminatedUnion.proxy.test.ts delete mode 100644 package/src/tests/proxys/enum.proxy.test.ts delete mode 100644 package/src/tests/proxys/map.proxy.test.ts delete mode 100644 package/src/tests/proxys/number.proxy.test.ts delete mode 100644 package/src/tests/proxys/object.proxy.test.ts delete mode 100644 package/src/tests/proxys/set.proxy.test.ts delete mode 100644 package/src/tests/proxys/string.proxy.test.ts delete mode 100644 package/src/tests/schemas/boolean.test.ts delete mode 100644 package/src/tests/schemas/date.test.ts delete mode 100644 package/src/tests/schemas/enum.test.ts delete mode 100644 package/src/tests/schemas/number.test.ts delete mode 100644 package/src/tests/schemas/string.test.ts delete mode 100644 package/src/tests/syncroState.test.ts delete mode 100644 package/tailwind.config.ts rename {package => svelte}/.gitignore (100%) rename {package => svelte}/.npmrc (100%) rename {package => svelte}/.prettierignore (100%) rename {package => svelte}/.prettierrc (100%) rename {package => svelte}/debug-variant.test.ts (100%) rename {package => svelte}/eslint.config.js (100%) rename {package => svelte}/package.json (60%) rename {package => svelte}/pnpm-lock.yaml (100%) create mode 100644 svelte/publish/fixVersionAfterPublication.js create mode 100644 svelte/publish/fixVersionBeforePublication.js create mode 100644 svelte/src/app.css rename {package => svelte}/src/app.d.ts (100%) rename {package => svelte}/src/app.html (100%) create mode 100644 svelte/src/lib/index.ts create mode 100644 svelte/src/lib/provider.svelte.ts rename {package => svelte}/src/routes/+layout.svelte (100%) rename {package => svelte}/src/routes/+page.svelte (86%) rename {package => svelte}/src/routes/proxy/+page.svelte (100%) rename {package => svelte}/src/routes/proxy/Test.svelte (100%) rename {package => svelte}/src/routes/proxy/proxy.svelte.ts (100%) rename {package => svelte}/static/favicon.png (100%) rename {package => svelte}/svelte.config.js (100%) rename {package => svelte}/tsconfig.json (100%) rename {package => svelte}/vite.config.ts (92%) create mode 100644 vanilla/.gitignore create mode 100644 vanilla/README.md create mode 100644 vanilla/lib/index.ts create mode 100644 vanilla/lib/provider.ts create mode 100644 vanilla/package.json create mode 100644 vanilla/publish/fixVersionAfterPublication.js create mode 100644 vanilla/publish/fixVersionBeforePublication.js create mode 100644 vanilla/tsconfig.json create mode 100644 vanilla/tsdown.config.ts diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/core/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000..d2870ed --- /dev/null +++ b/core/README.md @@ -0,0 +1,2 @@ +# Core +This is the core implementation of syncrostate. \ No newline at end of file diff --git a/package/src/lib/constants.ts b/core/lib/constants.ts similarity index 100% rename from package/src/lib/constants.ts rename to core/lib/constants.ts diff --git a/core/lib/index.ts b/core/lib/index.ts new file mode 100644 index 0000000..88d9c54 --- /dev/null +++ b/core/lib/index.ts @@ -0,0 +1,11 @@ +export { syncroState, type SyncroStateOptions } from "./proxys/syncroState.js"; +export { usePresence } from "./presence.js"; +export { y } from "./schemas/schema.js"; +export { type Provider } from "./provider.js"; +export { type ObjectShape } from "./schemas/object.js"; +export { + type SchemaOutput, + type InferSchemaType, + type Schema, + type RawSchemaOutput, +} from "./schemas/schema.js"; diff --git a/core/lib/presence.ts b/core/lib/presence.ts new file mode 100644 index 0000000..3548ad7 --- /dev/null +++ b/core/lib/presence.ts @@ -0,0 +1,81 @@ +import { Awareness as YAwareness } from "y-protocols/awareness"; +import { Doc } from "yjs"; +import { PRENSENCE, PRENSENCE_ID, PRESENCE_CONTEXT_KEY } from "./constants.js"; +import type { Provider } from "./provider.js"; + +export type PresenceUser = Record; + +export class Presence { + private awareness: YAwareness; + private provider: Provider; + private id: { value: string }; + private synced: { value: boolean }; + me: { value: PresenceUser }; + others: { value: PresenceUser[] }; + + constructor({ + doc, + awareness, + provider, + }: { + doc: Doc; + awareness?: YAwareness; + provider: Provider; + }) { + this.awareness = awareness ?? new YAwareness(doc); + this.provider = provider; + + this.id = provider.state(""); + this.synced = provider.state(false); + this.me = provider.state({}); + this.others = provider.state([]); + + provider.effect(() => { + if (this.synced.value) { + this.awareness.setLocalStateField(PRENSENCE, this.me.value); + } + }, []); + } + + private setOthers = () => { + const users = Array.from(this.awareness.getStates().values()); + + this.others.value = users + .filter((user) => user.id !== this.id.value) + .reduce((acc, user) => { + if (user[PRENSENCE_ID] && user[PRENSENCE_ID] !== this.id.value) { + acc.push(user[PRENSENCE]); + } + return acc; + }, [] as any); + }; + init = ({ + me = {}, + awareness, + }: { + me?: PresenceUser; + awareness?: YAwareness; + }) => { + if (awareness) { + this.awareness = awareness; + } + if (me.id && typeof me.id === "string") { + this.id.value = me.id as string; + } + this.me.value = me; + this.synced.value = true; + this.awareness.setLocalStateField(PRENSENCE_ID, this.id.value); + this.awareness.setLocalStateField(PRENSENCE, this.me.value); + this.setOthers(); + + this.awareness.on("update", this.setOthers); + }; +} + +export const usePresence = (getContext: (key: string) => any) => { + const presence = getContext(PRESENCE_CONTEXT_KEY) as Presence; + return { + others: presence.others, + me: presence.me, + }; +}; diff --git a/core/lib/provider.ts b/core/lib/provider.ts new file mode 100644 index 0000000..bdec7c8 --- /dev/null +++ b/core/lib/provider.ts @@ -0,0 +1,26 @@ +export type ProviderState = { + value: T; +}; + +export type ProviderEffect = (cb: () => void, deps?: any[]) => void; + +export type ProviderDerived = (cb: () => T) => { + value: T; +}; + +export interface Provider { + state: (initialValue: T) => { + value: T; + }; + effect: (cb: () => void, deps?: any[]) => void; + derived: (cb: () => T) => { + value: T; + }; + onMount: (cb: () => void) => void; + onDestroy: (cb: () => void) => void; + Map: typeof Map; + Set: typeof Set; + Date: typeof Date; + getContext: (key: string) => T; + setContext: (key: string, value: T) => void; +} diff --git a/core/lib/proxys/array.ts b/core/lib/proxys/array.ts new file mode 100644 index 0000000..c458c07 --- /dev/null +++ b/core/lib/proxys/array.ts @@ -0,0 +1,467 @@ +import * as Y from "yjs"; +import type { ArrayValidator } from "../schemas/array.js"; +import { + createSyncroState, + type State, + type SyncroStates, +} from "./syncroState.js"; +import type { SyncedContainer } from "./common.js"; +import type { ProviderState } from "../provider.js"; +import { + isArrayNull, + logError, + observeArray, + propertyToNumber, +} from "../utils.js"; +import { NULL_ARRAY } from "../constants.js"; + +export class SyncedArray { + state: State; + validator: ArrayValidator; + yType: Y.Array; + parent: SyncedContainer; + key: string | number; + syncroStates: ProviderState; + proxy: any; + isNull: ProviderState; + // array: any[] = $state(this.syncroStates.map((state) => state.value)); + + //🚨 Using a derived would be preferable but it breaks the tests :/ + // private array = $derived(this.syncroStates.map((state) => state.value)); + + private get array() { + return this.syncroStates.value.map((state) => state.value); + } + setNull = () => { + this.yType.delete(0, this.yType.length); + this.yType.insert(0, [new Y.Text(NULL_ARRAY)]); + this.isNull.value = true; + }; + // setNull = setArrayToNull.bind(this); + + deleteProperty = (target: any, prop: any) => { + const index = propertyToNumber(prop); + if (typeof index !== "number") { + return true; + } + + if (!this.validator.$schema.shape.$schema.optional) { + logError("Can not delete non optional property", index); + return true; + } + const syncroState = this.syncroStates.value[index]; + if (!syncroState) { + logError("Index does not exist", index); + return true; + } + syncroState.value = undefined; + return true; + }; + + set value(input: any[] | null | undefined) { + const { isValid, value } = this.validator.parse(input); + if (!isValid) { + logError("Invalid value", { value }); + } else { + this.state.transaction(() => { + if (!value) { + if (value === undefined) { + this.parent.deleteProperty({}, this.key); + } else { + this.setNull(); + } + } else { + if (this.isNull.value) { + this.isNull.value = false; + this.yType.delete(0, this.yType.length); + } + if (!this.isNull.value) { + const remainingStates = this.syncroStates.value.slice(value.length); + remainingStates.forEach((state) => { + state.destroy(); + }); + if (remainingStates.length) { + this.yType.delete(value.length, remainingStates.length); + } + } + this.syncroStates.value = value.map((item, index) => { + const previsousState = this.syncroStates.value[index]; + + if (previsousState) { + previsousState.value = item; + return previsousState; + } else { + return createSyncroState({ + key: index, + forceNewType: true, + validator: this.validator.$schema.shape, + parent: this, + value: item, + state: this.state, + }); + } + }); + } + }); + } + } + get value() { + if (this.isNull.value) { + return null; + } + return this.proxy; + } + + constructor({ + validator, + yType, + value, + parent, + key, + state, + }: { + validator: ArrayValidator; + yType: Y.Array; + value: any[]; + parent: SyncedContainer; + key: string | number; + state: State; + }) { + this.validator = validator; + this.yType = yType; + this.parent = parent; + this.key = key; + this.state = state; + this.syncroStates = state.provider.state([]); + this.isNull = state.provider.state(false); + yType.observe(this.observe); + + this.proxy = new Proxy([], { + get: (target: any, prop: any, receiver: any) => { + if (prop === "getState") { + return () => state; + } + if (prop === "getYType") { + return () => this.yType; + } + if (prop === "getYTypes") { + return () => this.yType.toArray(); + } + const p = propertyToNumber(prop); + if (Number.isInteger(p)) { + const syncroState = this.syncroStates.value[p as number]; + if (!syncroState) { + return undefined; + } + return syncroState.value; + } else if (typeof p === "string") { + if (p in this.methods) { + return this.methods[p as keyof typeof this.methods]; + } + + if (p[0] === "$") { + return Reflect.get(target, p); + } + + if (p === "toJSON") { + return this.toJSON(); + } + + if (p === "length") { + return this.array.length; + } + } else if (p === Symbol.toStringTag) { + return "Array"; + } else if (p === Symbol.iterator) { + const values = this.array.slice(); + return Reflect.get(values, p); + } + return Reflect.get(target, p, receiver); + }, + + set: (target: any, prop: any, value: any) => { + const p = propertyToNumber(prop); + if (Number.isInteger(p)) { + if (value === undefined) { + return this.deleteProperty(target, p); + } + + const syncroState = this.syncroStates.value[p as number]; + + if (!syncroState) { + this.state.transaction(() => { + this.syncroStates.value[p as number] = createSyncroState({ + key: p as number, + validator: this.validator.$schema.shape, + parent: this, + value, + state: this.state, + }); + }); + } else { + syncroState.value = value; + } + } + return true; + }, + deleteProperty: this.deleteProperty, + has: (target, prop) => { + const p = propertyToNumber(prop); + if (typeof p !== "number") { + // forward to arrayimplementation + return Reflect.has(target, p); + } + if (p < (this.array as any).lengthUntracked && p >= 0) { + return true; + } else { + return false; + } + }, + + getOwnPropertyDescriptor: (target, prop) => { + const p = propertyToNumber(prop); + if (p === "length") { + return { + enumerable: false, + configurable: false, + writable: true, + }; + } + if (typeof p === "number" && p >= 0 && p < this.yType.length) { + return { + enumerable: true, + configurable: true, + writable: true, + }; + } + return undefined; + }, + ownKeys: (target) => { + const keys: string[] = []; + for (let i = 0; i < this.yType.length; i++) { + keys.push(i + ""); + } + keys.push("length"); + return keys; + }, + }); + + this.sync(value); + } + + toJSON = () => { + return this.array; + }; + + sync = (value?: any[]) => { + this.state.transaction(() => { + this.syncroStates.value = []; + if (isArrayNull(this)) { + this.isNull.value = true; + return; + } + + if (this.state.initialized || value) { + for ( + let i = 0; + i < Math.max(value?.length || 0, this.yType.length); + i++ + ) { + this.syncroStates.value[i] = createSyncroState({ + key: i, + validator: this.validator.$schema.shape, + parent: this, + value: value?.[i] || this.validator.$schema.default?.[i], + state: this.state, + }); + } + } else { + if (this.validator.$schema.default) { + this.syncroStates.value = this.validator.$schema.default.map( + (item, index) => { + return createSyncroState({ + key: index, + validator: this.validator.$schema.shape, + parent: this, + value: item, + state: this.state, + }); + } + ); + } else if (this.validator.$schema.nullable && !value) { + this.setNull(); + } + } + }); + }; + + observe = observeArray.bind(this); + + methods = { + slice: (start?: number | undefined, end?: number | undefined) => { + return this.array.slice(start, end); + }, + toReversed: () => { + return this.array.toReversed(); + }, + forEach: (cb: (value: T, index: number, array: T[]) => void) => { + return this.array.forEach(cb); + }, + every: (cb: (value: T, index: number, array: T[]) => boolean) => { + return this.array.every(cb); + }, + filter: (cb: (value: T, index: number, array: T[]) => boolean) => { + return this.array.filter(cb); + }, + find: (cb: (value: T, index: number, array: T[]) => boolean) => { + return this.array.find(cb); + }, + findIndex: (cb: (value: T, index: number, array: T[]) => boolean) => { + return this.array.findIndex(cb); + }, + some: (cb: (value: T, index: number, array: T[]) => boolean) => { + return this.array.some(cb); + }, + includes: (value: T) => { + return this.array.includes(value); + }, + map: (cb: (value: T, index: number, array: T[]) => T) => { + return this.array.map(cb); + }, + reduce: ( + cb: (acc: X | undefined, value: T, index: number, array: T[]) => X, + initialValue?: X + ) => { + return this.array.reduce(cb, initialValue); + }, + indexOf: (value: T) => { + return this.array.indexOf(value); + }, + at: (index: number) => { + return this.array.at(index)?.value; + }, + // + // Mutatives methods + // + + pop: () => { + if (!this.syncroStates.value.length) { + return undefined; + } + const last = this.syncroStates.value.pop(); + this.state.transaction(() => { + this.yType.delete(this.yType.length - 1, 1); + last?.destroy(); + }); + return last?.value; + }, + shift: () => { + if (!this.syncroStates.value.length) { + return undefined; + } + const first = this.syncroStates.value.shift(); + this.state.transaction(() => { + this.yType.delete(0, 1); + first?.destroy(); + }); + return first?.value; + }, + unshift: (...items: T[]) => { + let result; + this.state.transaction(() => { + result = this.syncroStates.value.unshift( + ...items.map((item, index) => { + return createSyncroState({ + forceNewType: true, + key: index, + validator: this.validator.$schema.shape, + parent: this, + value: item, + state: this.state, + }); + }) + ); + }); + return result; + }, + push: (...items: T[]) => { + this.state.transaction(() => { + this.syncroStates.value.push( + ...items.map((item, index) => { + return createSyncroState({ + key: this.yType.length, + validator: this.validator.$schema.shape, + parent: this, + value: item, + state: this.state, + }); + }) + ); + }); + }, + splice: (start: number, deleteCount: number, ..._items: T[]) => { + let result: any[] = []; + this.state.transaction(() => { + // Normalize start index (handle negative values like native Array.splice) + const actualStart = + start < 0 + ? Math.max(0, this.syncroStates.value.length + start) + : Math.min(start, this.syncroStates.value.length); + + // Normalize deleteCount (don't delete more than available, treat negative as 0) + const actualDeleteCount = Math.min( + Math.max(0, deleteCount), + this.syncroStates.value.length - actualStart + ); + + // Delete from Y.js document (only if there are items to delete and within bounds) + if (actualDeleteCount > 0 && this.yType.length > actualStart) { + const yDeleteCount = Math.min( + actualDeleteCount, + this.yType.length - actualStart + ); + this.yType.delete(actualStart, yDeleteCount); + } + + // Create new SyncroState objects for inserted items + const newSyncroStates = _items.map((item, index) => { + return createSyncroState({ + key: actualStart + index, + forceNewType: true, + validator: this.validator.$schema.shape, + parent: this, + value: item, + state: this.state, + }); + }); + + // Destroy old SyncroState objects for deleted items (with bounds checking) + if (actualDeleteCount > 0) { + for (let i = 0; i < actualDeleteCount; i++) { + const state = this.syncroStates.value[actualStart + i]; + if (state) { + state.destroy(); + } + } + } + + // Update the syncroStates array with normalized values + result = this.syncroStates.value.splice( + actualStart, + actualDeleteCount, + ...newSyncroStates + ); + }); + + return result; + }, + }; + + destroy = () => { + this.syncroStates.value.forEach((state) => { + state.destroy(); + }); + this.syncroStates.value = []; + this.yType.unobserve(this.observe); + }; +} diff --git a/core/lib/proxys/base.ts b/core/lib/proxys/base.ts new file mode 100644 index 0000000..7638569 --- /dev/null +++ b/core/lib/proxys/base.ts @@ -0,0 +1,58 @@ +import * as Y from "yjs"; +import { NULL } from "../constants.js"; +import type { SyncedContainer } from "./common.js"; +import type { State } from "./syncroState.js"; +import type { ProviderState } from "../provider.js"; + +type ObserverCallback = (e: Y.YEvent, transact: Y.Transaction) => void; + +export class BaseSyncedType { + yType: Y.Text; + rawValue: ProviderState; + observeCallback?: ObserverCallback; + state: State; + parent: SyncedContainer; + key: string | number; + constructor(opts: { + yType: Y.Text; + key: string | number; + parent: SyncedContainer; + state: State; + }) { + this.yType = opts.yType; + this.rawValue = opts.state.provider.state(opts.yType.toString()); + this.yType.observe(this.observe); + this.parent = opts.parent; + this.key = opts.key; + this.state = opts.state; + } + + deletePropertyFromParent = () => { + this.parent.deleteProperty({}, this.key); + }; + + observe = (e: Y.YEvent, transact: Y.Transaction) => { + if (transact.origin !== this.state.transactionKey) { + this.rawValue.value = this.yType.toString(); + this.observeCallback?.(e, transact); + } + }; + + destroy = () => { + this.yType.unobserve(this.observe); + }; + + setYValue(value: string | null) { + if (this.rawValue.value !== value) { + const length = this.yType.length; + this.rawValue.value = value; + this.state.transaction(() => { + this.yType.applyDelta( + length + ? [{ delete: length }, { insert: value ?? NULL }] + : [{ insert: value ?? NULL }] + ); + }); + } + } +} diff --git a/core/lib/proxys/boolean.ts b/core/lib/proxys/boolean.ts new file mode 100644 index 0000000..e86465a --- /dev/null +++ b/core/lib/proxys/boolean.ts @@ -0,0 +1,46 @@ +import type { BooleanValidator } from "../schemas/boolean.js"; +import { BaseSyncedType } from "./base.js"; +import * as Y from "yjs"; +import type { SyncedContainer } from "./common.js"; +import { logError } from "../utils.js"; +import type { State } from "./syncroState.js"; + +// 🚨🚨🚨 design decision: boolean are defaulted to false if not optionnal or nullable and the value does not exist in the document. + +export class SyncedBoolean extends BaseSyncedType { + validator: BooleanValidator; + + get value() { + const value = this.validator.coerce(this.rawValue.value); + if (!this.validator.$schema.nullable && value === null) { + return this.validator.$schema.default || false; + } + if (!this.validator.$schema.optional && value === undefined) { + return this.validator.$schema.default || false; + } + return value; + } + + set value(value: boolean | null) { + if (!this.validator.isValid(value)) { + logError("Invalid value", { value }); + return; + } + if (value === undefined) { + this.deletePropertyFromParent(); + } else { + this.setYValue(this.validator.stringify(value)); + } + } + + constructor(opts: { + yType: Y.Text; + validator: BooleanValidator; + parent: SyncedContainer; + key: string | number; + state: State; + }) { + super(opts); + this.validator = opts.validator; + } +} diff --git a/core/lib/proxys/common.ts b/core/lib/proxys/common.ts new file mode 100644 index 0000000..567f95b --- /dev/null +++ b/core/lib/proxys/common.ts @@ -0,0 +1,10 @@ +import type { SyncedArray } from "./array.js"; +import type { SyncedSet } from "./set.js"; +import type { SyncedObject } from "./object.js"; +import type { SyncedMap } from "./map.js"; + +export type SyncedContainer = + | SyncedObject + | SyncedArray + | SyncedSet + | SyncedMap; diff --git a/core/lib/proxys/date.ts b/core/lib/proxys/date.ts new file mode 100644 index 0000000..278d0bd --- /dev/null +++ b/core/lib/proxys/date.ts @@ -0,0 +1,128 @@ +import * as Y from "yjs"; +import type { DateValidator } from "../schemas/date.js"; +import { NULL } from "../constants.js"; +import { BaseSyncedType } from "./base.js"; +import type { SyncedContainer } from "./common.js"; +import { logError } from "../utils.js"; +import type { State } from "./syncroState.js"; +import type { Provider } from "../provider.js"; +// 🚨🚨🚨 design decision: date are defaulted to new Date() if not optionnal or nullable and the value does not exist in the document. + +const SvelteDateProxy = (onSet: () => void, provider: Provider) => { + const date = new provider.Date(); + + class SyncedDate extends provider.Date { + constructor(...args: any[]) { + // @ts-ignore + super(...args); + + var proto = SyncedDate.prototype; + var date_proto = provider.Date.prototype; + + var methods = + /** @type {Array} */ Object.getOwnPropertyNames( + date_proto + ); + + for (const method of methods) { + if (method.startsWith("set")) { + // @ts-ignore + proto[method] = function (...args) { + // @ts-ignore + var result = date_proto[method].apply(this, args); + onSet(); + return result; + }; + } + } + } + } + + return new SyncedDate(); + // return new Proxy(date, { + // get(target, prop) { + // const result = Reflect.get(target, prop); + // console.log("result", result,prop); + // if (typeof result === "function") { + // return (...args: any[]) => { + // const ret = result.call(target, ...args); + // if (typeof prop === "string" && prop.startsWith("set")) { + // onSet(); + // } + // return ret; + // }; + // } else { + // return result; + // } + + // }, + + // }); +}; + +export class SyncedDate extends BaseSyncedType { + validator: DateValidator; + + date: Date; + + get value() { + const value = + this.rawValue.value === NULL || !this.rawValue.value ? null : this.date; + if (!this.validator.$schema.nullable && value === null) { + return this.date; + } + if (!this.validator.$schema.optional && value === undefined) { + return this.date; + } + return value; + } + + set value(value: Date | null | string | number) { + const isValid = this.validator.isValid(value); + if (!isValid) { + logError("Invalid value", { value }); + return; + } + if (value !== null && value !== undefined) { + this.setYValue(new Date(value).toISOString()); + this.date.setTime(new Date(value).getTime()); + } else { + if (value === undefined) { + this.deletePropertyFromParent(); + } else { + this.setYValue(null); + this.date.setTime(0); + } + } + } + + setValue = (string: string | null) => { + const { isValid, value } = this.validator.parse(string); + if (isValid) { + this.date.setTime(value?.getTime() || 0); + } + }; + + observeCallback = () => { + this.setValue(this.rawValue.value); + }; + + constructor(opts: { + yType: Y.Text; + validator: DateValidator; + parent: SyncedContainer; + key: string | number; + state: State; + }) { + super(opts); + this.date = SvelteDateProxy(() => { + const newRawValue = this.date.toISOString(); + const isNull = this.date.getTime() === 0; + if (newRawValue !== this.rawValue.value && !isNull) { + this.setYValue(newRawValue); + } + }, opts.state.provider); + this.validator = opts.validator; + this.setValue(this.rawValue.value); + } +} diff --git a/core/lib/proxys/discriminatedUnion.ts b/core/lib/proxys/discriminatedUnion.ts new file mode 100644 index 0000000..8d02c6b --- /dev/null +++ b/core/lib/proxys/discriminatedUnion.ts @@ -0,0 +1,224 @@ +import * as Y from "yjs"; +import type { DiscriminatedUnionValidator } from "../schemas/discriminatedUnion.js"; +import type { ObjectValidator } from "../schemas/object.js"; +import { createSyncroState, type State } from "./syncroState.js"; +import { SyncedObject } from "./object.js"; +import type { SyncedContainer } from "./common.js"; +import { logError } from "../utils.js"; +import type { Validator } from "../schemas/schema.js"; +import type { ProviderState } from "../provider.js"; + +export class SyncedDiscriminatedUnion { + state: State; + validator: DiscriminatedUnionValidator; + objectProxy: ProviderState; + parent: SyncedContainer; + key: string | number; + + get currentVariant() { + return this.objectProxy.value?.validator; + } + + get yType() { + return this.objectProxy.value?.yType; + } + + get isNull() { + return this.objectProxy.value?.isNull.value || false; + } + get proxy() { + return this.objectProxy.value!.proxy; + } + + set value(input: any) { + const { isValid, value } = this.validator.parse(input); + + if (!isValid) { + logError("Invalid value", { input }); + return; + } + if (!value) { + this.state.transaction(() => { + // Let discriminated union handle null and undefined + // It can be nullable or optional + // But the underlying object proxy can or can not reflect that aspect + if (input === undefined) { + this.parent.deleteProperty({}, this.key); + } else if (input === null) { + this.objectProxy.value!.setNull(); + } + }); + } else { + const newDiscriminantValue = + this.validator.$schema.discriminantKey in value && + (value as any)[this.validator.$schema.discriminantKey]; + const oldDiscriminantValue = + this.objectProxy?.value?.[ + this.validator.$schema.discriminantKey as keyof SyncedObject + ]; + if (newDiscriminantValue !== oldDiscriminantValue) { + this.swapValidator(newDiscriminantValue); + } + // Call the set trap of the object proxy + this.objectProxy.value!.value = value; + } + } + + get value() { + return this.objectProxy.value?.value; + } + + // Simple forwarding - discriminated union doesn't manage properties directly + deleteProperty = (target: any, p: any) => { + return this.objectProxy.value?.deleteProperty(target, p) || false; + }; + + setNull() { + this.objectProxy.value?.setNull(); + } + + // Find which variant matches the discriminant value + private getVariantByDiscriminant( + discriminantValue: any + ): ObjectValidator | null { + for (const variant of this.validator.$schema.variantValidators) { + const discriminantValidator = + variant.$schema.shape[this.validator.$schema.discriminantKey]; + if ( + discriminantValidator && + discriminantValidator.isValid(discriminantValue) + ) { + return variant; + } + } + return null; + } + + private swapValidator(discriminantValue?: string) { + if (discriminantValue === undefined) { + return; + } + const matchingValidator = this.getVariantByDiscriminant(discriminantValue); + if (matchingValidator) { + this.objectProxy.value!.validator = matchingValidator; + } + } + + constructor({ + state, + observe = true, + validator, + yType, + value, + parent, + key, + baseImplementation = {}, + }: { + state: State; + observe?: boolean; + validator: DiscriminatedUnionValidator; + yType: Y.Map; + value?: any; + parent: SyncedContainer; + key: string | number; + baseImplementation?: any; + }) { + this.parent = parent; + this.state = state; + this.key = key; + this.validator = validator; + this.objectProxy = this.state.provider.state(null); + + let objectValidator = this.validator.$schema.variantValidators[0]; + + if (observe) + if (yType.has(this.validator.$schema.discriminantKey)) { + const discriminantValue = yType.get( + this.validator.$schema.discriminantKey + ); + objectValidator = + this.getVariantByDiscriminant(discriminantValue) || objectValidator; + } else if (value) { + const discriminantValue = + value?.[this.validator.$schema.discriminantKey]; + objectValidator = + this.getVariantByDiscriminant(discriminantValue) || objectValidator; + } + + // We need to get the current variant from the yType if the document has already been initialized. + // Otherwise we need to get the variant from the validator default value if there is one. + // Otherwise we need to get the variant from the first validator in the array. + + this.objectProxy.value = new SyncedObject({ + validator: objectValidator, + yType, + parent, + key, + state, + value, + observe, + baseImplementation, + onObserve: this.observe, + }); + } + + toJSON = () => { + return this.objectProxy.value?.toJSON(); + }; + + observe = (e: Y.YMapEvent, _transaction: Y.Transaction) => { + // Only handle external changes (not our own) + const discriminantKeyChange = e.changes.keys.get( + this.validator.$schema.discriminantKey + ); + if (discriminantKeyChange) { + const discriminantValue = e.target + .get(this.validator.$schema.discriminantKey) + ?.toString(); + this.swapValidator(discriminantValue); + } + const objectProxy = this.objectProxy.value; + if (!objectProxy) return; + + const shape = this.currentVariant!.$schema.shape; + + Object.keys(objectProxy.syncroStates.value).forEach((k) => { + if (!(k in shape)) { + objectProxy.syncroStates.value[k].destroy(); + delete objectProxy.syncroStates.value[k]; + } + }); + + Object.entries(shape).forEach(([key, validator]) => { + const syncedState = objectProxy.syncroStates.value[key]; + + if (!syncedState) { + objectProxy.syncroStates.value[key] = createSyncroState({ + key, + validator: validator as Validator, + parent: objectProxy, + state: this.state, + }); + } else { + if (syncedState.validator.$schema !== shape[key].$schema) { + syncedState.destroy(); + objectProxy.syncroStates.value[key] = createSyncroState({ + key, + validator: validator as Validator, + parent: objectProxy, + state: this.state, + }); + } + } + }); + }; + + sync = (value?: any) => { + return this.objectProxy.value!.sync(value); + }; + + destroy = () => { + this.objectProxy.value?.destroy(); + this.objectProxy.value = null; + }; +} diff --git a/core/lib/proxys/enum.ts b/core/lib/proxys/enum.ts new file mode 100644 index 0000000..52a9178 --- /dev/null +++ b/core/lib/proxys/enum.ts @@ -0,0 +1,48 @@ +import * as Y from "yjs"; +import type { EnumValidator } from "../schemas/enum.js"; +import { BaseSyncedType } from "./base.js"; +import type { SyncedContainer } from "./common.js"; +import { logError } from "../utils.js"; +import type { State } from "./syncroState.js"; +// 🚨🚨🚨 design decision: enum are defaulted to the first value of the set if not optionnal or nullable and the value does not exist in the document. +export class SyncedEnum< + T extends string | number = string | number +> extends BaseSyncedType { + validator: EnumValidator; + private firstValue: T; + + get value() { + const value = this.validator.coerce(this.rawValue.value); + if (!this.validator.$schema.nullable && value === null) { + return this.validator.$schema.default || this.firstValue; + } + if (!this.validator.$schema.optional && value === undefined) { + return this.validator.$schema.default || this.firstValue; + } + return value; + } + + set value(value: T | null) { + if (!this.validator.isValid(value)) { + logError("Invalid value", { value }); + return; + } + if (value === undefined) { + this.deletePropertyFromParent(); + } else { + this.setYValue(this.validator.stringify(value)); + } + } + + constructor(opts: { + yType: Y.Text; + validator: EnumValidator; + parent: SyncedContainer; + key: string | number; + state: State; + }) { + super(opts); + this.firstValue = opts.validator.$schema.values.values().next().value!; + this.validator = opts.validator; + } +} diff --git a/core/lib/proxys/map.ts b/core/lib/proxys/map.ts new file mode 100644 index 0000000..99923e7 --- /dev/null +++ b/core/lib/proxys/map.ts @@ -0,0 +1,286 @@ +import * as Y from "yjs"; +import { logError } from "../utils.js"; +import { + createSyncroState, + type State, + type SyncroStates, +} from "./syncroState.js"; +import type { SyncedContainer } from "./common.js"; +import type { MapValidator } from "../schemas/map.js"; +import type { ProviderState } from "../provider.js"; +import { NULL_OBJECT } from "../constants.js"; +import type { Validator } from "../schemas/schema.js"; + +export class SyncedMap { + state: State; + validator: MapValidator; + yType: Y.Map; + parent: SyncedContainer; + key: string | number; + isNull: ProviderState; + syncroStates: ProviderState>; + syncroStatesValues: Map; + proxyMap: any; + + constructor(opts: { + yType: Y.Map; + validator: MapValidator; + parent: SyncedContainer; + key: string | number; + state: State; + value: any; + }) { + this.key = opts.key; + this.state = opts.state; + this.yType = opts.yType; + this.parent = opts.parent; + this.validator = opts.validator; + this.isNull = opts.state.provider.state(false); + this.syncroStates = opts.state.provider.state( + new opts.state.provider.Map() + ); + this.syncroStatesValues = new opts.state.provider.Map(); + + this.sync(opts.value); + this.yType.observe(this.observe); + + this.proxyMap = new Proxy(this.syncroStatesValues, { + get: (target, prop) => { + if (prop === "getState") { + return () => this.state; + } + if (prop === "getYType") { + return () => this.yType; + } + if (prop === "getYTypes") { + return () => Object.fromEntries(this.yType.entries()); + } + if (prop === Symbol.iterator) { + return () => this.syncroStatesValues.entries(); + } + + const result = Reflect.get(target, prop); + if (typeof result === "function") { + return (...args: any[]) => { + if (typeof prop === "string") { + switch (prop) { + case "set": { + const [key, value] = args; + const { isValid, value: parsedValue } = + this.validator.$schema.shape.parse(value); + if (!isValid) { + logError("Invalid value", { value }); + return false; + } + + const hasValue = this.syncroStates.value.has(value); + + if (hasValue) { + return false; + } + this.state.transaction(() => { + const state = createSyncroState({ + forceNewType: true, + key, + validator: this.validator.$schema.shape, + parent: this, + value: value, + state: this.state, + }); + this.addState(key, state); + }); + + return this.proxyMap; + } + case "delete": { + this.deleteProperty(target, args[0]); + + return this.proxyMap; + } + case "clear": + { + this.state.transaction(() => { + this.syncroStates.value.forEach((state) => + state.destroy() + ); + this.yType.clear(); + this.syncroStatesValues.clear(); + this.syncroStates.value.clear(); + }); + } + return this.proxyMap; + default: + break; + } + return result.call(target, ...args); + } + }; + } else { + return result; + } + }, + }); + } + + deleteProperty = (_target: any, p: any) => { + if (typeof p !== "string") { + return true; + } + + const syncroState = this.syncroStates.value.get(p); + if (!syncroState) { + logError("Property does not exist", p); + return true; + } + syncroState.destroy(); + this.yType.delete(p); + this.syncroStates.value.delete(p); + this.syncroStatesValues.delete(p); + return true; + }; + + setNull() { + this.state.transaction(() => { + this.syncroStates.value.forEach((state) => state.destroy()); + this.syncroStatesValues.clear(); + this.syncroStates.value.clear(); + this.isNull.value = true; + this.yType.set(NULL_OBJECT, new Y.Text(NULL_OBJECT)); + }); + } + observe = (e: Y.YMapEvent, _transaction: Y.Transaction) => { + if (_transaction.origin !== this.state.transactionKey) { + if (this.yType.has(NULL_OBJECT)) { + this.isNull.value = true; + this.syncroStates.value.forEach((state) => state.destroy()); + this.syncroStates.value.clear(); + this.syncroStatesValues.clear(); + return; + } + e.changes?.keys.forEach(({ action }, key) => { + const syncedState = this.syncroStates.value.get(key); + if (action === "delete" && syncedState) { + syncedState.destroy(); + this.syncroStates.value.delete(key); + this.syncroStatesValues.delete(key); + } + if (action === "add") { + // If a new key is added to the object and is valid, integrate it + const syncroState = createSyncroState({ + key, + validator: this.validator.$schema.shape as Validator, + state: this.state, + parent: this, + }); + this.addState(key, syncroState); + } + }); + } + }; + + addState = (key: string, state: SyncroStates) => { + this.syncroStates.value.set(key, state); + this.syncroStatesValues.set(key, state.value); + }; + + addValue = (key: string, value: any) => { + const state = createSyncroState({ + key, + validator: this.validator.$schema.shape, + parent: this, + value, + state: this.state, + }); + this.addState(key, state); + }; + + sync = (value: any) => { + this.state.transaction(() => { + this.syncroStates.value.clear(); + this.syncroStatesValues.clear(); + if (this.yType.has(NULL_OBJECT)) { + this.isNull.value = true; + return; + } + + if (value) { + Object.entries(value).forEach(([key, value]) => { + this.addValue(key, value); + }); + } else if (this.state.initialized) { + this.yType.forEach((value, key) => { + this.addValue(key, value); + }); + } else if (this.validator.$schema.default) { + this.validator.$schema.default?.forEach((value, key) => { + this.addValue(key, value); + }); + } else if (this.validator.$schema.nullable && !value) { + this.setNull(); + } + }); + }; + + toJSON = () => { + return Object.fromEntries(this.syncroStates.value.entries()); + }; + + destroy = () => { + this.syncroStates.value.clear(); + this.yType.unobserve(this.observe); + }; + + get value() { + if (this.isNull.value) { + return null; + } + + return this.proxyMap; + } + + set value(input: Map | null) { + const { isValid, value } = this.validator.parse(input); + this.state.transaction(() => { + if (!isValid) { + logError("Invalid value", { value }); + return; + } else { + if (!value) { + if (value === undefined) { + this.parent.deleteProperty({}, this.key); + } else { + this.setNull(); + } + } else { + if (this.isNull.value) { + this.isNull.value = false; + this.yType.delete(NULL_OBJECT); + } + + if (!this.isNull.value) { + const remainingStates = Array.from( + this.syncroStates.value.keys() + ).filter((key) => !(key in value)); + remainingStates.forEach((key) => { + this.deleteProperty({}, key); + }); + } + Array.from(value.entries()).forEach(([key, value]) => { + const state = this.syncroStates.value.get(key); + if (state) { + state.value = value; + this.syncroStatesValues.set(key, value); + } else { + const { isValid, value: parsedValue } = + this.validator.$schema.shape.parse(value); + + if (isValid) { + this.addValue(key, parsedValue); + } + } + }); + } + } + }); + } +} diff --git a/core/lib/proxys/number.ts b/core/lib/proxys/number.ts new file mode 100644 index 0000000..7785cb3 --- /dev/null +++ b/core/lib/proxys/number.ts @@ -0,0 +1,36 @@ +import * as Y from "yjs"; +import type { NumberValidator } from "../schemas/number.js"; +import { BaseSyncedType } from "./base.js"; +import type { SyncedContainer } from "./common.js"; +import { logError } from "../utils.js"; +import type { State } from "./syncroState.js"; +export class SyncedNumber extends BaseSyncedType { + validator: NumberValidator; + + get value() { + return this.validator.coerce(this.rawValue.value); + } + + set value(value: number | null) { + if (!this.validator.isValid(value)) { + logError("Invalid value", { value }); + return; + } + if (value === undefined) { + this.deletePropertyFromParent(); + } else { + this.setYValue(this.validator.stringify(value)); + } + } + + constructor(opts: { + yType: Y.Text; + validator: NumberValidator; + parent: SyncedContainer; + key: string | number; + state: State; + }) { + super(opts); + this.validator = opts.validator; + } +} diff --git a/core/lib/proxys/object.ts b/core/lib/proxys/object.ts new file mode 100644 index 0000000..cf7a9ea --- /dev/null +++ b/core/lib/proxys/object.ts @@ -0,0 +1,328 @@ +import * as Y from "yjs"; +import type { ObjectValidator } from "../schemas/object.js"; +import type { Validator } from "../schemas/schema.js"; +import { isMissingOptionnal } from "../utils.js"; +import { + type State, + type SyncroStates, + createSyncroState, +} from "./syncroState.js"; +import type { SyncedContainer } from "./common.js"; +import type { ProviderState } from "../provider.js"; +import { logError } from "../utils.js"; +import { NULL_OBJECT } from "../constants.js"; + +export class SyncedObject { + state: State; + validator: ObjectValidator; + yType: Y.Map; + syncroStates: ProviderState>; + baseImplementation = {}; + proxy: any; + parent: SyncedContainer; + key: string | number; + isNull: ProviderState; + onObserve?: (e: Y.YMapEvent, _transaction: Y.Transaction) => void; + + deleteProperty = (target: any, p: any) => { + if (typeof p !== "string") { + return true; + } + + const syncroState = this.syncroStates.value[p]; + if (!syncroState) { + logError("Property does not exist", p); + return true; + } else if (!syncroState.validator.$schema.optional) { + logError("Can not delete non optional property", p); + return true; + } + syncroState.destroy(); + this.yType.delete(p); + delete this.syncroStates.value[p]; + return true; + }; + + setNull() { + this.isNull.value = true; + this.state.transaction(() => { + // Clear existing keys to avoid stale state + Array.from(this.yType.keys()).forEach((k) => { + this.yType.delete(k); + }); + // Drop local children to free memory + Object.values(this.syncroStates.value).forEach((s) => s.destroy()); + this.syncroStates.value = {}; + this.yType.set(NULL_OBJECT, new Y.Text(NULL_OBJECT)); + }); + } + + set value(input: any) { + const { isValid, value } = this.validator.parse(input); + + if (!isValid) { + logError("Invalid value", { value }); + return; + } + + this.state.transaction(() => { + const shape = this.validator.$schema.shape; + + if (!value) { + if (value === undefined) { + this.parent.deleteProperty({}, this.key); + } else { + this.setNull(); + } + } else { + if (this.isNull.value) { + this.isNull.value = false; + this.yType.delete(NULL_OBJECT); + } + // Delete syncro states that are not in the new value + Object.keys(this.syncroStates.value) + .filter((key) => !(key in value)) + .forEach((key) => { + this.syncroStates.value[key]?.destroy(); + this.yType.delete(key); + delete this.syncroStates.value[key]; + }); + + Object.entries(value).forEach(([key, value]) => { + if (key in shape) { + const syncroState = this.syncroStates.value[key]; + if (syncroState) { + if (syncroState.validator.$schema !== shape[key].$schema) { + syncroState.destroy(); + this.syncroStates.value[key] = createSyncroState({ + key, + validator: shape[key], + parent: this, + state: this.state, + forceValue: true, + forceNewType: true, + value, + }); + } else { + syncroState.value = value; + } + } else { + this.syncroStates.value[key] = createSyncroState({ + key, + validator: shape[key], + parent: this, + state: this.state, + value, + }); + } + } + }); + } + }); + } + + get value() { + if (this.isNull.value) { + return null; + } + return this.proxy; + } + constructor({ + state, + observe = true, + validator, + yType, + baseImplementation = {}, + value, + parent, + key, + onObserve, + }: { + state: State; + observe?: boolean; + validator: ObjectValidator; + yType: Y.Map; + baseImplementation?: any; + value?: any; + parent: SyncedContainer; + key: string | number; + onObserve?: (e: Y.YMapEvent, _transaction: Y.Transaction) => void; + }) { + this.parent = parent; + this.state = state; + this.key = key; + this.validator = validator; + this.yType = yType; + this.baseImplementation = baseImplementation; + this.onObserve = onObserve; + this.syncroStates = state.provider.state({}); + this.isNull = state.provider.state(false); + + this.proxy = new Proxy( + {}, + { + get: (target: any, prop: any) => { + if (prop === "getState") { + return () => state; + } + if (prop === "getYType") { + return () => yType; + } + + if (prop === "getYTypes") { + return () => Object.fromEntries(yType.entries()); + } + + if (prop === "toJSON") { + return this.toJSON.bind(this); + } + + const syncroState = this.syncroStates.value[prop]; + + if (!syncroState) { + return undefined; + } + return syncroState.value; + }, + set: (target: any, key: any, value: any) => { + if (!(key in this.validator.$schema.shape)) { + return false; + } + if (value === undefined) { + return this.deleteProperty(target, key); + } + + const syncroState = this.syncroStates.value[key]; + if (!syncroState) { + this.state.transaction(() => { + this.syncroStates.value[key] = createSyncroState({ + key, + validator: this.validator.$schema.shape[key], + parent: this, + state: this.state, + value, + }); + }); + } else { + syncroState.value = value; + } + return true; + }, + + deleteProperty: this.deleteProperty, + + has: (target: any, prop: any) => { + if (typeof prop !== "string") { + return false; + } + return this.yType.has(prop); + }, + + getOwnPropertyDescriptor(target: any, prop: any) { + if ( + (typeof prop === "string" && yType.has(prop)) || + prop === "toJSON" + ) { + return { + enumerable: true, + configurable: true, + }; + } + + return undefined; + }, + + ownKeys: () => Array.from(this.yType.keys()), + } + ); + if (observe) { + yType.observe(this.observe); + this.sync(value); + } + } + observe = (e: Y.YMapEvent, _transaction: Y.Transaction) => { + if (_transaction.origin !== this.state.transactionKey) { + this.onObserve?.(e, _transaction); + if (this.yType.has(NULL_OBJECT)) { + this.isNull.value = true; + return; + } + e.changes?.keys.forEach(({ action }, key) => { + const syncedType = this.syncroStates.value[key]; + if (action === "delete" && syncedType) { + syncedType.destroy(); + delete this.syncroStates.value[key]; + } + if (action === "add") { + // If a new key is added to the object and is valid, integrate it + const validator = this.validator.$schema.shape[key]; + + const syncroState = createSyncroState({ + key, + validator: validator as Validator, + state: this.state, + parent: this, + }); + + Object.assign(this.syncroStates.value, { [key]: syncroState }); + } + }); + } + }; + + toJSON = () => { + if (this.isNull.value) { + return null; + } + return Object.entries(this.validator.$schema.shape).reduce( + (acc, [key, validator]) => { + const value = this.syncroStates.value[key]?.value; + if (value !== undefined) { + Object.assign(acc, { [key]: value }); + } + return acc; + }, + {} + ); + }; + + sync = (value?: any) => { + this.state.transaction(() => { + this.syncroStates.value = {}; + if (this.yType.has(NULL_OBJECT)) { + this.isNull.value = true; + return; + } + const hasDefaultValue = this.validator.$schema.default; + if (!hasDefaultValue) { + if (this.validator.$schema.nullable && !value) { + this.setNull(); + return; + } + } + ( + Object.entries(this.validator.$schema.shape) as [string, Validator][] + ).forEach(([key, validator]) => { + if (isMissingOptionnal({ validator, parent: this.yType, key })) { + return; + } + + this.syncroStates.value[key] = createSyncroState({ + key, + validator: validator, + parent: this, + value: value?.[key] || this.validator.$schema.default?.[key], + state: this.state, + }); + }); + }); + }; + + destroy = () => { + this.yType.unobserve(this.observe); + Object.values(this.syncroStates.value).forEach((syncroState) => { + syncroState.destroy(); + }); + this.syncroStates.value = {}; + }; +} diff --git a/core/lib/proxys/set.ts b/core/lib/proxys/set.ts new file mode 100644 index 0000000..f501ea0 --- /dev/null +++ b/core/lib/proxys/set.ts @@ -0,0 +1,274 @@ +import * as Y from "yjs"; +import { + isArrayNull, + logError, + observeArray, + propertyToNumber, + setArrayToNull, +} from "../utils.js"; +import { + createSyncroState, + type State, + type SyncroStates, +} from "./syncroState.js"; +import type { SyncedContainer } from "./common.js"; +import type { SetValidator } from "../schemas/set.js"; +import type { ProviderState } from "../provider.js"; + +export class SyncedSet { + state: State; + validator: SetValidator; + yType: Y.Array; + parent: SyncedContainer; + key: string | number; + isNull: ProviderState; + syncroStates: ProviderState; + syncroStatesValues: Set; + proxySet: any; + setNull = setArrayToNull.bind(this); + observe = observeArray.bind(this); + + constructor(opts: { + yType: Y.Array; + validator: SetValidator; + parent: SyncedContainer; + key: string | number; + state: State; + value: any; + }) { + this.key = opts.key; + this.state = opts.state; + this.yType = opts.yType; + this.parent = opts.parent; + this.validator = opts.validator; + this.isNull = opts.state.provider.state(false); + this.syncroStates = opts.state.provider.state([]); + this.syncroStatesValues = new opts.state.provider.Set(); + + this.sync(opts.value); + this.yType.observe(this.observe); + + this.proxySet = new Proxy(this.syncroStatesValues, { + get: (target, prop) => { + if (prop === "getState") { + return () => this.state; + } + if (prop === "getYType") { + return () => this.yType; + } + if (prop === "getYTypes") { + return () => this.yType.toArray(); + } + if (prop === Symbol.iterator) { + return () => this.syncroStatesValues.values(); + } + + const result = Reflect.get(target, prop); + if (typeof result === "function") { + return (...args: any[]) => { + if (typeof prop === "string") { + switch (prop) { + case "add": { + const { isValid, value } = this.validator.$schema.shape.parse( + args[0] + ); + + if (!isValid) { + logError("Invalid value", { value }); + return false; + } + + const hasValue = this.syncroStatesValues.has(value); + + if (hasValue) { + return false; + } + this.state.transaction(() => { + const state = createSyncroState({ + forceNewType: true, + key: this.syncroStatesValues.size, + validator: this.validator.$schema.shape, + parent: this, + value: value, + state: this.state, + }); + this.addState(state); + }); + + return this.proxySet; + } + case "delete": { + const stateIndex = Array.from( + this.syncroStates.value + ).findIndex((state) => state.value === args[0]); + if (stateIndex !== -1) { + this.syncroStates.value[stateIndex]?.destroy(); + } + this.deleteProperty(target, stateIndex); + return this.proxySet; + } + case "clear": + { + this.state.transaction(() => { + Array.from(this.syncroStates.value).forEach((state) => + state.destroy() + ); + this.yType.delete(0, this.yType.length); + this.syncroStatesValues.clear(); + this.syncroStates.value = []; + }); + } + return this.proxySet; + default: + break; + } + return result.call(target, ...args); + } + }; + } else { + return result; + } + }, + }); + } + + sync = (value: any) => { + this.state.transaction(() => { + this.syncroStatesValues.clear(); + this.syncroStates.value = []; + if (isArrayNull(this)) { + this.isNull.value = true; + return; + } + if (this.state.initialized || value) { + for ( + let i = 0; + i < Math.max(value?.length || 0, this.yType.length); + i++ + ) { + this.addState( + createSyncroState({ + key: i, + validator: this.validator.$schema.shape, + parent: this, + value: value?.[i], + state: this.state, + }) + ); + } + } else { + if (this.validator.$schema.default) { + this.syncroStates.value = Array.from( + this.validator.$schema.default + ).map((item, index) => { + const state = createSyncroState({ + key: index, + validator: this.validator.$schema.shape, + parent: this, + value: item, + state: this.state, + }); + this.syncroStatesValues.add(state.value); + return state; + }); + } else if (this.validator.$schema.nullable && !value) { + this.setNull(); + } + } + }); + }; + + toJSON = () => { + // it's not exactly right but maybe ok and useful + return Array.from(this.syncroStates.value).map((state) => state.value); + }; + + addState = (state: SyncroStates) => { + this.syncroStates.value.push(state); + this.syncroStatesValues.add(state.value); + }; + + deleteProperty = (target: any, prop: any) => { + const index = propertyToNumber(prop); + if (typeof index !== "number" || !this.syncroStates.value[index]) { + return true; + } + + this.syncroStatesValues.delete(this.syncroStates.value[index].value); + this.syncroStates.value[index].destroy(); + this.syncroStates.value.splice(index, 1); + this.yType.delete(index, 1); + }; + + destroy = () => { + this.syncroStatesValues.clear(); + this.syncroStates.value = []; + }; + + get value() { + if (this.isNull.value) { + return null; + } + + return this.proxySet; + } + + set value(input: Set | null) { + const { isValid, value } = this.validator.parse(input); + + this.state.transaction(() => { + if (!isValid) { + logError("Invalid value", { value }); + return; + } else { + if (!value) { + if (value === undefined) { + // this.parent.deleteProperty({}, this.key); + } else { + this.setNull(); + } + } else { + this.syncroStatesValues.clear(); + const valueArray = Array.from(value); + + if (this.isNull.value) { + this.isNull.value = false; + this.yType.delete(0, this.yType.length); + } + + if (!this.isNull.value) { + const remainingStates = Array.from(this.syncroStates.value).slice( + valueArray.length + ); + remainingStates.forEach((state) => { + state.destroy(); + }); + if (remainingStates.length) { + this.yType.delete(valueArray.length, remainingStates.length); + } + } + + this.syncroStates.value = valueArray.map((item, index) => { + const previsousState = this.syncroStates.value[index]; + if (previsousState) { + previsousState.value = item; + this.syncroStatesValues.add(previsousState.value); + return previsousState; + } else { + const state = createSyncroState({ + forceNewType: true, + key: index, + validator: this.validator.$schema.shape, + parent: this, + value: item, + state: this.state, + }); + this.syncroStatesValues.add(state.value); + return state; + } + }); + } + } + }); + } +} diff --git a/core/lib/proxys/syncroState.ts b/core/lib/proxys/syncroState.ts new file mode 100644 index 0000000..174bc8c --- /dev/null +++ b/core/lib/proxys/syncroState.ts @@ -0,0 +1,320 @@ +import type { SchemaOutput } from "../schemas/schema.js"; +import * as Y from "yjs"; +import { Awareness } from "y-protocols/awareness"; +import { SyncedObject } from "./object.js"; +import { ObjectValidator, type ObjectShape } from "../schemas/object.js"; +import type { Validator } from "../schemas/schema.js"; +import { SyncedEnum } from "./enum.js"; +import { SyncedDate } from "./date.js"; +import { SyncedBoolean } from "./boolean.js"; +import type { StringValidator } from "../schemas/string.js"; +import type { NumberValidator } from "../schemas/number.js"; +import type { EnumValidator } from "../schemas/enum.js"; +import type { DateValidator } from "../schemas/date.js"; +import type { BooleanValidator } from "../schemas/boolean.js"; +import { SyncedText } from "./text.js"; +import { SyncedNumber } from "./number.js"; +import { + getInitialStringifiedValue, + getTypeFromParent, + logError, +} from "../utils.js"; +import { CONTEXT_KEY, INITIALIZED, TRANSACTION_KEY } from "../constants.js"; +import { SyncedArray } from "./array.js"; +import type { ArrayValidator } from "../schemas/array.js"; +import type { SyncedContainer } from "./common.js"; +import { SyncedSet } from "./set.js"; +import type { SetValidator } from "../schemas/set.js"; +import { Presence, type PresenceUser } from "../presence.js"; +import { SyncedMap } from "./map.js"; +import type { MapValidator } from "../schemas/map.js"; +import type { LiteralValidator } from "../schemas/literal.js"; +import type { DiscriminatedUnionValidator } from "../schemas/discriminatedUnion.js"; +import { SyncedDiscriminatedUnion } from "./discriminatedUnion.js"; +import type { Provider } from "../provider.js"; + +export type SyncroStates = + | SyncedText + | SyncedNumber + | SyncedBoolean + | SyncedDate + | SyncedEnum + | SyncedObject + | SyncedArray + | SyncedSet + | SyncedMap + | SyncedDiscriminatedUnion; + +export type State

= { + synced: boolean; + awareness: Awareness; + doc: Y.Doc; + undoManager: Y.UndoManager; + initialized: boolean; + transaction: (fn: () => void) => void; + transactionKey: any; + presence: Presence

; + undo: () => void; + redo: () => void; + provider: Provider; +}; + +export type SyncroStateOptions = { + schema: T; + doc?: Y.Doc; + awareness?: Awareness; + presence?: Omit; + provider: Provider; + sync?: ({ + doc, + awareness, + synced, + }: { + doc: Y.Doc; + awareness: Awareness; + synced: (provider?: any) => void; + }) => Promise; +}; + +export const syncroState = ({ + schema, + sync, + doc: customDoc, + awareness: customAwareness, + presence: p, + provider, +}: SyncroStateOptions): SchemaOutput => { + const doc = customDoc ?? new Y.Doc(); + const awareness = customAwareness ?? new Awareness(doc); + const presence = new Presence({ doc, awareness, provider }); + const schemaValidator = new ObjectValidator(schema); + const stateMap = doc.getMap("$state"); + const undoManager = new Y.UndoManager(stateMap); + const transactionKey = new TRANSACTION_KEY(); + let state = provider.state({ + synced: sync ? false : true, + initialized: false, + awareness, + doc, + undoManager, + presence: presence as Presence

, + provider, + transaction: (fn: () => void) => { + state.value.doc.transact(fn, transactionKey); + }, + transactionKey, + undo: () => { + if (undoManager?.canUndo()) { + undoManager.undo(); + } + }, + redo: () => { + if (undoManager?.canRedo()) { + undoManager.redo(); + } + }, + }); + provider.setContext(CONTEXT_KEY, state); + + const syncroStateProxy = new SyncedObject({ + // @ts-ignore + parent: { + // TODO: does this need to be fixed ? Is this even used a some point ? idk + deleteProperty(target, pArg) { + logError("Not allowed"); + return true; + }, + }, + state: state.value, + key: "$state", + validator: schemaValidator, + observe: false, + yType: stateMap, + }); + + const initialize = (doc: Y.Doc, cb: () => void) => { + const text = doc.getText(INITIALIZED); + const initialized = text?.toString() === INITIALIZED; + Object.assign(doc, { initialized }); + state.value.initialized = initialized; + cb(); + state.value.initialized = true; + Object.assign(doc, { initialized: true }); + if (!initialized) { + text.delete(0, text.length); + text.insert(0, INITIALIZED); + } + }; + + const synced = (provider?: any) => { + initialize(doc, () => { + syncroStateProxy.sync(syncroStateProxy.value); + stateMap.observe(syncroStateProxy.observe); + presence.init({ me: p, awareness: provider?.awareness }); + state.value.synced = true; + }); + }; + + if (!sync) { + synced(); + } + + provider.onMount(() => { + if (sync) { + sync({ doc, awareness, synced }); + } + }); + + return syncroStateProxy.value; +}; + +export const createSyncroState = ({ + key, + validator, + forceNewType, + value, + parent, + state, + forceValue, +}: { + key: string | number; + validator: Validator; + value?: any; + forceNewType?: boolean; + parent: SyncedContainer; + state: State; + forceValue?: boolean; +}): SyncroStates => { + const type = getTypeFromParent({ + forceNewType, + parent: parent.yType, + key, + validator, + value, + }); + + if (forceValue && type instanceof Y.Text) { + const stringifiedValue = getInitialStringifiedValue(value, validator); + if ( + typeof stringifiedValue === "string" && + stringifiedValue !== type.toString() + ) { + state.transaction(() => { + type.applyDelta([ + { delete: type.length }, + { insert: stringifiedValue }, + ]); + }); + } + } + + switch (validator.$schema.kind) { + default: + case "string": { + return new SyncedText({ + yType: type as Y.Text, + validator: validator as StringValidator, + parent, + key, + state, + }); + } + case "number": { + return new SyncedNumber({ + yType: type as Y.Text, + validator: validator as NumberValidator, + parent, + key, + state, + }); + } + case "boolean": { + return new SyncedBoolean({ + yType: type as Y.Text, + validator: validator as BooleanValidator, + parent, + key, + state, + }); + } + case "date": { + return new SyncedDate({ + yType: type as Y.Text, + validator: validator as DateValidator, + parent, + key, + state, + }); + } + case "enum": { + return new SyncedEnum({ + yType: type as Y.Text, + validator: validator as EnumValidator, + parent, + key, + state, + }); + } + case "object": { + return new SyncedObject({ + yType: type as Y.Map, + validator: validator as ObjectValidator, + baseImplementation: {}, + value, + parent, + key, + state, + }); + } + case "set": { + return new SyncedSet({ + yType: type as Y.Array, + validator: validator as SetValidator, + value, + parent, + key, + state, + }); + } + case "array": { + return new SyncedArray({ + yType: type as Y.Array, + validator: validator as ArrayValidator, + value, + parent, + key, + state, + }); + } + case "map": { + return new SyncedMap({ + yType: type as Y.Map, + validator: validator as MapValidator, + value, + parent, + key, + state, + }); + } + case "literal": { + // Literals are stored as text since they're primitive values + return new SyncedText({ + yType: type as Y.Text, + validator: validator as LiteralValidator as any, + parent, + key, + state, + }); + } + case "discriminatedUnion": { + return new SyncedDiscriminatedUnion({ + yType: type as Y.Map, + validator: validator as DiscriminatedUnionValidator, + value, + parent, + key, + state, + }); + } + } +}; diff --git a/core/lib/proxys/text.ts b/core/lib/proxys/text.ts new file mode 100644 index 0000000..c2fb4b4 --- /dev/null +++ b/core/lib/proxys/text.ts @@ -0,0 +1,38 @@ +import type { StringValidator } from "../schemas/string.js"; +import * as Y from "yjs"; +import { BaseSyncedType } from "./base.js"; +import { NULL } from "../constants.js"; +import { logError } from "../utils.js"; +import type { State } from "./syncroState.js"; +import type { SyncedContainer } from "./common.js"; + +export class SyncedText extends BaseSyncedType { + validator: StringValidator; + + get value() { + return this.rawValue.value === NULL ? null : this.rawValue.value; + } + set value(value: string | null) { + const isValid = this.validator.isValid(value); + if (!isValid) { + logError("Invalid value", { value }); + return; + } + if (value === undefined) { + this.deletePropertyFromParent(); + } else { + this.setYValue(this.validator.stringify(value)); + } + } + + constructor(opts: { + yType: Y.Text; + validator: StringValidator; + parent: SyncedContainer; + key: string | number; + state: State; + }) { + super(opts); + this.validator = opts.validator; + } +} diff --git a/package/src/lib/schemas/array.ts b/core/lib/schemas/array.ts similarity index 100% rename from package/src/lib/schemas/array.ts rename to core/lib/schemas/array.ts diff --git a/package/src/lib/schemas/base.ts b/core/lib/schemas/base.ts similarity index 100% rename from package/src/lib/schemas/base.ts rename to core/lib/schemas/base.ts diff --git a/package/src/lib/schemas/boolean.ts b/core/lib/schemas/boolean.ts similarity index 100% rename from package/src/lib/schemas/boolean.ts rename to core/lib/schemas/boolean.ts diff --git a/core/lib/schemas/date.ts b/core/lib/schemas/date.ts new file mode 100644 index 0000000..5245be4 --- /dev/null +++ b/core/lib/schemas/date.ts @@ -0,0 +1,96 @@ +import { NULL } from "../constants.js"; +import { BaseValidator, type BaseSchema } from "./base.js"; + +export type DateSchema = BaseSchema & { + kind: "date"; + min?: Date; + max?: Date; +}; + +export class DateValidator< + O extends boolean = false, + N extends boolean = false +> extends BaseValidator { + constructor() { + super({ kind: "date", optional: false, nullable: false }); + } + + min(date: Date) { + this.$schema.min = date; + return this as DateValidator; + } + + max(date: Date) { + this.$schema.max = date; + return this as DateValidator; + } + private isStringADate(value: string): boolean { + try { + return !isNaN(new Date(value).getTime()); + } catch (error) { + return false; + } + } + + private get defaultValue(): Date | null { + return this.$schema.default || null; + } + + isValid = (value: any): boolean => { + if (value instanceof Date) { + if (this.$schema.min && value < this.$schema.min) { + return false; + } + if (this.$schema.max && value > this.$schema.max) { + return false; + } + return true; + } + if (value === NULL || value === null) { + return this.$schema.nullable; + } + if (value === undefined) { + return this.$schema.optional; + } + + return false; + }; + + parse(value: string | null): { isValid: boolean; value: Date | null } { + const coerced = this.coerce(value); + return { + isValid: this.isValid(coerced), + value: coerced, + }; + } + coerce(value: string | null): Date | null { + if (value === NULL || value === null || value === undefined) { + if (this.$schema.nullable) { + return null; + } else { + return this.defaultValue; + } + } + + if (value === undefined) { + return this.$schema.optional ? null : this.defaultValue; + } + + if (this.isStringADate(value)) { + return new Date(value); + } + + return this.$schema.nullable ? null : this.defaultValue; + } + stringify = (value: any): string => { + if (value instanceof Date) { + return value.toISOString(); + } else { + if (this.$schema.nullable) { + return NULL; + } else { + return this.defaultValue?.toISOString() || NULL; + } + } + }; +} diff --git a/package/src/lib/schemas/discriminatedUnion.ts b/core/lib/schemas/discriminatedUnion.ts similarity index 100% rename from package/src/lib/schemas/discriminatedUnion.ts rename to core/lib/schemas/discriminatedUnion.ts diff --git a/package/src/lib/schemas/enum.ts b/core/lib/schemas/enum.ts similarity index 100% rename from package/src/lib/schemas/enum.ts rename to core/lib/schemas/enum.ts diff --git a/package/src/lib/schemas/literal.ts b/core/lib/schemas/literal.ts similarity index 100% rename from package/src/lib/schemas/literal.ts rename to core/lib/schemas/literal.ts diff --git a/package/src/lib/schemas/map.ts b/core/lib/schemas/map.ts similarity index 100% rename from package/src/lib/schemas/map.ts rename to core/lib/schemas/map.ts diff --git a/package/src/lib/schemas/number.ts b/core/lib/schemas/number.ts similarity index 100% rename from package/src/lib/schemas/number.ts rename to core/lib/schemas/number.ts diff --git a/package/src/lib/schemas/object.ts b/core/lib/schemas/object.ts similarity index 100% rename from package/src/lib/schemas/object.ts rename to core/lib/schemas/object.ts diff --git a/package/src/lib/schemas/richtext.ts b/core/lib/schemas/richtext.ts similarity index 100% rename from package/src/lib/schemas/richtext.ts rename to core/lib/schemas/richtext.ts diff --git a/core/lib/schemas/schema.ts b/core/lib/schemas/schema.ts new file mode 100644 index 0000000..d1fc4e8 --- /dev/null +++ b/core/lib/schemas/schema.ts @@ -0,0 +1,169 @@ +import type { Simplify } from "../types.js"; +import { ArrayValidator, type ArraySchema } from "./array.js"; +import { BaseValidator, type BaseSchema } from "./base.js"; +import { BooleanValidator, type BooleanSchema } from "./boolean.js"; +import { DateValidator, type DateSchema } from "./date.js"; +import { EnumValidator, type EnumSchema } from "./enum.js"; +import { StringValidator, type StringSchema } from "./string.js"; +import { + ObjectValidator, + type ObjectSchema, + type ObjectShape, +} from "./object.js"; +import { RichTextValidator, type RichTextSchema } from "./richtext.js"; +import { NumberValidator, type NumberSchema } from "./number.js"; +import { SetValidator, type SetSchema } from "./set.js"; +import type { State } from "../proxys/syncroState.js"; +import type { + Array as YArray, + Map as YMap, + AbstractType, + Text as YText, +} from "yjs"; +import { MapValidator, type MapSchema } from "./map.js"; +import { LiteralValidator, type LiteralSchema } from "./literal.js"; +import { + DiscriminatedUnionValidator, + type DiscriminatedUnionSchema, + type InferDiscriminatedUnionType, +} from "./discriminatedUnion.js"; + +export type Schema = + | ArraySchema + | ObjectSchema + | BooleanSchema + | DateSchema + | StringSchema + | NumberSchema + // | RichTextSchema + | EnumSchema + | SetSchema + | MapSchema + | LiteralSchema + | DiscriminatedUnionSchema; + +export type PrimitiveValidator = BaseValidator; +export type Validator = + | PrimitiveValidator + | ArrayValidator + | ObjectValidator + | SetValidator + | MapValidator + | LiteralValidator + | DiscriminatedUnionValidator; + +export const y = { + boolean: () => new BooleanValidator(), + date: () => new DateValidator(), + enum: (...values: T[]) => + new EnumValidator(...values), + string: () => new StringValidator(), + richText: () => new RichTextValidator(), + object: (shape: T) => new ObjectValidator(shape), + array: (shape: T) => new ArrayValidator(shape), + number: () => new NumberValidator(), + set: (shape: T) => new SetValidator(shape), + map: (shape: T) => new MapValidator(shape), + literal: (value: T) => + new LiteralValidator(value), + discriminatedUnion: < + K extends string, + T extends ObjectValidator[] + >( + discriminantKey: K, + variants: T + ) => new DiscriminatedUnionValidator(discriminantKey, variants), +}; + +// I need to add these properties as optional because of typescript. +// Looking for a better solution. + +type Optional = N extends true + ? T | undefined + : T; + +type InferYTypeFromShape = + Shape extends ObjectShape + ? { + [K in keyof Shape]: Optional< + InferYTypeFromShape, + Shape[K]["$schema"]["optional"] + >; + } + : Shape extends ArrayValidator + ? YArray + : Shape extends ObjectValidator + ? YMap + : Shape extends SetValidator + ? YArray + : Shape extends MapValidator + ? YMap + : YText; + +type Getters< + T extends "object" | "array" | "map", + Shape extends Validator | ObjectShape +> = { + getState?: () => State; + getYTypes?: () => T extends "array" + ? AbstractType[] + : T extends "map" + ? Record> + : InferYTypeFromShape; + getYType?: () => T extends "array" ? YArray : YMap; +}; + +type WithGetters< + T, + TT extends "object" | "array" | "map", + Shape extends Validator | ObjectShape, + N extends boolean = false, + O extends boolean = false +> = O extends true ? T : N extends true ? T : T & Getters; + +type NORO = N extends true + ? O extends true + ? T | null | undefined + : T | null + : O extends true + ? T | undefined + : T; + +export type InferSchemaType = T extends DiscriminatedUnionValidator< + infer K, + infer Variants, + infer O, + infer N +> + ? NORO> + : T extends ObjectValidator + ? NORO> + : T extends BaseValidator + ? NORO ? T : never> + : T extends ArrayValidator + ? WithGetters[]>, "array", Shape, N, O> + : T extends SetValidator + ? WithGetters>>, "array", Shape, N, O> + : T extends MapValidator + ? WithGetters< + NORO>>, + "map", + Shape, + N, + O + > + : never; + +export type SchemaOutput = WithGetters< + Simplify<{ + [K in keyof T]: InferSchemaType; + }>, + "object", + T, + false, + false +>; + +export type RawSchemaOutput = Simplify<{ + [K in keyof T]: InferSchemaType; +}>; diff --git a/package/src/lib/schemas/set.ts b/core/lib/schemas/set.ts similarity index 100% rename from package/src/lib/schemas/set.ts rename to core/lib/schemas/set.ts diff --git a/package/src/lib/schemas/string.ts b/core/lib/schemas/string.ts similarity index 100% rename from package/src/lib/schemas/string.ts rename to core/lib/schemas/string.ts diff --git a/package/src/lib/types.ts b/core/lib/types.ts similarity index 100% rename from package/src/lib/types.ts rename to core/lib/types.ts diff --git a/core/lib/utils.ts b/core/lib/utils.ts new file mode 100644 index 0000000..6e53075 --- /dev/null +++ b/core/lib/utils.ts @@ -0,0 +1,224 @@ +import * as Y from "yjs"; +import type { Validator } from "./schemas/schema.js"; +import { NULL, NULL_ARRAY } from "./constants.js"; +import type { BaseValidator } from "./schemas/base.js"; +import { DEV } from "esm-env"; +import type { SyncedArray } from "./proxys/array.js"; +import { SyncedSet } from "./proxys/set.js"; +import { createSyncroState } from "./proxys/syncroState.js"; + +export const isMissingOptionnal = ({ + parent, + key, + validator, +}: { + parent: Y.Map | Y.Array; + key: string | number; + validator: Validator; +}) => { + const exists = + parent instanceof Y.Map + ? parent.has(String(key)) + : !!parent.get(Number(key)); + const isMissingOptionnal = validator.$schema.optional && !exists; + const hasDefault = validator.$schema.default !== undefined; + return isMissingOptionnal && !hasDefault; +}; + +export const getInitialStringifiedValue = ( + value: any, + validator: Validator +) => { + if ( + validator.$schema.kind === "array" || + validator.$schema.kind === "object" || + validator.$schema.kind === "map" || + validator.$schema.kind === "set" || + validator.$schema.kind === "discriminatedUnion" + ) { + return undefined; + } + const DEFAULT_VALUE = + value === null ? null : value ?? validator.$schema.default; + + const isValid = validator.isValid(DEFAULT_VALUE); + if (!isValid) { + if (validator.$schema.nullable) { + return NULL; + } + return undefined; + } + if (DEFAULT_VALUE !== undefined) { + const stringifiedDefaultValue = (validator as BaseValidator).stringify( + DEFAULT_VALUE + ); + + return stringifiedDefaultValue; + } +}; + +export const getTypeFromParent = < + T extends Y.Array | Y.Map | Y.Text +>({ + parent, + key, + validator, + forceNewType, + value, +}: { + parent: Y.Map | Y.Array; + key: string | number; + value?: string; + forceNewType?: boolean; + validator: Validator; +}): T => { + const isArray = parent instanceof Y.Array; + const instance = getInstance(validator) as new () => + | Y.Array + | Y.Map + | Y.Text; + const isText = instance === Y.Text; + const stringifiedValue = getInitialStringifiedValue(value, validator); + + const type = isText ? new Y.Text(stringifiedValue) : new instance(); + const typeInParent = ( + isArray ? parent.get(Number(key)) : parent.get(String(key)) + ) as T; + + const setAndReturnType = () => { + if (isArray) { + parent.insert(Number(key), [type]); + } else { + parent.delete(String(key)); + parent.set(String(key), type); + } + return type as T; + }; + + if (!typeInParent || typeInParent._item?.deleted || forceNewType) { + return setAndReturnType() as T; + } + + if (!(typeInParent instanceof instance)) { + return setAndReturnType() as T; + } else { + return typeInParent as T; + } +}; + +export const getInstance = ( + validator: Validator +): (new () => Y.AbstractType) | null => { + switch (validator.$schema.kind) { + case "map": + case "object": + case "discriminatedUnion": + return Y.Map; + case "set": + case "array": + return Y.Array; + default: + return Y.Text; + } +}; + +export const logError = (...args: any[]) => { + if (DEV) { + console.error(...args); + } +}; + +export const isInitialized = ({ yType }: { yType: Y.AbstractType }) => { + // @ts-ignore + return yType.doc?.initialized; +}; + +// From https://github.com/YousefED/SyncedStore/blob/main/packages/core/src/array.ts +export const propertyToNumber = (p: string | number | symbol) => { + if (typeof p === "string" && p.trim().length) { + const asNum = Number(p); + if (Number.isInteger(asNum)) { + return asNum; + } + } + return p; +}; + +export function setArrayToNull(this: SyncedArray | SyncedSet) { + this.state.transaction(() => { + this.syncroStates.value.forEach((state) => state.destroy()); + if (this instanceof SyncedSet) { + this.syncroStatesValues.clear(); + } + this.syncroStates.value = []; + this.isNull.value = true; + this.yType.delete(0, this.yType.length); + this.yType.insert(0, [new Y.Text(NULL_ARRAY)]); + }); +} + +export const isArrayNull = ({ yType }: { yType: Y.Array }) => { + return ( + yType.length === 1 && + yType.get(0) instanceof Y.Text && + yType.get(0).toString() === NULL_ARRAY + ); +}; + +export function observeArray( + this: SyncedArray | SyncedSet, + e: Y.YArrayEvent, + _transaction: Y.Transaction +) { + if (_transaction.origin !== this.state.transactionKey) { + if (isArrayNull(this)) { + this.isNull.value = true; + return; + } + + let start = 0; + e.delta.forEach(({ retain, delete: _delete, insert }) => { + if (retain) { + start += retain; + } + if (_delete) { + const deleted = this.syncroStates.value.splice(start, _delete); + deleted.forEach((state) => { + state.destroy(); + }); + start -= _delete; + } + if (Array.isArray(insert)) { + for (let i = 0; i < insert.length; i++) { + if ( + insert[i] instanceof Y.Text && + insert[i].toString() === NULL_ARRAY + ) { + this.isNull.value = true; + return; + } + this.syncroStates.value.splice( + start, + 0, + createSyncroState({ + key: start, + validator: this.validator.$schema.shape, + parent: this, + state: this.state, + }) + ); + start += 1; + } + } + }); + + if (this instanceof SyncedSet) { + this.syncroStatesValues.clear(); + this.syncroStates.value + .map((state) => state.value) + .forEach((value) => { + this.syncroStatesValues.add(value); + }); + } + } +} diff --git a/core/package.json b/core/package.json new file mode 100644 index 0000000..4ae7753 --- /dev/null +++ b/core/package.json @@ -0,0 +1,36 @@ +{ + "name": "@syncrostate/core", + "module": "index.ts", + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "scripts": { + "build": "tsdown", + "build:watch": "tsdown --watch", + "test": "vitest", + "test:ui": "vitest --ui", + "patch": "npm version patch && npm run build && npm publish" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "@vitest/coverage-v8": "^3.2.4", + "esm-env": "^1.2.2", + "y-protocols": "^1.0.6", + "yjs": "^13.6.27" + }, + "devDependencies": { + "@vitest/ui": "^3.2.4", + "alien-signals": "^3.0.0", + "tsdown": "^0.15.5", + "typescript": "^5.9.2", + "vitest": "^3.2.4" + } +} diff --git a/core/tests/fixture.ts b/core/tests/fixture.ts new file mode 100644 index 0000000..8414e0d --- /dev/null +++ b/core/tests/fixture.ts @@ -0,0 +1,73 @@ +import { computed, effect, signal } from "alien-signals"; +import type { Provider } from "../lib/provider.js"; +import type { ObjectShape } from "../lib/schemas/object.js"; +import { + syncroState, + type SyncroStateOptions, +} from "../lib/proxys/syncroState.js"; + +// Polyfill for $state in test environment +(globalThis as any).$state = (value: T): T => value; + +const vanillaState = (initialValue: T) => { + let state = signal(initialValue); + return { + get value() { + return state(); + }, + set value(value: T) { + state(value); + }, + }; +}; + +const vanillaEffect = (cb: () => void, deps?: any[]) => { + effect(() => { + cb(); + }); +}; + +const vanillaDerived = (cb: () => T) => { + const derivedValue = computed(cb); + return { + get value() { + return derivedValue(); + }, + }; +}; + +export class VanillaProvider implements Provider { + private context = new Map(); + + getContext = (key: string): T => { + if (!this.context.has(key)) { + throw new Error(`Context key "${key}" not found`); + } + return this.context.get(key) as T; + }; + + setContext = (key: string, value: T): void => { + this.context.set(key, value); + }; + state = vanillaState; + effect = vanillaEffect; + derived = vanillaDerived; + Map = Map; + Set = Set; + onMount = (cb: () => void) => { + cb(); + }; + onDestroy = (cb: () => void) => { + // cb(); + }; + Date = Date; +} + +export const createSyncroState = ( + options: Omit, "provider"> +) => { + return syncroState({ + ...options, + provider: new VanillaProvider(), + }); +}; diff --git a/core/tests/integration/discriminatedUnion.integration.test.ts b/core/tests/integration/discriminatedUnion.integration.test.ts new file mode 100644 index 0000000..2191ae2 --- /dev/null +++ b/core/tests/integration/discriminatedUnion.integration.test.ts @@ -0,0 +1,199 @@ +import { describe, it, expect } from "vitest"; +import { y } from "../../lib/index.js"; +import { createSyncroState } from "../fixture.js"; +import * as Y from "yjs"; + +describe("DiscriminatedUnion Integration Tests", () => { + it("should work with the full syncroState system", () => { + const apiResponseSchema = y.discriminatedUnion("status", [ + y.object({ status: y.literal("success"), data: y.string() }), + y.object({ status: y.literal("error"), message: y.string() }), + ]); + + const schema = { + response: apiResponseSchema, + }; + + const doc = new Y.Doc(); + const state1 = createSyncroState({ schema, doc }); + const state2 = createSyncroState({ schema, doc }); + + // Test initial state + expect(state1.response).toBeDefined(); + expect(state2.response).toBeDefined(); + + // Test setting success variant + state1.response = { status: "success", data: "Hello world" }; + + expect(state1.response.status).toBe("success"); + expect(state1.response.data).toBe("Hello world"); + expect(state2.response.status).toBe("success"); + if (state2.response.status === "success") { + expect(state2.response.data).toBe("Hello world"); + } + + // Test switching to error variant + state1.response = { status: "error", message: "Something went wrong" }; + + expect(state1.response.status).toBe("error"); + expect(state1.response.message).toBe("Something went wrong"); + expect(state2.response.status).toBe("error"); + if (state2.response.status === "error") { + expect(state2.response.message).toBe("Something went wrong"); + } + + console.log(JSON.stringify(state1.response, null, 2)); + // Test property access + expect("status" in state1.response).toBe(true); + expect("message" in state1.response).toBe(true); + expect("data" in state1.response).toBe(false); + + // Test Object.keys + const keys = Object.keys(state1.response); + expect(keys).toContain("status"); + expect(keys).toContain("message"); + expect(keys).not.toContain("data"); + + // Test JSON serialization + const json = JSON.stringify(state1.response); + const parsed = JSON.parse(json); + expect(parsed.status).toBe("error"); + expect(parsed.message).toBe("Something went wrong"); + }); + + it("should handle nullable discriminated unions", () => { + const schema = { + result: y + .discriminatedUnion("type", [ + y.object({ type: y.literal("data"), value: y.string() }), + y.object({ type: y.literal("error"), code: y.number() }), + ]) + .nullable(), + }; + + const doc = new Y.Doc(); + const state = createSyncroState({ schema, doc }); + + // Test null assignment + state.result = null; + expect(state.result).toBeNull(); + + // Test setting value after null + state.result = { type: "data", value: "test" }; + expect(state.result.type).toBe("data"); + expect(state.result.value).toBe("test"); + }); + + it("should handle complex nested discriminated unions", () => { + const userSchema = y.discriminatedUnion("role", [ + y.object({ + role: y.literal("admin"), + name: y.string(), + permissions: y.array(y.string()), + }), + y.object({ + role: y.literal("user"), + name: y.string(), + email: y.string(), + }), + y.object({ role: y.literal("guest"), sessionId: y.string() }), + ]); + + const schema = { user: userSchema }; + const doc = new Y.Doc(); + const state1 = createSyncroState({ schema, doc }); + const state2 = createSyncroState({ schema, doc }); + + // Test admin user + state1.user = { + role: "admin", + name: "John Admin", + permissions: ["read", "write", "delete"], + }; + + expect(state2.user.role).toBe("admin"); + if (state2.user.role === "admin") { + expect(state2.user.name).toBe("John Admin"); + expect(state2.user.permissions).toEqual(["read", "write", "delete"]); + } + + // Test switching to regular user + state1.user = { + role: "user", + name: "Jane User", + email: "jane@example.com", + }; + + expect(state2.user.role).toBe("user"); + if (state2.user.role === "user") { + expect(state2.user.name).toBe("Jane User"); + expect(state2.user.email).toBe("jane@example.com"); + } + expect("permissions" in state2.user).toBe(false); + + // Test switching to guest + state1.user = { + role: "guest", + sessionId: "guest-123", + }; + + expect(state2.user.role).toBe("guest"); + if (state2.user.role === "guest") { + expect(state2.user.sessionId).toBe("guest-123"); + } + expect("name" in state2.user).toBe(false); + expect("email" in state2.user).toBe(false); + }); +}); + +describe("DiscriminatedUnion Integration Tests Complex", () => { + it("should work with nested containers", () => { + const apiResponseSchema = y.discriminatedUnion("status", [ + y.object({ + status: y.literal("success"), + data: y.string(), + nested: y.object({ nested: y.string() }), + }), + y.object({ + status: y.literal("error"), + message: y.string(), + nested: y.array(y.object({ nested: y.string() })), + }), + ]); + + const schema = { + response: apiResponseSchema, + }; + + const doc = new Y.Doc(); + const state1 = createSyncroState({ schema, doc }); + const state2 = createSyncroState({ schema, doc }); + + state1.response = { + status: "success", + data: "Hello world", + nested: { nested: "nested" }, + }; + + expect(state2.response.status).toBe("success"); + + if (state2.response.status === "success") { + expect(state2.response.nested.nested).toBe("nested"); + } else { + expect(true).toBe(false); + } + + state1.response = { + status: "error", + message: "Something went wrong", + nested: [{ nested: "nested" }], + }; + + if (state2.response.status === "error") { + expect(state2.response.nested[0].nested).toBe("nested"); + expect("nested" in state2.response.nested).toBe(false); + } else { + expect(true).toBe(false); + } + }); +}); diff --git a/core/tests/proxys/array.bounds.test.ts b/core/tests/proxys/array.bounds.test.ts new file mode 100644 index 0000000..b9563ae --- /dev/null +++ b/core/tests/proxys/array.bounds.test.ts @@ -0,0 +1,193 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { y } from "../../lib/index.js"; +import { createSyncroState } from "../fixture.js"; +import * as Y from "yjs"; + +describe("Array Bounds Validation Tests", () => { + const doc = new Y.Doc(); + const schema = { + testArray: y.array(y.string()), + }; + + const state = createSyncroState({ schema, doc }); + + beforeEach(() => { + state.testArray = ["a", "b", "c", "d", "e"]; + }); + + describe("Negative start index handling", () => { + it("should handle negative start index like native Array.splice", () => { + // Test -1 (last element) + state.testArray.splice(-1, 1, "z"); + expect([...state.testArray]).toEqual(["a", "b", "c", "d", "z"]); + + // Reset + state.testArray = ["a", "b", "c", "d", "e"]; + + // Test -2 (second to last) + state.testArray.splice(-2, 1, "y"); + expect([...state.testArray]).toEqual(["a", "b", "c", "y", "e"]); + + // Reset + state.testArray = ["a", "b", "c", "d", "e"]; + + // Test negative index beyond array length (should start at 0) + state.testArray.splice(-10, 1, "start"); + expect([...state.testArray]).toEqual(["start", "b", "c", "d", "e"]); + }); + + it("should handle negative start with Y.js document consistency", () => { + state.testArray.splice(-1, 1, "last"); + + const yArray = state.testArray.getYType?.(); + expect(yArray!.length).toBe(5); + expect(yArray!.toJSON()).toEqual(["a", "b", "c", "d", "last"]); + }); + }); + + describe("Start index beyond array length", () => { + it("should handle start index beyond array length", () => { + // Insert at end when start > length + state.testArray.splice(10, 0, "end1", "end2"); + expect([...state.testArray]).toEqual([ + "a", + "b", + "c", + "d", + "e", + "end1", + "end2", + ]); + }); + + it("should handle start index beyond array length with Y.js consistency", () => { + state.testArray.splice(100, 0, "far"); + + const yArray = state.testArray.getYType?.(); + expect(yArray!.length).toBe(6); + expect(yArray!.toJSON()).toEqual(["a", "b", "c", "d", "e", "far"]); + }); + }); + + describe("DeleteCount validation", () => { + it("should handle deleteCount larger than remaining elements", () => { + // Try to delete 100 items starting at index 2 (only 3 items remain) + const result = state.testArray.splice(2, 100, "x"); + + expect([...state.testArray]).toEqual(["a", "b", "x"]); + expect(result.length).toBe(3); // Should return the 3 deleted items + }); + + it("should handle deleteCount larger than remaining with Y.js consistency", () => { + state.testArray.splice(1, 1000); + + const yArray = state.testArray.getYType?.(); + expect(yArray!.length).toBe(1); + expect(yArray!.toJSON()).toEqual(["a"]); + }); + + it("should handle negative deleteCount (should be treated as 0)", () => { + const originalLength = state.testArray.length; + state.testArray.splice(2, -5, "inserted"); + + // Should only insert, not delete anything + expect(state.testArray.length).toBe(originalLength + 1); + expect([...state.testArray]).toEqual([ + "a", + "b", + "inserted", + "c", + "d", + "e", + ]); + }); + }); + + describe("Edge cases", () => { + it("should handle empty array splice operations", () => { + state.testArray = []; + + // Insert into empty array + state.testArray.splice(0, 0, "first"); + expect([...state.testArray]).toEqual(["first"]); + + // Insert beyond bounds in empty array + state.testArray.splice(10, 0, "second"); + expect([...state.testArray]).toEqual(["first", "second"]); + }); + + it("should handle zero deleteCount with insertions", () => { + state.testArray.splice(2, 0, "x", "y"); + expect([...state.testArray]).toEqual(["a", "b", "x", "y", "c", "d", "e"]); + expect(state.testArray.length).toBe(7); + }); + + it("should handle splice with no insertions (deletion only)", () => { + state.testArray.splice(1, 3); + expect([...state.testArray]).toEqual(["a", "e"]); + expect(state.testArray.length).toBe(2); + }); + + it("should handle single element array operations", () => { + state.testArray = ["only"]; + + // Replace single element + state.testArray.splice(0, 1, "replaced"); + expect([...state.testArray]).toEqual(["replaced"]); + + // Delete single element + state.testArray.splice(0, 1); + expect([...state.testArray]).toEqual([]); + expect(state.testArray.length).toBe(0); + }); + }); + + describe("Boundary stress tests", () => { + it("should handle multiple boundary violations in sequence", () => { + // Start with fresh array + state.testArray = ["a", "b", "c"]; + + // Negative start, excessive deleteCount + state.testArray.splice(-10, 100, "x"); + expect([...state.testArray]).toEqual(["x"]); + + // Beyond bounds start + state.testArray.splice(100, 0, "y"); + expect([...state.testArray]).toEqual(["x", "y"]); + + // Verify Y.js consistency + const yArray = state.testArray.getYType?.(); + expect(yArray!.toJSON()).toEqual(["x", "y"]); + }); + + it('should not throw "Length exceeded!" errors with invalid bounds', () => { + // These operations should not crash + expect(() => { + state.testArray.splice(-1000, 1000, "safe"); + }).not.toThrow(); + + expect(() => { + state.testArray.splice(1000, 1000, "also-safe"); + }).not.toThrow(); + + expect(() => { + state.testArray.splice(0, -100); + }).not.toThrow(); + }); + }); + + describe("Return value validation", () => { + it("should return correct deleted elements", () => { + const deleted = state.testArray.splice(1, 2, "x"); + + // Should return the deleted SyncroState objects + expect(deleted.length).toBe(2); + // The actual values depend on the SyncroState implementation + }); + + it("should return empty array when no elements deleted", () => { + const deleted = state.testArray.splice(2, 0, "inserted"); + expect(deleted.length).toBe(0); + }); + }); +}); diff --git a/core/tests/proxys/array.proxy.test.ts b/core/tests/proxys/array.proxy.test.ts new file mode 100644 index 0000000..833bb4b --- /dev/null +++ b/core/tests/proxys/array.proxy.test.ts @@ -0,0 +1,389 @@ +import { + describe, + it, + expect, + beforeEach, + afterEach, + test, + beforeAll, +} from "vitest"; +import { y } from "../../lib/index.js"; +import { createSyncroState } from "../fixture.js"; + +let createDocument = () => + createSyncroState({ + schema: { + array: y.array(y.string()), + nullableArray: y.array(y.string()).nullable(), + optionalArray: y.array(y.string()).optional(), + nullableOptionalArray: y.array(y.string()).nullable().optional(), + arrayWithDefault: y.array(y.string()).default(["default"]), + arrayWithDefaultAndOptional: y + .array(y.string()) + .default(["default"]) + .optional(), + arrayWithDefaultAndNullable: y + .array(y.string()) + .default(["default"]) + .nullable(), + arrayWithDefaultAndNullableAndOptional: y + .array(y.string()) + .default(["default"]) + .nullable() + .optional(), + }, + }); + +let state = createDocument(); + +describe("ArrayProxy", () => { + describe("Initial values", () => { + it("should be an array", () => { + expect(Array.isArray(state.array)).toBe(true); + expect(state.array.length).toBe(0); + }); + it("should have null as default value for nullable array", () => { + expect(state.nullableArray).toBe(null); + }); + it("should have undefined as default value for optional array", () => { + expect(state.optionalArray).toBe(undefined); + }); + it("should have undefined as default value for nullable optional array", () => { + expect(state.nullableOptionalArray).toBe(undefined); + }); + it("should have default value for array with default", () => { + expect(state.arrayWithDefault).toEqual(["default"]); + }); + it("should have default value for optional array with default", () => { + expect(state.arrayWithDefaultAndOptional).toEqual(["default"]); + }); + it("should have default value for nullable array with default", () => { + expect(state.arrayWithDefaultAndNullable).toEqual(["default"]); + }); + it("should have default value for nullable optional array with default", () => { + expect(state.arrayWithDefaultAndNullableAndOptional).toEqual(["default"]); + }); + }); + + describe("Setters", () => { + describe("Array", () => { + beforeEach(() => { + state.array = ["test"]; + }); + + it("should set the value", () => { + state.array = ["hello", "world"]; + expect(state.array).toEqual(["hello", "world"]); + }); + + it("should not set the value to null", () => { + (state.array as any) = null; + expect(state.array).toEqual(["test"]); + }); + + it("should not set the value to undefined", () => { + (state.array as any) = undefined; + expect(state.array).toEqual(["test"]); + }); + + it("should not set the value to a string", () => { + (state.array as any) = "invalid"; + expect(state.array).toEqual(["test"]); + }); + + it("should not set the value to a number", () => { + (state.array as any) = 123; + expect(state.array).toEqual(["test"]); + }); + + it("should not set invalid array items", () => { + (state.array as any) = [123, true, {}]; + expect(state.array).toEqual(["test"]); + }); + + it("should support array methods", async () => { + state.array.push("world"); + expect(state.array).toEqual(["test", "world"]); + + state.array.pop(); + expect(state.array).toEqual(["test"]); + + state.array.unshift("hello"); + expect(state.array).toEqual(["hello", "test"]); + + state.array.shift(); + expect(state.array).toEqual(["test"]); + + state.array = ["a", "b", "c"]; + expect(state.array.slice(1)).toEqual(["b", "c"]); + expect(state.array.map((x: string) => x.toUpperCase())).toEqual([ + "A", + "B", + "C", + ]); + expect(state.array.filter((x: string) => x !== "b")).toEqual([ + "a", + "c", + ]); + }); + }); + + describe("Nullable Array", () => { + beforeEach(async () => { + state.nullableArray = ["test"]; + }); + + it("should set the value", () => { + state.nullableArray = ["hello", "world"]; + expect(state.nullableArray).toEqual(["hello", "world"]); + }); + + it("should set the value to null", () => { + state.nullableArray = null; + expect(state.nullableArray).toBe(null); + }); + + it("should not set the value to undefined", () => { + (state.nullableArray as any) = undefined; + expect([...state.nullableArray]).toStrictEqual(["test"]); + }); + + it("should not set the value to a string", () => { + (state.nullableArray as any) = "invalid"; + expect([...state.nullableArray]).toStrictEqual(["test"]); + }); + + it("should not set the value to a number", () => { + (state.nullableArray as any) = 123; + expect([...state.nullableArray]).toStrictEqual(["test"]); + }); + + it("should not set invalid array items", () => { + (state.nullableArray as any) = [123, true, {}]; + expect([...state.nullableArray]).toStrictEqual(["test"]); + }); + }); + + describe("Optional Array", () => { + beforeAll(() => { + state.optionalArray = ["test"]; + }); + beforeEach(() => { + state.optionalArray = ["test"]; + }); + + test("should set the value", () => { + state.optionalArray = ["hello", "world"]; + expect(state.optionalArray).toEqual(["hello", "world"]); + }); + + test("should set the value to undefined", () => { + state.optionalArray = undefined; + expect(state.optionalArray).toBe(undefined); + }); + + test("should not set the value to null", () => { + (state.optionalArray as any) = null; + expect(state.optionalArray).toEqual(["test"]); + }); + + test("should not set the value to a string", () => { + (state.optionalArray as any) = "invalid"; + expect(state.optionalArray).toEqual(["test"]); + }); + + it("should not set the value to a number", () => { + (state.optionalArray as any) = 123; + expect(state.optionalArray).toEqual(["test"]); + }); + + it("should not set invalid array items", () => { + (state.optionalArray as any) = [123, true, {}]; + expect(state.optionalArray).toEqual(["test"]); + }); + }); + + describe("Nullable Optional Array", () => { + beforeEach(() => { + state.nullableOptionalArray = ["test"]; + }); + + it("should set the value", () => { + state.nullableOptionalArray = ["hello", "world"]; + expect(state.nullableOptionalArray).toEqual(["hello", "world"]); + }); + + it("should set the value to null", () => { + state.nullableOptionalArray = null; + expect(state.nullableOptionalArray).toBe(null); + }); + + it("should set the value to undefined", () => { + state.nullableOptionalArray = undefined; + expect(state.nullableOptionalArray).toBe(undefined); + }); + + it("should not set the value to a string", () => { + (state.nullableOptionalArray as any) = "invalid"; + expect(state.nullableOptionalArray).toEqual(["test"]); + }); + + it("should not set the value to a number", () => { + (state.nullableOptionalArray as any) = 123; + expect(state.nullableOptionalArray).toEqual(["test"]); + }); + + it("should not set invalid array items", () => { + (state.nullableOptionalArray as any) = [123, true, {}]; + expect(state.nullableOptionalArray).toEqual(["test"]); + }); + }); + + describe("Array With Default", () => { + beforeEach(() => { + state.arrayWithDefault = ["test"]; + }); + + it("should set the value", () => { + state.arrayWithDefault = ["hello", "world"]; + expect(state.arrayWithDefault).toEqual(["hello", "world"]); + }); + + it("should not set the value to null", () => { + (state.arrayWithDefault as any) = null; + expect(state.arrayWithDefault).toEqual(["test"]); + }); + + it("should not set the value to undefined", () => { + (state.arrayWithDefault as any) = undefined; + expect(state.arrayWithDefault).toEqual(["test"]); + }); + + it("should not set the value to a string", () => { + (state.arrayWithDefault as any) = "invalid"; + expect(state.arrayWithDefault).toEqual(["test"]); + }); + + it("should not set the value to a number", () => { + (state.arrayWithDefault as any) = 123; + expect(state.arrayWithDefault).toEqual(["test"]); + }); + + it("should not set invalid array items", () => { + (state.arrayWithDefault as any) = [123, true, {}]; + expect(state.arrayWithDefault).toEqual(["test"]); + }); + }); + + describe("Array With Default And Optional", () => { + beforeEach(() => { + state.arrayWithDefaultAndOptional = ["test"]; + }); + + it("should set the value", () => { + state.arrayWithDefaultAndOptional = ["hello", "world"]; + expect(state.arrayWithDefaultAndOptional).toEqual(["hello", "world"]); + }); + + it("should not set the value to null", () => { + (state.arrayWithDefaultAndOptional as any) = null; + expect(state.arrayWithDefaultAndOptional).toEqual(["test"]); + }); + + it("should set the value to undefined", () => { + state.arrayWithDefaultAndOptional = undefined; + expect(state.arrayWithDefaultAndOptional).toBe(undefined); + }); + + it("should not set the value to a string", () => { + (state.arrayWithDefaultAndOptional as any) = "invalid"; + expect(state.arrayWithDefaultAndOptional).toEqual(["test"]); + }); + + it("should not set the value to a number", () => { + (state.arrayWithDefaultAndOptional as any) = 123; + expect(state.arrayWithDefaultAndOptional).toEqual(["test"]); + }); + + it("should not set invalid array items", () => { + (state.arrayWithDefaultAndOptional as any) = [123, true, {}]; + expect(state.arrayWithDefaultAndOptional).toEqual(["test"]); + }); + }); + + describe("Array With Default And Nullable", () => { + beforeEach(() => { + state.arrayWithDefaultAndNullable = ["test"]; + }); + + it("should set the value", () => { + state.arrayWithDefaultAndNullable = ["hello", "world"]; + expect(state.arrayWithDefaultAndNullable).toEqual(["hello", "world"]); + }); + + it("should set the value to null", () => { + state.arrayWithDefaultAndNullable = null; + expect(state.arrayWithDefaultAndNullable).toBe(null); + }); + + it("should not set the value to undefined", () => { + (state.arrayWithDefaultAndNullable as any) = undefined; + expect([...state.arrayWithDefaultAndNullable]).toStrictEqual(["test"]); + }); + + it("should not set the value to a string", () => { + (state.arrayWithDefaultAndNullable as any) = "invalid"; + expect([...state.arrayWithDefaultAndNullable]).toStrictEqual(["test"]); + }); + + it("should not set the value to a number", () => { + (state.arrayWithDefaultAndNullable as any) = 123; + expect([...state.arrayWithDefaultAndNullable]).toStrictEqual(["test"]); + }); + + it("should not set invalid array items", () => { + (state.arrayWithDefaultAndNullable as any) = [123, true, {}]; + expect([...state.arrayWithDefaultAndNullable]).toStrictEqual(["test"]); + }); + }); + + describe("Array With Default And Nullable And Optional", () => { + beforeEach(() => { + state.arrayWithDefaultAndNullableAndOptional = ["test"]; + }); + + it("should set the value", () => { + state.arrayWithDefaultAndNullableAndOptional = ["hello", "world"]; + expect(state.arrayWithDefaultAndNullableAndOptional).toEqual([ + "hello", + "world", + ]); + }); + + it("should set the value to null", () => { + state.arrayWithDefaultAndNullableAndOptional = null; + expect(state.arrayWithDefaultAndNullableAndOptional).toBe(null); + }); + + it("should set the value to undefined", () => { + state.arrayWithDefaultAndNullableAndOptional = undefined; + expect(state.arrayWithDefaultAndNullableAndOptional).toBe(undefined); + }); + + it("should not set the value to a string", () => { + (state.arrayWithDefaultAndNullableAndOptional as any) = "invalid"; + expect(state.arrayWithDefaultAndNullableAndOptional).toEqual(["test"]); + }); + + it("should not set the value to a number", () => { + (state.arrayWithDefaultAndNullableAndOptional as any) = 123; + expect(state.arrayWithDefaultAndNullableAndOptional).toEqual(["test"]); + }); + + it("should not set invalid array items", () => { + (state.arrayWithDefaultAndNullableAndOptional as any) = [123, true, {}]; + expect(state.arrayWithDefaultAndNullableAndOptional).toEqual(["test"]); + }); + }); + }); +}); diff --git a/core/tests/proxys/array.splice.diagnostic.test.ts b/core/tests/proxys/array.splice.diagnostic.test.ts new file mode 100644 index 0000000..fbdbd41 --- /dev/null +++ b/core/tests/proxys/array.splice.diagnostic.test.ts @@ -0,0 +1,220 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { y } from "../../lib/index.js"; +import { createSyncroState } from "../fixture.js"; +import * as Y from "yjs"; + +const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +describe("Array Splice Diagnostic Tests", () => { + const doc = new Y.Doc(); + const schema = { + testArray: y.array(y.string()), + }; + + const state = createSyncroState({ schema, doc }); + + // Initialize with some data + + beforeEach(() => { + state.testArray = ["a", "b", "c", "d", "e"]; + }); + + // it('should verify initial Y.js document setup', () => { + // state.testArray = ['a', 'b', 'c', 'd', 'e']; + + // // Test 1: Check if Y.js document has the array + // const yArray = state.testArray.getYType?.(); + // console.log('Initial Y.js array length:', yArray!.length); + // console.log('Initial Y.js array content:', yArray!.toArray()); + // console.log('Initial proxy array length:', state.testArray.length); + // console.log('Initial proxy array content:', [...state.testArray]); + + // // Basic assertions + // expect(state.testArray.length).toBe(5); + // expect([...state.testArray]).toEqual(['a', 'b', 'c', 'd', 'e']); + + // // Critical test: Y.js document should match proxy + // expect(yArray!.length).toBe(5); + // expect(yArray!.toJSON()).toEqual(['a', 'b', 'c', 'd', 'e']); + // }); + + // it('should verify Y.js document state after simple splice', () => { + // // Before splice + // const yArrayBefore = state.testArray.getYType?.(); + // console.log('Before splice - Y.js length:', yArrayBefore!.length); + // console.log('Before splice - Y.js content:', yArrayBefore!.toArray()); + + // // Perform splice + // state.testArray.splice(1, 1, 'x'); + + // // After splice + // const yArrayAfter = state.testArray.getYType?.(); + // console.log('After splice - Y.js length:', yArrayAfter!.length); + // console.log('After splice - Y.js content:', yArrayAfter!.toArray()); + // console.log('After splice - proxy length:', state.testArray.length); + // console.log('After splice - proxy content:', [...state.testArray]); + + // // Critical assertions + // expect(state.testArray.length).toBe(5); + // expect([...state.testArray]).toEqual(['a', 'x', 'c', 'd', 'e']); + // expect(yArrayAfter!.length).toBe(5); + // expect(yArrayAfter!.toJSON()).toEqual(['a', 'x', 'c', 'd', 'e']); + // }); + + // it('should verify Y.js document state after deletion-only splice', () => { + // // Before splice + // const yArrayBefore = state.testArray.getYType?.(); + // console.log('Before deletion splice - Y.js length:', yArrayBefore!.length); + + // // Perform deletion splice + // state.testArray.splice(1, 2); + + // // After splice + // const yArrayAfter = state.testArray.getYType?.(); + // console.log('After deletion splice - Y.js length:', yArrayAfter!.length); + // console.log('After deletion splice - Y.js content:', yArrayAfter!.toJSON()); + // console.log('After deletion splice - proxy length:', state.testArray.length); + // console.log('After deletion splice - proxy content:', [...state.testArray]); + + // // Critical assertions + // expect(state.testArray.length).toBe(3); + // expect([...state.testArray]).toEqual(['a', 'd', 'e']); + // expect(yArrayAfter!.length).toBe(3); + // expect(yArrayAfter!.toJSON()).toEqual(['a', 'd', 'e']); + // }); + + // it('should verify Y.js document state after insertion-only splice', () => { + // // Before splice + // const yArrayBefore = state.testArray.getYType?.(); + // console.log('Before insertion splice - Y.js length:', yArrayBefore!.length); + + // // Perform insertion splice + // state.testArray.splice(2, 0, 'x', 'y'); + + // // After splice + // const yArrayAfter = state.testArray.getYType?.(); + // console.log('After insertion splice - Y.js length:', yArrayAfter!.length); + // console.log('After insertion splice - Y.js content:', yArrayAfter!.toArray()); + // console.log('After insertion splice - proxy length:', state.testArray.length); + // console.log('After insertion splice - proxy content:', [...state.testArray]); + + // // Critical assertions + // expect(state.testArray.length).toBe(7); + // expect([...state.testArray]).toEqual(['a', 'b', 'x', 'y', 'c', 'd', 'e']); + // expect(yArrayAfter!.length).toBe(7); + // expect(yArrayAfter!.toJSON()).toEqual(['a', 'b', 'x', 'y', 'c', 'd', 'e']); + // }); + + // it('should verify empty array handling', () => { + // // Test empty array assignment + // console.log('Before clearing - proxy length:', state.testArray.length); + // console.log('Before clearing - proxy type:', typeof state.testArray); + + // state.testArray = []; + + // console.log('After clearing - proxy length:', state.testArray?.length); + // console.log('After clearing - proxy type:', typeof state.testArray); + // console.log('After clearing - proxy value:', state.testArray); + + // // Check if array becomes null (the reported issue) + // if (state.testArray === null) { + // console.log('ISSUE CONFIRMED: Array becomes null instead of empty array'); + // } + + // // Y.js document state + // const yArray = state.testArray?.getYType?.(); + // console.log('After clearing - Y.js length:', yArray?.length); + // console.log('After clearing - Y.js content:', yArray?.toArray()); + + // // This test will fail if the empty array issue exists + // expect(state.testArray).not.toBe(null); + // if (state.testArray) { + // expect(state.testArray.length).toBe(0); + // expect(yArray?.length).toBe(0); + // } + // }); + + it("should verify Y.js transaction behavior", () => { + // Check if transactions are working properly + console.log("Testing transaction behavior..."); + + let transactionCount = 0; + doc.on("update", () => { + transactionCount++; + console.log("Y.js document update event fired, count:", transactionCount); + }); + + // Perform splice + state.testArray.splice(1, 1, "transaction-test"); + + console.log("Total transaction count:", transactionCount); + console.log("Final Y.js content:", state.testArray.getYType?.()?.toArray()); + console.log("Final proxy content:", [...state.testArray]); + + // Should have at least one transaction + expect(transactionCount).toBeGreaterThan(0); + }); + + it("should verify syncroStates array consistency", () => { + // Access internal syncroStates if possible + const arrayProxy = state.testArray; + console.log("Array proxy type:", typeof arrayProxy); + console.log("Array proxy constructor:", arrayProxy.constructor.name); + + // Check if we can access internal state + if (arrayProxy.syncroStates) { + console.log("SyncroStates length:", arrayProxy.syncroStates.length); + console.log( + "SyncroStates types:", + arrayProxy.syncroStates.map((s) => typeof s) + ); + } + + if (arrayProxy.yType) { + console.log("YType length:", arrayProxy.yType.length); + console.log("YType content:", arrayProxy.yType.toArray()); + } + + // Perform splice and check internal consistency + state.testArray.splice(1, 1, "internal-test"); + + if (arrayProxy.syncroStates && arrayProxy.yType) { + console.log( + "After splice - SyncroStates length:", + arrayProxy.syncroStates.length + ); + console.log("After splice - YType length:", arrayProxy.yType.length); + + expect(arrayProxy.syncroStates.length).toBe(arrayProxy.yType.length); + expect(arrayProxy.syncroStates.length).toBe(state.testArray.length); + } + }); + + it("should verify multi-document synchronization setup", () => { + // Create second document and state + const schema = { testArray: y.array(y.string()) }; + const state2 = createSyncroState({ schema, doc }); + + // Initialize second state + state2.testArray = ["x", "y", "z"]; + + console.log("Doc1 initial:", state.testArray.getYType?.()?.toJSON()); + console.log("Doc2 initial:", state2.testArray.getYType?.()?.toJSON()); + + console.log("Doc1 after sync:", state.testArray.getYType?.()?.toJSON()); + console.log("Doc2 after sync:", state2.testArray.getYType?.()?.toJSON()); + console.log("State1 after sync:", [...state.testArray]); + console.log("State2 after sync:", [...state2.testArray]); + + // Test if states converge (they might not due to the sync issue) + const doc1Content = state.testArray.getYType?.()?.toJSON() || []; + const doc2Content = state2.testArray.getYType?.()?.toJSON() || []; + + if (JSON.stringify(doc1Content) !== JSON.stringify(doc2Content)) { + console.log("ISSUE: Documents did not converge properly"); + } + + // This might fail due to synchronization issues + expect(doc1Content).toEqual(doc2Content); + }); +}); diff --git a/core/tests/proxys/boolean.proxy.test.ts b/core/tests/proxys/boolean.proxy.test.ts new file mode 100644 index 0000000..3bd83ec --- /dev/null +++ b/core/tests/proxys/boolean.proxy.test.ts @@ -0,0 +1,346 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { y } from "../../lib/index.js"; +import { createSyncroState } from "../fixture.js"; + +const state = createSyncroState({ + schema: { + boolean: y.boolean(), + nullableBoolean: y.boolean().nullable(), + optionnalBoolean: y.boolean().optional(), + nullableOptionnalBoolean: y.boolean().nullable().optional(), + booleanWithDefault: y.boolean().default(false), + booleanWithDefaultAndOptional: y.boolean().default(false).optional(), + booleanWithDefaultAndNullable: y.boolean().default(false).nullable(), + booleanWithDefaultAndNullableAndOptional: y + .boolean() + .default(false) + .nullable() + .optional(), + }, +}); + +describe("BooleanProxy", () => { + describe("Initial values", () => { + it("should be a boolean", () => { + expect(state.boolean).toBeTypeOf("boolean"); + }); + + it("should have null as default value for nullable boolean", () => { + expect(state.nullableBoolean).toBe(null); + }); + + it("should have undefined as default value for optional boolean", () => { + expect(state.optionnalBoolean).toBe(undefined); + }); + + it("should have undefined as default value for nullable optional boolean", () => { + expect(state.nullableOptionnalBoolean).toBe(undefined); + }); + + it("should have default value for boolean with default", () => { + expect(state.booleanWithDefault).toBe(false); + }); + + it("should have default value for optional boolean with default", () => { + expect(state.booleanWithDefaultAndOptional).toBe(false); + }); + + it("should have default value for nullable boolean with default", () => { + expect(state.booleanWithDefaultAndNullable).toBe(false); + }); + + it("should have default value for nullable optional boolean with default", () => { + expect(state.booleanWithDefaultAndNullableAndOptional).toBe(false); + }); + }); + + describe("Setters", () => { + describe("Boolean", () => { + beforeEach(() => { + state.boolean = true; + }); + + it("should set the value", () => { + state.boolean = false; + expect(state.boolean).toBe(false); + }); + + it("should not set the value to null", () => { + state.boolean = null; + expect(state.boolean).toBe(true); + }); + + it("should not set the value to undefined", () => { + state.boolean = undefined; + expect(state.boolean).toBe(true); + }); + + it("should not set the value to a string", () => { + state.boolean = "true"; + expect(state.boolean).toBe(true); + }); + + it("should not set the value to a number", () => { + state.boolean = 1; + expect(state.boolean).toBe(true); + }); + + it("should not set the value to an object", () => { + state.boolean = {}; + expect(state.boolean).toBe(true); + }); + }); + + describe("Nullable Boolean", () => { + beforeEach(() => { + state.nullableBoolean = true; + }); + + it("should set the value", () => { + state.nullableBoolean = false; + expect(state.nullableBoolean).toBe(false); + }); + + it("should set the value to null", () => { + state.nullableBoolean = null; + expect(state.nullableBoolean).toBe(null); + }); + + it("should not set the value to undefined", () => { + state.nullableBoolean = undefined; + expect(state.nullableBoolean).toBe(true); + }); + + it("should not set the value to a string", () => { + state.nullableBoolean = "true"; + expect(state.nullableBoolean).toBe(true); + }); + + it("should not set the value to a number", () => { + state.nullableBoolean = 1; + expect(state.nullableBoolean).toBe(true); + }); + + it("should not set the value to an object", () => { + state.nullableBoolean = {}; + expect(state.nullableBoolean).toBe(true); + }); + }); + + describe("Optional Boolean", () => { + beforeEach(() => { + state.optionnalBoolean = true; + }); + + it("should set the value", () => { + state.optionnalBoolean = false; + expect(state.optionnalBoolean).toBe(false); + }); + + it("should not set the value to null", () => { + state.optionnalBoolean = null; + expect(state.optionnalBoolean).toBe(true); + }); + + it("should set the value to undefined", () => { + state.optionnalBoolean = undefined; + expect(state.optionnalBoolean).toBe(undefined); + }); + + it("should not set the value to a string", () => { + state.optionnalBoolean = "true"; + expect(state.optionnalBoolean).toBe(true); + }); + + it("should not set the value to a number", () => { + state.optionnalBoolean = 1; + expect(state.optionnalBoolean).toBe(true); + }); + + it("should not set the value to an object", () => { + state.optionnalBoolean = {}; + expect(state.optionnalBoolean).toBe(true); + }); + }); + + describe("Nullable Optional Boolean", () => { + beforeEach(() => { + state.nullableOptionnalBoolean = true; + }); + + it("should set the value", () => { + state.nullableOptionnalBoolean = false; + expect(state.nullableOptionnalBoolean).toBe(false); + }); + + it("should set the value to null", () => { + state.nullableOptionnalBoolean = null; + expect(state.nullableOptionnalBoolean).toBe(null); + }); + + it("should set the value to undefined", () => { + state.nullableOptionnalBoolean = undefined; + expect(state.nullableOptionnalBoolean).toBe(undefined); + }); + + it("should not set the value to a string", () => { + state.nullableOptionnalBoolean = "true"; + expect(state.nullableOptionnalBoolean).toBe(true); + }); + + it("should not set the value to a number", () => { + state.nullableOptionnalBoolean = 1; + expect(state.nullableOptionnalBoolean).toBe(true); + }); + + it("should not set the value to an object", () => { + state.nullableOptionnalBoolean = {}; + expect(state.nullableOptionnalBoolean).toBe(true); + }); + }); + + describe("Boolean With Default", () => { + beforeEach(() => { + state.booleanWithDefault = true; + }); + + it("should set the value", () => { + state.booleanWithDefault = false; + expect(state.booleanWithDefault).toBe(false); + }); + + it("should not set the value to null", () => { + state.booleanWithDefault = null; + expect(state.booleanWithDefault).toBe(true); + }); + + it("should not set the value to undefined", () => { + state.booleanWithDefault = undefined; + expect(state.booleanWithDefault).toBe(true); + }); + + it("should not set the value to a string", () => { + state.booleanWithDefault = "true"; + expect(state.booleanWithDefault).toBe(true); + }); + + it("should not set the value to a number", () => { + state.booleanWithDefault = 1; + expect(state.booleanWithDefault).toBe(true); + }); + + it("should not set the value to an object", () => { + state.booleanWithDefault = {}; + expect(state.booleanWithDefault).toBe(true); + }); + }); + + describe("Boolean With Default And Optional", () => { + beforeEach(() => { + state.booleanWithDefaultAndOptional = true; + }); + + it("should set the value", () => { + state.booleanWithDefaultAndOptional = false; + expect(state.booleanWithDefaultAndOptional).toBe(false); + }); + + it("should not set the value to null", () => { + state.booleanWithDefaultAndOptional = null; + expect(state.booleanWithDefaultAndOptional).toBe(true); + }); + + it("should set the value to undefined", () => { + state.booleanWithDefaultAndOptional = undefined; + expect(state.booleanWithDefaultAndOptional).toBe(undefined); + }); + + it("should not set the value to a string", () => { + state.booleanWithDefaultAndOptional = "true"; + expect(state.booleanWithDefaultAndOptional).toBe(true); + }); + + it("should not set the value to a number", () => { + state.booleanWithDefaultAndOptional = 1; + expect(state.booleanWithDefaultAndOptional).toBe(true); + }); + + it("should not set the value to an object", () => { + state.booleanWithDefaultAndOptional = {}; + expect(state.booleanWithDefaultAndOptional).toBe(true); + }); + }); + + describe("Boolean With Default And Nullable", () => { + beforeEach(() => { + state.booleanWithDefaultAndNullable = true; + }); + + it("should set the value", () => { + state.booleanWithDefaultAndNullable = false; + expect(state.booleanWithDefaultAndNullable).toBe(false); + }); + + it("should set the value to null", () => { + state.booleanWithDefaultAndNullable = null; + expect(state.booleanWithDefaultAndNullable).toBe(null); + }); + + it("should not set the value to undefined", () => { + state.booleanWithDefaultAndNullable = undefined; + expect(state.booleanWithDefaultAndNullable).toBe(true); + }); + + it("should not set the value to a string", () => { + state.booleanWithDefaultAndNullable = "true"; + expect(state.booleanWithDefaultAndNullable).toBe(true); + }); + + it("should not set the value to a number", () => { + state.booleanWithDefaultAndNullable = 1; + expect(state.booleanWithDefaultAndNullable).toBe(true); + }); + + it("should not set the value to an object", () => { + state.booleanWithDefaultAndNullable = {}; + expect(state.booleanWithDefaultAndNullable).toBe(true); + }); + }); + + describe("Boolean With Default And Nullable And Optional", () => { + beforeEach(() => { + state.booleanWithDefaultAndNullableAndOptional = true; + }); + + it("should set the value", () => { + state.booleanWithDefaultAndNullableAndOptional = false; + expect(state.booleanWithDefaultAndNullableAndOptional).toBe(false); + }); + + it("should set the value to null", () => { + state.booleanWithDefaultAndNullableAndOptional = null; + expect(state.booleanWithDefaultAndNullableAndOptional).toBe(null); + }); + + it("should set the value to undefined", () => { + state.booleanWithDefaultAndNullableAndOptional = undefined; + expect(state.booleanWithDefaultAndNullableAndOptional).toBe(undefined); + }); + + it("should not set the value to a string", () => { + state.booleanWithDefaultAndNullableAndOptional = "true"; + expect(state.booleanWithDefaultAndNullableAndOptional).toBe(true); + }); + + it("should not set the value to a number", () => { + state.booleanWithDefaultAndNullableAndOptional = 1; + expect(state.booleanWithDefaultAndNullableAndOptional).toBe(true); + }); + + it("should not set the value to an object", () => { + state.booleanWithDefaultAndNullableAndOptional = {}; + expect(state.booleanWithDefaultAndNullableAndOptional).toBe(true); + }); + }); + }); +}); diff --git a/core/tests/proxys/date.proxy.test.ts b/core/tests/proxys/date.proxy.test.ts new file mode 100644 index 0000000..837070c --- /dev/null +++ b/core/tests/proxys/date.proxy.test.ts @@ -0,0 +1,500 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { y } from "../../lib/index.js"; +import { createSyncroState } from "../fixture.js"; + +const defaultDate = new Date("2024-01-01"); + +const state = createSyncroState({ + schema: { + date: y.date(), + nullableDate: y.date().nullable(), + optionnalDate: y.date().optional(), + nullableOptionnalDate: y.date().nullable().optional(), + dateWithDefault: y.date().default(defaultDate), + dateWithDefaultAndOptional: y.date().default(defaultDate).optional(), + dateWithDefaultAndNullable: y.date().default(defaultDate).nullable(), + dateWithDefaultAndNullableAndOptional: y + .date() + .default(defaultDate) + .nullable() + .optional(), + }, +}); + +describe("DateProxy", () => { + describe("Initial values", () => { + it("should be a date", () => { + expect(state.date).toBeInstanceOf(Date); + }); + + it("should have null as default value for nullable date", () => { + expect(state.nullableDate).toBe(null); + }); + + it("should have undefined as default value for optional date", () => { + expect(state.optionnalDate).toBe(undefined); + }); + + it("should have undefined as default value for nullable optional date", () => { + expect(state.nullableOptionnalDate).toBe(undefined); + }); + + it("should have default value for date with default", () => { + expect(state.dateWithDefault.toISOString()).toEqual( + defaultDate.toISOString() + ); + }); + + it("should have default value for optional date with default", () => { + expect(state.dateWithDefaultAndOptional?.toISOString()).toEqual( + defaultDate.toISOString() + ); + }); + + it("should have default value for nullable date with default", () => { + expect(state.dateWithDefaultAndNullable?.toISOString()).toEqual( + defaultDate.toISOString() + ); + }); + + it("should have default value for nullable optional date with default", () => { + expect( + state.dateWithDefaultAndNullableAndOptional?.toISOString() + ).toEqual(defaultDate.toISOString()); + }); + }); + + describe("Setters", () => { + const testDate = new Date("2024-02-01"); + + describe("Date", () => { + beforeEach(() => { + state.date = testDate; + }); + + it("should set the value", () => { + const newDate = new Date("2024-03-01"); + state.date = newDate; + expect(state.date.toISOString()).toEqual(newDate.toISOString()); + }); + + it("should not set the value to null", () => { + state.date = null; + expect(state.date.toISOString()).toEqual(testDate.toISOString()); + }); + + it("should not set the value to undefined", () => { + state.date = undefined; + expect(state.date.toISOString()).toEqual(testDate.toISOString()); + }); + + it("should not set the value to a string", () => { + state.date = "2024-03-01"; + expect(state.date.toISOString()).toEqual(testDate.toISOString()); + }); + + it("should not set the value to a number", () => { + state.date = 123; + expect(state.date.toISOString()).toEqual(testDate.toISOString()); + }); + + it("should not set the value to an object", () => { + state.date = {}; + expect(state.date.toISOString()).toEqual(testDate.toISOString()); + }); + }); + + describe("Date operations", () => { + beforeEach(() => { + state.date = new Date("2024-03-15T12:00:00.000Z"); + }); + + it("should handle setFullYear operation", () => { + state.date.setFullYear(2025); + expect(state.date.getFullYear()).toBe(2025); + }); + + it("should handle setMonth operation", () => { + state.date.setMonth(5); // June (0-based) + expect(state.date.getMonth()).toBe(5); + }); + + it("should handle setDate operation", () => { + state.date.setDate(20); + expect(state.date.getDate()).toBe(20); + }); + + it("should handle setHours operation", () => { + state.date.setHours(15); + expect(state.date.getHours()).toBe(15); + }); + + it("should handle setMinutes operation", () => { + state.date.setMinutes(30); + expect(state.date.getMinutes()).toBe(30); + }); + + it("should handle setSeconds operation", () => { + state.date.setSeconds(45); + expect(state.date.getSeconds()).toBe(45); + }); + + it("should handle setMilliseconds operation", () => { + state.date.setMilliseconds(500); + expect(state.date.getMilliseconds()).toBe(500); + }); + + it("should handle multiple operations in sequence", () => { + state.date.setFullYear(2025); + state.date.setMonth(6); + state.date.setDate(25); + state.date.setUTCHours(14); + state.date.setUTCMinutes(30); + state.date.setUTCSeconds(15); + state.date.setUTCMilliseconds(250); + + expect(state.date.toISOString()).toBe("2025-07-25T14:30:15.250Z"); + }); + + it("should handle setTime operation", () => { + const timestamp = new Date("2025-12-25T00:00:00.000Z").getTime(); + state.date.setTime(timestamp); + expect(state.date.toISOString()).toBe("2025-12-25T00:00:00.000Z"); + }); + + it("should handle UTC operations", () => { + state.date.setUTCFullYear(2025); + state.date.setUTCMonth(11); // December (0-based) + state.date.setUTCDate(25); + + expect(state.date.getUTCFullYear()).toBe(2025); + expect(state.date.getUTCMonth()).toBe(11); + expect(state.date.getUTCDate()).toBe(25); + }); + }); + describe("Nullable Date", () => { + beforeEach(() => { + state.nullableDate = testDate; + }); + + it("should set the value", () => { + const newDate = new Date("2024-03-01"); + state.nullableDate = newDate; + expect(state.nullableDate.toISOString()).toEqual(newDate.toISOString()); + }); + + it("should set the value to null", () => { + state.nullableDate = null; + expect(state.nullableDate).toBe(null); + }); + + it("should not set the value to undefined", () => { + state.nullableDate = undefined; + expect(state.nullableDate?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to a string", () => { + state.nullableDate = "2024-03-01" as any; + expect(state.nullableDate?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to a number", () => { + state.nullableDate = 123 as any; + expect(state.nullableDate?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to an object", () => { + state.nullableDate = {} as any; + expect(state.nullableDate?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + }); + + describe("Optional Date", () => { + beforeEach(() => { + state.optionnalDate = testDate; + }); + + it("should set the value", () => { + const newDate = new Date("2024-03-01"); + state.optionnalDate = newDate; + expect(state.optionnalDate?.toISOString()).toEqual( + newDate.toISOString() + ); + }); + + it("should not set the value to null", () => { + state.optionnalDate = null; + expect(state.optionnalDate?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should set the value to undefined", () => { + state.optionnalDate = undefined; + expect(state.optionnalDate).toBe(undefined); + }); + + it("should not set the value to a string", () => { + state.optionnalDate = "2024-03-01" as any; + expect(state.optionnalDate?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to a number", () => { + state.optionnalDate = 123 as any; + expect(state.optionnalDate?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to an object", () => { + state.optionnalDate = {} as any; + expect(state.optionnalDate?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + }); + + describe("Nullable Optional Date", () => { + beforeEach(() => { + state.nullableOptionnalDate = testDate; + }); + + it("should set the value", () => { + const newDate = new Date("2024-03-01"); + state.nullableOptionnalDate = newDate; + expect(state.nullableOptionnalDate?.toISOString()).toEqual( + newDate.toISOString() + ); + }); + + it("should set the value to null", () => { + state.nullableOptionnalDate = null; + expect(state.nullableOptionnalDate).toBe(null); + }); + + it("should set the value to undefined", () => { + state.nullableOptionnalDate = undefined; + expect(state.nullableOptionnalDate).toBe(undefined); + }); + + it("should not set the value to a string", () => { + state.nullableOptionnalDate = "2024-03-01" as any; + expect(state.nullableOptionnalDate?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to a number", () => { + state.nullableOptionnalDate = 123 as any; + expect(state.nullableOptionnalDate?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to an object", () => { + state.nullableOptionnalDate = {} as any; + expect(state.nullableOptionnalDate?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + }); + + describe("Date With Default", () => { + beforeEach(() => { + state.dateWithDefault = testDate; + }); + + it("should set the value", () => { + const newDate = new Date("2024-03-01"); + state.dateWithDefault = newDate; + expect(state.dateWithDefault?.toISOString()).toEqual( + newDate.toISOString() + ); + }); + + it("should not set the value to null", () => { + state.dateWithDefault = null; + expect(state.dateWithDefault?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to undefined", () => { + state.dateWithDefault = undefined; + expect(state.dateWithDefault?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to a string", () => { + state.dateWithDefault = "2024-03-01" as any; + expect(state.dateWithDefault.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to a number", () => { + state.dateWithDefault = 123 as any; + expect(state.dateWithDefault.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to an object", () => { + state.dateWithDefault = {} as any; + expect(state.dateWithDefault.toISOString()).toEqual( + testDate.toISOString() + ); + }); + }); + + describe("Date With Default And Optional", () => { + beforeEach(() => { + state.dateWithDefaultAndOptional = testDate; + }); + + it("should set the value", () => { + const newDate = new Date("2024-03-01"); + state.dateWithDefaultAndOptional = newDate; + expect(state.dateWithDefaultAndOptional?.toISOString()).toEqual( + newDate.toISOString() + ); + }); + + it("should not set the value to null", () => { + state.dateWithDefaultAndOptional = null; + expect(state.dateWithDefaultAndOptional?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should set the value to undefined", () => { + state.dateWithDefaultAndOptional = undefined; + expect(state.dateWithDefaultAndOptional).toBe(undefined); + }); + + it("should not set the value to a string", () => { + state.dateWithDefaultAndOptional = "2024-03-01" as any; + expect(state.dateWithDefaultAndOptional?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to a number", () => { + state.dateWithDefaultAndOptional = 123 as any; + expect(state.dateWithDefaultAndOptional?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to an object", () => { + state.dateWithDefaultAndOptional = {} as any; + expect(state.dateWithDefaultAndOptional?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + }); + + describe("Date With Default And Nullable", () => { + beforeEach(() => { + state.dateWithDefaultAndNullable = testDate; + }); + + it("should set the value", () => { + const newDate = new Date("2024-03-01"); + state.dateWithDefaultAndNullable = newDate; + expect(state.dateWithDefaultAndNullable?.toISOString()).toEqual( + newDate.toISOString() + ); + }); + + it("should set the value to null", () => { + state.dateWithDefaultAndNullable = null; + expect(state.dateWithDefaultAndNullable).toBe(null); + }); + + it("should not set the value to undefined", () => { + state.dateWithDefaultAndNullable = undefined; + expect(state.dateWithDefaultAndNullable?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to a string", () => { + state.dateWithDefaultAndNullable = "2024-03-01" as any; + expect(state.dateWithDefaultAndNullable?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to a number", () => { + state.dateWithDefaultAndNullable = 123 as any; + expect(state.dateWithDefaultAndNullable?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + + it("should not set the value to an object", () => { + state.dateWithDefaultAndNullable = {} as any; + expect(state.dateWithDefaultAndNullable?.toISOString()).toEqual( + testDate.toISOString() + ); + }); + }); + + describe("Date With Default And Nullable And Optional", () => { + beforeEach(() => { + state.dateWithDefaultAndNullableAndOptional = testDate; + }); + + it("should set the value", () => { + const newDate = new Date("2024-03-01"); + state.dateWithDefaultAndNullableAndOptional = newDate; + expect( + state.dateWithDefaultAndNullableAndOptional?.toISOString() + ).toEqual(newDate.toISOString()); + }); + + it("should set the value to null", () => { + state.dateWithDefaultAndNullableAndOptional = null; + expect(state.dateWithDefaultAndNullableAndOptional).toBe(null); + }); + + it("should set the value to undefined", () => { + state.dateWithDefaultAndNullableAndOptional = undefined; + expect(state.dateWithDefaultAndNullableAndOptional).toBe(undefined); + }); + + it("should not set the value to a string", () => { + state.dateWithDefaultAndNullableAndOptional = "2024-03-01" as any; + expect( + state.dateWithDefaultAndNullableAndOptional?.toISOString() + ).toEqual(testDate.toISOString()); + }); + + it("should not set the value to a number", () => { + state.dateWithDefaultAndNullableAndOptional = 123 as any; + expect( + state.dateWithDefaultAndNullableAndOptional?.toISOString() + ).toEqual(testDate.toISOString()); + }); + + it("should not set the value to an object", () => { + state.dateWithDefaultAndNullableAndOptional = {} as any; + expect( + state.dateWithDefaultAndNullableAndOptional?.toISOString() + ).toEqual(testDate.toISOString()); + }); + }); + }); +}); diff --git a/core/tests/proxys/discriminatedUnion.proxy.test.ts b/core/tests/proxys/discriminatedUnion.proxy.test.ts new file mode 100644 index 0000000..508f550 --- /dev/null +++ b/core/tests/proxys/discriminatedUnion.proxy.test.ts @@ -0,0 +1,359 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import * as Y from "yjs"; +import { SyncedDiscriminatedUnion } from "../../lib/proxys/discriminatedUnion.js"; +import { y } from "../../lib/schemas/schema.js"; +import type { State } from "../../lib/proxys/syncroState.js"; +import { VanillaProvider } from "../fixture.js"; + +// Mock State for testing +const createMockState = (): State => { + const doc = new Y.Doc(); + const transactionKey = "test-transaction"; + const provider = new VanillaProvider(); + + return { + doc, + awareness: null as any, + initialized: true, + synced: true, + undoManager: null as any, + transactionKey, + transaction: (fn: () => void) => { + doc.transact(fn, transactionKey); + }, + undo: () => {}, + redo: () => {}, + presence: null as any, + provider, + }; +}; + +// Mock parent container +const createMockParent = (yDoc: Y.Doc) => ({ + yType: yDoc.getMap("root"), + deleteProperty: () => true, +}); + +describe("SyncedDiscriminatedUnion", () => { + let yDoc: Y.Doc; + let mockState: State; + let mockParent: ReturnType; + + beforeEach(() => { + yDoc = new Y.Doc(); + mockState = createMockState(); + // IMPORTANT: Use the same document for state and yType! + mockState.doc = yDoc; + mockParent = createMockParent(yDoc); + }); + + describe("Basic functionality", () => { + it("should create a discriminated union proxy", () => { + const validator = y.discriminatedUnion("status", [ + y.object({ status: y.literal("success"), data: y.string() }), + y.object({ status: y.literal("failed"), error: y.string() }), + ]); + + const yType = yDoc.getMap("test"); + const syncedUnion = new SyncedDiscriminatedUnion({ + state: mockState, + validator, + yType, + parent: mockParent, + key: "test", + observe: false, + }); + + expect(syncedUnion).toBeDefined(); + expect(syncedUnion.validator).toBe(validator); + expect(syncedUnion.currentVariant).toBe( + validator.$schema.variantValidators[0] + ); + }); + + it("should set and get success variant values", () => { + const validator = y.discriminatedUnion("status", [ + y.object({ status: y.literal("success"), data: y.string() }), + y.object({ status: y.literal("failed"), error: y.string() }), + ]); + + const yType = yDoc.getMap("test"); + const syncedUnion = new SyncedDiscriminatedUnion({ + state: mockState, + validator, + yType, + parent: mockParent, + key: "test", + observe: false, + }); + + const successValue = { status: "success", data: "Hello world" }; + syncedUnion.value = successValue; + + expect(syncedUnion.currentVariant).toBeTruthy(); + expect(syncedUnion.value.status).toBe("success"); + expect(syncedUnion.value.data).toBe("Hello world"); + }); + + it("should set and get failed variant values", () => { + const validator = y.discriminatedUnion("status", [ + y.object({ status: y.literal("success"), data: y.string() }), + y.object({ status: y.literal("failed"), error: y.string() }), + ]); + + const yType = yDoc.getMap("test"); + const syncedUnion = new SyncedDiscriminatedUnion({ + state: mockState, + validator, + yType, + parent: mockParent, + key: "test", + observe: false, + }); + + const failedValue = { status: "failed", error: "Something went wrong" }; + syncedUnion.value = failedValue; + + expect(syncedUnion.currentVariant).toBeTruthy(); + expect(syncedUnion.value.status).toBe("failed"); + expect(syncedUnion.value.error).toBe("Something went wrong"); + }); + }); + + describe("Variant switching", () => { + it("should switch variants when discriminant changes", () => { + const validator = y.discriminatedUnion("status", [ + y.object({ status: y.literal("success"), data: y.string() }), + y.object({ status: y.literal("failed"), error: y.string() }), + ]); + + const yType = yDoc.getMap("test"); + const syncedUnion = new SyncedDiscriminatedUnion({ + state: mockState, + validator, + yType, + parent: mockParent, + key: "test", + observe: true, + }); + + // Start with success + const successValue = { status: "success", data: "Hello world" }; + syncedUnion.value = successValue; + + expect(syncedUnion.value.status).toBe("success"); + expect(syncedUnion.value.data).toBe("Hello world"); + + // Switch to failed + const failedValue = { status: "failed", error: "Something went wrong" }; + syncedUnion.value = failedValue; + + // Check that the value changed correctly (most important) + expect(syncedUnion.value.status).toBe("failed"); + expect(syncedUnion.value.error).toBe("Something went wrong"); + // The discriminant should not have data property anymore + expect(syncedUnion.value.data).toBe(undefined); + }); + + it("should handle property updates within the same variant", () => { + const validator = y.discriminatedUnion("status", [ + y.object({ status: y.literal("success"), data: y.string() }), + y.object({ status: y.literal("failed"), error: y.string() }), + ]); + + const yType = yDoc.getMap("test"); + const syncedUnion = new SyncedDiscriminatedUnion({ + state: mockState, + validator, + yType, + parent: mockParent, + key: "test", + observe: false, + }); + + // Set initial value + syncedUnion.value = { status: "success", data: "Initial data" }; + const initialVariant = syncedUnion.currentVariant; + + // Update data property + syncedUnion.value.data = "Updated data"; + + expect(syncedUnion.currentVariant).toBe(initialVariant); // Same variant + expect(syncedUnion.value.status).toBe("success"); + expect(syncedUnion.value.data).toBe("Updated data"); + }); + }); + + describe("Null and undefined handling", () => { + it("should handle null values for nullable unions", () => { + const validator = y + .discriminatedUnion("status", [ + y.object({ status: y.literal("success"), data: y.string() }), + y.object({ status: y.literal("failed"), error: y.string() }), + ]) + .nullable(); + + const yType = yDoc.getMap("test"); + const syncedUnion = new SyncedDiscriminatedUnion({ + state: mockState, + validator, + yType, + parent: mockParent, + key: "test", + observe: false, + }); + + syncedUnion.value = null; + expect(syncedUnion.isNull).toBe(true); + expect(syncedUnion.value).toBeNull(); + }); + + it("should handle undefined values for optional unions", () => { + const validator = y + .discriminatedUnion("status", [ + y.object({ status: y.literal("success"), data: y.string() }), + y.object({ status: y.literal("failed"), error: y.string() }), + ]) + .optional(); + + const yType = yDoc.getMap("test"); + const syncedUnion = new SyncedDiscriminatedUnion({ + state: mockState, + validator, + yType, + parent: mockParent, + key: "test", + observe: false, + }); + + // This should trigger parent deletion for undefined values + syncedUnion.value = undefined; + // Note: In a real scenario, this would delete the property from parent + }); + }); + + describe("JSON serialization", () => { + it("should serialize to JSON correctly", () => { + const validator = y.discriminatedUnion("status", [ + y.object({ status: y.literal("success"), data: y.string() }), + y.object({ status: y.literal("failed"), error: y.string() }), + ]); + + const yType = yDoc.getMap("test"); + const syncedUnion = new SyncedDiscriminatedUnion({ + state: mockState, + validator, + yType, + parent: mockParent, + key: "test", + observe: true, + }); + + const successValue = { status: "success", data: "Hello world" }; + syncedUnion.value = successValue; + + const json = syncedUnion.toJSON(); + expect(json).toEqual(successValue); + }); + + it("should return null for null unions", () => { + const validator = y + .discriminatedUnion("status", [ + y.object({ status: y.literal("success"), data: y.string() }), + y.object({ status: y.literal("failed"), error: y.string() }), + ]) + .nullable(); + + const yType = yDoc.getMap("test"); + const syncedUnion = new SyncedDiscriminatedUnion({ + state: mockState, + validator, + yType, + parent: mockParent, + key: "test", + observe: false, + }); + + syncedUnion.value = null; + const json = syncedUnion.toJSON(); + expect(json).toBeNull(); + }); + }); + + describe("Proxy behavior", () => { + it("should support property access through proxy", () => { + const validator = y.discriminatedUnion("status", [ + y.object({ status: y.literal("success"), data: y.string() }), + y.object({ status: y.literal("failed"), error: y.string() }), + ]); + + const yType = yDoc.getMap("test"); + const syncedUnion = new SyncedDiscriminatedUnion({ + state: mockState, + validator, + yType, + parent: mockParent, + key: "test", + observe: false, + }); + + syncedUnion.value = { status: "success", data: "Hello world" }; + + // Access through proxy + expect(syncedUnion.proxy.status).toBe("success"); + expect(syncedUnion.proxy.data).toBe("Hello world"); + }); + + it("should support property enumeration", () => { + const validator = y.discriminatedUnion("status", [ + y.object({ status: y.literal("success"), data: y.string() }), + y.object({ status: y.literal("failed"), error: y.string() }), + ]); + + const yType = yDoc.getMap("test"); + const syncedUnion = new SyncedDiscriminatedUnion({ + state: mockState, + validator, + yType, + parent: mockParent, + key: "test", + observe: true, + }); + + syncedUnion.value = { status: "success", data: "Hello world" }; + + const keys = Object.keys(syncedUnion.value); + + expect(keys).toContain("status"); + expect(keys).toContain("data"); + }); + }); + + describe("Cleanup", () => { + it("should cleanup resources on destroy", () => { + const validator = y + .discriminatedUnion("status", [ + y.object({ status: y.literal("success"), data: y.string() }), + y.object({ status: y.literal("failed"), error: y.string() }), + ]) + .default({ status: "success", data: "Hello world" }); + + const yType = yDoc.getMap("test"); + const syncedUnion = new SyncedDiscriminatedUnion({ + state: mockState, + validator, + yType, + parent: mockParent, + key: "test", + observe: false, + }); + + syncedUnion.value = { status: "success", data: "Hello world" }; + expect(syncedUnion.objectProxy.value).not.toBeNull(); + + syncedUnion.destroy(); + expect(syncedUnion.objectProxy.value).toBeNull(); + expect(syncedUnion.currentVariant).toBeFalsy(); + }); + }); +}); diff --git a/core/tests/proxys/enum.proxy.test.ts b/core/tests/proxys/enum.proxy.test.ts new file mode 100644 index 0000000..d7c6171 --- /dev/null +++ b/core/tests/proxys/enum.proxy.test.ts @@ -0,0 +1,346 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { y } from "../../lib/index.js"; +import { createSyncroState } from "../fixture.js"; + +const state = createSyncroState({ + schema: { + enum: y.enum("a", "b", "c"), + nullableEnum: y.enum("a", "b", "c").nullable(), + optionnalEnum: y.enum("a", "b", "c").optional(), + nullableOptionnalEnum: y.enum("a", "b", "c").nullable().optional(), + enumWithDefault: y.enum("a", "b", "c").default("a"), + enumWithDefaultAndOptional: y.enum("a", "b", "c").default("a").optional(), + enumWithDefaultAndNullable: y.enum("a", "b", "c").default("a").nullable(), + enumWithDefaultAndNullableAndOptional: y + .enum("a", "b", "c") + .default("a") + .nullable() + .optional(), + }, +}); + +describe("EnumProxy", () => { + describe("Initial values", () => { + it("should be a string", () => { + expect(state.enum).toBeTypeOf("string"); + }); + + it("should have null as default value for nullable enum", () => { + expect(state.nullableEnum).toBe(null); + }); + + it("should have undefined as default value for optional enum", () => { + expect(state.optionnalEnum).toBe(undefined); + }); + + it("should have undefined as default value for nullable optional enum", () => { + expect(state.nullableOptionnalEnum).toBe(undefined); + }); + + it("should have default value for enum with default", () => { + expect(state.enumWithDefault).toBe("a"); + }); + + it("should have default value for optional enum with default", () => { + expect(state.enumWithDefaultAndOptional).toBe("a"); + }); + + it("should have default value for nullable enum with default", () => { + expect(state.enumWithDefaultAndNullable).toBe("a"); + }); + + it("should have default value for nullable optional enum with default", () => { + expect(state.enumWithDefaultAndNullableAndOptional).toBe("a"); + }); + }); + + describe("Setters", () => { + describe("Enum", () => { + beforeEach(() => { + state.enum = "a"; + }); + + it("should set the value to a valid enum value", () => { + state.enum = "b"; + expect(state.enum).toBe("b"); + }); + + it("should not set the value to an invalid enum value", () => { + state.enum = "d"; + expect(state.enum).toBe("a"); + }); + + it("should not set the value to null", () => { + state.enum = null; + expect(state.enum).toBe("a"); + }); + + it("should not set the value to undefined", () => { + state.enum = undefined; + expect(state.enum).toBe("a"); + }); + + it("should not set the value to a number", () => { + state.enum = 123; + expect(state.enum).toBe("a"); + }); + + it("should not set the value to an object", () => { + state.enum = {}; + expect(state.enum).toBe("a"); + }); + }); + + describe("Nullable Enum", () => { + beforeEach(() => { + state.nullableEnum = "a"; + }); + + it("should set the value to a valid enum value", () => { + state.nullableEnum = "b"; + expect(state.nullableEnum).toBe("b"); + }); + + it("should not set the value to an invalid enum value", () => { + state.nullableEnum = "d"; + expect(state.nullableEnum).toBe("a"); + }); + + it("should set the value to null", () => { + state.nullableEnum = null; + expect(state.nullableEnum).toBe(null); + }); + + it("should not set the value to undefined", () => { + state.nullableEnum = undefined; + expect(state.nullableEnum).toBe("a"); + }); + + it("should not set the value to a number", () => { + state.nullableEnum = 123; + expect(state.nullableEnum).toBe("a"); + }); + + it("should not set the value to an object", () => { + state.nullableEnum = {}; + expect(state.nullableEnum).toBe("a"); + }); + }); + + describe("Optional Enum", () => { + beforeEach(() => { + state.optionnalEnum = "a"; + }); + + it("should set the value to a valid enum value", () => { + state.optionnalEnum = "b"; + expect(state.optionnalEnum).toBe("b"); + }); + + it("should not set the value to an invalid enum value", () => { + state.optionnalEnum = "d"; + expect(state.optionnalEnum).toBe("a"); + }); + + it("should not set the value to null", () => { + state.optionnalEnum = null; + expect(state.optionnalEnum).toBe("a"); + }); + + it("should set the value to undefined", () => { + state.optionnalEnum = undefined; + expect(state.optionnalEnum).toBe(undefined); + }); + + it("should not set the value to a number", () => { + state.optionnalEnum = 123; + expect(state.optionnalEnum).toBe("a"); + }); + + it("should not set the value to an object", () => { + state.optionnalEnum = {}; + expect(state.optionnalEnum).toBe("a"); + }); + }); + + describe("Nullable Optional Enum", () => { + beforeEach(() => { + state.nullableOptionnalEnum = "a"; + }); + + it("should set the value to a valid enum value", () => { + state.nullableOptionnalEnum = "b"; + expect(state.nullableOptionnalEnum).toBe("b"); + }); + + it("should not set the value to an invalid enum value", () => { + state.nullableOptionnalEnum = "d"; + expect(state.nullableOptionnalEnum).toBe("a"); + }); + + it("should set the value to null", () => { + state.nullableOptionnalEnum = null; + expect(state.nullableOptionnalEnum).toBe(null); + }); + + it("should set the value to undefined", () => { + state.nullableOptionnalEnum = undefined; + expect(state.nullableOptionnalEnum).toBe(undefined); + }); + + it("should not set the value to a number", () => { + state.nullableOptionnalEnum = 123; + expect(state.nullableOptionnalEnum).toBe("a"); + }); + + it("should not set the value to an object", () => { + state.nullableOptionnalEnum = {}; + expect(state.nullableOptionnalEnum).toBe("a"); + }); + }); + + describe("Enum With Default", () => { + beforeEach(() => { + state.enumWithDefault = "b"; + }); + + it("should set the value to a valid enum value", () => { + state.enumWithDefault = "c"; + expect(state.enumWithDefault).toBe("c"); + }); + + it("should not set the value to an invalid enum value", () => { + state.enumWithDefault = "d"; + expect(state.enumWithDefault).toBe("b"); + }); + + it("should not set the value to null", () => { + state.enumWithDefault = null; + expect(state.enumWithDefault).toBe("b"); + }); + + it("should not set the value to undefined", () => { + state.enumWithDefault = undefined; + expect(state.enumWithDefault).toBe("b"); + }); + + it("should not set the value to a number", () => { + state.enumWithDefault = 123; + expect(state.enumWithDefault).toBe("b"); + }); + + it("should not set the value to an object", () => { + state.enumWithDefault = {}; + expect(state.enumWithDefault).toBe("b"); + }); + }); + + describe("Enum With Default And Optional", () => { + beforeEach(() => { + state.enumWithDefaultAndOptional = "b"; + }); + + it("should set the value to a valid enum value", () => { + state.enumWithDefaultAndOptional = "c"; + expect(state.enumWithDefaultAndOptional).toBe("c"); + }); + + it("should not set the value to an invalid enum value", () => { + state.enumWithDefaultAndOptional = "d"; + expect(state.enumWithDefaultAndOptional).toBe("b"); + }); + + it("should not set the value to null", () => { + state.enumWithDefaultAndOptional = null; + expect(state.enumWithDefaultAndOptional).toBe("b"); + }); + + it("should set the value to undefined", () => { + state.enumWithDefaultAndOptional = undefined; + expect(state.enumWithDefaultAndOptional).toBe(undefined); + }); + + it("should not set the value to a number", () => { + state.enumWithDefaultAndOptional = 123; + expect(state.enumWithDefaultAndOptional).toBe("b"); + }); + + it("should not set the value to an object", () => { + state.enumWithDefaultAndOptional = {}; + expect(state.enumWithDefaultAndOptional).toBe("b"); + }); + }); + + describe("Enum With Default And Nullable", () => { + beforeEach(() => { + state.enumWithDefaultAndNullable = "b"; + }); + + it("should set the value to a valid enum value", () => { + state.enumWithDefaultAndNullable = "c"; + expect(state.enumWithDefaultAndNullable).toBe("c"); + }); + + it("should not set the value to an invalid enum value", () => { + state.enumWithDefaultAndNullable = "d"; + expect(state.enumWithDefaultAndNullable).toBe("b"); + }); + + it("should set the value to null", () => { + state.enumWithDefaultAndNullable = null; + expect(state.enumWithDefaultAndNullable).toBe(null); + }); + + it("should not set the value to undefined", () => { + state.enumWithDefaultAndNullable = undefined; + expect(state.enumWithDefaultAndNullable).toBe("b"); + }); + + it("should not set the value to a number", () => { + state.enumWithDefaultAndNullable = 123; + expect(state.enumWithDefaultAndNullable).toBe("b"); + }); + + it("should not set the value to an object", () => { + state.enumWithDefaultAndNullable = {}; + expect(state.enumWithDefaultAndNullable).toBe("b"); + }); + }); + + describe("Enum With Default And Nullable And Optional", () => { + beforeEach(() => { + state.enumWithDefaultAndNullableAndOptional = "b"; + }); + + it("should set the value to a valid enum value", () => { + state.enumWithDefaultAndNullableAndOptional = "c"; + expect(state.enumWithDefaultAndNullableAndOptional).toBe("c"); + }); + + it("should not set the value to an invalid enum value", () => { + state.enumWithDefaultAndNullableAndOptional = "d"; + expect(state.enumWithDefaultAndNullableAndOptional).toBe("b"); + }); + + it("should set the value to null", () => { + state.enumWithDefaultAndNullableAndOptional = null; + expect(state.enumWithDefaultAndNullableAndOptional).toBe(null); + }); + + it("should set the value to undefined", () => { + state.enumWithDefaultAndNullableAndOptional = undefined; + expect(state.enumWithDefaultAndNullableAndOptional).toBe(undefined); + }); + + it("should not set the value to a number", () => { + state.enumWithDefaultAndNullableAndOptional = 123; + expect(state.enumWithDefaultAndNullableAndOptional).toBe("b"); + }); + + it("should not set the value to an object", () => { + state.enumWithDefaultAndNullableAndOptional = {}; + expect(state.enumWithDefaultAndNullableAndOptional).toBe("b"); + }); + }); + }); +}); diff --git a/core/tests/proxys/map.proxy.test.ts b/core/tests/proxys/map.proxy.test.ts new file mode 100644 index 0000000..428d851 --- /dev/null +++ b/core/tests/proxys/map.proxy.test.ts @@ -0,0 +1,325 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { y } from "../../lib/index.js"; +import { createSyncroState } from "../fixture.js"; + +type StringMap = Map; +type ObjectMap = Map; + +const state = createSyncroState({ + schema: { + map: y.map(y.string()), + nullableMap: y.map(y.string()).nullable(), + optionalMap: y.map(y.string()).optional(), + nullableOptionalMap: y.map(y.string()).nullable().optional(), + mapWithDefault: y.map(y.string()).default(new Map([["key", "default"]])), + mapWithDefaultAndOptional: y + .map(y.string()) + .default(new Map([["key", "default"]])) + .optional(), + mapWithDefaultAndNullable: y + .map(y.string()) + .default(new Map([["key", "default"]])) + .nullable(), + mapWithDefaultAndNullableAndOptional: y + .map(y.string()) + .default(new Map([["key", "default"]])) + .nullable() + .optional(), + mapWithObject: y.map( + y.object({ + name: y.string(), + age: y.number(), + }) + ), + }, +}); + +describe("MapProxy", () => { + describe("Initial values", () => { + it("should be a Map", () => { + expect(state.map instanceof Map).toBe(true); + expect(state.map.size).toBe(0); + }); + + it("should have null as default value for nullable map", () => { + expect(state.nullableMap).toBe(null); + }); + + it("should have undefined as default value for optional map", () => { + expect(state.optionalMap).toBe(undefined); + }); + + it("should have undefined as default value for nullable optional map", () => { + expect(state.nullableOptionalMap).toBe(undefined); + }); + + it("should have default value for map with default", () => { + expect(state.mapWithDefault instanceof Map).toBe(true); + expect(state.mapWithDefault.get("key")).toBe("default"); + }); + + it("should have default value for optional map with default", () => { + expect(state.mapWithDefaultAndOptional instanceof Map).toBe(true); + expect(state.mapWithDefaultAndOptional.get("key")).toBe("default"); + }); + + it("should have default value for nullable map with default", () => { + expect(state.mapWithDefaultAndNullable instanceof Map).toBe(true); + expect(state.mapWithDefaultAndNullable.get("key")).toBe("default"); + }); + + it("should have default value for nullable optional map with default", () => { + expect(state.mapWithDefaultAndNullableAndOptional instanceof Map).toBe( + true + ); + expect(state.mapWithDefaultAndNullableAndOptional.get("key")).toBe( + "default" + ); + }); + }); + + describe("Setters", () => { + describe("Map", () => { + beforeEach(() => { + state.map = new Map([["test", "value"]]) as StringMap; + }); + + it("should set the value", () => { + state.map = new Map([ + ["hello", "world"], + ["foo", "bar"], + ]) as StringMap; + expect(Array.from(state.map.entries())).toEqual([ + ["hello", "world"], + ["foo", "bar"], + ]); + }); + + it("should not set invalid values", () => { + const initialValue = Array.from(state.map.entries()); + + // Test invalid primitive values + const invalidValues = [null, undefined, "invalid", 123, true, {}]; + for (const value of invalidValues) { + try { + (state.map as any) = value; + } catch {} + expect(Array.from(state.map.entries())).toEqual(initialValue); + } + + // Test invalid map values + const invalidMapValue = new Map(); + invalidMapValue.set("key1", 123); + invalidMapValue.set("key2", true); + + try { + (state.map as any) = invalidMapValue; + } catch {} + expect(Array.from(state.map.entries())).toEqual(initialValue); + }); + + it("should support map methods", () => { + state.map.set("key", "value"); + expect(state.map.get("key")).toBe("value"); + + state.map.delete("key"); + expect(state.map.has("key")).toBe(false); + + state.map.clear(); + expect(state.map.size).toBe(0); + }); + }); + + describe("Nullable Map", () => { + beforeEach(() => { + state.nullableMap = new Map([["test", "value"]]) as StringMap; + }); + + it("should set the value", () => { + state.nullableMap = new Map([ + ["hello", "world"], + ["foo", "bar"], + ]) as StringMap; + expect(Array.from(state.nullableMap!.entries())).toEqual([ + ["hello", "world"], + ["foo", "bar"], + ]); + }); + + it("should set the value to null", () => { + (state.nullableMap as any) = null; + expect(state.nullableMap).toBe(null); + }); + + it("should not set invalid values", () => { + const initialValue = Array.from(state.nullableMap!.entries()); + + // Test invalid primitive values + const invalidValues = [undefined, "invalid", 123, true, {}]; + for (const value of invalidValues) { + try { + (state.nullableMap as any) = value; + } catch {} + expect(Array.from(state.nullableMap!.entries())).toEqual( + initialValue + ); + } + + // Test invalid map values + const invalidMapValue = new Map(); + invalidMapValue.set("key1", 123); + invalidMapValue.set("key2", true); + + try { + (state.nullableMap as any) = invalidMapValue; + } catch {} + expect(Array.from(state.nullableMap!.entries())).toEqual(initialValue); + }); + }); + + describe("Optional Map", () => { + beforeEach(() => { + state.optionalMap = new Map([["test", "value"]]) as StringMap; + }); + + it("should set the value", () => { + state.optionalMap = new Map([ + ["hello", "world"], + ["foo", "bar"], + ]) as StringMap; + expect(Array.from(state.optionalMap!.entries())).toEqual([ + ["hello", "world"], + ["foo", "bar"], + ]); + }); + + it("should set the value to undefined", () => { + (state.optionalMap as any) = undefined; + expect(state.optionalMap).toBe(undefined); + }); + + it("should not set invalid values", () => { + const initialValue = Array.from(state.optionalMap!.entries()); + + // Test invalid primitive values + const invalidValues = [null, "invalid", 123, true, {}]; + for (const value of invalidValues) { + try { + (state.optionalMap as any) = value; + } catch {} + expect(Array.from(state.optionalMap!.entries())).toEqual( + initialValue + ); + } + + // Test invalid map values + const invalidMapValue = new Map(); + invalidMapValue.set("key1", 123); + invalidMapValue.set("key2", true); + + try { + (state.optionalMap as any) = invalidMapValue; + } catch {} + expect(Array.from(state.optionalMap!.entries())).toEqual(initialValue); + }); + }); + + describe("Nullable Optional Map", () => { + beforeEach(() => { + state.nullableOptionalMap = new Map([["test", "value"]]) as StringMap; + }); + + it("should set the value", () => { + state.nullableOptionalMap = new Map([ + ["hello", "world"], + ["foo", "bar"], + ]) as StringMap; + expect(Array.from(state.nullableOptionalMap!.entries())).toEqual([ + ["hello", "world"], + ["foo", "bar"], + ]); + }); + + it("should set the value to null", () => { + (state.nullableOptionalMap as any) = null; + expect(state.nullableOptionalMap).toBe(null); + }); + + it("should set the value to undefined", () => { + (state.nullableOptionalMap as any) = undefined; + expect(state.nullableOptionalMap).toBe(undefined); + }); + + it("should not set invalid values", () => { + const initialValue = Array.from(state.nullableOptionalMap!.entries()); + + // Test invalid primitive values + const invalidValues = ["invalid", 123, true, {}]; + for (const value of invalidValues) { + try { + (state.nullableOptionalMap as any) = value; + } catch {} + expect(Array.from(state.nullableOptionalMap!.entries())).toEqual( + initialValue + ); + } + + // Test invalid map values + const invalidMapValue = new Map(); + invalidMapValue.set("key1", 123); + invalidMapValue.set("key2", true); + + try { + (state.nullableOptionalMap as any) = invalidMapValue; + } catch {} + expect(Array.from(state.nullableOptionalMap!.entries())).toEqual( + initialValue + ); + }); + }); + + describe("Map With Object Values", () => { + beforeEach(() => { + state.mapWithObject = new Map([ + ["test", { name: "John", age: 30 }], + ]) as ObjectMap; + }); + + it("should set valid object values", () => { + state.mapWithObject = new Map([ + ["user1", { name: "John", age: 30 }], + ["user2", { name: "Jane", age: 25 }], + ]) as ObjectMap; + expect(Array.from(state.mapWithObject.entries())).toEqual([ + ["user1", { name: "John", age: 30 }], + ["user2", { name: "Jane", age: 25 }], + ]); + }); + + it("should not set invalid object values", () => { + const initialValue = Array.from(state.mapWithObject.entries()); + + // Test invalid map values + const invalidMapValue = new Map(); + invalidMapValue.set("user1", { name: 123, age: "invalid" }); + invalidMapValue.set("user2", { name: "Jane", age: true }); + + try { + (state.mapWithObject as any) = invalidMapValue; + } catch {} + expect(Array.from(state.mapWithObject.entries())).toEqual(initialValue); + }); + + it("should support partial updates through map methods", () => { + state.mapWithObject.set("user1", { name: "John Doe", age: 31 }); + expect(state.mapWithObject.get("user1")).toEqual({ + name: "John Doe", + age: 31, + }); + + state.mapWithObject.delete("user1"); + expect(state.mapWithObject.has("user1")).toBe(false); + }); + }); + }); +}); diff --git a/core/tests/proxys/number.proxy.test.ts b/core/tests/proxys/number.proxy.test.ts new file mode 100644 index 0000000..9611a14 --- /dev/null +++ b/core/tests/proxys/number.proxy.test.ts @@ -0,0 +1,346 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { y } from "../../lib/index.js"; +import { createSyncroState } from "../fixture.js"; + +const state = createSyncroState({ + schema: { + number: y.number(), + nullableNumber: y.number().nullable(), + optionnalNumber: y.number().optional(), + nullableOptionnalNumber: y.number().nullable().optional(), + numberWithDefault: y.number().default(0), + numberWithDefaultAndOptional: y.number().default(0).optional(), + numberWithDefaultAndNullable: y.number().default(0).nullable(), + numberWithDefaultAndNullableAndOptional: y + .number() + .default(0) + .nullable() + .optional(), + }, +}); + +describe("NumberProxy", () => { + describe("Initial values", () => { + it("should be a number", () => { + expect(state.number).toBeTypeOf("number"); + }); + + it("should have null as default value for nullable number", () => { + expect(state.nullableNumber).toBe(null); + }); + + it("should have undefined as default value for optional number", () => { + expect(state.optionnalNumber).toBe(undefined); + }); + + it("should have undefined as default value for nullable optional number", () => { + expect(state.nullableOptionnalNumber).toBe(undefined); + }); + + it("should have default value for number with default", () => { + expect(state.numberWithDefault).toBe(0); + }); + + it("should have default value for optional number with default", () => { + expect(state.numberWithDefaultAndOptional).toBe(0); + }); + + it("should have default value for nullable number with default", () => { + expect(state.numberWithDefaultAndNullable).toBe(0); + }); + + it("should have default value for nullable optional number with default", () => { + expect(state.numberWithDefaultAndNullableAndOptional).toBe(0); + }); + }); + + describe("Setters", () => { + describe("Number", () => { + beforeEach(() => { + state.number = 42; + }); + + it("should set the value", () => { + state.number = 123; + expect(state.number).toBe(123); + }); + + it("should not set the value to null", () => { + state.number = null; + expect(state.number).toBe(42); + }); + + it("should not set the value to undefined", () => { + state.number = undefined; + expect(state.number).toBe(42); + }); + + it("should not set the value to a string", () => { + state.number = "123"; + expect(state.number).toBe(42); + }); + + it("should not set the value to a boolean", () => { + state.number = true; + expect(state.number).toBe(42); + }); + + it("should not set the value to an object", () => { + state.number = {}; + expect(state.number).toBe(42); + }); + }); + + describe("Nullable Number", () => { + beforeEach(() => { + state.nullableNumber = 42; + }); + + it("should set the value", () => { + state.nullableNumber = 123; + expect(state.nullableNumber).toBe(123); + }); + + it("should set the value to null", () => { + state.nullableNumber = null; + expect(state.nullableNumber).toBe(null); + }); + + it("should not set the value to undefined", () => { + state.nullableNumber = undefined; + expect(state.nullableNumber).toBe(42); + }); + + it("should not set the value to a string", () => { + state.nullableNumber = "123"; + expect(state.nullableNumber).toBe(42); + }); + + it("should not set the value to a boolean", () => { + state.nullableNumber = true; + expect(state.nullableNumber).toBe(42); + }); + + it("should not set the value to an object", () => { + state.nullableNumber = {}; + expect(state.nullableNumber).toBe(42); + }); + }); + + describe("Optional Number", () => { + beforeEach(() => { + state.optionnalNumber = 42; + }); + + it("should set the value", () => { + state.optionnalNumber = 123; + expect(state.optionnalNumber).toBe(123); + }); + + it("should not set the value to null", () => { + state.optionnalNumber = null; + expect(state.optionnalNumber).toBe(42); + }); + + it("should set the value to undefined", () => { + state.optionnalNumber = undefined; + expect(state.optionnalNumber).toBe(undefined); + }); + + it("should not set the value to a string", () => { + state.optionnalNumber = "123"; + expect(state.optionnalNumber).toBe(42); + }); + + it("should not set the value to a boolean", () => { + state.optionnalNumber = true; + expect(state.optionnalNumber).toBe(42); + }); + + it("should not set the value to an object", () => { + state.optionnalNumber = {}; + expect(state.optionnalNumber).toBe(42); + }); + }); + + describe("Nullable Optional Number", () => { + beforeEach(() => { + state.nullableOptionnalNumber = 42; + }); + + it("should set the value", () => { + state.nullableOptionnalNumber = 123; + expect(state.nullableOptionnalNumber).toBe(123); + }); + + it("should set the value to null", () => { + state.nullableOptionnalNumber = null; + expect(state.nullableOptionnalNumber).toBe(null); + }); + + it("should set the value to undefined", () => { + state.nullableOptionnalNumber = undefined; + expect(state.nullableOptionnalNumber).toBe(undefined); + }); + + it("should not set the value to a string", () => { + state.nullableOptionnalNumber = "123"; + expect(state.nullableOptionnalNumber).toBe(42); + }); + + it("should not set the value to a boolean", () => { + state.nullableOptionnalNumber = true; + expect(state.nullableOptionnalNumber).toBe(42); + }); + + it("should not set the value to an object", () => { + state.nullableOptionnalNumber = {}; + expect(state.nullableOptionnalNumber).toBe(42); + }); + }); + + describe("Number With Default", () => { + beforeEach(() => { + state.numberWithDefault = 42; + }); + + it("should set the value", () => { + state.numberWithDefault = 123; + expect(state.numberWithDefault).toBe(123); + }); + + it("should not set the value to null", () => { + state.numberWithDefault = null; + expect(state.numberWithDefault).toBe(42); + }); + + it("should not set the value to undefined", () => { + state.numberWithDefault = undefined; + expect(state.numberWithDefault).toBe(42); + }); + + it("should not set the value to a string", () => { + state.numberWithDefault = "123"; + expect(state.numberWithDefault).toBe(42); + }); + + it("should not set the value to a boolean", () => { + state.numberWithDefault = true; + expect(state.numberWithDefault).toBe(42); + }); + + it("should not set the value to an object", () => { + state.numberWithDefault = {}; + expect(state.numberWithDefault).toBe(42); + }); + }); + + describe("Number With Default And Optional", () => { + beforeEach(() => { + state.numberWithDefaultAndOptional = 42; + }); + + it("should set the value", () => { + state.numberWithDefaultAndOptional = 123; + expect(state.numberWithDefaultAndOptional).toBe(123); + }); + + it("should not set the value to null", () => { + state.numberWithDefaultAndOptional = null; + expect(state.numberWithDefaultAndOptional).toBe(42); + }); + + it("should set the value to undefined", () => { + state.numberWithDefaultAndOptional = undefined; + expect(state.numberWithDefaultAndOptional).toBe(undefined); + }); + + it("should not set the value to a string", () => { + state.numberWithDefaultAndOptional = "123"; + expect(state.numberWithDefaultAndOptional).toBe(42); + }); + + it("should not set the value to a boolean", () => { + state.numberWithDefaultAndOptional = true; + expect(state.numberWithDefaultAndOptional).toBe(42); + }); + + it("should not set the value to an object", () => { + state.numberWithDefaultAndOptional = {}; + expect(state.numberWithDefaultAndOptional).toBe(42); + }); + }); + + describe("Number With Default And Nullable", () => { + beforeEach(() => { + state.numberWithDefaultAndNullable = 42; + }); + + it("should set the value", () => { + state.numberWithDefaultAndNullable = 123; + expect(state.numberWithDefaultAndNullable).toBe(123); + }); + + it("should set the value to null", () => { + state.numberWithDefaultAndNullable = null; + expect(state.numberWithDefaultAndNullable).toBe(null); + }); + + it("should not set the value to undefined", () => { + state.numberWithDefaultAndNullable = undefined; + expect(state.numberWithDefaultAndNullable).toBe(42); + }); + + it("should not set the value to a string", () => { + state.numberWithDefaultAndNullable = "123"; + expect(state.numberWithDefaultAndNullable).toBe(42); + }); + + it("should not set the value to a boolean", () => { + state.numberWithDefaultAndNullable = true; + expect(state.numberWithDefaultAndNullable).toBe(42); + }); + + it("should not set the value to an object", () => { + state.numberWithDefaultAndNullable = {}; + expect(state.numberWithDefaultAndNullable).toBe(42); + }); + }); + + describe("Number With Default And Nullable And Optional", () => { + beforeEach(() => { + state.numberWithDefaultAndNullableAndOptional = 42; + }); + + it("should set the value", () => { + state.numberWithDefaultAndNullableAndOptional = 123; + expect(state.numberWithDefaultAndNullableAndOptional).toBe(123); + }); + + it("should set the value to null", () => { + state.numberWithDefaultAndNullableAndOptional = null; + expect(state.numberWithDefaultAndNullableAndOptional).toBe(null); + }); + + it("should set the value to undefined", () => { + state.numberWithDefaultAndNullableAndOptional = undefined; + expect(state.numberWithDefaultAndNullableAndOptional).toBe(undefined); + }); + + it("should not set the value to a string", () => { + state.numberWithDefaultAndNullableAndOptional = "123"; + expect(state.numberWithDefaultAndNullableAndOptional).toBe(42); + }); + + it("should not set the value to a boolean", () => { + state.numberWithDefaultAndNullableAndOptional = true; + expect(state.numberWithDefaultAndNullableAndOptional).toBe(42); + }); + + it("should not set the value to an object", () => { + state.numberWithDefaultAndNullableAndOptional = {}; + expect(state.numberWithDefaultAndNullableAndOptional).toBe(42); + }); + }); + }); +}); diff --git a/core/tests/proxys/object.proxy.test.ts b/core/tests/proxys/object.proxy.test.ts new file mode 100644 index 0000000..2474e4f --- /dev/null +++ b/core/tests/proxys/object.proxy.test.ts @@ -0,0 +1,462 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { y } from "../../lib/index.js"; +import { createSyncroState } from "../fixture.js"; + +const state = createSyncroState({ + schema: { + object: y.object({ + name: y.string(), + age: y.number(), + }), + nullableObject: y + .object({ + name: y.string(), + age: y.number(), + }) + .nullable(), + optionalObject: y + .object({ + name: y.string(), + age: y.number(), + }) + .optional(), + nullableOptionalObject: y + .object({ + name: y.string(), + age: y.number(), + }) + .nullable() + .optional(), + objectWithDefault: y + .object({ + name: y.string(), + age: y.number(), + }) + .default({ name: "default", age: 0 }), + objectWithDefaultAndOptional: y + .object({ + name: y.string(), + age: y.number(), + }) + .default({ name: "default", age: 0 }) + .optional(), + objectWithDefaultAndNullable: y + .object({ + name: y.string(), + age: y.number(), + }) + .default({ name: "default", age: 0 }) + .nullable(), + objectWithDefaultAndNullableAndOptional: y + .object({ + name: y.string(), + age: y.number(), + }) + .default({ name: "default", age: 0 }) + .nullable() + .optional(), + }, +}); + +describe("ObjectProxy", () => { + describe("Initial values", () => { + it("should be an object", () => { + expect(state.object).toBeTypeOf("object"); + expect(state.object.name).toBeTypeOf("string"); + expect(state.object.age).toBeTypeOf("number"); + }); + + it("should have null as default value for nullable object", () => { + expect(state.nullableObject).toBe(null); + }); + + it("should have undefined as default value for optional object", () => { + expect(state.optionalObject).toBe(undefined); + }); + + it("should have undefined as default value for nullable optional object", () => { + expect(state.nullableOptionalObject).toBe(undefined); + }); + + it("should have default value for object with default", () => { + expect(state.objectWithDefault).toEqual({ name: "default", age: 0 }); + }); + + it("should have default value for optional object with default", () => { + expect(state.objectWithDefaultAndOptional).toEqual({ + name: "default", + age: 0, + }); + }); + + it("should have default value for nullable object with default", () => { + expect(state.objectWithDefaultAndNullable).toEqual({ + name: "default", + age: 0, + }); + }); + + it("should have default value for nullable optional object with default", () => { + expect(state.objectWithDefaultAndNullableAndOptional).toEqual({ + name: "default", + age: 0, + }); + }); + }); + + describe("Setters", () => { + describe("Object", () => { + beforeEach(() => { + state.object = { name: "test", age: 25 }; + }); + + it("should set the value", () => { + state.object = { name: "hello world", age: 30 }; + expect(state.object).toEqual({ name: "hello world", age: 30 }); + }); + + it("should not set the value to null", () => { + (state.object as any) = null; + expect(state.object).toEqual({ name: "test", age: 25 }); + }); + + it("should not set the value to undefined", () => { + (state.object as any) = undefined; + expect(state.object).toEqual({ name: "test", age: 25 }); + }); + + it("should not set the value to a string", () => { + (state.object as any) = "invalid"; + expect(state.object).toEqual({ name: "test", age: 25 }); + }); + + it("should not set the value to a number", () => { + (state.object as any) = 123; + expect(state.object).toEqual({ name: "test", age: 25 }); + }); + + it("should not set invalid object properties", () => { + (state.object as any) = { name: 123, age: "invalid" }; + expect(state.object).toEqual({ name: "test", age: 25 }); + }); + }); + + describe("Nullable Object", () => { + beforeEach(() => { + state.nullableObject = { name: "test", age: 25 }; + }); + + it("should set the value", () => { + state.nullableObject = { name: "hello world", age: 30 }; + expect(state.nullableObject).toEqual({ name: "hello world", age: 30 }); + }); + + it("should set the value to null", () => { + state.nullableObject = null; + expect(state.nullableObject).toBe(null); + }); + + it("should not set the value to undefined", () => { + (state.nullableObject as any) = undefined; + expect(JSON.parse(JSON.stringify(state.nullableObject))).toStrictEqual({ + name: "test", + age: 25, + }); + }); + + it("should not set the value to a string", () => { + (state.nullableObject as any) = "invalid"; + expect(state.nullableObject).toEqual({ name: "test", age: 25 }); + }); + + it("should not set the value to a number", () => { + (state.nullableObject as any) = 123; + expect(state.nullableObject).toEqual({ name: "test", age: 25 }); + }); + + it("should not set invalid object properties", () => { + (state.nullableObject as any) = { name: 123, age: "invalid" }; + expect(state.nullableObject).toEqual({ name: "test", age: 25 }); + }); + }); + + describe("Optional Object", () => { + beforeEach(() => { + state.optionalObject = { name: "test", age: 25 }; + }); + + it("should set the value", () => { + state.optionalObject = { name: "hello world", age: 30 }; + expect(state.optionalObject).toEqual({ name: "hello world", age: 30 }); + }); + + it("should not set the value to null", () => { + (state.optionalObject as any) = null; + expect(state.optionalObject).toEqual({ name: "test", age: 25 }); + }); + + it("should set the value to undefined", () => { + state.optionalObject = undefined; + expect(state.optionalObject).toBe(undefined); + }); + + it("should not set the value to a string", () => { + (state.optionalObject as any) = "invalid"; + expect(state.optionalObject).toEqual({ name: "test", age: 25 }); + }); + + it("should not set the value to a number", () => { + (state.optionalObject as any) = 123; + expect(state.optionalObject).toEqual({ name: "test", age: 25 }); + }); + + it("should not set invalid object properties", () => { + (state.optionalObject as any) = { name: 123, age: "invalid" }; + expect(state.optionalObject).toEqual({ name: "test", age: 25 }); + }); + }); + + describe("Nullable Optional Object", () => { + beforeEach(() => { + state.nullableOptionalObject = { name: "test", age: 25 }; + }); + + it("should set the value", () => { + state.nullableOptionalObject = { name: "hello world", age: 30 }; + expect(state.nullableOptionalObject).toEqual({ + name: "hello world", + age: 30, + }); + }); + + it("should set the value to null", () => { + state.nullableOptionalObject = null; + expect(state.nullableOptionalObject).toBe(null); + }); + + it("should set the value to undefined", () => { + state.nullableOptionalObject = undefined; + expect(state.nullableOptionalObject).toBe(undefined); + }); + + it("should not set the value to a string", () => { + (state.nullableOptionalObject as any) = "invalid"; + expect(state.nullableOptionalObject).toEqual({ name: "test", age: 25 }); + }); + + it("should not set the value to a number", () => { + (state.nullableOptionalObject as any) = 123; + expect(state.nullableOptionalObject).toEqual({ name: "test", age: 25 }); + }); + + it("should not set invalid object properties", () => { + (state.nullableOptionalObject as any) = { name: 123, age: "invalid" }; + expect(state.nullableOptionalObject).toEqual({ name: "test", age: 25 }); + }); + }); + + describe("Object With Default", () => { + beforeEach(() => { + state.objectWithDefault = { name: "test", age: 25 }; + }); + + it("should set the value", () => { + state.objectWithDefault = { name: "hello world", age: 30 }; + expect(state.objectWithDefault).toEqual({ + name: "hello world", + age: 30, + }); + }); + + it("should not set the value to null", () => { + (state.objectWithDefault as any) = null; + expect(state.objectWithDefault).toEqual({ name: "test", age: 25 }); + }); + + it("should not set the value to undefined", () => { + (state.objectWithDefault as any) = undefined; + expect(state.objectWithDefault).toEqual({ name: "test", age: 25 }); + }); + + it("should not set the value to a string", () => { + (state.objectWithDefault as any) = "invalid"; + expect(state.objectWithDefault).toEqual({ name: "test", age: 25 }); + }); + + it("should not set the value to a number", () => { + (state.objectWithDefault as any) = 123; + expect(state.objectWithDefault).toEqual({ name: "test", age: 25 }); + }); + + it("should not set invalid object properties", () => { + (state.objectWithDefault as any) = { name: 123, age: "invalid" }; + expect(state.objectWithDefault).toEqual({ name: "test", age: 25 }); + }); + }); + + describe("Object With Default And Optional", () => { + beforeEach(() => { + state.objectWithDefaultAndOptional = { name: "test", age: 25 }; + }); + + it("should set the value", () => { + state.objectWithDefaultAndOptional = { name: "hello world", age: 30 }; + expect(state.objectWithDefaultAndOptional).toEqual({ + name: "hello world", + age: 30, + }); + }); + + it("should not set the value to null", () => { + (state.objectWithDefaultAndOptional as any) = null; + expect(state.objectWithDefaultAndOptional).toEqual({ + name: "test", + age: 25, + }); + }); + + it("should set the value to undefined", () => { + state.objectWithDefaultAndOptional = undefined; + expect(state.objectWithDefaultAndOptional).toBe(undefined); + }); + + it("should not set the value to a string", () => { + (state.objectWithDefaultAndOptional as any) = "invalid"; + expect(state.objectWithDefaultAndOptional).toEqual({ + name: "test", + age: 25, + }); + }); + + it("should not set the value to a number", () => { + (state.objectWithDefaultAndOptional as any) = 123; + expect(state.objectWithDefaultAndOptional).toEqual({ + name: "test", + age: 25, + }); + }); + + it("should not set invalid object properties", () => { + (state.objectWithDefaultAndOptional as any) = { + name: 123, + age: "invalid", + }; + expect(state.objectWithDefaultAndOptional).toEqual({ + name: "test", + age: 25, + }); + }); + }); + + describe("Object With Default And Nullable", () => { + beforeEach(() => { + state.objectWithDefaultAndNullable = { name: "test", age: 25 }; + }); + + it("should set the value", () => { + state.objectWithDefaultAndNullable = { name: "hello world", age: 30 }; + expect(state.objectWithDefaultAndNullable).toEqual({ + name: "hello world", + age: 30, + }); + }); + + it("should set the value to null", () => { + state.objectWithDefaultAndNullable = null; + expect(state.objectWithDefaultAndNullable).toBe(null); + }); + + it("should not set the value to undefined", () => { + (state.objectWithDefaultAndNullable as any) = undefined; + expect(state.objectWithDefaultAndNullable).toEqual({ + name: "test", + age: 25, + }); + }); + + it("should not set the value to a string", () => { + (state.objectWithDefaultAndNullable as any) = "invalid"; + expect(state.objectWithDefaultAndNullable).toEqual({ + name: "test", + age: 25, + }); + }); + + it("should not set the value to a number", () => { + (state.objectWithDefaultAndNullable as any) = 123; + expect(state.objectWithDefaultAndNullable).toEqual({ + name: "test", + age: 25, + }); + }); + + it("should not set invalid object properties", () => { + (state.objectWithDefaultAndNullable as any) = { + name: 123, + age: "invalid", + }; + expect(state.objectWithDefaultAndNullable).toEqual({ + name: "test", + age: 25, + }); + }); + }); + + describe("Object With Default And Nullable And Optional", () => { + beforeEach(() => { + state.objectWithDefaultAndNullableAndOptional = { + name: "test", + age: 25, + }; + }); + + it("should set the value", () => { + state.objectWithDefaultAndNullableAndOptional = { + name: "hello world", + age: 30, + }; + expect(state.objectWithDefaultAndNullableAndOptional).toEqual({ + name: "hello world", + age: 30, + }); + }); + + it("should set the value to null", () => { + state.objectWithDefaultAndNullableAndOptional = null; + expect(state.objectWithDefaultAndNullableAndOptional).toBe(null); + }); + + it("should set the value to undefined", () => { + state.objectWithDefaultAndNullableAndOptional = undefined; + expect(state.objectWithDefaultAndNullableAndOptional).toBe(undefined); + }); + + it("should not set the value to a string", () => { + (state.objectWithDefaultAndNullableAndOptional as any) = "invalid"; + expect(state.objectWithDefaultAndNullableAndOptional).toEqual({ + name: "test", + age: 25, + }); + }); + + it("should not set the value to a number", () => { + (state.objectWithDefaultAndNullableAndOptional as any) = 123; + expect(state.objectWithDefaultAndNullableAndOptional).toEqual({ + name: "test", + age: 25, + }); + }); + + it("should not set invalid object properties", () => { + (state.objectWithDefaultAndNullableAndOptional as any) = { + name: 123, + age: "invalid", + }; + expect(state.objectWithDefaultAndNullableAndOptional).toEqual({ + name: "test", + age: 25, + }); + }); + }); + }); +}); diff --git a/core/tests/proxys/set.proxy.test.ts b/core/tests/proxys/set.proxy.test.ts new file mode 100644 index 0000000..736c690 --- /dev/null +++ b/core/tests/proxys/set.proxy.test.ts @@ -0,0 +1,250 @@ +import { describe, it, expect, beforeEach, beforeAll } from "vitest"; +import { y } from "../../lib/index.js"; +import { createSyncroState } from "../fixture.js"; + +let createDocument = () => + createSyncroState({ + schema: { + set: y.set(y.string()), + nullableSet: y.set(y.string()).nullable(), + optionalSet: y.set(y.string()).optional(), + nullableOptionalSet: y.set(y.string()).nullable().optional(), + setWithDefault: y.set(y.string()).default(new Set(["default"])), + setWithDefaultAndOptional: y + .set(y.string()) + .default(new Set(["default"])) + .optional(), + setWithDefaultAndNullable: y + .set(y.string()) + .default(new Set(["default"])) + .nullable(), + setWithDefaultAndNullableAndOptional: y + .set(y.string()) + .default(new Set(["default"])) + .nullable() + .optional(), + }, + }); + +let state = createDocument(); + +describe("SetProxy", () => { + describe("Initial values", () => { + it("should be a Set", () => { + expect(state.set instanceof Set).toBe(true); + expect(state.set.size).toBe(0); + }); + + it("should have null as default value for nullable set", () => { + expect(state.nullableSet).toBe(null); + }); + + it("should have undefined as default value for optional set", () => { + expect(state.optionalSet).toBe(undefined); + }); + + it("should have undefined as default value for nullable optional set", () => { + expect(state.nullableOptionalSet).toBe(undefined); + }); + + it("should have default value for set with default", () => { + console.log("state.setWithDefault", Array.from(state.setWithDefault)); + expect(Array.from(state.setWithDefault)).toEqual(["default"]); + }); + + it("should have default value for optional set with default", () => { + expect(Array.from(state.setWithDefaultAndOptional || [])).toEqual([ + "default", + ]); + }); + + it("should have default value for nullable set with default", () => { + expect(Array.from(state.setWithDefaultAndNullable || [])).toEqual([ + "default", + ]); + }); + + it("should have default value for nullable optional set with default", () => { + expect( + Array.from(state.setWithDefaultAndNullableAndOptional || []) + ).toEqual(["default"]); + }); + }); + + // const expectEqual = (a: Set, b: any) => { + // expect([...Array.from(a)]).toStrictEqual(b); + // }; + describe("Setters", () => { + // describe('Set', () => { + // beforeEach(() => { + // state.set = new Set(['test']); + // }); + + // it('should set the value', () => { + // state.set = new Set(['hello', 'world']); + // expect(Array.from(state.set)).toEqual(['hello', 'world']); + // }); + + // it('should not set the value to null', () => { + // (state.set as any) = null; + // expect(Array.from(state.set)).toEqual(['test']); + // }); + + // it('should not set the value to undefined', () => { + // (state.set as any) = undefined; + // expect(Array.from(state.set)).toEqual(['test']); + // }); + + // it('should not set the value to a string', () => { + // (state.set as any) = 'invalid'; + // expect(Array.from(state.set)).toEqual(['test']); + // }); + + // it('should not set the value to a number', () => { + // (state.set as any) = 123; + // expect(Array.from(state.set)).toEqual(['test']); + // }); + + // it('should not set invalid set items', () => { + // (state.set as any) = new Set([123, true, {}]); + // expect(Array.from(state.set)).toEqual(['test']); + // }); + + // it('should support set methods', () => { + // state.set.add('world'); + // console.log(state.set); + // expect(Array.from(state.set)).toEqual(['test', 'world']); + + // state.set.delete('world'); + // expect(Array.from(state.set)).toEqual(['test']); + + // state.set.add('hello'); + // expect(Array.from(state.set)).toEqual(['test', 'hello']); + + // expect(state.set.has('test')).toBe(true); + // expect(state.set.has('world')).toBe(false); + // state.set.clear(); + // expect(Array.from(state.set)).toEqual([]); + // }); + // }); + + // describe('Nullable Set', () => { + // beforeEach(() => { + // state.nullableSet = new Set(['test']); + // }); + + // it('should set the value', () => { + // state.nullableSet = new Set(['hello', 'world']); + // expect(Array.from(state.nullableSet)).toEqual(['hello', 'world']); + // }); + + // it('should set the value to null', () => { + // state.nullableSet = null; + // expect(state.nullableSet).toBe(null); + // }); + + // it('should not set the value to undefined', () => { + // (state.nullableSet as any) = undefined; + // expect(Array.from(state.nullableSet)).toEqual(['test']); + // }); + + // it('should not set the value to a string', () => { + // (state.nullableSet as any) = 'invalid'; + // expect(Array.from(state.nullableSet)).toEqual(['test']); + // }); + + // it('should not set the value to a number', () => { + // (state.nullableSet as any) = 123; + // expect(Array.from(state.nullableSet)).toEqual(['test']); + // }); + + // it('should not set invalid set items', () => { + // (state.nullableSet as any) = new Set([123, true, {}]); + // expect(Array.from(state.nullableSet)).toEqual(['test']); + // }); + // }); + + describe("Optional Set", () => { + beforeAll(() => { + state.optionalSet = new Set(["test"]); + }); + + beforeEach(() => { + state.optionalSet = new Set(["test"]); + }); + + it("should set the value", () => { + state.optionalSet = new Set(["hello", "world"]); + expect(Array.from(state.optionalSet)).toEqual(["hello", "world"]); + }); + + // it('should set the value to undefined', () => { + // state.optionalSet = undefined; + // expect(state.optionalSet).toBe(undefined); + // }); + + it("should not set the value to null", () => { + (state.optionalSet as any) = null; + expect(Array.from(state.optionalSet || [])).toEqual(["test"]); + }); + + it("should not set the value to a string", () => { + (state.optionalSet as any) = "invalid"; + expect(Array.from(state.optionalSet || [])).toEqual(["test"]); + }); + + it("should not set the value to a number", () => { + (state.optionalSet as any) = 123; + expect(Array.from(state.optionalSet || [])).toEqual(["test"]); + }); + + it("should not set invalid set items", () => { + (state.optionalSet as any) = new Set([123, true, {}]); + expect(Array.from(state.optionalSet || [])).toEqual(["test"]); + }); + }); + + describe("Nullable Optional Set", () => { + beforeAll(() => { + state.nullableOptionalSet = new Set(["test"]); + }); + + beforeEach(() => { + state.nullableOptionalSet = new Set(["test"]); + }); + + it("should set the value", () => { + state.nullableOptionalSet = new Set(["hello", "world"]); + expect(Array.from(state.nullableOptionalSet)).toEqual([ + "hello", + "world", + ]); + }); + + it("should set the value to null", () => { + state.nullableOptionalSet = null; + expect(state.nullableOptionalSet).toBe(null); + }); + + // it('should set the value to undefined', () => { + // state.nullableOptionalSet = undefined; + // expect(state.nullableOptionalSet).toBe(undefined); + // }); + + it("should not set the value to a string", () => { + (state.nullableOptionalSet as any) = "invalid"; + expect(Array.from(state.nullableOptionalSet || [])).toEqual(["test"]); + }); + + it("should not set the value to a number", () => { + (state.nullableOptionalSet as any) = 123; + expect(Array.from(state.nullableOptionalSet || [])).toEqual(["test"]); + }); + + it("should not set invalid set items", () => { + (state.nullableOptionalSet as any) = new Set([123, true, {}]); + expect(Array.from(state.nullableOptionalSet || [])).toEqual(["test"]); + }); + }); + }); +}); diff --git a/core/tests/proxys/string.proxy.test.ts b/core/tests/proxys/string.proxy.test.ts new file mode 100644 index 0000000..86cb3ff --- /dev/null +++ b/core/tests/proxys/string.proxy.test.ts @@ -0,0 +1,339 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { y } from "../../lib/index.js"; +import { createSyncroState } from "../fixture.js"; + +const state = createSyncroState({ + schema: { + string: y.string(), + nullableString: y.string().nullable(), + optionnalString: y.string().optional(), + nullableOptionnalString: y.string().nullable().optional(), + stringWithDefault: y.string().default("default"), + stringWithDefaultAndOptional: y.string().default("default").optional(), + stringWithDefaultAndNullable: y.string().default("default").nullable(), + stringWithDefaultAndNullableAndOptional: y + .string() + .default("default") + .nullable() + .optional(), + }, +}); + +describe("StringProxy", () => { + describe("Initial values", () => { + it("should be a string", () => { + expect(state.string).toBeTypeOf("string"); + }); + it("should have null as default value for nullable string", () => { + expect(state.nullableString).toBe(null); + }); + + it("should have undefined as default value for optional string", () => { + expect(state.optionnalString).toBe(undefined); + }); + + it("should have undefined as default value for nullable optional string", () => { + expect(state.nullableOptionnalString).toBe(undefined); + }); + + it("should have default value for string with default", () => { + expect(state.stringWithDefault).toBe("default"); + }); + + it("should have default value for optional string with default", () => { + expect(state.stringWithDefaultAndOptional).toBe("default"); + }); + + it("should have default value for nullable string with default", () => { + expect(state.stringWithDefaultAndNullable).toBe("default"); + }); + + it("should have default value for nullable optional string with default", () => { + expect(state.stringWithDefaultAndNullableAndOptional).toBe("default"); + }); + }); + + describe("Setters", () => { + describe("String", () => { + beforeEach(() => { + state.string = "test"; + }); + it("should set the value", () => { + state.string = "hello world"; + expect(state.string).toBe("hello world"); + }); + + it("should set the value to empty string", () => { + state.string = ""; + expect(state.string).toBe(""); + }); + + it("should not set the value to null", () => { + state.string = null; + expect(state.string).toBe("test"); + }); + + it("should not set the value to undefined", () => { + state.string = undefined; + expect(state.string).toBe("test"); + }); + + it("should not set the value to a number", () => { + state.string = 123; + expect(state.string).toBe("test"); + }); + + it("should not set the value to an object", () => { + state.string = {}; + expect(state.string).toBe("test"); + }); + }); + + describe("Nullable String", () => { + beforeEach(() => { + state.nullableString = "test"; + }); + it("should set the value", () => { + state.nullableString = "hello world"; + expect(state.nullableString).toBe("hello world"); + }); + + it("should set the value to empty string", () => { + state.nullableString = ""; + expect(state.nullableString).toBe(""); + }); + + it("should set the value to null", () => { + state.nullableString = null; + expect(state.nullableString).toBe(null); + }); + + it("should not set the value to undefined", () => { + state.nullableString = undefined; + expect(state.nullableString).toBe("test"); + }); + + it("should not set the value to a number", () => { + state.nullableString = 123; + expect(state.nullableString).toBe("test"); + }); + + it("should not set the value to an object", () => { + state.nullableString = {}; + expect(state.nullableString).toBe("test"); + }); + }); + + describe("Optional String", () => { + beforeEach(() => { + state.optionnalString = "test"; + }); + it("should set the value", () => { + state.optionnalString = "hello world"; + expect(state.optionnalString).toBe("hello world"); + }); + + it("should set the value to empty string", () => { + state.optionnalString = ""; + expect(state.optionnalString).toBe(""); + }); + + it("should not set the value to null", () => { + state.optionnalString = null; + expect(state.optionnalString).toBe("test"); + }); + + it("should set the value to undefined", () => { + state.optionnalString = undefined; + expect(state.optionnalString).toBe(undefined); + }); + + it("should not set the value to a number", () => { + state.optionnalString = 123; + expect(state.optionnalString).toBe("test"); + }); + + it("should not set the value to an object", () => { + state.optionnalString = {}; + expect(state.optionnalString).toBe("test"); + }); + }); + + describe("Nullable Optional String", () => { + beforeEach(() => { + state.nullableOptionnalString = "test"; + }); + it("should set the value", () => { + state.nullableOptionnalString = "hello world"; + expect(state.nullableOptionnalString).toBe("hello world"); + }); + + it("should set the value to empty string", () => { + state.nullableOptionnalString = ""; + expect(state.nullableOptionnalString).toBe(""); + }); + + it("should set the value to null", () => { + state.nullableOptionnalString = null; + expect(state.nullableOptionnalString).toBe(null); + }); + + it("should set the value to undefined", () => { + state.nullableOptionnalString = undefined; + expect(state.nullableOptionnalString).toBe(undefined); + }); + + it("should not set the value to a number", () => { + state.nullableOptionnalString = 123; + expect(state.nullableOptionnalString).toBe("test"); + }); + + it("should not set the value to an object", () => { + state.nullableOptionnalString = {}; + expect(state.nullableOptionnalString).toBe("test"); + }); + }); + + describe("String With Default", () => { + beforeEach(() => { + state.stringWithDefault = "test"; + }); + it("should set the value", () => { + state.stringWithDefault = "hello world"; + expect(state.stringWithDefault).toBe("hello world"); + }); + + it("should set the value to empty string", () => { + state.stringWithDefault = ""; + expect(state.stringWithDefault).toBe(""); + }); + + it("should not set the value to null", () => { + state.stringWithDefault = null; + expect(state.stringWithDefault).toBe("test"); + }); + + it("should not set the value to undefined", () => { + state.stringWithDefault = undefined; + expect(state.stringWithDefault).toBe("test"); + }); + + it("should not set the value to a number", () => { + state.stringWithDefault = 123; + expect(state.stringWithDefault).toBe("test"); + }); + + it("should not set the value to an object", () => { + state.stringWithDefault = {}; + expect(state.stringWithDefault).toBe("test"); + }); + }); + + describe("String With Default And Optional", () => { + beforeEach(() => { + state.stringWithDefaultAndOptional = "test"; + }); + it("should set the value", () => { + state.stringWithDefaultAndOptional = "hello world"; + expect(state.stringWithDefaultAndOptional).toBe("hello world"); + }); + + it("should set the value to empty string", () => { + state.stringWithDefaultAndOptional = ""; + expect(state.stringWithDefaultAndOptional).toBe(""); + }); + + it("should not set the value to null", () => { + state.stringWithDefaultAndOptional = null; + expect(state.stringWithDefaultAndOptional).toBe("test"); + }); + + it("should set the value to undefined", () => { + state.stringWithDefaultAndOptional = undefined; + expect(state.stringWithDefaultAndOptional).toBe(undefined); + }); + + it("should not set the value to a number", () => { + state.stringWithDefaultAndOptional = 123; + expect(state.stringWithDefaultAndOptional).toBe("test"); + }); + + it("should not set the value to an object", () => { + state.stringWithDefaultAndOptional = {}; + expect(state.stringWithDefaultAndOptional).toBe("test"); + }); + }); + + describe("String With Default And Nullable", () => { + beforeEach(() => { + state.stringWithDefaultAndNullable = "test"; + }); + it("should set the value", () => { + state.stringWithDefaultAndNullable = "hello world"; + expect(state.stringWithDefaultAndNullable).toBe("hello world"); + }); + + it("should set the value to empty string", () => { + state.stringWithDefaultAndNullable = ""; + expect(state.stringWithDefaultAndNullable).toBe(""); + }); + + it("should set the value to null", () => { + state.stringWithDefaultAndNullable = null; + expect(state.stringWithDefaultAndNullable).toBe(null); + }); + + it("should not set the value to undefined", () => { + state.stringWithDefaultAndNullable = undefined; + expect(state.stringWithDefaultAndNullable).toBe("test"); + }); + + it("should not set the value to a number", () => { + state.stringWithDefaultAndNullable = 123; + expect(state.stringWithDefaultAndNullable).toBe("test"); + }); + + it("should not set the value to an object", () => { + state.stringWithDefaultAndNullable = {}; + expect(state.stringWithDefaultAndNullable).toBe("test"); + }); + }); + + describe("String With Default And Nullable And Optional", () => { + beforeEach(() => { + state.stringWithDefaultAndNullableAndOptional = "test"; + }); + it("should set the value", () => { + state.stringWithDefaultAndNullableAndOptional = "hello world"; + expect(state.stringWithDefaultAndNullableAndOptional).toBe( + "hello world" + ); + }); + + it("should set the value to empty string", () => { + state.stringWithDefaultAndNullableAndOptional = ""; + expect(state.stringWithDefaultAndNullableAndOptional).toBe(""); + }); + + it("should set the value to null", () => { + state.stringWithDefaultAndNullableAndOptional = null; + expect(state.stringWithDefaultAndNullableAndOptional).toBe(null); + }); + + it("should set the value to undefined", () => { + state.stringWithDefaultAndNullableAndOptional = undefined; + expect(state.stringWithDefaultAndNullableAndOptional).toBe(undefined); + }); + + it("should not set the value to a number", () => { + state.stringWithDefaultAndNullableAndOptional = 123; + expect(state.stringWithDefaultAndNullableAndOptional).toBe("test"); + }); + + it("should not set the value to an object", () => { + state.stringWithDefaultAndNullableAndOptional = {}; + expect(state.stringWithDefaultAndNullableAndOptional).toBe("test"); + }); + }); + }); +}); diff --git a/package/src/tests/schemas/array.test.ts b/core/tests/schemas/array.test.ts similarity index 100% rename from package/src/tests/schemas/array.test.ts rename to core/tests/schemas/array.test.ts diff --git a/core/tests/schemas/boolean.test.ts b/core/tests/schemas/boolean.test.ts new file mode 100644 index 0000000..6eb77cd --- /dev/null +++ b/core/tests/schemas/boolean.test.ts @@ -0,0 +1,88 @@ +import { describe, it, expect } from "vitest"; +import { y } from "../../lib/schemas/schema.js"; +import { NULL } from "../../lib/constants.js"; + +describe("BooleanValidator", () => { + describe("basic validation", () => { + const schema = y.boolean(); + + it("should validate boolean values", () => { + expect(schema.isValid(true)).toBe(true); + expect(schema.isValid(false)).toBe(true); + }); + + it("should reject non-boolean values", () => { + expect(schema.isValid("true")).toBe(false); + expect(schema.isValid("false")).toBe(false); + expect(schema.isValid(1)).toBe(false); + expect(schema.isValid(0)).toBe(false); + expect(schema.isValid({})).toBe(false); + expect(schema.isValid([])).toBe(false); + expect(schema.isValid(null)).toBe(false); + expect(schema.isValid(undefined)).toBe(false); + }); + }); + + describe("optional", () => { + const schema = y.boolean().optional(); + + it("should allow undefined when optional", () => { + expect(schema.isValid(undefined)).toBe(true); + expect(schema.isValid(true)).toBe(true); + expect(schema.isValid(false)).toBe(true); + }); + }); + + describe("nullable", () => { + const schema = y.boolean().nullable(); + + it("should allow null when nullable", () => { + expect(schema.isValid(null)).toBe(true); + expect(schema.isValid(true)).toBe(true); + expect(schema.isValid(false)).toBe(true); + }); + }); + + describe("parse", () => { + const schema = y.boolean(); + + it("should parse valid boolean strings", () => { + expect(schema.parse("true")).toEqual({ isValid: true, value: true }); + expect(schema.parse("false")).toEqual({ isValid: true, value: false }); + }); + + it("should handle invalid inputs", () => { + expect(schema.parse("invalid")).toEqual({ isValid: false, value: null }); + expect(schema.parse(null)).toEqual({ isValid: false, value: null }); + }); + }); + + describe("coercion", () => { + const schema = y.boolean(); + + it("should coerce string representations", () => { + expect(schema.coerce("true")).toBe(true); + expect(schema.coerce("false")).toBe(false); + }); + + it("should handle null and invalid inputs", () => { + expect(schema.coerce(null)).toBe(null); + expect(schema.coerce("invalid")).toBe(null); + expect(schema.coerce("2")).toBe(null); + }); + }); + + describe("stringify", () => { + const schema = y.boolean(); + + it("should stringify boolean values correctly", () => { + expect(schema.stringify(true)).toBe("true"); + expect(schema.stringify(false)).toBe("false"); + }); + + it("should handle null and invalid inputs", () => { + expect(schema.stringify(null)).toBe(NULL); + expect(schema.stringify("invalid")).toBe(NULL); + }); + }); +}); diff --git a/core/tests/schemas/date.test.ts b/core/tests/schemas/date.test.ts new file mode 100644 index 0000000..9421184 --- /dev/null +++ b/core/tests/schemas/date.test.ts @@ -0,0 +1,107 @@ +import { describe, it, expect } from "vitest"; +import { y } from "../../lib/schemas/schema.js"; +import { NULL } from "../../lib/constants.js"; + +describe("DateValidator", () => { + describe("basic validation", () => { + const schema = y.date(); + const testDate = new Date("2024-01-07T12:00:00Z"); + + it("should validate Date objects", () => { + expect(schema.isValid(testDate)).toBe(true); + expect(schema.isValid(new Date())).toBe(true); + }); + + it("should reject invalid dates", () => { + expect(schema.isValid("invalid-date")).toBe(false); + expect(schema.isValid("2024-13-45")).toBe(false); + expect(schema.isValid({})).toBe(false); + expect(schema.isValid([])).toBe(false); + expect(schema.isValid(null)).toBe(false); + expect(schema.isValid(undefined)).toBe(false); + }); + }); + + describe("min/max constraints", () => { + const minDate = new Date("2024-01-01"); + const maxDate = new Date("2024-12-31"); + const schema = y.date().min(minDate).max(maxDate); + + it("should validate dates within range", () => { + expect(schema.isValid(new Date("2024-06-15"))).toBe(true); + expect(schema.isValid(minDate)).toBe(true); + expect(schema.isValid(maxDate)).toBe(true); + }); + + it("should reject dates outside range", () => { + expect(schema.isValid(new Date("2023-12-31"))).toBe(false); + expect(schema.isValid(new Date("2025-01-01"))).toBe(false); + }); + }); + + describe("optional", () => { + const schema = y.date().optional(); + + it("should allow undefined when optional", () => { + expect(schema.isValid(undefined)).toBe(true); + expect(schema.isValid(new Date())).toBe(true); + }); + }); + + describe("nullable", () => { + const schema = y.date().nullable(); + + it("should allow null when nullable", () => { + expect(schema.isValid(null)).toBe(true); + expect(schema.isValid(new Date())).toBe(true); + }); + }); + + describe("parse", () => { + const schema = y.date(); + const testDate = new Date("2024-01-07T12:00:00Z"); + + it("should parse valid date strings", () => { + const result = schema.parse("2024-01-07T12:00:00Z"); + expect(result.isValid).toBe(true); + expect(result.value?.toISOString()).toBe(testDate.toISOString()); + }); + + it("should handle invalid inputs", () => { + expect(schema.parse("invalid-date")).toEqual({ + isValid: false, + value: null, + }); + expect(schema.parse(null)).toEqual({ isValid: false, value: null }); + }); + }); + + describe("coercion", () => { + const schema = y.date(); + const testDate = new Date("2024-01-07T12:00:00Z"); + + it("should coerce valid date strings", () => { + const result = schema.coerce("2024-01-07T12:00:00Z"); + expect(result?.toISOString()).toBe(testDate.toISOString()); + }); + + it("should handle null and invalid inputs", () => { + expect(schema.coerce(null)).toBe(null); + expect(schema.coerce("invalid-date")).toBe(null); + }); + }); + + describe("stringify", () => { + const schema = y.date(); + const testDate = new Date("2024-01-07T12:00:00Z"); + + it("should stringify dates correctly", () => { + expect(schema.stringify(testDate)).toBe(testDate.toISOString()); + }); + + it("should handle null and invalid inputs", () => { + expect(schema.stringify(null)).toBe(NULL); + expect(schema.stringify("invalid-date")).toBe(NULL); + }); + }); +}); diff --git a/package/src/tests/schemas/discriminatedUnion.test.ts b/core/tests/schemas/discriminatedUnion.test.ts similarity index 100% rename from package/src/tests/schemas/discriminatedUnion.test.ts rename to core/tests/schemas/discriminatedUnion.test.ts diff --git a/core/tests/schemas/enum.test.ts b/core/tests/schemas/enum.test.ts new file mode 100644 index 0000000..11eb3f5 --- /dev/null +++ b/core/tests/schemas/enum.test.ts @@ -0,0 +1,96 @@ +import { describe, it, expect } from "vitest"; +import { y } from "../../lib/schemas/schema.js"; +import { NULL } from "../../lib/constants.js"; + +describe("EnumValidator", () => { + describe("basic validation", () => { + const schema = y.enum("red", "green", "blue"); + const numSchema = y.enum(1, 2, 3); + + it("should validate string enum values", () => { + expect(schema.isValid("red")).toBe(true); + expect(schema.isValid("green")).toBe(true); + expect(schema.isValid("blue")).toBe(true); + }); + + it("should validate number enum values", () => { + expect(numSchema.isValid(1)).toBe(true); + expect(numSchema.isValid(2)).toBe(true); + expect(numSchema.isValid(3)).toBe(true); + }); + + it("should reject invalid enum values", () => { + expect(schema.isValid("yellow")).toBe(false); + expect(schema.isValid("")).toBe(false); + expect(schema.isValid(123)).toBe(false); + expect(schema.isValid({})).toBe(false); + expect(schema.isValid([])).toBe(false); + expect(schema.isValid(null)).toBe(false); + expect(schema.isValid(undefined)).toBe(false); + + expect(numSchema.isValid(4)).toBe(false); + expect(numSchema.isValid(0)).toBe(false); + expect(numSchema.isValid("1")).toBe(false); + }); + }); + + describe("optional", () => { + const schema = y.enum("red", "green", "blue").optional(); + + it("should allow undefined when optional", () => { + expect(schema.isValid(undefined)).toBe(true); + expect(schema.isValid("red")).toBe(true); + }); + }); + + describe("nullable", () => { + const schema = y.enum("red", "green", "blue").nullable(); + + it("should allow null when nullable", () => { + expect(schema.isValid(null)).toBe(true); + expect(schema.isValid("red")).toBe(true); + }); + }); + + describe("parse", () => { + const schema = y.enum("red", "green", "blue"); + + it("should parse valid enum strings", () => { + expect(schema.parse("red")).toEqual({ isValid: true, value: "red" }); + expect(schema.parse("green")).toEqual({ isValid: true, value: "green" }); + }); + + it("should handle invalid inputs", () => { + expect(schema.parse("yellow")).toEqual({ isValid: false, value: null }); + expect(schema.parse(null)).toEqual({ isValid: false, value: null }); + }); + }); + + describe("coercion", () => { + const schema = y.enum("red", "green", "blue"); + + it("should coerce valid enum values", () => { + expect(schema.coerce("red")).toBe("red"); + expect(schema.coerce("green")).toBe("green"); + }); + + it("should handle null and invalid inputs", () => { + expect(schema.coerce(null)).toBe(null); + expect(schema.coerce("yellow")).toBe(null); + }); + }); + + describe("stringify", () => { + const schema = y.enum("red", "green", "blue"); + + it("should stringify enum values correctly", () => { + expect(schema.stringify("red")).toBe("red"); + expect(schema.stringify("green")).toBe("green"); + }); + + it("should handle null and invalid inputs", () => { + expect(schema.stringify(null)).toBe(NULL); + expect(schema.stringify("yellow")).toBe(""); + }); + }); +}); diff --git a/package/src/tests/schemas/literal.test.ts b/core/tests/schemas/literal.test.ts similarity index 100% rename from package/src/tests/schemas/literal.test.ts rename to core/tests/schemas/literal.test.ts diff --git a/package/src/tests/schemas/map.test.ts b/core/tests/schemas/map.test.ts similarity index 100% rename from package/src/tests/schemas/map.test.ts rename to core/tests/schemas/map.test.ts diff --git a/core/tests/schemas/number.test.ts b/core/tests/schemas/number.test.ts new file mode 100644 index 0000000..610e6b3 --- /dev/null +++ b/core/tests/schemas/number.test.ts @@ -0,0 +1,88 @@ +import { describe, it, expect } from "vitest"; +import { y } from "../../lib/schemas/schema.js"; +import { NULL } from "../../lib/constants.js"; + +describe("NumberValidator", () => { + describe("basic validation", () => { + const schema = y.number(); + + it("should validate number values", () => { + expect(schema.isValid(123)).toBe(true); + expect(schema.isValid(0)).toBe(true); + expect(schema.isValid(-123)).toBe(true); + expect(schema.isValid(123.456)).toBe(true); + }); + + it("should reject non-number values", () => { + expect(schema.isValid("123")).toBe(false); + expect(schema.isValid({})).toBe(false); + expect(schema.isValid([])).toBe(false); + expect(schema.isValid(null)).toBe(false); + expect(schema.isValid(undefined)).toBe(false); + expect(schema.isValid(NaN)).toBe(false); + }); + }); + + describe("optional", () => { + const schema = y.number().optional(); + + it("should allow undefined when optional", () => { + expect(schema.isValid(undefined)).toBe(true); + expect(schema.isValid(123)).toBe(true); + }); + }); + + describe("nullable", () => { + const schema = y.number().nullable(); + + it("should allow null when nullable", () => { + expect(schema.isValid(null)).toBe(true); + expect(schema.isValid(123)).toBe(true); + }); + }); + + describe("parse", () => { + const schema = y.number(); + + it("should parse valid number strings", () => { + expect(schema.parse("123")).toEqual({ isValid: true, value: 123 }); + expect(schema.parse("-123.456")).toEqual({ + isValid: true, + value: -123.456, + }); + }); + + it("should handle invalid inputs", () => { + expect(schema.parse("abc")).toEqual({ isValid: false, value: null }); + expect(schema.parse(null)).toEqual({ isValid: false, value: null }); + }); + }); + + describe("coercion", () => { + const schema = y.number(); + + it("should coerce valid number strings", () => { + expect(schema.coerce("123")).toBe(123); + expect(schema.coerce("-123.456")).toBe(-123.456); + }); + + it("should handle null and invalid inputs", () => { + expect(schema.coerce(null)).toBe(null); + expect(schema.coerce("abc")).toBe(null); + }); + }); + + describe("stringify", () => { + const schema = y.number(); + + it("should stringify numbers correctly", () => { + expect(schema.stringify(123)).toBe("123"); + expect(schema.stringify(-123.456)).toBe("-123.456"); + }); + + it("should handle null and invalid inputs", () => { + expect(schema.stringify(null)).toBe(NULL); + expect(schema.stringify("abc")).toBe(NULL); + }); + }); +}); diff --git a/package/src/tests/schemas/object.test.ts b/core/tests/schemas/object.test.ts similarity index 100% rename from package/src/tests/schemas/object.test.ts rename to core/tests/schemas/object.test.ts diff --git a/package/src/tests/schemas/set.test.ts b/core/tests/schemas/set.test.ts similarity index 100% rename from package/src/tests/schemas/set.test.ts rename to core/tests/schemas/set.test.ts diff --git a/core/tests/schemas/string.test.ts b/core/tests/schemas/string.test.ts new file mode 100644 index 0000000..0844c50 --- /dev/null +++ b/core/tests/schemas/string.test.ts @@ -0,0 +1,88 @@ +import { describe, it, expect } from "vitest"; +import { y } from "../../lib/schemas/schema.js"; +import { NULL } from "../../lib/constants.js"; + +describe("StringValidator", () => { + describe("basic validation", () => { + const schema = y.string(); + + it("should validate string values", () => { + expect(schema.isValid("hello")).toBe(true); + expect(schema.isValid("")).toBe(true); + }); + + it("should reject non-string values", () => { + expect(schema.isValid(123)).toBe(false); + expect(schema.isValid({})).toBe(false); + expect(schema.isValid([])).toBe(false); + expect(schema.isValid(null)).toBe(false); + expect(schema.isValid(undefined)).toBe(false); + }); + }); + + describe("optional", () => { + const schema = y.string().optional(); + + it("should allow undefined when optional", () => { + expect(schema.isValid(undefined)).toBe(true); + // expect(schema.isValid('test')).toBe(true); + }); + }); + + describe("nullable", () => { + const schema = y.string().nullable(); + + it("should allow null when nullable", () => { + expect(schema.isValid(null)).toBe(true); + expect(schema.isValid("test")).toBe(true); + }); + }); + + describe("min length", () => { + const schema = y.string().min(3); + + it("should validate strings with minimum length", () => { + expect(schema.isValid("abc")).toBe(true); + expect(schema.isValid("abcd")).toBe(true); + expect(schema.isValid("ab")).toBe(false); + }); + }); + + describe("max length", () => { + const schema = y.string().max(3); + + it("should validate strings with maximum length", () => { + expect(schema.isValid("abc")).toBe(true); + expect(schema.isValid("ab")).toBe(true); + expect(schema.isValid("abcd")).toBe(false); + }); + }); + + describe("pattern", () => { + const schema = y.string().pattern(/^[A-Z]+$/); + + it("should validate strings matching the pattern", () => { + expect(schema.isValid("ABC")).toBe(true); + expect(schema.isValid("abc")).toBe(false); + expect(schema.isValid("123")).toBe(false); + }); + }); + + describe("coercion", () => { + const schema = y.string(); + + it("should coerce values correctly", () => { + expect(schema.coerce("test")).toBe("test"); + expect(schema.coerce(null as any)).toBe(null); + }); + }); + + describe("stringify", () => { + const schema = y.string(); + + it("should stringify values correctly", () => { + expect(schema.stringify("test")).toBe("test"); + expect(schema.stringify(null)).toBe(NULL); + }); + }); +}); diff --git a/core/tests/syncroState.test.ts b/core/tests/syncroState.test.ts new file mode 100644 index 0000000..bda79bf --- /dev/null +++ b/core/tests/syncroState.test.ts @@ -0,0 +1,292 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { y } from "../lib/index.js"; +import { Doc } from "yjs"; +import { createSyncroState } from "./fixture.js"; + +const schema = { + text: y.string().optional().nullable().default("default"), + number: y.number().optional().nullable().default(1), + boolean: y.boolean().optional().nullable().default(false), + array: y.array(y.string()).optional().nullable().default(["default"]), + date: y.date().optional().nullable().default(new Date()), + enum: y.enum("a", "b", "c").optional().nullable().default("a"), + object: y + .object({ + text: y.string().optional().nullable().default("default"), + number: y.number().optional().nullable().default(1), + boolean: y.boolean().optional().nullable().default(false), + }) + .optional() + .nullable() + .default({ text: "default", number: 1, boolean: false }), + set: y.set(y.string()).optional().nullable().default(["default"]), + map: y + .map(y.string()) + .optional() + .nullable() + .default(new Map([["key", "value"]])), + arrayOfObjects: y + .array(y.object({ text: y.string() })) + .optional() + .nullable() + .default([{ text: "default" }]), + discriminatedUnion: y.discriminatedUnion("type", [ + y + .object({ type: y.literal("a"), value: y.string() }) + .default({ type: "a", value: "hello" }), + y + .object({ type: y.literal("b"), value: y.number() }) + .default({ type: "b", value: 1 }), + ]), +}; + +const doc = new Doc(); +const state1 = createSyncroState({ schema, doc }); +const state2 = createSyncroState({ schema, doc }); + +describe("SyncroState", () => { + describe("text field", () => { + it("should sync regular text mutation", () => { + state1.text = "world"; + expect(state1.text).toBe("world"); + expect(state2.text).toBe("world"); + }); + + it("should sync null text mutation", () => { + state1.text = null; + expect(state1.text).toBe(null); + expect(state2.text).toBe(null); + }); + + it("should sync undefined text mutation", () => { + state1.text = undefined; + expect(state1.text).toBe(undefined); + expect(state2.text).toBe(undefined); + }); + }); + + describe("number field", () => { + it("should sync regular number mutation", () => { + state1.number = 42; + expect(state1.number).toBe(42); + expect(state2.number).toBe(42); + }); + + it("should sync null number mutation", () => { + state1.number = null; + expect(state1.number).toBe(null); + expect(state2.number).toBe(null); + }); + + it("should sync undefined number mutation", () => { + state1.number = undefined; + expect(state1.number).toBe(undefined); + expect(state2.number).toBe(undefined); + }); + }); + + describe("boolean field", () => { + it("should sync regular boolean mutation", () => { + state1.boolean = true; + expect(state1.boolean).toBe(true); + expect(state2.boolean).toBe(true); + }); + + it("should sync null boolean mutation", () => { + state1.boolean = null; + expect(state1.boolean).toBe(null); + expect(state2.boolean).toBe(null); + }); + + it("should sync undefined boolean mutation", () => { + state1.boolean = undefined; + expect(state1.boolean).toBe(undefined); + expect(state2.boolean).toBe(undefined); + }); + }); + + describe("date field", () => { + it("should sync regular date mutation", () => { + const newDate = new Date("2024-01-01"); + state1.date = newDate; + expect(state1.date?.toISOString()).toEqual(newDate.toISOString()); + expect(state2.date?.toISOString()).toEqual(newDate.toISOString()); + }); + + it("should sync null date mutation", () => { + state1.date = null; + expect(state1.date).toBe(null); + expect(state2.date).toBe(null); + }); + + it("should sync undefined date mutation", () => { + state1.date = undefined; + expect(state1.date).toBe(undefined); + expect(state2.date).toBe(undefined); + }); + }); + + describe("enum field", () => { + it("should sync regular enum mutation", () => { + state1.enum = "b"; + expect(state1.enum).toBe("b"); + expect(state2.enum).toBe("b"); + }); + + it("should sync null enum mutation", () => { + state1.enum = null; + expect(state1.enum).toBe(null); + expect(state2.enum).toBe(null); + }); + + it("should sync undefined enum mutation", () => { + state1.enum = undefined; + expect(state1.enum).toBe(undefined); + expect(state2.enum).toBe(undefined); + }); + }); + + describe("object field", () => { + it("should sync regular object mutation", () => { + state1.object = { text: "hello", number: 42, boolean: true }; + expect(state1.object).toEqual({ + text: "hello", + number: 42, + boolean: true, + }); + expect(state2.object).toEqual({ + text: "hello", + number: 42, + boolean: true, + }); + }); + + it("should sync null object mutation", () => { + (state1.object as any) = null; + expect(state1.object).toBe(null); + expect(state2.object).toBe(null); + }); + + it("should sync undefined object mutation", () => { + (state1.object as any) = undefined; + expect(state1.object).toBe(undefined); + expect(state2.object).toBe(undefined); + }); + }); + + describe("array field", () => { + it("should sync regular array mutation", () => { + state1.array = ["hello", "world"]; + expect(state1.array).toEqual(["hello", "world"]); + expect(state2.array).toEqual(["hello", "world"]); + }); + + it("should sync null array mutation", () => { + (state1.array as any) = null; + expect(state1.array).toBe(null); + expect(state2.array).toBe(null); + }); + + it("should sync undefined array mutation", () => { + (state1.array as any) = undefined; + expect(state1.array).toBe(undefined); + expect(state2.array).toBe(undefined); + }); + }); + + describe("set field", () => { + it("should sync regular set mutation", () => { + state1.set = new Set(["hello", "world"]); + expect(Array.from(state1.set)).toEqual(["hello", "world"]); + expect(Array.from(state2.set)).toEqual(["hello", "world"]); + }); + + it("should sync null set mutation", () => { + (state1.set as any) = null; + expect(state1.set).toBe(null); + expect(state2.set).toBe(null); + }); + + it("should sync undefined set mutation", () => { + (state1.set as any) = undefined; + expect(state1.set).toBe(undefined); + expect(state2.set).toBe(undefined); + }); + }); + + describe("map field", () => { + it("should sync regular map mutation", () => { + state1.map = new Map([ + ["hello", "world"], + ["test", "value"], + ]); + + expect(Array.from(state1.map.entries())).toEqual([ + ["hello", "world"], + ["test", "value"], + ]); + expect(Array.from(state2.map.entries())).toEqual([ + ["hello", "world"], + ["test", "value"], + ]); + }); + + it("should sync null map mutation", () => { + (state1.map as any) = null; + expect(state1.map).toBe(null); + expect(state2.map).toBe(null); + }); + + it("should sync undefined map mutation", () => { + (state1.map as any) = undefined; + expect(state1.map).toBe(undefined); + expect(state2.map).toBe(undefined); + }); + + it("should sync map operations", () => { + state1.map = new Map(); + state1.map.set("key1", "value1"); + expect(state1.map.get("key1")).toBe("value1"); + expect(state2.map.get("key1")).toBe("value1"); + + state1.map.delete("key1"); + expect(state1.map.has("key1")).toBe(false); + expect(state2.map.has("key1")).toBe(false); + + state1.map.set("key2", "value2"); + state1.map.clear(); + expect(state1.map.size).toBe(0); + expect(state2.map.size).toBe(0); + }); + }); + + describe("discriminatedUnion field", () => { + it("should sync regular discriminatedUnion mutation", () => { + expect(state1.discriminatedUnion).toEqual({ type: "a", value: "hello" }); + expect(state2.discriminatedUnion).toEqual({ type: "a", value: "hello" }); + }); + + it("should sync null discriminatedUnion mutation", () => { + (state1.discriminatedUnion as any) = null; + expect(state1.discriminatedUnion).toEqual({ type: "a", value: "hello" }); + expect(state2.discriminatedUnion).toEqual({ type: "a", value: "hello" }); + }); + + it("should sync undefined discriminatedUnion mutation", () => { + (state1.discriminatedUnion as any) = undefined; + expect(state1.discriminatedUnion).toEqual({ type: "a", value: "hello" }); + expect(state2.discriminatedUnion).toEqual({ type: "a", value: "hello" }); + }); + + it("should sync discriminatedUnion mutation with different type", async () => { + state1.discriminatedUnion = { type: "b", value: 42 }; + + await wait(100); + expect(state1.discriminatedUnion).toEqual({ type: "b", value: 42 }); + expect(state2.discriminatedUnion).toEqual({ type: "b", value: 42 }); + }); + }); +}); + +const wait = async (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/core/tsconfig.json b/core/tsconfig.json new file mode 100644 index 0000000..9c62f74 --- /dev/null +++ b/core/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/core/tsdown.config.ts b/core/tsdown.config.ts new file mode 100644 index 0000000..491cdfd --- /dev/null +++ b/core/tsdown.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + entry: ["./lib/index.ts"], + outDir: "./dist", + format: "esm", + sourcemap: true, + clean: true, + dts: true, + tsconfig: "./tsconfig.json", +}); diff --git a/core/vite.config.ts b/core/vite.config.ts new file mode 100644 index 0000000..dcedac6 --- /dev/null +++ b/core/vite.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [], + test: { + // include: [ + // 'src/tests/**/map.proxy.{test,spec}.{js,ts}', + // 'src/tests/**/map.{test,spec}.{js,ts}', + // 'src/tests/**/syncroState.{test,spec}.{js,ts}' + // ], + // include: ['src/tests/**/syncroState.{test,spec}.{js,ts}'], + include: ["tests/**/*.{test,spec}.{js,ts}"], + coverage: { + provider: "v8", + enabled: true, + include: ["src/lib/**/*.{svelte,ts}"], + }, + }, +}); diff --git a/demo/package.json b/demo/package.json index 26cc46f..ad62f74 100644 --- a/demo/package.json +++ b/demo/package.json @@ -42,7 +42,7 @@ "konva": "^9.3.18", "roughjs": "^4.6.6", "svelte-konva": "^0.3.1", - "syncrostate": "workspace:*", + "@syncrostate/svelte": "workspace:*", "y-partyserver": "^0.0.31" } } diff --git a/package/postcss.config.js b/package/postcss.config.js deleted file mode 100644 index 0f77216..0000000 --- a/package/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {} - } -}; diff --git a/package/src/app.css b/package/src/app.css deleted file mode 100644 index a31e444..0000000 --- a/package/src/app.css +++ /dev/null @@ -1,3 +0,0 @@ -@import 'tailwindcss/base'; -@import 'tailwindcss/components'; -@import 'tailwindcss/utilities'; diff --git a/package/src/lib/index.ts b/package/src/lib/index.ts deleted file mode 100644 index 2fa1939..0000000 --- a/package/src/lib/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { syncroState } from './proxys/syncroState.svelte.js'; -export { usePresence } from './presence.svelte.js'; -export { y } from './schemas/schema.js'; diff --git a/package/src/lib/presence.svelte.ts b/package/src/lib/presence.svelte.ts deleted file mode 100644 index d6b41e8..0000000 --- a/package/src/lib/presence.svelte.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Awareness as YAwareness } from 'y-protocols/awareness'; -import { Doc } from 'yjs'; -import { PRENSENCE, PRENSENCE_ID, PRESENCE_CONTEXT_KEY } from './constants.js'; -import { getContext } from 'svelte'; - -export type PresenceUser = Record; - -export class Presence { - private awareness: YAwareness; - private id = $state(); - private synced = $state(false); - me = $state(); - others = $state([]); - - constructor({ doc, awareness }: { doc: Doc; awareness?: YAwareness }) { - this.awareness = awareness ?? new YAwareness(doc); - - $effect(() => { - if (this.synced) { - this.awareness.setLocalStateField(PRENSENCE, this.me); - } - }); - } - - private setOthers = () => { - const users = Array.from(this.awareness.getStates().values()); - - this.others = users - .filter((user) => user.id !== this.id) - .reduce((acc, user) => { - if (user[PRENSENCE_ID] && user[PRENSENCE_ID] !== this.id) { - acc.push(user[PRENSENCE]); - } - return acc; - }, [] as any); - }; - init = ({ me = {}, awareness }: { me?: PresenceUser; awareness?: YAwareness }) => { - if (awareness) { - this.awareness = awareness; - } - if (me.id && typeof me.id === 'string') { - this.id = me.id as string; - } - this.me = me; - this.synced = true; - this.awareness.setLocalStateField(PRENSENCE_ID, this.id); - this.awareness.setLocalStateField(PRENSENCE, this.me); - this.setOthers(); - - this.awareness.on('update', this.setOthers); - }; -} - -export const usePresence = () => { - const presence = getContext(PRESENCE_CONTEXT_KEY) as Presence; - return { - others: presence.others, - me: presence.me - }; -}; diff --git a/package/src/lib/proxys/array.svelte.ts b/package/src/lib/proxys/array.svelte.ts deleted file mode 100644 index 6a6ed7b..0000000 --- a/package/src/lib/proxys/array.svelte.ts +++ /dev/null @@ -1,437 +0,0 @@ -import * as Y from 'yjs'; -import type { ArrayValidator } from '../schemas/array.js'; -import { createSyncroState, type State, type SyncroStates } from './syncroState.svelte.js'; -import type { SyncedContainer } from './common.js'; - -import { isArrayNull, logError, observeArray, propertyToNumber, setArrayToNull } from '../utils.js'; -import { NULL_ARRAY } from '$lib/constants.js'; - -export class SyncedArray { - state: State; - validator: ArrayValidator; - yType: Y.Array; - parent: SyncedContainer; - key: string | number; - syncroStates = $state([]); - proxy: any; - isNull = $state(false); - // array: any[] = $state(this.syncroStates.map((state) => state.value)); - - //🚨 Using a derived would be preferable but it breaks the tests :/ - // private array = $derived(this.syncroStates.map((state) => state.value)); - - private get array() { - return this.syncroStates.map((state) => state.value); - } - setNull = () => { - this.yType.delete(0, this.yType.length); - this.yType.insert(0, [new Y.Text(NULL_ARRAY)]); - this.isNull = true; - }; - // setNull = setArrayToNull.bind(this); - - deleteProperty = (target: any, prop: any) => { - const index = propertyToNumber(prop); - if (typeof index !== 'number') { - return true; - } - - if (!this.validator.$schema.shape.$schema.optional) { - logError('Can not delete non optional property', index); - return true; - } - const syncroState = this.syncroStates[index]; - if (!syncroState) { - logError('Index does not exist', index); - return true; - } - syncroState.value = undefined; - return true; - }; - - set value(input: any[] | null | undefined) { - const { isValid, value } = this.validator.parse(input); - if (!isValid) { - logError('Invalid value', { value }); - } else { - this.state.transaction(() => { - if (!value) { - if (value === undefined) { - this.parent.deleteProperty({}, this.key); - } else { - this.setNull(); - } - } else { - if (this.isNull) { - this.isNull = false; - this.yType.delete(0, this.yType.length); - } - if (!this.isNull) { - const remainingStates = this.syncroStates.slice(value.length); - remainingStates.forEach((state) => { - state.destroy(); - }); - if (remainingStates.length) { - this.yType.delete(value.length, remainingStates.length); - } - } - this.syncroStates = value.map((item, index) => { - const previsousState = this.syncroStates[index]; - - if (previsousState) { - previsousState.value = item; - return previsousState; - } else { - return createSyncroState({ - key: index, - forceNewType: true, - validator: this.validator.$schema.shape, - parent: this, - value: item, - state: this.state - }); - } - }); - } - }); - } - } - get value() { - if (this.isNull) { - return null; - } - return this.proxy; - } - - constructor({ - validator, - yType, - value, - parent, - key, - state - }: { - validator: ArrayValidator; - yType: Y.Array; - value: any[]; - parent: SyncedContainer; - key: string | number; - state: State; - }) { - this.validator = validator; - this.yType = yType; - this.parent = parent; - this.key = key; - this.state = state; - yType.observe(this.observe); - - this.proxy = new Proxy([], { - get: (target: any, prop: any, receiver: any) => { - if (prop === 'getState') { - return () => state; - } - if (prop === 'getYType') { - return () => this.yType; - } - if (prop === 'getYTypes') { - return () => this.yType.toArray(); - } - const p = propertyToNumber(prop); - if (Number.isInteger(p)) { - const syncroState = this.syncroStates[p as number]; - if (!syncroState) { - return undefined; - } - return syncroState.value; - } else if (typeof p === 'string') { - if (p in this.methods) { - return this.methods[p as keyof typeof this.methods]; - } - - if (p[0] === '$') { - return Reflect.get(target, p); - } - - if (p === 'toJSON') { - return this.toJSON(); - } - - if (p === 'length') { - return this.array.length; - } - } else if (p === Symbol.toStringTag) { - return 'Array'; - } else if (p === Symbol.iterator) { - const values = this.array.slice(); - return Reflect.get(values, p); - } - return Reflect.get(target, p, receiver); - }, - - set: (target: any, prop: any, value: any) => { - const p = propertyToNumber(prop); - if (Number.isInteger(p)) { - if (value === undefined) { - return this.deleteProperty(target, p); - } - - const syncroState = this.syncroStates[p as number]; - - if (!syncroState) { - this.state.transaction(() => { - this.syncroStates[p as number] = createSyncroState({ - key: p as number, - validator: this.validator.$schema.shape, - parent: this, - value, - state: this.state - }); - }); - } else { - syncroState.value = value; - } - } - return true; - }, - deleteProperty: this.deleteProperty, - has: (target, prop) => { - const p = propertyToNumber(prop); - if (typeof p !== 'number') { - // forward to arrayimplementation - return Reflect.has(target, p); - } - if (p < (this.array as any).lengthUntracked && p >= 0) { - return true; - } else { - return false; - } - }, - - getOwnPropertyDescriptor: (target, prop) => { - const p = propertyToNumber(prop); - if (p === 'length') { - return { - enumerable: false, - configurable: false, - writable: true - }; - } - if (typeof p === 'number' && p >= 0 && p < this.yType.length) { - return { - enumerable: true, - configurable: true, - writable: true - }; - } - return undefined; - }, - ownKeys: (target) => { - const keys: string[] = []; - for (let i = 0; i < this.yType.length; i++) { - keys.push(i + ''); - } - keys.push('length'); - return keys; - } - }); - - this.sync(value); - } - - toJSON = () => { - return this.array; - }; - - sync = (value?: any[]) => { - this.state.transaction(() => { - this.syncroStates = []; - if (isArrayNull(this)) { - this.isNull = true; - return; - } - - if (this.state.initialized || value) { - for (let i = 0; i < Math.max(value?.length || 0, this.yType.length); i++) { - this.syncroStates[i] = createSyncroState({ - key: i, - validator: this.validator.$schema.shape, - parent: this, - value: value?.[i] || this.validator.$schema.default?.[i], - state: this.state - }); - } - } else { - if (this.validator.$schema.default) { - this.syncroStates = this.validator.$schema.default.map((item, index) => { - return createSyncroState({ - key: index, - validator: this.validator.$schema.shape, - parent: this, - value: item, - state: this.state - }); - }); - } else if (this.validator.$schema.nullable && !value) { - this.setNull(); - } - } - }); - }; - - observe = observeArray.bind(this); - - methods = { - slice: (start?: number | undefined, end?: number | undefined) => { - return this.array.slice(start, end); - }, - toReversed: () => { - return this.array.toReversed(); - }, - forEach: (cb: (value: T, index: number, array: T[]) => void) => { - return this.array.forEach(cb); - }, - every: (cb: (value: T, index: number, array: T[]) => boolean) => { - return this.array.every(cb); - }, - filter: (cb: (value: T, index: number, array: T[]) => boolean) => { - return this.array.filter(cb); - }, - find: (cb: (value: T, index: number, array: T[]) => boolean) => { - return this.array.find(cb); - }, - findIndex: (cb: (value: T, index: number, array: T[]) => boolean) => { - return this.array.findIndex(cb); - }, - some: (cb: (value: T, index: number, array: T[]) => boolean) => { - return this.array.some(cb); - }, - includes: (value: T) => { - return this.array.includes(value); - }, - map: (cb: (value: T, index: number, array: T[]) => T) => { - return this.array.map(cb); - }, - reduce: ( - cb: (acc: X | undefined, value: T, index: number, array: T[]) => X, - initialValue?: X - ) => { - return this.array.reduce(cb, initialValue); - }, - indexOf: (value: T) => { - return this.array.indexOf(value); - }, - at: (index: number) => { - return this.array.at(index)?.value; - }, - // - // Mutatives methods - // - - pop: () => { - if (!this.syncroStates.length) { - return undefined; - } - const last = this.syncroStates.pop(); - this.state.transaction(() => { - this.yType.delete(this.yType.length - 1, 1); - last?.destroy(); - }); - return last?.value; - }, - shift: () => { - if (!this.syncroStates.length) { - return undefined; - } - const first = this.syncroStates.shift(); - this.state.transaction(() => { - this.yType.delete(0, 1); - first?.destroy(); - }); - return first?.value; - }, - unshift: (...items: T[]) => { - let result; - this.state.transaction(() => { - result = this.syncroStates.unshift( - ...items.map((item, index) => { - return createSyncroState({ - forceNewType: true, - key: index, - validator: this.validator.$schema.shape, - parent: this, - value: item, - state: this.state - }); - }) - ); - }); - return result; - }, - push: (...items: T[]) => { - this.state.transaction(() => { - this.syncroStates.push( - ...items.map((item, index) => { - return createSyncroState({ - key: this.yType.length, - validator: this.validator.$schema.shape, - parent: this, - value: item, - state: this.state - }); - }) - ); - }); - }, - splice: (start: number, deleteCount: number, ..._items: T[]) => { - let result: any[] = []; - this.state.transaction(() => { - // Normalize start index (handle negative values like native Array.splice) - const actualStart = start < 0 ? Math.max(0, this.syncroStates.length + start) : Math.min(start, this.syncroStates.length); - - // Normalize deleteCount (don't delete more than available, treat negative as 0) - const actualDeleteCount = Math.min(Math.max(0, deleteCount), this.syncroStates.length - actualStart); - - // Delete from Y.js document (only if there are items to delete and within bounds) - if (actualDeleteCount > 0 && this.yType.length > actualStart) { - const yDeleteCount = Math.min(actualDeleteCount, this.yType.length - actualStart); - this.yType.delete(actualStart, yDeleteCount); - } - - // Create new SyncroState objects for inserted items - const newSyncroStates = _items.map((item, index) => { - return createSyncroState({ - key: actualStart + index, - forceNewType: true, - validator: this.validator.$schema.shape, - parent: this, - value: item, - state: this.state - }); - }); - - // Destroy old SyncroState objects for deleted items (with bounds checking) - if (actualDeleteCount > 0) { - for (let i = 0; i < actualDeleteCount; i++) { - const state = this.syncroStates[actualStart + i]; - if (state) { - state.destroy(); - } - } - } - - // Update the syncroStates array with normalized values - result = this.syncroStates.splice(actualStart, actualDeleteCount, ...newSyncroStates); - }); - - return result; - } - }; - - destroy = () => { - this.syncroStates.forEach((state) => { - state.destroy(); - }); - this.syncroStates = []; - this.yType.unobserve(this.observe); - }; -} diff --git a/package/src/lib/proxys/base.svelte.ts b/package/src/lib/proxys/base.svelte.ts deleted file mode 100644 index b5d091b..0000000 --- a/package/src/lib/proxys/base.svelte.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as Y from 'yjs'; -import { NULL } from '../constants.js'; -import type { SyncedContainer } from './common.js'; -import type { State } from './syncroState.svelte.js'; - -type ObserverCallback = (e: Y.YEvent, transact: Y.Transaction) => void; - -export class BaseSyncedType { - yType: Y.Text; - rawValue = $state(''); - observeCallback?: ObserverCallback; - state: State; - parent: SyncedContainer; - key: string | number; - constructor(opts: { - yType: Y.Text; - key: string | number; - parent: SyncedContainer; - state: State; - }) { - this.yType = opts.yType; - this.rawValue = opts.yType.toString(); - this.yType.observe(this.observe); - this.parent = opts.parent; - this.key = opts.key; - this.state = opts.state; - } - - deletePropertyFromParent = () => { - this.parent.deleteProperty({}, this.key); - }; - - observe = (e: Y.YEvent, transact: Y.Transaction) => { - if (transact.origin !== this.state.transactionKey) { - this.rawValue = this.yType.toString(); - this.observeCallback?.(e, transact); - } - }; - - destroy = () => { - this.yType.unobserve(this.observe); - }; - - setYValue(value: string | null) { - if (this.rawValue !== value) { - const length = this.yType.length; - this.rawValue = value; - this.state.transaction(() => { - this.yType.applyDelta( - length ? [{ delete: length }, { insert: value ?? NULL }] : [{ insert: value ?? NULL }] - ); - }); - } - } -} diff --git a/package/src/lib/proxys/boolean.svelte.ts b/package/src/lib/proxys/boolean.svelte.ts deleted file mode 100644 index 81c3942..0000000 --- a/package/src/lib/proxys/boolean.svelte.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { BooleanValidator } from '../schemas/boolean.js'; -import { BaseSyncedType } from './base.svelte.js'; -import * as Y from 'yjs'; -import type { SyncedContainer } from './common.js'; -import { logError } from '../utils.js'; -import type { State } from './syncroState.svelte.js'; - -// 🚨🚨🚨 design decision: boolean are defaulted to false if not optionnal or nullable and the value does not exist in the document. - -export class SyncedBoolean extends BaseSyncedType { - validator: BooleanValidator; - - get value() { - const value = this.validator.coerce(this.rawValue); - if (!this.validator.$schema.nullable && value === null) { - return this.validator.$schema.default || false; - } - if (!this.validator.$schema.optional && value === undefined) { - return this.validator.$schema.default || false; - } - return value; - } - - set value(value: boolean | null) { - if (!this.validator.isValid(value)) { - logError('Invalid value', { value }); - return; - } - if (value === undefined) { - this.deletePropertyFromParent(); - } else { - this.setYValue(this.validator.stringify(value)); - } - } - - constructor(opts: { - yType: Y.Text; - validator: BooleanValidator; - parent: SyncedContainer; - key: string | number; - state: State; - }) { - super(opts); - this.validator = opts.validator; - } -} diff --git a/package/src/lib/proxys/common.ts b/package/src/lib/proxys/common.ts deleted file mode 100644 index 0e120e9..0000000 --- a/package/src/lib/proxys/common.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { SyncedArray } from './array.svelte.js'; -import type { SyncedSet } from './set.svelte.js'; -import type { SyncedObject } from './object.svelte.js'; -import type { SyncedMap } from './map.svelte.js'; - -export type SyncedContainer = SyncedObject | SyncedArray | SyncedSet | SyncedMap; diff --git a/package/src/lib/proxys/date.svelte.ts b/package/src/lib/proxys/date.svelte.ts deleted file mode 100644 index 79921cf..0000000 --- a/package/src/lib/proxys/date.svelte.ts +++ /dev/null @@ -1,94 +0,0 @@ -import * as Y from 'yjs'; -import type { DateValidator } from '../schemas/date.js'; -import { SvelteDate } from 'svelte/reactivity'; -import { NULL } from '../constants.js'; -import { BaseSyncedType } from './base.svelte.js'; -import type { SyncedContainer } from './common.js'; -import { logError } from '../utils.js'; -import type { State } from './syncroState.svelte.js'; -// 🚨🚨🚨 design decision: date are defaulted to new Date() if not optionnal or nullable and the value does not exist in the document. - -const SvelteDateProxy = (onSet: () => void) => { - const date = new SvelteDate(); - return new Proxy(date, { - get(target, prop) { - const result = Reflect.get(target, prop); - if (typeof result === 'function') { - return (...args: any[]) => { - const ret = result.call(target, ...args); - if (typeof prop === 'string' && prop.startsWith('set')) { - onSet(); - } - return ret; - }; - } else { - return result; - } - } - }); -}; - -export class SyncedDate extends BaseSyncedType { - validator: DateValidator; - - date = SvelteDateProxy(() => { - const newRawValue = this.date.toISOString(); - const isNull = this.date.getTime() === 0; - if (newRawValue !== this.rawValue && !isNull) { - this.setYValue(newRawValue); - } - }); - - get value() { - const value = this.rawValue === NULL || !this.rawValue ? null : this.date; - if (!this.validator.$schema.nullable && value === null) { - return this.date; - } - if (!this.validator.$schema.optional && value === undefined) { - return this.date; - } - return value; - } - - set value(value: Date | null | string | number) { - const isValid = this.validator.isValid(value); - if (!isValid) { - logError('Invalid value', { value }); - return; - } - if (value !== null && value !== undefined) { - this.setYValue(new Date(value).toISOString()); - this.date.setTime(new Date(value).getTime()); - } else { - if (value === undefined) { - this.deletePropertyFromParent(); - } else { - this.setYValue(null); - this.date.setTime(0); - } - } - } - - setValue = (string: string | null) => { - const { isValid, value } = this.validator.parse(string); - if (isValid) { - this.date.setTime(value?.getTime() || 0); - } - }; - - observeCallback = () => { - this.setValue(this.rawValue); - }; - - constructor(opts: { - yType: Y.Text; - validator: DateValidator; - parent: SyncedContainer; - key: string | number; - state: State; - }) { - super(opts); - this.validator = opts.validator; - this.setValue(this.rawValue); - } -} diff --git a/package/src/lib/proxys/discriminatedUnion.svelte.ts b/package/src/lib/proxys/discriminatedUnion.svelte.ts deleted file mode 100644 index b18156e..0000000 --- a/package/src/lib/proxys/discriminatedUnion.svelte.ts +++ /dev/null @@ -1,206 +0,0 @@ -import * as Y from 'yjs'; -import type { DiscriminatedUnionValidator } from '../schemas/discriminatedUnion.js'; -import type { ObjectValidator } from '../schemas/object.js'; -import { createSyncroState, type State } from './syncroState.svelte.js'; -import { SyncedObject } from './object.svelte.js'; -import type { SyncedContainer } from './common.js'; -import { logError } from '../utils.js'; -import { NULL_OBJECT } from '$lib/constants.js'; -import type { Validator } from '$lib/schemas/schema.js'; - -export class SyncedDiscriminatedUnion { - state: State; - validator: DiscriminatedUnionValidator; - objectProxy: SyncedObject | null = $state(null); - parent: SyncedContainer; - key: string | number; - - get currentVariant() { - return this.objectProxy?.validator; - } - - get yType() { - return this.objectProxy?.yType; - } - - get isNull() { - return this.objectProxy?.isNull || false; - } - get proxy() { - return this.objectProxy!.proxy; - } - - set value(input: any) { - const { isValid, value } = this.validator.parse(input); - - if (!isValid) { - logError('Invalid value', { input }); - return; - } - if (!value) { - this.state.transaction(() => { - // Let discriminated union handle null and undefined - // It can be nullable or optional - // But the underlying object proxy can or can not reflect that aspect - if (input === undefined) { - this.parent.deleteProperty({}, this.key); - } else if (input === null) { - this.objectProxy!.setNull(); - } - }); - } else { - const newDiscriminantValue = - this.validator.$schema.discriminantKey in value && - (value as any)[this.validator.$schema.discriminantKey]; - const oldDiscriminantValue = - this.objectProxy?.value?.[this.validator.$schema.discriminantKey]; - if (newDiscriminantValue !== oldDiscriminantValue) { - this.swapValidator(newDiscriminantValue); - } - // Call the set trap of the object proxy - this.objectProxy!.value = value; - } - } - - get value() { - return this.objectProxy!.value; - } - - // Simple forwarding - discriminated union doesn't manage properties directly - deleteProperty = (target: any, p: any) => { - return this.objectProxy?.deleteProperty(target, p) || false; - }; - - setNull() { - this.objectProxy?.setNull(); - } - - // Find which variant matches the discriminant value - private getVariantByDiscriminant(discriminantValue: any): ObjectValidator | null { - for (const variant of this.validator.$schema.variantValidators) { - const discriminantValidator = variant.$schema.shape[this.validator.$schema.discriminantKey]; - if (discriminantValidator && discriminantValidator.isValid(discriminantValue)) { - return variant; - } - } - return null; - } - - private swapValidator(discriminantValue?: string) { - if (discriminantValue === undefined) { - return; - } - const matchingValidator = this.getVariantByDiscriminant(discriminantValue); - if (matchingValidator) { - this.objectProxy!.validator = matchingValidator; - } - } - - constructor({ - state, - observe = true, - validator, - yType, - value, - parent, - key, - baseImplementation = {} - }: { - state: State; - observe?: boolean; - validator: DiscriminatedUnionValidator; - yType: Y.Map; - value?: any; - parent: SyncedContainer; - key: string | number; - baseImplementation?: any; - }) { - this.parent = parent; - this.state = state; - this.key = key; - this.validator = validator; - - let objectValidator = this.validator.$schema.variantValidators[0]; - - if (observe) - if (yType.has(this.validator.$schema.discriminantKey)) { - const discriminantValue = yType.get(this.validator.$schema.discriminantKey); - objectValidator = this.getVariantByDiscriminant(discriminantValue) || objectValidator; - } else if (value) { - const discriminantValue = value?.[this.validator.$schema.discriminantKey]; - objectValidator = this.getVariantByDiscriminant(discriminantValue) || objectValidator; - } - - // We need to get the current variant from the yType if the document has already been initialized. - // Otherwise we need to get the variant from the validator default value if there is one. - // Otherwise we need to get the variant from the first validator in the array. - - this.objectProxy = new SyncedObject({ - validator: objectValidator, - yType, - parent, - key, - state, - value, - observe, - baseImplementation, - onObserve: this.observe - }); - } - - toJSON = () => { - return this.objectProxy!.toJSON(); - }; - - observe = (e: Y.YMapEvent, _transaction: Y.Transaction) => { - // Only handle external changes (not our own) - const discriminantKeyChange = e.changes.keys.get(this.validator.$schema.discriminantKey); - if (discriminantKeyChange) { - const discriminantValue = e.target.get(this.validator.$schema.discriminantKey)?.toString(); - this.swapValidator(discriminantValue); - } - const objectProxy = this.objectProxy; - if (!objectProxy) return; - - const shape = this.currentVariant!.$schema.shape; - - Object.keys(objectProxy.syncroStates).forEach((k) => { - if (!(k in shape)) { - objectProxy.syncroStates[k].destroy(); - delete objectProxy.syncroStates[k]; - } - }); - - Object.entries(shape).forEach(([key, validator]) => { - const syncedState = objectProxy.syncroStates[key]; - - if (!syncedState) { - objectProxy.syncroStates[key] = createSyncroState({ - key, - validator: validator as Validator, - parent: objectProxy, - state: this.state - }); - } else { - if (syncedState.validator !== shape[key]) { - syncedState.destroy(); - objectProxy.syncroStates[key] = createSyncroState({ - key, - validator: validator as Validator, - parent: objectProxy, - state: this.state - }); - } - } - }); - }; - - sync = (value?: any) => { - return this.objectProxy!.sync(value); - }; - - destroy = () => { - this.objectProxy!.destroy(); - this.objectProxy = null; - }; -} diff --git a/package/src/lib/proxys/enum.svelte.ts b/package/src/lib/proxys/enum.svelte.ts deleted file mode 100644 index ee5011b..0000000 --- a/package/src/lib/proxys/enum.svelte.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as Y from 'yjs'; -import type { EnumValidator } from '../schemas/enum.js'; -import { BaseSyncedType } from './base.svelte.js'; -import type { SyncedContainer } from './common.js'; -import { logError } from '../utils.js'; -import type { State } from './syncroState.svelte.js'; -// 🚨🚨🚨 design decision: enum are defaulted to the first value of the set if not optionnal or nullable and the value does not exist in the document. -export class SyncedEnum extends BaseSyncedType { - validator: EnumValidator; - private firstValue: T; - - get value() { - const value = this.validator.coerce(this.rawValue); - if (!this.validator.$schema.nullable && value === null) { - return this.validator.$schema.default || this.firstValue; - } - if (!this.validator.$schema.optional && value === undefined) { - return this.validator.$schema.default || this.firstValue; - } - return value; - } - - set value(value: T | null) { - if (!this.validator.isValid(value)) { - logError('Invalid value', { value }); - return; - } - if (value === undefined) { - this.deletePropertyFromParent(); - } else { - this.setYValue(this.validator.stringify(value)); - } - } - - constructor(opts: { - yType: Y.Text; - validator: EnumValidator; - parent: SyncedContainer; - key: string | number; - state: State; - }) { - super(opts); - this.firstValue = opts.validator.$schema.values.values().next().value; - this.validator = opts.validator; - } -} diff --git a/package/src/lib/proxys/map.svelte.ts b/package/src/lib/proxys/map.svelte.ts deleted file mode 100644 index 9dc4a57..0000000 --- a/package/src/lib/proxys/map.svelte.ts +++ /dev/null @@ -1,272 +0,0 @@ -import * as Y from 'yjs'; -import { SvelteMap } from 'svelte/reactivity'; -import { logError } from '../utils.js'; -import { createSyncroState, type State, type SyncroStates } from './syncroState.svelte.js'; -import type { SyncedContainer } from './common.js'; -import type { MapValidator } from '$lib/schemas/map.js'; -import { NULL_OBJECT } from '$lib/constants.js'; -import type { Validator } from '$lib/schemas/schema.js'; - -export class SyncedMap { - state: State; - validator: MapValidator; - yType: Y.Map; - parent: SyncedContainer; - key: string | number; - isNull = $state(false); - syncroStates = new SvelteMap(); - syncroStatesValues = new SvelteMap(); - - constructor(opts: { - yType: Y.Map; - validator: MapValidator; - parent: SyncedContainer; - key: string | number; - state: State; - value: any; - }) { - this.key = opts.key; - this.state = opts.state; - this.yType = opts.yType; - this.parent = opts.parent; - this.validator = opts.validator; - this.sync(opts.value); - this.yType.observe(this.observe); - } - - deleteProperty = (_target: any, p: any) => { - if (typeof p !== 'string') { - return true; - } - - const syncroState = this.syncroStates.get(p); - if (!syncroState) { - logError('Property does not exist', p); - return true; - } - syncroState.destroy(); - this.yType.delete(p); - this.syncroStates.delete(p); - this.syncroStatesValues.delete(p); - return true; - }; - - setNull() { - this.state.transaction(() => { - this.syncroStates.forEach((state) => state.destroy()); - this.syncroStatesValues.clear(); - this.syncroStates.clear(); - this.isNull = true; - this.yType.set(NULL_OBJECT, new Y.Text(NULL_OBJECT)); - }); - } - observe = (e: Y.YMapEvent, _transaction: Y.Transaction) => { - if (_transaction.origin !== this.state.transactionKey) { - if (this.yType.has(NULL_OBJECT)) { - this.isNull = true; - this.syncroStates.forEach((state) => state.destroy()); - this.syncroStates.clear(); - this.syncroStatesValues.clear(); - return; - } - e.changes?.keys.forEach(({ action }, key) => { - const syncedState = this.syncroStates.get(key); - if (action === 'delete' && syncedState) { - syncedState.destroy(); - this.syncroStates.delete(key); - this.syncroStatesValues.delete(key); - } - if (action === 'add') { - // If a new key is added to the object and is valid, integrate it - const syncroState = createSyncroState({ - key, - validator: this.validator.$schema.shape as Validator, - state: this.state, - parent: this - }); - this.addState(key, syncroState); - } - }); - } - }; - - addState = (key: string, state: SyncroStates) => { - this.syncroStates.set(key, state); - this.syncroStatesValues.set(key, state.value); - }; - - addValue = (key: string, value: any) => { - const state = createSyncroState({ - key, - validator: this.validator.$schema.shape, - parent: this, - value, - state: this.state - }); - this.addState(key, state); - }; - - sync = (value: any) => { - this.state.transaction(() => { - this.syncroStates.clear(); - this.syncroStatesValues.clear(); - if (this.yType.has(NULL_OBJECT)) { - this.isNull = true; - return; - } - - if (value) { - Object.entries(value).forEach(([key, value]) => { - this.addValue(key, value); - }); - } else if (this.state.initialized) { - this.yType.forEach((value, key) => { - this.addValue(key, value); - }); - } else if (this.validator.$schema.default) { - this.validator.$schema.default?.forEach((value, key) => { - this.addValue(key, value); - }); - } else if (this.validator.$schema.nullable && !value) { - this.setNull(); - } - }); - }; - - toJSON = () => { - return Object.fromEntries(this.syncroStates.entries()); - }; - - destroy = () => { - this.syncroStates.clear(); - this.yType.unobserve(this.observe); - }; - - proxyMap = new Proxy(this.syncroStatesValues, { - get: (target, prop) => { - if (prop === 'getState') { - return () => this.state; - } - if (prop === 'getYType') { - return () => this.yType; - } - if (prop === 'getYTypes') { - return () => Object.fromEntries(this.yType.entries()); - } - if (prop === Symbol.iterator) { - return () => this.syncroStatesValues.entries(); - } - - const result = Reflect.get(target, prop); - if (typeof result === 'function') { - return (...args: any[]) => { - if (typeof prop === 'string') { - switch (prop) { - case 'set': { - const [key, value] = args; - const { isValid, value: parsedValue } = this.validator.$schema.shape.parse(value); - console.log({ isValid, value: parsedValue }); - if (!isValid) { - logError('Invalid value', { value }); - return false; - } - - const hasValue = this.syncroStates.has(value); - - if (hasValue) { - return false; - } - this.state.transaction(() => { - const state = createSyncroState({ - forceNewType: true, - key, - validator: this.validator.$schema.shape, - parent: this, - value: value, - state: this.state - }); - this.addState(key, state); - }); - - return this.proxyMap; - } - case 'delete': { - this.deleteProperty(target, args[0]); - - return this.proxyMap; - } - case 'clear': - { - this.state.transaction(() => { - this.syncroStates.forEach((state) => state.destroy()); - this.yType.clear(); - this.syncroStatesValues.clear(); - this.syncroStates.clear(); - }); - } - return this.proxyMap; - default: - break; - } - return result.call(target, ...args); - } - }; - } else { - return result; - } - } - }); - - get value() { - if (this.isNull) { - return null; - } - - return this.proxyMap; - } - - set value(input: Map | null) { - const { isValid, value } = this.validator.parse(input); - this.state.transaction(() => { - if (!isValid) { - logError('Invalid value', { value }); - return; - } else { - if (!value) { - if (value === undefined) { - this.parent.deleteProperty({}, this.key); - } else { - this.setNull(); - } - } else { - if (this.isNull) { - this.isNull = false; - this.yType.delete(NULL_OBJECT); - } - - if (!this.isNull) { - const remainingStates = Array.from(this.syncroStates.keys()).filter( - (key) => !(key in value) - ); - remainingStates.forEach((key) => { - this.deleteProperty({}, key); - }); - } - Array.from(value.entries()).forEach(([key, value]) => { - const state = this.syncroStates.get(key); - if (state) { - state.value = value; - this.syncroStatesValues.set(key, value); - } else { - const { isValid, value: parsedValue } = this.validator.$schema.shape.parse(value); - - if (isValid) { - this.addValue(key, parsedValue); - } - } - }); - } - } - }); - } -} diff --git a/package/src/lib/proxys/number.svelte.ts b/package/src/lib/proxys/number.svelte.ts deleted file mode 100644 index 9fd9c8c..0000000 --- a/package/src/lib/proxys/number.svelte.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as Y from 'yjs'; -import type { NumberValidator } from '../schemas/number.js'; -import { BaseSyncedType } from './base.svelte.js'; -import type { SyncedContainer } from './common.js'; -import { logError } from '../utils.js'; -import type { State } from './syncroState.svelte.js'; -export class SyncedNumber extends BaseSyncedType { - validator: NumberValidator; - - get value() { - return this.validator.coerce(this.rawValue); - } - - set value(value: number | null) { - if (!this.validator.isValid(value)) { - logError('Invalid value', { value }); - return; - } - if (value === undefined) { - this.deletePropertyFromParent(); - } else { - this.setYValue(this.validator.stringify(value)); - } - } - - constructor(opts: { - yType: Y.Text; - validator: NumberValidator; - parent: SyncedContainer; - key: string | number; - state: State; - }) { - super(opts); - this.validator = opts.validator; - } -} diff --git a/package/src/lib/proxys/object.svelte.ts b/package/src/lib/proxys/object.svelte.ts deleted file mode 100644 index ce6dd27..0000000 --- a/package/src/lib/proxys/object.svelte.ts +++ /dev/null @@ -1,315 +0,0 @@ -import * as Y from 'yjs'; -import type { ObjectValidator } from '../schemas/object.js'; -import type { Validator } from '../schemas/schema.js'; -import { isMissingOptionnal } from '../utils.js'; -import { type State, type SyncroStates, createSyncroState } from './syncroState.svelte.js'; -import type { SyncedContainer } from './common.js'; -import { logError } from '../utils.js'; -import { NULL_OBJECT } from '$lib/constants.js'; - -export class SyncedObject { - state: State; - validator: ObjectValidator; - yType: Y.Map; - syncroStates = $state>({}); - baseImplementation = {}; - proxy: any; - parent: SyncedContainer; - key: string | number; - isNull: boolean = $state(false); - onObserve?: (e: Y.YMapEvent, _transaction: Y.Transaction) => void; - - deleteProperty = (target: any, p: any) => { - if (typeof p !== 'string') { - return true; - } - - const syncroState = this.syncroStates[p]; - if (!syncroState) { - logError('Property does not exist', p); - return true; - } else if (!syncroState.validator.$schema.optional) { - logError('Can not delete non optional property', p); - return true; - } - syncroState.destroy(); - this.yType.delete(p); - delete this.syncroStates[p]; - return true; - }; - - setNull() { - this.isNull = true; - this.state.transaction(() => { - // Clear existing keys to avoid stale state - Array.from(this.yType.keys()).forEach((k) => { - this.yType.delete(k); - }); - // Drop local children to free memory - Object.values(this.syncroStates).forEach((s) => s.destroy()); - this.syncroStates = {}; - this.yType.set(NULL_OBJECT, new Y.Text(NULL_OBJECT)); - }); - } - - set value(input: any) { - const { isValid, value } = this.validator.parse(input); - - if (!isValid) { - logError('Invalid value', { value }); - return; - } - - this.state.transaction(() => { - const shape = this.validator.$schema.shape; - - if (!value) { - if (value === undefined) { - this.parent.deleteProperty({}, this.key); - } else { - this.setNull(); - } - } else { - if (this.isNull) { - this.isNull = false; - this.yType.delete(NULL_OBJECT); - } - // Delete syncro states that are not in the new value - Object.keys(this.syncroStates) - .filter((key) => !(key in value)) - .forEach((key) => { - this.syncroStates[key].destroy(); - this.yType.delete(key); - delete this.syncroStates[key]; - }); - - Object.entries(value).forEach(([key, value]) => { - if (key in shape) { - const syncroState = this.syncroStates[key]; - if (syncroState) { - if (syncroState.validator.$schema !== shape[key].$schema) { - syncroState.destroy(); - this.syncroStates[key] = createSyncroState({ - key, - validator: shape[key], - parent: this, - state: this.state, - forceValue: true, - forceNewType: true, - value - }); - } else { - syncroState.value = value; - } - } else { - this.syncroStates[key] = createSyncroState({ - key, - validator: shape[key], - parent: this, - state: this.state, - value - }); - } - } - }); - } - }); - } - - get value() { - if (this.isNull) { - return null; - } - return this.proxy; - } - constructor({ - state, - observe = true, - validator, - yType, - baseImplementation = {}, - value, - parent, - key, - onObserve - }: { - state: State; - observe?: boolean; - validator: ObjectValidator; - yType: Y.Map; - baseImplementation?: any; - value?: any; - parent: SyncedContainer; - key: string | number; - onObserve?: (e: Y.YMapEvent, _transaction: Y.Transaction) => void; - }) { - this.parent = parent; - this.state = state; - this.key = key; - this.validator = validator; - this.yType = yType; - this.baseImplementation = baseImplementation; - this.onObserve = onObserve; - - this.proxy = new Proxy( - {}, - { - get: (target: any, prop: any) => { - if (prop === 'getState') { - return () => state; - } - if (prop === 'getYType') { - return () => yType; - } - - if (prop === 'getYTypes') { - return () => Object.fromEntries(yType.entries()); - } - - if (prop === 'toJSON') { - return this.toJSON.bind(this); - } - - const syncroState = this.syncroStates[prop]; - - if (!syncroState) { - return undefined; - } - return syncroState.value; - }, - set: (target: any, key: any, value: any) => { - if (!(key in this.validator.$schema.shape)) { - return false; - } - if (value === undefined) { - return this.deleteProperty(target, key); - } - - const syncroState = this.syncroStates[key]; - if (!syncroState) { - this.state.transaction(() => { - this.syncroStates[key] = createSyncroState({ - key, - validator: this.validator.$schema.shape[key], - parent: this, - state: this.state, - value - }); - }); - } else { - syncroState.value = value; - } - return true; - }, - - deleteProperty: this.deleteProperty, - - has: (target: any, prop: any) => { - if (typeof prop !== 'string') { - return false; - } - return this.yType.has(prop); - }, - - getOwnPropertyDescriptor(target: any, prop: any) { - if ((typeof prop === 'string' && yType.has(prop)) || prop === 'toJSON') { - return { - enumerable: true, - configurable: true - }; - } - - return undefined; - }, - - ownKeys: () => Array.from(this.yType.keys()) - } - ); - if (observe) { - yType.observe(this.observe); - this.sync(value); - } - } - observe = (e: Y.YMapEvent, _transaction: Y.Transaction) => { - if (_transaction.origin !== this.state.transactionKey) { - this.onObserve?.(e, _transaction); - if (this.yType.has(NULL_OBJECT)) { - this.isNull = true; - return; - } - e.changes?.keys.forEach(({ action }, key) => { - const syncedType = this.syncroStates[key]; - if (action === 'delete' && syncedType) { - syncedType.destroy(); - delete this.syncroStates[key]; - } - if (action === 'add') { - // If a new key is added to the object and is valid, integrate it - const validator = this.validator.$schema.shape[key]; - - const syncroState = createSyncroState({ - key, - validator: validator as Validator, - state: this.state, - parent: this - }); - - Object.assign(this.syncroStates, { [key]: syncroState }); - } - }); - } - }; - - toJSON = () => { - if (this.isNull) { - return null; - } - return Object.entries(this.validator.$schema.shape).reduce((acc, [key, validator]) => { - const value = this.syncroStates[key]?.value; - if (value !== undefined) { - Object.assign(acc, { [key]: value }); - } - return acc; - }, {}); - }; - - sync = (value?: any) => { - this.state.transaction(() => { - this.syncroStates = {}; - if (this.yType.has(NULL_OBJECT)) { - this.isNull = true; - return; - } - const hasDefaultValue = this.validator.$schema.default; - if (!hasDefaultValue) { - if (this.validator.$schema.nullable && !value) { - this.setNull(); - return; - } - } - (Object.entries(this.validator.$schema.shape) as [string, Validator][]).forEach( - ([key, validator]) => { - if (isMissingOptionnal({ validator, parent: this.yType, key })) { - return; - } - - this.syncroStates[key] = createSyncroState({ - key, - validator: validator, - parent: this, - value: value?.[key] || this.validator.$schema.default?.[key], - state: this.state - }); - } - ); - }); - }; - - destroy = () => { - this.yType.unobserve(this.observe); - Object.values(this.syncroStates).forEach((syncroState) => { - syncroState.destroy(); - }); - this.syncroStates = {}; - }; -} diff --git a/package/src/lib/proxys/richText.svelte.ts b/package/src/lib/proxys/richText.svelte.ts deleted file mode 100644 index c604d70..0000000 --- a/package/src/lib/proxys/richText.svelte.ts +++ /dev/null @@ -1,142 +0,0 @@ -// import type { RichTextValidator } from '../schemas/richtext.js'; -// import * as Y from 'yjs'; - -// type Delta = -// | { -// insert: string; -// attributes: Record; -// } -// | { -// delete: number; -// } -// | { -// retain: number; -// }; - -// type RichText = { -// text: string; -// marks: Record; -// }[]; - -// const deltaToRichText = (deltas: Delta[]): RichText => { -// let richText: RichText = []; - -// for (let i = 0; i < deltas.length; i++) { -// const delta = deltas[i]; -// if (!richText[i]) { -// richText.push({ text: '', marks: {} }); -// } -// if ('insert' in delta) { -// richText[i].text += delta.insert; -// if ('attributes' in delta) { -// richText[i].marks = delta.attributes; -// } -// } -// } -// return richText; -// }; - -// type OnSetCallback = (params: { -// index: number; -// markKey?: string; -// value: any; -// type: 'text' | 'mark'; -// }) => void; - -// const createRecursiveProxy = ( -// target: any, -// path: (string | number)[] = [], -// onSet?: OnSetCallback -// ): any => { -// return new Proxy(target, { -// get: (obj, prop: string | symbol) => { -// const value = obj[prop]; -// if (typeof prop === 'symbol' || prop === 'toJSON') { -// return value; -// } - -// if (value && typeof value === 'object') { -// return createRecursiveProxy(value, path.concat(prop), onSet); -// } - -// return value; -// }, -// set: (obj, prop: string | symbol, value) => { -// const newPath = [...path, prop]; - -// if (onSet) { -// const index = path.find((p): p is number => typeof p === 'number') ?? 0; - -// if (path.includes('marks')) { -// onSet({ -// index, -// markKey: String(prop), -// value, -// type: 'mark' -// }); -// } else if (prop === 'text') { -// onSet({ -// index, -// value, -// type: 'text' -// }); -// } -// } - -// obj[prop] = value; -// return true; -// } -// }); -// }; - -// export class SyncedRichText { -// INTERNAL_ID = crypto.randomUUID(); -// validator: RichTextValidator; -// private yType: Y.Text; - -// #textContent = $state(''); -// #content = $state([{ text: '', marks: {} }]); -// #contentProxy = createRecursiveProxy(this.#content, [], (params) => { -// if (params.type === 'mark') { -// this.format(params.index, 1, { [params.markKey!]: params.value }); -// } else if (params.type === 'text') { -// this.delete(params.index); -// this.insert(params.index, params.value); -// } -// }); - -// constructor(yType: Y.Text, validator: RichTextValidator) { -// this.yType = yType; -// this.#textContent = yType.toString(); -// this.#content = deltaToRichText(this.yType.toDelta()); -// this.validator = validator; - -// this.yType.observe((e, transact) => { -// if (transact.origin !== this.INTERNAL_ID) { -// this.#textContent = this.yType.toString(); -// this.#content = deltaToRichText(this.yType.toDelta()); -// } -// }); -// } - -// get text() { -// return this.#textContent; -// } - -// get content() { -// return this.#contentProxy; -// } - -// format = (index: number, length: number, attributes?: Record) => { -// this.yType.format(index, length, attributes || {}); -// }; -// insert = (index: number, value: string, attributes?: Record) => { -// this.yType.insert(index, value, attributes || {}); -// }; -// delete = (index: number, length: number = 1) => { -// this.yType.delete(index, length); -// }; -// applyDelta = (...delta: Delta[]) => { -// this.yType.applyDelta(delta); -// }; -// } diff --git a/package/src/lib/proxys/set.svelte.ts b/package/src/lib/proxys/set.svelte.ts deleted file mode 100644 index f872cfd..0000000 --- a/package/src/lib/proxys/set.svelte.ts +++ /dev/null @@ -1,248 +0,0 @@ -import * as Y from 'yjs'; -import { SvelteSet } from 'svelte/reactivity'; -import { isArrayNull, logError, observeArray, propertyToNumber, setArrayToNull } from '../utils.js'; -import { createSyncroState, type State, type SyncroStates } from './syncroState.svelte.js'; -import type { SyncedContainer } from './common.js'; -import type { SetValidator } from '../schemas/set.js'; - -export class SyncedSet { - state: State; - validator: SetValidator; - yType: Y.Array; - parent: SyncedContainer; - key: string | number; - isNull = $state(false); - syncroStates = $state([]); - syncroStatesValues = new SvelteSet(); - setNull = setArrayToNull.bind(this); - observe = observeArray.bind(this); - - constructor(opts: { - yType: Y.Array; - validator: SetValidator; - parent: SyncedContainer; - key: string | number; - state: State; - value: any; - }) { - this.key = opts.key; - this.state = opts.state; - this.yType = opts.yType; - this.parent = opts.parent; - this.validator = opts.validator; - this.sync(opts.value); - this.yType.observe(this.observe); - } - - sync = (value: any) => { - this.state.transaction(() => { - this.syncroStatesValues.clear(); - this.syncroStates = []; - if (isArrayNull(this)) { - this.isNull = true; - return; - } - if (this.state.initialized || value) { - for (let i = 0; i < Math.max(value?.length || 0, this.yType.length); i++) { - this.addState( - createSyncroState({ - key: i, - validator: this.validator.$schema.shape, - parent: this, - value: value?.[i], - state: this.state - }) - ); - } - } else { - if (this.validator.$schema.default) { - this.syncroStates = Array.from(this.validator.$schema.default).map((item, index) => { - const state = createSyncroState({ - key: index, - validator: this.validator.$schema.shape, - parent: this, - value: item, - state: this.state - }); - this.syncroStatesValues.add(state.value); - return state; - }); - } else if (this.validator.$schema.nullable && !value) { - this.setNull(); - } - } - }); - }; - - toJSON = () => { - // it's not exactly right but maybe ok and useful - return Array.from(this.syncroStates).map((state) => state.value); - }; - - addState = (state: SyncroStates) => { - this.syncroStates.push(state); - this.syncroStatesValues.add(state.value); - }; - - deleteProperty = (target: any, prop: any) => { - const index = propertyToNumber(prop); - if (typeof index !== 'number' || !this.syncroStates[index]) { - return true; - } - - this.syncroStatesValues.delete(this.syncroStates[index].value); - this.syncroStates[index].destroy(); - this.syncroStates.splice(index, 1); - this.yType.delete(index, 1); - }; - - destroy = () => { - this.syncroStatesValues.clear(); - this.syncroStates = []; - }; - - proxySet = new Proxy(this.syncroStatesValues, { - get: (target, prop) => { - if (prop === 'getState') { - return () => this.state; - } - if (prop === 'getYType') { - return () => this.yType; - } - if (prop === 'getYTypes') { - return () => this.yType.toArray(); - } - if (prop === Symbol.iterator) { - return () => this.syncroStatesValues.values(); - } - - const result = Reflect.get(target, prop); - if (typeof result === 'function') { - return (...args: any[]) => { - if (typeof prop === 'string') { - switch (prop) { - case 'add': { - const { isValid, value } = this.validator.$schema.shape.parse(args[0]); - - if (!isValid) { - logError('Invalid value', { value }); - return false; - } - - const hasValue = this.syncroStatesValues.has(value); - - if (hasValue) { - return false; - } - this.state.transaction(() => { - const state = createSyncroState({ - forceNewType: true, - key: this.syncroStatesValues.size, - validator: this.validator.$schema.shape, - parent: this, - value: value, - state: this.state - }); - this.addState(state); - }); - - return this.proxySet; - } - case 'delete': { - const stateIndex = Array.from(this.syncroStates).findIndex( - (state) => state.value === args[0] - ); - if (stateIndex !== -1) { - this.syncroStates[stateIndex].destroy(); - } - this.deleteProperty(target, stateIndex); - return this.proxySet; - } - case 'clear': - { - this.state.transaction(() => { - Array.from(this.syncroStates).forEach((state) => state.destroy()); - this.yType.delete(0, this.yType.length); - this.syncroStatesValues.clear(); - this.syncroStates = []; - }); - } - return this.proxySet; - default: - break; - } - return result.call(target, ...args); - } - }; - } else { - return result; - } - } - }); - - get value() { - if (this.isNull) { - return null; - } - - return this.proxySet; - } - - set value(input: Set | null) { - const { isValid, value } = this.validator.parse(input); - - this.state.transaction(() => { - if (!isValid) { - logError('Invalid value', { value }); - return; - } else { - if (!value) { - if (value === undefined) { - // this.parent.deleteProperty({}, this.key); - } else { - this.setNull(); - } - } else { - this.syncroStatesValues.clear(); - const valueArray = Array.from(value); - - if (this.isNull) { - this.isNull = false; - this.yType.delete(0, this.yType.length); - } - - if (!this.isNull) { - const remainingStates = Array.from(this.syncroStates).slice(valueArray.length); - remainingStates.forEach((state) => { - state.destroy(); - }); - if (remainingStates.length) { - this.yType.delete(valueArray.length, remainingStates.length); - } - } - - this.syncroStates = valueArray.map((item, index) => { - const previsousState = this.syncroStates[index]; - if (previsousState) { - previsousState.value = item; - console.log('state', previsousState.value); - this.syncroStatesValues.add(previsousState.value); - return previsousState; - } else { - const state = createSyncroState({ - forceNewType: true, - key: index, - validator: this.validator.$schema.shape, - parent: this, - value: item, - state: this.state - }); - this.syncroStatesValues.add(state.value); - return state; - } - }); - } - } - }); - } -} diff --git a/package/src/lib/proxys/syncroState.svelte.ts b/package/src/lib/proxys/syncroState.svelte.ts deleted file mode 100644 index b42a958..0000000 --- a/package/src/lib/proxys/syncroState.svelte.ts +++ /dev/null @@ -1,307 +0,0 @@ -import type { SchemaOutput } from '../schemas/schema.js'; -import * as Y from 'yjs'; -import { Awareness } from 'y-protocols/awareness'; -import { SyncedObject } from './object.svelte.js'; -import { ObjectValidator, type ObjectShape } from '../schemas/object.js'; -import type { Validator } from '../schemas/schema.js'; -import { SyncedEnum } from './enum.svelte.js'; -import { SyncedDate } from './date.svelte.js'; -import { SyncedBoolean } from './boolean.svelte.js'; -import type { StringValidator } from '../schemas/string.js'; -import type { NumberValidator } from '../schemas/number.js'; -import type { EnumValidator } from '../schemas/enum.js'; -import type { DateValidator } from '../schemas/date.js'; -import type { BooleanValidator } from '../schemas/boolean.js'; -import { SyncedText } from './text.svelte.js'; -import { SyncedNumber } from './number.svelte.js'; -import { getInitialStringifiedValue, getTypeFromParent, logError } from '../utils.js'; -import { onMount, setContext } from 'svelte'; -import { CONTEXT_KEY, INITIALIZED, TRANSACTION_KEY } from '../constants.js'; -import { SyncedArray } from './array.svelte.js'; -import type { ArrayValidator } from '../schemas/array.js'; -import type { SyncedContainer } from './common.js'; -import { SyncedSet } from './set.svelte.js'; -import type { SetValidator } from '../schemas/set.js'; -import { Presence, type PresenceUser } from '$lib/presence.svelte.js'; -import { SyncedMap } from './map.svelte.js'; -import type { MapValidator } from '$lib/schemas/map.js'; -import type { LiteralValidator } from '../schemas/literal.js'; -import type { DiscriminatedUnionValidator } from '../schemas/discriminatedUnion.js'; -import { SyncedDiscriminatedUnion } from './discriminatedUnion.svelte.js'; - -export type SyncroStates = - | SyncedText - | SyncedNumber - | SyncedBoolean - | SyncedDate - | SyncedEnum - | SyncedObject - | SyncedArray - | SyncedSet - | SyncedMap - | SyncedDiscriminatedUnion; - -// For testing purpose -const safeSetContext = (key: string, value: any) => { - try { - setContext(key, value); - } catch (e) { - // - } -}; - -export type State

= { - synced: boolean; - awareness: Awareness; - doc: Y.Doc; - undoManager: Y.UndoManager; - initialized: boolean; - transaction: (fn: () => void) => void; - transactionKey: any; - presence: Presence

; - undo: () => void; - redo: () => void; -}; - -export const syncroState = ({ - schema, - sync, - doc: customDoc, - awareness: customAwareness, - presence: p -}: { - schema: T; - doc?: Y.Doc; - awareness?: Awareness; - presence?: Omit; - sync?: ({ - doc, - awareness, - synced - }: { - doc: Y.Doc; - awareness: Awareness; - synced: (provider?: any) => void; - }) => Promise; -}): SchemaOutput => { - const doc = customDoc ?? new Y.Doc(); - const awareness = customAwareness ?? new Awareness(doc); - const presence = new Presence({ doc, awareness }); - const schemaValidator = new ObjectValidator(schema); - const stateMap = doc.getMap('$state'); - const undoManager = new Y.UndoManager(stateMap); - const transactionKey = new TRANSACTION_KEY(); - let state = $state({ - synced: sync ? false : true, - initialized: false, - awareness, - doc, - undoManager, - presence: presence as Presence

, - transaction: (fn: () => void) => { - state.doc.transact(fn, transactionKey); - }, - transactionKey, - undo: () => { - if (undoManager?.canUndo()) { - undoManager.undo(); - } - }, - redo: () => { - if (undoManager?.canRedo()) { - undoManager.redo(); - } - } - }); - safeSetContext(CONTEXT_KEY, state); - - const syncroStateProxy = new SyncedObject({ - // @ts-ignore - parent: { - // TODO: does this need to be fixed ? Is this even used a some point ? idk - deleteProperty(target, pArg) { - logError('Not allowed'); - return true; - } - }, - state, - key: '$state', - validator: schemaValidator, - observe: false, - yType: stateMap - }); - - const initialize = (doc: Y.Doc, cb: () => void) => { - const text = doc.getText(INITIALIZED); - const initialized = text?.toString() === INITIALIZED; - Object.assign(doc, { initialized }); - state.initialized = initialized; - cb(); - state.initialized = true; - Object.assign(doc, { initialized: true }); - if (!initialized) { - text.delete(0, text.length); - text.insert(0, INITIALIZED); - } - }; - - const synced = (provider?: any) => { - initialize(doc, () => { - syncroStateProxy.sync(syncroStateProxy.value); - stateMap.observe(syncroStateProxy.observe); - presence.init({ me: p, awareness: provider?.awareness }); - state.synced = true; - }); - }; - - if (!sync) { - synced(); - } - - onMount(() => { - if (sync) { - sync({ doc, awareness, synced }); - } - }); - - return syncroStateProxy.value; -}; - -export const createSyncroState = ({ - key, - validator, - forceNewType, - value, - parent, - state, - forceValue -}: { - key: string | number; - validator: Validator; - value?: any; - forceNewType?: boolean; - parent: SyncedContainer; - state: State; - forceValue?: boolean; -}): SyncroStates => { - const type = getTypeFromParent({ forceNewType, parent: parent.yType, key, validator, value }); - - if (forceValue && type instanceof Y.Text) { - const stringifiedValue = getInitialStringifiedValue(value, validator); - if (typeof stringifiedValue === 'string' && stringifiedValue !== type.toString()) { - state.transaction(() => { - type.applyDelta([{ delete: type.length }, { insert: stringifiedValue }]); - }); - } - } - - switch (validator.$schema.kind) { - default: - case 'string': { - return new SyncedText({ - yType: type as Y.Text, - validator: validator as StringValidator, - parent, - key, - state - }); - } - case 'number': { - return new SyncedNumber({ - yType: type as Y.Text, - validator: validator as NumberValidator, - parent, - key, - state - }); - } - case 'boolean': { - return new SyncedBoolean({ - yType: type as Y.Text, - validator: validator as BooleanValidator, - parent, - key, - state - }); - } - case 'date': { - return new SyncedDate({ - yType: type as Y.Text, - validator: validator as DateValidator, - parent, - key, - state - }); - } - case 'enum': { - return new SyncedEnum({ - yType: type as Y.Text, - validator: validator as EnumValidator, - parent, - key, - state - }); - } - case 'object': { - return new SyncedObject({ - yType: type as Y.Map, - validator: validator as ObjectValidator, - baseImplementation: {}, - value, - parent, - key, - state - }); - } - case 'set': { - return new SyncedSet({ - yType: type as Y.Array, - validator: validator as SetValidator, - value, - parent, - key, - state - }); - } - case 'array': { - return new SyncedArray({ - yType: type as Y.Array, - validator: validator as ArrayValidator, - value, - parent, - key, - state - }); - } - case 'map': { - return new SyncedMap({ - yType: type as Y.Map, - validator: validator as MapValidator, - value, - parent, - key, - state - }); - } - case 'literal': { - // Literals are stored as text since they're primitive values - return new SyncedText({ - yType: type as Y.Text, - validator: validator as LiteralValidator as any, - parent, - key, - state - }); - } - case 'discriminatedUnion': { - return new SyncedDiscriminatedUnion({ - yType: type as Y.Map, - validator: validator as DiscriminatedUnionValidator, - value, - parent, - key, - state - }); - } - } -}; diff --git a/package/src/lib/proxys/text.svelte.ts b/package/src/lib/proxys/text.svelte.ts deleted file mode 100644 index 6c3fd2e..0000000 --- a/package/src/lib/proxys/text.svelte.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { StringValidator } from '../schemas/string.js'; -import * as Y from 'yjs'; -import { BaseSyncedType } from './base.svelte.js'; -import { NULL } from '../constants.js'; -import { logError } from '../utils.js'; -import type { State } from './syncroState.svelte.js'; -import type { SyncedContainer } from './common.js'; - -export class SyncedText extends BaseSyncedType { - validator: StringValidator; - - get value() { - return this.rawValue === NULL ? null : this.rawValue; - } - set value(value: string | null) { - const isValid = this.validator.isValid(value); - if (!isValid) { - logError('Invalid value', { value }); - return; - } - if (value === undefined) { - this.deletePropertyFromParent(); - } else { - this.setYValue(this.validator.stringify(value)); - } - } - - constructor(opts: { - yType: Y.Text; - validator: StringValidator; - parent: SyncedContainer; - key: string | number; - state: State; - }) { - super(opts); - this.validator = opts.validator; - } -} diff --git a/package/src/lib/schemas/date.ts b/package/src/lib/schemas/date.ts deleted file mode 100644 index 2d07e75..0000000 --- a/package/src/lib/schemas/date.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { NULL } from '$lib/constants.js'; -import { BaseValidator, type BaseSchema } from './base.js'; - -export type DateSchema = BaseSchema & { - kind: 'date'; - min?: Date; - max?: Date; -}; - -export class DateValidator< - O extends boolean = false, - N extends boolean = false -> extends BaseValidator { - constructor() { - super({ kind: 'date', optional: false, nullable: false }); - } - - min(date: Date) { - this.$schema.min = date; - return this as DateValidator; - } - - max(date: Date) { - this.$schema.max = date; - return this as DateValidator; - } - private isStringADate(value: string): boolean { - try { - return !isNaN(new Date(value).getTime()); - } catch (error) { - return false; - } - } - - private get defaultValue(): Date | null { - return this.$schema.default || null; - } - - isValid = (value: any): boolean => { - if (value instanceof Date) { - if (this.$schema.min && value < this.$schema.min) { - return false; - } - if (this.$schema.max && value > this.$schema.max) { - return false; - } - return true; - } - if (value === NULL || value === null) { - return this.$schema.nullable; - } - if (value === undefined) { - return this.$schema.optional; - } - - return false; - }; - - parse(value: string | null): { isValid: boolean; value: Date | null } { - const coerced = this.coerce(value); - return { - isValid: this.isValid(coerced), - value: coerced - }; - } - coerce(value: string | null): Date | null { - if (value === NULL || value === null || value === undefined) { - if (this.$schema.nullable) { - return null; - } else { - return this.defaultValue; - } - } - - if (value === undefined) { - return this.$schema.optional ? null : this.defaultValue; - } - - if (this.isStringADate(value)) { - return new Date(value); - } - - return this.$schema.nullable ? null : this.defaultValue; - } - stringify = (value: any): string => { - if (value instanceof Date) { - return value.toISOString(); - } else { - if (this.$schema.nullable) { - return NULL; - } else { - return this.defaultValue?.toISOString() || NULL; - } - } - }; -} diff --git a/package/src/lib/schemas/schema.ts b/package/src/lib/schemas/schema.ts deleted file mode 100644 index ac6f86c..0000000 --- a/package/src/lib/schemas/schema.ts +++ /dev/null @@ -1,121 +0,0 @@ -import type { Simplify } from '../types.js'; -import { ArrayValidator, type ArraySchema } from './array.js'; -import { BaseValidator, type BaseSchema } from './base.js'; -import { BooleanValidator, type BooleanSchema } from './boolean.js'; -import { DateValidator, type DateSchema } from './date.js'; -import { EnumValidator, type EnumSchema } from './enum.js'; -import { StringValidator, type StringSchema } from './string.js'; -import { ObjectValidator, type ObjectSchema, type ObjectShape } from './object.js'; -import { RichTextValidator, type RichTextSchema } from './richtext.js'; -import { NumberValidator, type NumberSchema } from './number.js'; -import { SetValidator, type SetSchema } from './set.js'; -import type { State } from '$lib/proxys/syncroState.svelte.js'; -import type { Array as YArray, Map as YMap, AbstractType, Text as YText } from 'yjs'; -import { MapValidator, type MapSchema } from './map.js'; -import { LiteralValidator, type LiteralSchema } from './literal.js'; -import { - DiscriminatedUnionValidator, - type DiscriminatedUnionSchema, - type InferDiscriminatedUnionType -} from './discriminatedUnion.js'; - -export type Schema = - | ArraySchema - | ObjectSchema - | BooleanSchema - | DateSchema - | StringSchema - | NumberSchema - // | RichTextSchema - | EnumSchema - | SetSchema - | MapSchema - | LiteralSchema - | DiscriminatedUnionSchema; - -export type PrimitiveValidator = BaseValidator; -export type Validator = - | PrimitiveValidator - | ArrayValidator - | ObjectValidator - | SetValidator - | MapValidator - | LiteralValidator - | DiscriminatedUnionValidator; - -export const y = { - boolean: () => new BooleanValidator(), - date: () => new DateValidator(), - enum: (...values: T[]) => new EnumValidator(...values), - string: () => new StringValidator(), - richText: () => new RichTextValidator(), - object: (shape: T) => new ObjectValidator(shape), - array: (shape: T) => new ArrayValidator(shape), - number: () => new NumberValidator(), - set: (shape: T) => new SetValidator(shape), - map: (shape: T) => new MapValidator(shape), - literal: (value: T) => new LiteralValidator(value), - discriminatedUnion: []>( - discriminantKey: K, - variants: T - ) => new DiscriminatedUnionValidator(discriminantKey, variants) -}; - -// I need to add these properties as optional because of typescript. -// Looking for a better solution. - -type Optional = N extends true ? T | undefined : T; - -type InferYTypeFromShape = Shape extends ObjectShape - ? { [K in keyof Shape]: Optional, Shape[K]['$schema']['optional']> } - : Shape extends ArrayValidator - ? YArray - : Shape extends ObjectValidator - ? YMap - : Shape extends SetValidator - ? YArray - : Shape extends MapValidator - ? YMap - : YText; - -type Getters = { - getState?: () => State; - getYTypes?: () => T extends 'array' - ? AbstractType[] - : T extends 'map' - ? Record> - : InferYTypeFromShape; - getYType?: () => T extends 'array' ? YArray : YMap; -}; - -type NORO = N extends true - ? O extends true - ? T | null | undefined - : T | null - : O extends true - ? T | undefined - : T; - -export type InferSchemaType = - T extends DiscriminatedUnionValidator - ? NORO> - : T extends ObjectValidator - ? NORO> - : T extends BaseValidator - ? NORO ? T : never> - : T extends ArrayValidator - ? InferSchemaType[] & Getters<'array', Shape> - : T extends SetValidator - ? NORO>> & Getters<'array', Shape> - : T extends MapValidator - ? NORO>> & Getters<'map', Shape> - : never; - -export type SchemaOutput = Simplify<{ - [K in keyof T]: InferSchemaType; -}> & - Getters<'object', T>; - -export type RawSchemaOutput = Simplify<{ - [K in keyof T]: InferSchemaType; -}>; diff --git a/package/src/lib/utils.ts b/package/src/lib/utils.ts deleted file mode 100644 index 4233f3a..0000000 --- a/package/src/lib/utils.ts +++ /dev/null @@ -1,201 +0,0 @@ -import * as Y from 'yjs'; -import type { Validator } from './schemas/schema.js'; -import { NULL, NULL_ARRAY } from './constants.js'; -import type { BaseValidator } from './schemas/base.js'; -import { DEV } from 'esm-env'; -import type { SyncedArray } from './proxys/array.svelte.js'; -import { SyncedSet } from './proxys/set.svelte.js'; -import { createSyncroState } from './proxys/syncroState.svelte.js'; - -export const isMissingOptionnal = ({ - parent, - key, - validator -}: { - parent: Y.Map | Y.Array; - key: string | number; - validator: Validator; -}) => { - const exists = parent instanceof Y.Map ? parent.has(String(key)) : !!parent.get(Number(key)); - const isMissingOptionnal = validator.$schema.optional && !exists; - const hasDefault = validator.$schema.default !== undefined; - return isMissingOptionnal && !hasDefault; -}; - -export const getInitialStringifiedValue = (value: any, validator: Validator) => { - if ( - validator.$schema.kind === 'array' || - validator.$schema.kind === 'object' || - validator.$schema.kind === 'map' || - validator.$schema.kind === 'set' || - validator.$schema.kind === 'discriminatedUnion' - ) { - return undefined; - } - const DEFAULT_VALUE = value === null ? null : (value ?? validator.$schema.default); - - const isValid = validator.isValid(DEFAULT_VALUE); - if (!isValid) { - if (validator.$schema.nullable) { - return NULL; - } - return undefined; - } - if (DEFAULT_VALUE !== undefined) { - const stringifiedDefaultValue = (validator as BaseValidator).stringify(DEFAULT_VALUE); - - return stringifiedDefaultValue; - } -}; - -export const getTypeFromParent = | Y.Map | Y.Text>({ - parent, - key, - validator, - forceNewType, - value -}: { - parent: Y.Map | Y.Array; - key: string | number; - value?: string; - forceNewType?: boolean; - validator: Validator; -}): T => { - const isArray = parent instanceof Y.Array; - const instance = getInstance(validator) as new () => Y.Array | Y.Map | Y.Text; - const isText = instance === Y.Text; - const stringifiedValue = getInitialStringifiedValue(value, validator); - - const type = isText ? new Y.Text(stringifiedValue) : new instance(); - const typeInParent = (isArray ? parent.get(Number(key)) : parent.get(String(key))) as T; - - const setAndReturnType = () => { - if (isArray) { - parent.insert(Number(key), [type]); - } else { - parent.delete(String(key)); - parent.set(String(key), type); - } - return type as T; - }; - - if (!typeInParent || typeInParent._item?.deleted || forceNewType) { - return setAndReturnType() as T; - } - - if (!(typeInParent instanceof instance)) { - return setAndReturnType() as T; - } else { - return typeInParent as T; - } -}; - -export const getInstance = (validator: Validator): (new () => Y.AbstractType) | null => { - switch (validator.$schema.kind) { - case 'map': - case 'object': - case 'discriminatedUnion': - return Y.Map; - case 'set': - case 'array': - return Y.Array; - default: - return Y.Text; - } -}; - -export const logError = (...args: any[]) => { - if (DEV) { - console.error(...args); - } -}; - -export const isInitialized = ({ yType }: { yType: Y.AbstractType }) => { - // @ts-ignore - return yType.doc?.initialized; -}; - -// From https://github.com/YousefED/SyncedStore/blob/main/packages/core/src/array.ts -export const propertyToNumber = (p: string | number | symbol) => { - if (typeof p === 'string' && p.trim().length) { - const asNum = Number(p); - if (Number.isInteger(asNum)) { - return asNum; - } - } - return p; -}; - -export function setArrayToNull(this: SyncedArray | SyncedSet) { - this.state.transaction(() => { - this.syncroStates.forEach((state) => state.destroy()); - if (this instanceof SyncedSet) { - this.syncroStatesValues.clear(); - } - this.syncroStates = []; - this.isNull = true; - this.yType.delete(0, this.yType.length); - this.yType.insert(0, [new Y.Text(NULL_ARRAY)]); - }); -} - -export const isArrayNull = ({ yType }: { yType: Y.Array }) => { - return ( - yType.length === 1 && yType.get(0) instanceof Y.Text && yType.get(0).toString() === NULL_ARRAY - ); -}; - -export function observeArray( - this: SyncedArray | SyncedSet, - e: Y.YArrayEvent, - _transaction: Y.Transaction -) { - if (_transaction.origin !== this.state.transactionKey) { - if (isArrayNull(this)) { - this.isNull = true; - return; - } - - let start = 0; - e.delta.forEach(({ retain, delete: _delete, insert }) => { - if (retain) { - start += retain; - } - if (_delete) { - const deleted = this.syncroStates.splice(start, _delete); - deleted.forEach((state) => { - state.destroy(); - }); - start -= _delete; - } - if (Array.isArray(insert)) { - for (let i = 0; i < insert.length; i++) { - if (insert[i] instanceof Y.Text && insert[i].toString() === NULL_ARRAY) { - this.isNull = true; - return; - } - this.syncroStates.splice( - start, - 0, - createSyncroState({ - key: start, - validator: this.validator.$schema.shape, - parent: this, - state: this.state - }) - ); - start += 1; - } - } - }); - - if (this instanceof SyncedSet) { - this.syncroStatesValues.clear(); - this.syncroStates - .map((state) => state.value) - .forEach((value) => { - this.syncroStatesValues.add(value); - }); - } - } -} diff --git a/package/src/tests/integration/discriminatedUnion.integration.test.ts b/package/src/tests/integration/discriminatedUnion.integration.test.ts deleted file mode 100644 index 496196a..0000000 --- a/package/src/tests/integration/discriminatedUnion.integration.test.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { syncroState, y } from '../../lib/index.js'; -import * as Y from 'yjs'; - -describe('DiscriminatedUnion Integration Tests', () => { - it('should work with the full syncroState system', () => { - const apiResponseSchema = y.discriminatedUnion('status', [ - y.object({ status: y.literal('success'), data: y.string() }), - y.object({ status: y.literal('error'), message: y.string() }) - ]); - - const schema = { - response: apiResponseSchema - }; - - const doc = new Y.Doc(); - const state1 = syncroState({ schema, doc }); - const state2 = syncroState({ schema, doc }); - - // Test initial state - expect(state1.response).toBeDefined(); - expect(state2.response).toBeDefined(); - - // Test setting success variant - state1.response = { status: 'success', data: 'Hello world' }; - - expect(state1.response.status).toBe('success'); - expect(state1.response.data).toBe('Hello world'); - expect(state2.response.status).toBe('success'); - if (state2.response.status === 'success') { - expect(state2.response.data).toBe('Hello world'); - } - - // Test switching to error variant - state1.response = { status: 'error', message: 'Something went wrong' }; - - expect(state1.response.status).toBe('error'); - expect(state1.response.message).toBe('Something went wrong'); - expect(state2.response.status).toBe('error'); - if (state2.response.status === 'error') { - expect(state2.response.message).toBe('Something went wrong'); - } - - console.log(JSON.stringify(state1.response, null, 2)); - // Test property access - expect('status' in state1.response).toBe(true); - expect('message' in state1.response).toBe(true); - expect('data' in state1.response).toBe(false); - - // Test Object.keys - const keys = Object.keys(state1.response); - expect(keys).toContain('status'); - expect(keys).toContain('message'); - expect(keys).not.toContain('data'); - - // Test JSON serialization - const json = JSON.stringify(state1.response); - const parsed = JSON.parse(json); - expect(parsed.status).toBe('error'); - expect(parsed.message).toBe('Something went wrong'); - }); - - it('should handle nullable discriminated unions', () => { - const schema = { - result: y - .discriminatedUnion('type', [ - y.object({ type: y.literal('data'), value: y.string() }), - y.object({ type: y.literal('error'), code: y.number() }) - ]) - .nullable() - }; - - const doc = new Y.Doc(); - const state = syncroState({ schema, doc }); - - // Test null assignment - state.result = null; - expect(state.result).toBeNull(); - - // Test setting value after null - state.result = { type: 'data', value: 'test' }; - expect(state.result.type).toBe('data'); - expect(state.result.value).toBe('test'); - }); - - it('should handle complex nested discriminated unions', () => { - const userSchema = y.discriminatedUnion('role', [ - y.object({ role: y.literal('admin'), name: y.string(), permissions: y.array(y.string()) }), - y.object({ role: y.literal('user'), name: y.string(), email: y.string() }), - y.object({ role: y.literal('guest'), sessionId: y.string() }) - ]); - - const schema = { user: userSchema }; - const doc = new Y.Doc(); - const state1 = syncroState({ schema, doc }); - const state2 = syncroState({ schema, doc }); - - // Test admin user - state1.user = { - role: 'admin', - name: 'John Admin', - permissions: ['read', 'write', 'delete'] - }; - - expect(state2.user.role).toBe('admin'); - if (state2.user.role === 'admin') { - expect(state2.user.name).toBe('John Admin'); - expect(state2.user.permissions).toEqual(['read', 'write', 'delete']); - } - - // Test switching to regular user - state1.user = { - role: 'user', - name: 'Jane User', - email: 'jane@example.com' - }; - - expect(state2.user.role).toBe('user'); - if (state2.user.role === 'user') { - expect(state2.user.name).toBe('Jane User'); - expect(state2.user.email).toBe('jane@example.com'); - } - expect('permissions' in state2.user).toBe(false); - - // Test switching to guest - state1.user = { - role: 'guest', - sessionId: 'guest-123' - }; - - expect(state2.user.role).toBe('guest'); - if (state2.user.role === 'guest') { - expect(state2.user.sessionId).toBe('guest-123'); - } - expect('name' in state2.user).toBe(false); - expect('email' in state2.user).toBe(false); - }); -}); - -describe('DiscriminatedUnion Integration Tests Complex', () => { - it('should work with nested containers', () => { - const apiResponseSchema = y.discriminatedUnion('status', [ - y.object({ - status: y.literal('success'), - data: y.string(), - nested: y.object({ nested: y.string() }) - }), - y.object({ - status: y.literal('error'), - message: y.string(), - nested: y.array(y.object({ nested: y.string() })) - }) - ]); - - const schema = { - response: apiResponseSchema - }; - - const doc = new Y.Doc(); - const state1 = syncroState({ schema, doc }); - const state2 = syncroState({ schema, doc }); - - state1.response = { status: 'success', data: 'Hello world', nested: { nested: 'nested' } }; - - expect(state2.response.status).toBe('success'); - - if (state2.response.status === 'success') { - expect(state2.response.nested.nested).toBe('nested'); - } else { - expect(true).toBe(false); - } - - state1.response = { - status: 'error', - message: 'Something went wrong', - nested: [{ nested: 'nested' }] - }; - - if (state2.response.status === 'error') { - expect(state2.response.nested[0].nested).toBe('nested'); - expect('nested' in state2.response.nested).toBe(false); - } else { - expect(true).toBe(false); - } - }); -}); diff --git a/package/src/tests/proxys/array.bounds.test.ts b/package/src/tests/proxys/array.bounds.test.ts deleted file mode 100644 index 60b5cbf..0000000 --- a/package/src/tests/proxys/array.bounds.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { syncroState, y } from '../../lib/index.js'; -import * as Y from 'yjs'; - -describe('Array Bounds Validation Tests', () => { - const doc = new Y.Doc(); - const schema = { - testArray: y.array(y.string()) - }; - - const state = syncroState({ schema, doc }); - - beforeEach(() => { - state.testArray = ['a', 'b', 'c', 'd', 'e']; - }); - - describe('Negative start index handling', () => { - it('should handle negative start index like native Array.splice', () => { - // Test -1 (last element) - state.testArray.splice(-1, 1, 'z'); - expect([...state.testArray]).toEqual(['a', 'b', 'c', 'd', 'z']); - - // Reset - state.testArray = ['a', 'b', 'c', 'd', 'e']; - - // Test -2 (second to last) - state.testArray.splice(-2, 1, 'y'); - expect([...state.testArray]).toEqual(['a', 'b', 'c', 'y', 'e']); - - // Reset - state.testArray = ['a', 'b', 'c', 'd', 'e']; - - // Test negative index beyond array length (should start at 0) - state.testArray.splice(-10, 1, 'start'); - expect([...state.testArray]).toEqual(['start', 'b', 'c', 'd', 'e']); - }); - - it('should handle negative start with Y.js document consistency', () => { - state.testArray.splice(-1, 1, 'last'); - - const yArray = state.testArray.getYType?.(); - expect(yArray!.length).toBe(5); - expect(yArray!.toJSON()).toEqual(['a', 'b', 'c', 'd', 'last']); - }); - }); - - describe('Start index beyond array length', () => { - it('should handle start index beyond array length', () => { - // Insert at end when start > length - state.testArray.splice(10, 0, 'end1', 'end2'); - expect([...state.testArray]).toEqual(['a', 'b', 'c', 'd', 'e', 'end1', 'end2']); - }); - - it('should handle start index beyond array length with Y.js consistency', () => { - state.testArray.splice(100, 0, 'far'); - - const yArray = state.testArray.getYType?.(); - expect(yArray!.length).toBe(6); - expect(yArray!.toJSON()).toEqual(['a', 'b', 'c', 'd', 'e', 'far']); - }); - }); - - describe('DeleteCount validation', () => { - it('should handle deleteCount larger than remaining elements', () => { - // Try to delete 100 items starting at index 2 (only 3 items remain) - const result = state.testArray.splice(2, 100, 'x'); - - expect([...state.testArray]).toEqual(['a', 'b', 'x']); - expect(result.length).toBe(3); // Should return the 3 deleted items - }); - - it('should handle deleteCount larger than remaining with Y.js consistency', () => { - state.testArray.splice(1, 1000); - - const yArray = state.testArray.getYType?.(); - expect(yArray!.length).toBe(1); - expect(yArray!.toJSON()).toEqual(['a']); - }); - - it('should handle negative deleteCount (should be treated as 0)', () => { - const originalLength = state.testArray.length; - state.testArray.splice(2, -5, 'inserted'); - - // Should only insert, not delete anything - expect(state.testArray.length).toBe(originalLength + 1); - expect([...state.testArray]).toEqual(['a', 'b', 'inserted', 'c', 'd', 'e']); - }); - }); - - describe('Edge cases', () => { - it('should handle empty array splice operations', () => { - state.testArray = []; - - // Insert into empty array - state.testArray.splice(0, 0, 'first'); - expect([...state.testArray]).toEqual(['first']); - - // Insert beyond bounds in empty array - state.testArray.splice(10, 0, 'second'); - expect([...state.testArray]).toEqual(['first', 'second']); - }); - - it('should handle zero deleteCount with insertions', () => { - state.testArray.splice(2, 0, 'x', 'y'); - expect([...state.testArray]).toEqual(['a', 'b', 'x', 'y', 'c', 'd', 'e']); - expect(state.testArray.length).toBe(7); - }); - - it('should handle splice with no insertions (deletion only)', () => { - state.testArray.splice(1, 3); - expect([...state.testArray]).toEqual(['a', 'e']); - expect(state.testArray.length).toBe(2); - }); - - it('should handle single element array operations', () => { - state.testArray = ['only']; - - // Replace single element - state.testArray.splice(0, 1, 'replaced'); - expect([...state.testArray]).toEqual(['replaced']); - - // Delete single element - state.testArray.splice(0, 1); - expect([...state.testArray]).toEqual([]); - expect(state.testArray.length).toBe(0); - }); - }); - - describe('Boundary stress tests', () => { - it('should handle multiple boundary violations in sequence', () => { - // Start with fresh array - state.testArray = ['a', 'b', 'c']; - - // Negative start, excessive deleteCount - state.testArray.splice(-10, 100, 'x'); - expect([...state.testArray]).toEqual(['x']); - - // Beyond bounds start - state.testArray.splice(100, 0, 'y'); - expect([...state.testArray]).toEqual(['x', 'y']); - - // Verify Y.js consistency - const yArray = state.testArray.getYType?.(); - expect(yArray!.toJSON()).toEqual(['x', 'y']); - }); - - it('should not throw "Length exceeded!" errors with invalid bounds', () => { - // These operations should not crash - expect(() => { - state.testArray.splice(-1000, 1000, 'safe'); - }).not.toThrow(); - - expect(() => { - state.testArray.splice(1000, 1000, 'also-safe'); - }).not.toThrow(); - - expect(() => { - state.testArray.splice(0, -100); - }).not.toThrow(); - }); - }); - - describe('Return value validation', () => { - it('should return correct deleted elements', () => { - const deleted = state.testArray.splice(1, 2, 'x'); - - // Should return the deleted SyncroState objects - expect(deleted.length).toBe(2); - // The actual values depend on the SyncroState implementation - }); - - it('should return empty array when no elements deleted', () => { - const deleted = state.testArray.splice(2, 0, 'inserted'); - expect(deleted.length).toBe(0); - }); - }); -}); diff --git a/package/src/tests/proxys/array.proxy.test.ts b/package/src/tests/proxys/array.proxy.test.ts deleted file mode 100644 index 342cd17..0000000 --- a/package/src/tests/proxys/array.proxy.test.ts +++ /dev/null @@ -1,364 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, test, beforeAll } from 'vitest'; -import { syncroState, y } from '../../lib/index.js'; - -let createDocument = () => - syncroState({ - schema: { - array: y.array(y.string()), - nullableArray: y.array(y.string()).nullable(), - optionalArray: y.array(y.string()).optional(), - nullableOptionalArray: y.array(y.string()).nullable().optional(), - arrayWithDefault: y.array(y.string()).default(['default']), - arrayWithDefaultAndOptional: y.array(y.string()).default(['default']).optional(), - arrayWithDefaultAndNullable: y.array(y.string()).default(['default']).nullable(), - arrayWithDefaultAndNullableAndOptional: y - .array(y.string()) - .default(['default']) - .nullable() - .optional() - } - }); - -let state = createDocument(); - -describe('ArrayProxy', () => { - describe('Initial values', () => { - it('should be an array', () => { - expect(Array.isArray(state.array)).toBe(true); - expect(state.array.length).toBe(0); - }); - it('should have null as default value for nullable array', () => { - expect(state.nullableArray).toBe(null); - }); - it('should have undefined as default value for optional array', () => { - expect(state.optionalArray).toBe(undefined); - }); - it('should have undefined as default value for nullable optional array', () => { - expect(state.nullableOptionalArray).toBe(undefined); - }); - it('should have default value for array with default', () => { - expect(state.arrayWithDefault).toEqual(['default']); - }); - it('should have default value for optional array with default', () => { - expect(state.arrayWithDefaultAndOptional).toEqual(['default']); - }); - it('should have default value for nullable array with default', () => { - expect(state.arrayWithDefaultAndNullable).toEqual(['default']); - }); - it('should have default value for nullable optional array with default', () => { - expect(state.arrayWithDefaultAndNullableAndOptional).toEqual(['default']); - }); - }); - - describe('Setters', () => { - describe('Array', () => { - beforeEach(() => { - state.array = ['test']; - }); - - it('should set the value', () => { - state.array = ['hello', 'world']; - expect(state.array).toEqual(['hello', 'world']); - }); - - it('should not set the value to null', () => { - (state.array as any) = null; - expect(state.array).toEqual(['test']); - }); - - it('should not set the value to undefined', () => { - (state.array as any) = undefined; - expect(state.array).toEqual(['test']); - }); - - it('should not set the value to a string', () => { - (state.array as any) = 'invalid'; - expect(state.array).toEqual(['test']); - }); - - it('should not set the value to a number', () => { - (state.array as any) = 123; - expect(state.array).toEqual(['test']); - }); - - it('should not set invalid array items', () => { - (state.array as any) = [123, true, {}]; - expect(state.array).toEqual(['test']); - }); - - it('should support array methods', async () => { - state.array.push('world'); - expect(state.array).toEqual(['test', 'world']); - - state.array.pop(); - expect(state.array).toEqual(['test']); - - state.array.unshift('hello'); - expect(state.array).toEqual(['hello', 'test']); - - state.array.shift(); - expect(state.array).toEqual(['test']); - - state.array = ['a', 'b', 'c']; - expect(state.array.slice(1)).toEqual(['b', 'c']); - expect(state.array.map((x: string) => x.toUpperCase())).toEqual(['A', 'B', 'C']); - expect(state.array.filter((x: string) => x !== 'b')).toEqual(['a', 'c']); - }); - }); - - describe('Nullable Array', () => { - beforeEach(async () => { - state.nullableArray = ['test']; - }); - - it('should set the value', () => { - state.nullableArray = ['hello', 'world']; - expect(state.nullableArray).toEqual(['hello', 'world']); - }); - - it('should set the value to null', () => { - state.nullableArray = null; - expect(state.nullableArray).toBe(null); - }); - - it('should not set the value to undefined', () => { - (state.nullableArray as any) = undefined; - expect([...state.nullableArray]).toStrictEqual(['test']); - }); - - it('should not set the value to a string', () => { - (state.nullableArray as any) = 'invalid'; - expect([...state.nullableArray]).toStrictEqual(['test']); - }); - - it('should not set the value to a number', () => { - (state.nullableArray as any) = 123; - expect([...state.nullableArray]).toStrictEqual(['test']); - }); - - it('should not set invalid array items', () => { - (state.nullableArray as any) = [123, true, {}]; - expect([...state.nullableArray]).toStrictEqual(['test']); - }); - }); - - describe('Optional Array', () => { - beforeAll(() => { - state.optionalArray = ['test']; - }); - beforeEach(() => { - state.optionalArray = ['test']; - }); - - test('should set the value', () => { - state.optionalArray = ['hello', 'world']; - expect(state.optionalArray).toEqual(['hello', 'world']); - }); - - test('should set the value to undefined', () => { - state.optionalArray = undefined; - expect(state.optionalArray).toBe(undefined); - }); - - test('should not set the value to null', () => { - (state.optionalArray as any) = null; - expect(state.optionalArray).toEqual(['test']); - }); - - test('should not set the value to a string', () => { - (state.optionalArray as any) = 'invalid'; - expect(state.optionalArray).toEqual(['test']); - }); - - it('should not set the value to a number', () => { - (state.optionalArray as any) = 123; - expect(state.optionalArray).toEqual(['test']); - }); - - it('should not set invalid array items', () => { - (state.optionalArray as any) = [123, true, {}]; - expect(state.optionalArray).toEqual(['test']); - }); - }); - - describe('Nullable Optional Array', () => { - beforeEach(() => { - state.nullableOptionalArray = ['test']; - }); - - it('should set the value', () => { - state.nullableOptionalArray = ['hello', 'world']; - expect(state.nullableOptionalArray).toEqual(['hello', 'world']); - }); - - it('should set the value to null', () => { - state.nullableOptionalArray = null; - expect(state.nullableOptionalArray).toBe(null); - }); - - it('should set the value to undefined', () => { - state.nullableOptionalArray = undefined; - expect(state.nullableOptionalArray).toBe(undefined); - }); - - it('should not set the value to a string', () => { - (state.nullableOptionalArray as any) = 'invalid'; - expect(state.nullableOptionalArray).toEqual(['test']); - }); - - it('should not set the value to a number', () => { - (state.nullableOptionalArray as any) = 123; - expect(state.nullableOptionalArray).toEqual(['test']); - }); - - it('should not set invalid array items', () => { - (state.nullableOptionalArray as any) = [123, true, {}]; - expect(state.nullableOptionalArray).toEqual(['test']); - }); - }); - - describe('Array With Default', () => { - beforeEach(() => { - state.arrayWithDefault = ['test']; - }); - - it('should set the value', () => { - state.arrayWithDefault = ['hello', 'world']; - expect(state.arrayWithDefault).toEqual(['hello', 'world']); - }); - - it('should not set the value to null', () => { - (state.arrayWithDefault as any) = null; - expect(state.arrayWithDefault).toEqual(['test']); - }); - - it('should not set the value to undefined', () => { - (state.arrayWithDefault as any) = undefined; - expect(state.arrayWithDefault).toEqual(['test']); - }); - - it('should not set the value to a string', () => { - (state.arrayWithDefault as any) = 'invalid'; - expect(state.arrayWithDefault).toEqual(['test']); - }); - - it('should not set the value to a number', () => { - (state.arrayWithDefault as any) = 123; - expect(state.arrayWithDefault).toEqual(['test']); - }); - - it('should not set invalid array items', () => { - (state.arrayWithDefault as any) = [123, true, {}]; - expect(state.arrayWithDefault).toEqual(['test']); - }); - }); - - describe('Array With Default And Optional', () => { - beforeEach(() => { - state.arrayWithDefaultAndOptional = ['test']; - }); - - it('should set the value', () => { - state.arrayWithDefaultAndOptional = ['hello', 'world']; - expect(state.arrayWithDefaultAndOptional).toEqual(['hello', 'world']); - }); - - it('should not set the value to null', () => { - (state.arrayWithDefaultAndOptional as any) = null; - expect(state.arrayWithDefaultAndOptional).toEqual(['test']); - }); - - it('should set the value to undefined', () => { - state.arrayWithDefaultAndOptional = undefined; - expect(state.arrayWithDefaultAndOptional).toBe(undefined); - }); - - it('should not set the value to a string', () => { - (state.arrayWithDefaultAndOptional as any) = 'invalid'; - expect(state.arrayWithDefaultAndOptional).toEqual(['test']); - }); - - it('should not set the value to a number', () => { - (state.arrayWithDefaultAndOptional as any) = 123; - expect(state.arrayWithDefaultAndOptional).toEqual(['test']); - }); - - it('should not set invalid array items', () => { - (state.arrayWithDefaultAndOptional as any) = [123, true, {}]; - expect(state.arrayWithDefaultAndOptional).toEqual(['test']); - }); - }); - - describe('Array With Default And Nullable', () => { - beforeEach(() => { - state.arrayWithDefaultAndNullable = ['test']; - }); - - it('should set the value', () => { - state.arrayWithDefaultAndNullable = ['hello', 'world']; - expect(state.arrayWithDefaultAndNullable).toEqual(['hello', 'world']); - }); - - it('should set the value to null', () => { - state.arrayWithDefaultAndNullable = null; - expect(state.arrayWithDefaultAndNullable).toBe(null); - }); - - it('should not set the value to undefined', () => { - (state.arrayWithDefaultAndNullable as any) = undefined; - expect([...state.arrayWithDefaultAndNullable]).toStrictEqual(['test']); - }); - - it('should not set the value to a string', () => { - (state.arrayWithDefaultAndNullable as any) = 'invalid'; - expect([...state.arrayWithDefaultAndNullable]).toStrictEqual(['test']); - }); - - it('should not set the value to a number', () => { - (state.arrayWithDefaultAndNullable as any) = 123; - expect([...state.arrayWithDefaultAndNullable]).toStrictEqual(['test']); - }); - - it('should not set invalid array items', () => { - (state.arrayWithDefaultAndNullable as any) = [123, true, {}]; - expect([...state.arrayWithDefaultAndNullable]).toStrictEqual(['test']); - }); - }); - - describe('Array With Default And Nullable And Optional', () => { - beforeEach(() => { - state.arrayWithDefaultAndNullableAndOptional = ['test']; - }); - - it('should set the value', () => { - state.arrayWithDefaultAndNullableAndOptional = ['hello', 'world']; - expect(state.arrayWithDefaultAndNullableAndOptional).toEqual(['hello', 'world']); - }); - - it('should set the value to null', () => { - state.arrayWithDefaultAndNullableAndOptional = null; - expect(state.arrayWithDefaultAndNullableAndOptional).toBe(null); - }); - - it('should set the value to undefined', () => { - state.arrayWithDefaultAndNullableAndOptional = undefined; - expect(state.arrayWithDefaultAndNullableAndOptional).toBe(undefined); - }); - - it('should not set the value to a string', () => { - (state.arrayWithDefaultAndNullableAndOptional as any) = 'invalid'; - expect(state.arrayWithDefaultAndNullableAndOptional).toEqual(['test']); - }); - - it('should not set the value to a number', () => { - (state.arrayWithDefaultAndNullableAndOptional as any) = 123; - expect(state.arrayWithDefaultAndNullableAndOptional).toEqual(['test']); - }); - - it('should not set invalid array items', () => { - (state.arrayWithDefaultAndNullableAndOptional as any) = [123, true, {}]; - expect(state.arrayWithDefaultAndNullableAndOptional).toEqual(['test']); - }); - }); - }); -}); diff --git a/package/src/tests/proxys/array.splice.diagnostic.test.ts b/package/src/tests/proxys/array.splice.diagnostic.test.ts deleted file mode 100644 index 6346535..0000000 --- a/package/src/tests/proxys/array.splice.diagnostic.test.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { syncroState, y } from '../../lib/index.js'; -import * as Y from 'yjs'; - -const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - -describe('Array Splice Diagnostic Tests', () => { - const doc = new Y.Doc(); - const schema = { - testArray: y.array(y.string()) - }; - - const state = syncroState({ schema, doc }); - - // Initialize with some data - - beforeEach(() => { - state.testArray = ['a', 'b', 'c', 'd', 'e']; - }); - - // it('should verify initial Y.js document setup', () => { - // state.testArray = ['a', 'b', 'c', 'd', 'e']; - - // // Test 1: Check if Y.js document has the array - // const yArray = state.testArray.getYType?.(); - // console.log('Initial Y.js array length:', yArray!.length); - // console.log('Initial Y.js array content:', yArray!.toArray()); - // console.log('Initial proxy array length:', state.testArray.length); - // console.log('Initial proxy array content:', [...state.testArray]); - - // // Basic assertions - // expect(state.testArray.length).toBe(5); - // expect([...state.testArray]).toEqual(['a', 'b', 'c', 'd', 'e']); - - // // Critical test: Y.js document should match proxy - // expect(yArray!.length).toBe(5); - // expect(yArray!.toJSON()).toEqual(['a', 'b', 'c', 'd', 'e']); - // }); - - // it('should verify Y.js document state after simple splice', () => { - // // Before splice - // const yArrayBefore = state.testArray.getYType?.(); - // console.log('Before splice - Y.js length:', yArrayBefore!.length); - // console.log('Before splice - Y.js content:', yArrayBefore!.toArray()); - - // // Perform splice - // state.testArray.splice(1, 1, 'x'); - - // // After splice - // const yArrayAfter = state.testArray.getYType?.(); - // console.log('After splice - Y.js length:', yArrayAfter!.length); - // console.log('After splice - Y.js content:', yArrayAfter!.toArray()); - // console.log('After splice - proxy length:', state.testArray.length); - // console.log('After splice - proxy content:', [...state.testArray]); - - // // Critical assertions - // expect(state.testArray.length).toBe(5); - // expect([...state.testArray]).toEqual(['a', 'x', 'c', 'd', 'e']); - // expect(yArrayAfter!.length).toBe(5); - // expect(yArrayAfter!.toJSON()).toEqual(['a', 'x', 'c', 'd', 'e']); - // }); - - // it('should verify Y.js document state after deletion-only splice', () => { - // // Before splice - // const yArrayBefore = state.testArray.getYType?.(); - // console.log('Before deletion splice - Y.js length:', yArrayBefore!.length); - - // // Perform deletion splice - // state.testArray.splice(1, 2); - - // // After splice - // const yArrayAfter = state.testArray.getYType?.(); - // console.log('After deletion splice - Y.js length:', yArrayAfter!.length); - // console.log('After deletion splice - Y.js content:', yArrayAfter!.toJSON()); - // console.log('After deletion splice - proxy length:', state.testArray.length); - // console.log('After deletion splice - proxy content:', [...state.testArray]); - - // // Critical assertions - // expect(state.testArray.length).toBe(3); - // expect([...state.testArray]).toEqual(['a', 'd', 'e']); - // expect(yArrayAfter!.length).toBe(3); - // expect(yArrayAfter!.toJSON()).toEqual(['a', 'd', 'e']); - // }); - - // it('should verify Y.js document state after insertion-only splice', () => { - // // Before splice - // const yArrayBefore = state.testArray.getYType?.(); - // console.log('Before insertion splice - Y.js length:', yArrayBefore!.length); - - // // Perform insertion splice - // state.testArray.splice(2, 0, 'x', 'y'); - - // // After splice - // const yArrayAfter = state.testArray.getYType?.(); - // console.log('After insertion splice - Y.js length:', yArrayAfter!.length); - // console.log('After insertion splice - Y.js content:', yArrayAfter!.toArray()); - // console.log('After insertion splice - proxy length:', state.testArray.length); - // console.log('After insertion splice - proxy content:', [...state.testArray]); - - // // Critical assertions - // expect(state.testArray.length).toBe(7); - // expect([...state.testArray]).toEqual(['a', 'b', 'x', 'y', 'c', 'd', 'e']); - // expect(yArrayAfter!.length).toBe(7); - // expect(yArrayAfter!.toJSON()).toEqual(['a', 'b', 'x', 'y', 'c', 'd', 'e']); - // }); - - // it('should verify empty array handling', () => { - // // Test empty array assignment - // console.log('Before clearing - proxy length:', state.testArray.length); - // console.log('Before clearing - proxy type:', typeof state.testArray); - - // state.testArray = []; - - // console.log('After clearing - proxy length:', state.testArray?.length); - // console.log('After clearing - proxy type:', typeof state.testArray); - // console.log('After clearing - proxy value:', state.testArray); - - // // Check if array becomes null (the reported issue) - // if (state.testArray === null) { - // console.log('ISSUE CONFIRMED: Array becomes null instead of empty array'); - // } - - // // Y.js document state - // const yArray = state.testArray?.getYType?.(); - // console.log('After clearing - Y.js length:', yArray?.length); - // console.log('After clearing - Y.js content:', yArray?.toArray()); - - // // This test will fail if the empty array issue exists - // expect(state.testArray).not.toBe(null); - // if (state.testArray) { - // expect(state.testArray.length).toBe(0); - // expect(yArray?.length).toBe(0); - // } - // }); - - it('should verify Y.js transaction behavior', () => { - // Check if transactions are working properly - console.log('Testing transaction behavior...'); - - let transactionCount = 0; - doc.on('update', () => { - transactionCount++; - console.log('Y.js document update event fired, count:', transactionCount); - }); - - // Perform splice - state.testArray.splice(1, 1, 'transaction-test'); - - console.log('Total transaction count:', transactionCount); - console.log('Final Y.js content:', state.testArray.getYType?.()?.toArray()); - console.log('Final proxy content:', [...state.testArray]); - - // Should have at least one transaction - expect(transactionCount).toBeGreaterThan(0); - }); - - it('should verify syncroStates array consistency', () => { - // Access internal syncroStates if possible - const arrayProxy = state.testArray; - console.log('Array proxy type:', typeof arrayProxy); - console.log('Array proxy constructor:', arrayProxy.constructor.name); - - // Check if we can access internal state - if (arrayProxy.syncroStates) { - console.log('SyncroStates length:', arrayProxy.syncroStates.length); - console.log( - 'SyncroStates types:', - arrayProxy.syncroStates.map((s) => typeof s) - ); - } - - if (arrayProxy.yType) { - console.log('YType length:', arrayProxy.yType.length); - console.log('YType content:', arrayProxy.yType.toArray()); - } - - // Perform splice and check internal consistency - state.testArray.splice(1, 1, 'internal-test'); - - if (arrayProxy.syncroStates && arrayProxy.yType) { - console.log('After splice - SyncroStates length:', arrayProxy.syncroStates.length); - console.log('After splice - YType length:', arrayProxy.yType.length); - - expect(arrayProxy.syncroStates.length).toBe(arrayProxy.yType.length); - expect(arrayProxy.syncroStates.length).toBe(state.testArray.length); - } - }); - - it('should verify multi-document synchronization setup', () => { - // Create second document and state - const schema = { testArray: y.array(y.string()) }; - const state2 = syncroState({ schema, doc }); - - // Initialize second state - state2.testArray = ['x', 'y', 'z']; - - console.log('Doc1 initial:', state.testArray.getYType?.()?.toJSON()); - console.log('Doc2 initial:', state2.testArray.getYType?.()?.toJSON()); - - console.log('Doc1 after sync:', state.testArray.getYType?.()?.toJSON()); - console.log('Doc2 after sync:', state2.testArray.getYType?.()?.toJSON()); - console.log('State1 after sync:', [...state.testArray]); - console.log('State2 after sync:', [...state2.testArray]); - - // Test if states converge (they might not due to the sync issue) - const doc1Content = state.testArray.getYType?.()?.toJSON() || []; - const doc2Content = state2.testArray.getYType?.()?.toJSON() || []; - - if (JSON.stringify(doc1Content) !== JSON.stringify(doc2Content)) { - console.log('ISSUE: Documents did not converge properly'); - } - - // This might fail due to synchronization issues - expect(doc1Content).toEqual(doc2Content); - }); -}); diff --git a/package/src/tests/proxys/boolean.proxy.test.ts b/package/src/tests/proxys/boolean.proxy.test.ts deleted file mode 100644 index 48eb86b..0000000 --- a/package/src/tests/proxys/boolean.proxy.test.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { syncroState, y } from '../../lib/index.js'; - -const state = syncroState({ - schema: { - boolean: y.boolean(), - nullableBoolean: y.boolean().nullable(), - optionnalBoolean: y.boolean().optional(), - nullableOptionnalBoolean: y.boolean().nullable().optional(), - booleanWithDefault: y.boolean().default(false), - booleanWithDefaultAndOptional: y.boolean().default(false).optional(), - booleanWithDefaultAndNullable: y.boolean().default(false).nullable(), - booleanWithDefaultAndNullableAndOptional: y.boolean().default(false).nullable().optional() - } -}); - -describe('BooleanProxy', () => { - describe('Initial values', () => { - it('should be a boolean', () => { - expect(state.boolean).toBeTypeOf('boolean'); - }); - - it('should have null as default value for nullable boolean', () => { - expect(state.nullableBoolean).toBe(null); - }); - - it('should have undefined as default value for optional boolean', () => { - expect(state.optionnalBoolean).toBe(undefined); - }); - - it('should have undefined as default value for nullable optional boolean', () => { - expect(state.nullableOptionnalBoolean).toBe(undefined); - }); - - it('should have default value for boolean with default', () => { - expect(state.booleanWithDefault).toBe(false); - }); - - it('should have default value for optional boolean with default', () => { - expect(state.booleanWithDefaultAndOptional).toBe(false); - }); - - it('should have default value for nullable boolean with default', () => { - expect(state.booleanWithDefaultAndNullable).toBe(false); - }); - - it('should have default value for nullable optional boolean with default', () => { - expect(state.booleanWithDefaultAndNullableAndOptional).toBe(false); - }); - }); - - describe('Setters', () => { - describe('Boolean', () => { - beforeEach(() => { - state.boolean = true; - }); - - it('should set the value', () => { - state.boolean = false; - expect(state.boolean).toBe(false); - }); - - it('should not set the value to null', () => { - state.boolean = null; - expect(state.boolean).toBe(true); - }); - - it('should not set the value to undefined', () => { - state.boolean = undefined; - expect(state.boolean).toBe(true); - }); - - it('should not set the value to a string', () => { - state.boolean = 'true'; - expect(state.boolean).toBe(true); - }); - - it('should not set the value to a number', () => { - state.boolean = 1; - expect(state.boolean).toBe(true); - }); - - it('should not set the value to an object', () => { - state.boolean = {}; - expect(state.boolean).toBe(true); - }); - }); - - describe('Nullable Boolean', () => { - beforeEach(() => { - state.nullableBoolean = true; - }); - - it('should set the value', () => { - state.nullableBoolean = false; - expect(state.nullableBoolean).toBe(false); - }); - - it('should set the value to null', () => { - state.nullableBoolean = null; - expect(state.nullableBoolean).toBe(null); - }); - - it('should not set the value to undefined', () => { - state.nullableBoolean = undefined; - expect(state.nullableBoolean).toBe(true); - }); - - it('should not set the value to a string', () => { - state.nullableBoolean = 'true'; - expect(state.nullableBoolean).toBe(true); - }); - - it('should not set the value to a number', () => { - state.nullableBoolean = 1; - expect(state.nullableBoolean).toBe(true); - }); - - it('should not set the value to an object', () => { - state.nullableBoolean = {}; - expect(state.nullableBoolean).toBe(true); - }); - }); - - describe('Optional Boolean', () => { - beforeEach(() => { - state.optionnalBoolean = true; - }); - - it('should set the value', () => { - state.optionnalBoolean = false; - expect(state.optionnalBoolean).toBe(false); - }); - - it('should not set the value to null', () => { - state.optionnalBoolean = null; - expect(state.optionnalBoolean).toBe(true); - }); - - it('should set the value to undefined', () => { - state.optionnalBoolean = undefined; - expect(state.optionnalBoolean).toBe(undefined); - }); - - it('should not set the value to a string', () => { - state.optionnalBoolean = 'true'; - expect(state.optionnalBoolean).toBe(true); - }); - - it('should not set the value to a number', () => { - state.optionnalBoolean = 1; - expect(state.optionnalBoolean).toBe(true); - }); - - it('should not set the value to an object', () => { - state.optionnalBoolean = {}; - expect(state.optionnalBoolean).toBe(true); - }); - }); - - describe('Nullable Optional Boolean', () => { - beforeEach(() => { - state.nullableOptionnalBoolean = true; - }); - - it('should set the value', () => { - state.nullableOptionnalBoolean = false; - expect(state.nullableOptionnalBoolean).toBe(false); - }); - - it('should set the value to null', () => { - state.nullableOptionnalBoolean = null; - expect(state.nullableOptionnalBoolean).toBe(null); - }); - - it('should set the value to undefined', () => { - state.nullableOptionnalBoolean = undefined; - expect(state.nullableOptionnalBoolean).toBe(undefined); - }); - - it('should not set the value to a string', () => { - state.nullableOptionnalBoolean = 'true'; - expect(state.nullableOptionnalBoolean).toBe(true); - }); - - it('should not set the value to a number', () => { - state.nullableOptionnalBoolean = 1; - expect(state.nullableOptionnalBoolean).toBe(true); - }); - - it('should not set the value to an object', () => { - state.nullableOptionnalBoolean = {}; - expect(state.nullableOptionnalBoolean).toBe(true); - }); - }); - - describe('Boolean With Default', () => { - beforeEach(() => { - state.booleanWithDefault = true; - }); - - it('should set the value', () => { - state.booleanWithDefault = false; - expect(state.booleanWithDefault).toBe(false); - }); - - it('should not set the value to null', () => { - state.booleanWithDefault = null; - expect(state.booleanWithDefault).toBe(true); - }); - - it('should not set the value to undefined', () => { - state.booleanWithDefault = undefined; - expect(state.booleanWithDefault).toBe(true); - }); - - it('should not set the value to a string', () => { - state.booleanWithDefault = 'true'; - expect(state.booleanWithDefault).toBe(true); - }); - - it('should not set the value to a number', () => { - state.booleanWithDefault = 1; - expect(state.booleanWithDefault).toBe(true); - }); - - it('should not set the value to an object', () => { - state.booleanWithDefault = {}; - expect(state.booleanWithDefault).toBe(true); - }); - }); - - describe('Boolean With Default And Optional', () => { - beforeEach(() => { - state.booleanWithDefaultAndOptional = true; - }); - - it('should set the value', () => { - state.booleanWithDefaultAndOptional = false; - expect(state.booleanWithDefaultAndOptional).toBe(false); - }); - - it('should not set the value to null', () => { - state.booleanWithDefaultAndOptional = null; - expect(state.booleanWithDefaultAndOptional).toBe(true); - }); - - it('should set the value to undefined', () => { - state.booleanWithDefaultAndOptional = undefined; - expect(state.booleanWithDefaultAndOptional).toBe(undefined); - }); - - it('should not set the value to a string', () => { - state.booleanWithDefaultAndOptional = 'true'; - expect(state.booleanWithDefaultAndOptional).toBe(true); - }); - - it('should not set the value to a number', () => { - state.booleanWithDefaultAndOptional = 1; - expect(state.booleanWithDefaultAndOptional).toBe(true); - }); - - it('should not set the value to an object', () => { - state.booleanWithDefaultAndOptional = {}; - expect(state.booleanWithDefaultAndOptional).toBe(true); - }); - }); - - describe('Boolean With Default And Nullable', () => { - beforeEach(() => { - state.booleanWithDefaultAndNullable = true; - }); - - it('should set the value', () => { - state.booleanWithDefaultAndNullable = false; - expect(state.booleanWithDefaultAndNullable).toBe(false); - }); - - it('should set the value to null', () => { - state.booleanWithDefaultAndNullable = null; - expect(state.booleanWithDefaultAndNullable).toBe(null); - }); - - it('should not set the value to undefined', () => { - state.booleanWithDefaultAndNullable = undefined; - expect(state.booleanWithDefaultAndNullable).toBe(true); - }); - - it('should not set the value to a string', () => { - state.booleanWithDefaultAndNullable = 'true'; - expect(state.booleanWithDefaultAndNullable).toBe(true); - }); - - it('should not set the value to a number', () => { - state.booleanWithDefaultAndNullable = 1; - expect(state.booleanWithDefaultAndNullable).toBe(true); - }); - - it('should not set the value to an object', () => { - state.booleanWithDefaultAndNullable = {}; - expect(state.booleanWithDefaultAndNullable).toBe(true); - }); - }); - - describe('Boolean With Default And Nullable And Optional', () => { - beforeEach(() => { - state.booleanWithDefaultAndNullableAndOptional = true; - }); - - it('should set the value', () => { - state.booleanWithDefaultAndNullableAndOptional = false; - expect(state.booleanWithDefaultAndNullableAndOptional).toBe(false); - }); - - it('should set the value to null', () => { - state.booleanWithDefaultAndNullableAndOptional = null; - expect(state.booleanWithDefaultAndNullableAndOptional).toBe(null); - }); - - it('should set the value to undefined', () => { - state.booleanWithDefaultAndNullableAndOptional = undefined; - expect(state.booleanWithDefaultAndNullableAndOptional).toBe(undefined); - }); - - it('should not set the value to a string', () => { - state.booleanWithDefaultAndNullableAndOptional = 'true'; - expect(state.booleanWithDefaultAndNullableAndOptional).toBe(true); - }); - - it('should not set the value to a number', () => { - state.booleanWithDefaultAndNullableAndOptional = 1; - expect(state.booleanWithDefaultAndNullableAndOptional).toBe(true); - }); - - it('should not set the value to an object', () => { - state.booleanWithDefaultAndNullableAndOptional = {}; - expect(state.booleanWithDefaultAndNullableAndOptional).toBe(true); - }); - }); - }); -}); diff --git a/package/src/tests/proxys/date.proxy.test.ts b/package/src/tests/proxys/date.proxy.test.ts deleted file mode 100644 index 948f98a..0000000 --- a/package/src/tests/proxys/date.proxy.test.ts +++ /dev/null @@ -1,431 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { syncroState, y } from '../../lib/index.js'; - -const defaultDate = new Date('2024-01-01'); - -const state = syncroState({ - schema: { - date: y.date(), - nullableDate: y.date().nullable(), - optionnalDate: y.date().optional(), - nullableOptionnalDate: y.date().nullable().optional(), - dateWithDefault: y.date().default(defaultDate), - dateWithDefaultAndOptional: y.date().default(defaultDate).optional(), - dateWithDefaultAndNullable: y.date().default(defaultDate).nullable(), - dateWithDefaultAndNullableAndOptional: y.date().default(defaultDate).nullable().optional() - } -}); - -describe('DateProxy', () => { - describe('Initial values', () => { - it('should be a date', () => { - expect(state.date).toBeInstanceOf(Date); - }); - - it('should have null as default value for nullable date', () => { - expect(state.nullableDate).toBe(null); - }); - - it('should have undefined as default value for optional date', () => { - expect(state.optionnalDate).toBe(undefined); - }); - - it('should have undefined as default value for nullable optional date', () => { - expect(state.nullableOptionnalDate).toBe(undefined); - }); - - it('should have default value for date with default', () => { - expect(state.dateWithDefault.toISOString()).toEqual(defaultDate.toISOString()); - }); - - it('should have default value for optional date with default', () => { - expect(state.dateWithDefaultAndOptional?.toISOString()).toEqual(defaultDate.toISOString()); - }); - - it('should have default value for nullable date with default', () => { - expect(state.dateWithDefaultAndNullable?.toISOString()).toEqual(defaultDate.toISOString()); - }); - - it('should have default value for nullable optional date with default', () => { - expect(state.dateWithDefaultAndNullableAndOptional?.toISOString()).toEqual( - defaultDate.toISOString() - ); - }); - }); - - describe('Setters', () => { - const testDate = new Date('2024-02-01'); - - describe('Date', () => { - beforeEach(() => { - state.date = testDate; - }); - - it('should set the value', () => { - const newDate = new Date('2024-03-01'); - state.date = newDate; - expect(state.date.toISOString()).toEqual(newDate.toISOString()); - }); - - it('should not set the value to null', () => { - state.date = null; - expect(state.date.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to undefined', () => { - state.date = undefined; - expect(state.date.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to a string', () => { - state.date = '2024-03-01'; - expect(state.date.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to a number', () => { - state.date = 123; - expect(state.date.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to an object', () => { - state.date = {}; - expect(state.date.toISOString()).toEqual(testDate.toISOString()); - }); - }); - - describe('Date operations', () => { - beforeEach(() => { - state.date = new Date('2024-03-15T12:00:00.000Z'); - }); - - it('should handle setFullYear operation', () => { - state.date.setFullYear(2025); - expect(state.date.getFullYear()).toBe(2025); - }); - - it('should handle setMonth operation', () => { - state.date.setMonth(5); // June (0-based) - expect(state.date.getMonth()).toBe(5); - }); - - it('should handle setDate operation', () => { - state.date.setDate(20); - expect(state.date.getDate()).toBe(20); - }); - - it('should handle setHours operation', () => { - state.date.setHours(15); - expect(state.date.getHours()).toBe(15); - }); - - it('should handle setMinutes operation', () => { - state.date.setMinutes(30); - expect(state.date.getMinutes()).toBe(30); - }); - - it('should handle setSeconds operation', () => { - state.date.setSeconds(45); - expect(state.date.getSeconds()).toBe(45); - }); - - it('should handle setMilliseconds operation', () => { - state.date.setMilliseconds(500); - expect(state.date.getMilliseconds()).toBe(500); - }); - - it('should handle multiple operations in sequence', () => { - state.date.setFullYear(2025); - state.date.setMonth(6); - state.date.setDate(25); - state.date.setUTCHours(14); - state.date.setUTCMinutes(30); - state.date.setUTCSeconds(15); - state.date.setUTCMilliseconds(250); - - expect(state.date.toISOString()).toBe('2025-07-25T14:30:15.250Z'); - }); - - it('should handle setTime operation', () => { - const timestamp = new Date('2025-12-25T00:00:00.000Z').getTime(); - state.date.setTime(timestamp); - expect(state.date.toISOString()).toBe('2025-12-25T00:00:00.000Z'); - }); - - it('should handle UTC operations', () => { - state.date.setUTCFullYear(2025); - state.date.setUTCMonth(11); // December (0-based) - state.date.setUTCDate(25); - - expect(state.date.getUTCFullYear()).toBe(2025); - expect(state.date.getUTCMonth()).toBe(11); - expect(state.date.getUTCDate()).toBe(25); - }); - }); - describe('Nullable Date', () => { - beforeEach(() => { - state.nullableDate = testDate; - }); - - it('should set the value', () => { - const newDate = new Date('2024-03-01'); - state.nullableDate = newDate; - expect(state.nullableDate.toISOString()).toEqual(newDate.toISOString()); - }); - - it('should set the value to null', () => { - state.nullableDate = null; - expect(state.nullableDate).toBe(null); - }); - - it('should not set the value to undefined', () => { - state.nullableDate = undefined; - expect(state.nullableDate?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to a string', () => { - state.nullableDate = '2024-03-01' as any; - expect(state.nullableDate?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to a number', () => { - state.nullableDate = 123 as any; - expect(state.nullableDate?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to an object', () => { - state.nullableDate = {} as any; - expect(state.nullableDate?.toISOString()).toEqual(testDate.toISOString()); - }); - }); - - describe('Optional Date', () => { - beforeEach(() => { - state.optionnalDate = testDate; - }); - - it('should set the value', () => { - const newDate = new Date('2024-03-01'); - state.optionnalDate = newDate; - expect(state.optionnalDate?.toISOString()).toEqual(newDate.toISOString()); - }); - - it('should not set the value to null', () => { - state.optionnalDate = null; - expect(state.optionnalDate?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should set the value to undefined', () => { - state.optionnalDate = undefined; - expect(state.optionnalDate).toBe(undefined); - }); - - it('should not set the value to a string', () => { - state.optionnalDate = '2024-03-01' as any; - expect(state.optionnalDate?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to a number', () => { - state.optionnalDate = 123 as any; - expect(state.optionnalDate?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to an object', () => { - state.optionnalDate = {} as any; - expect(state.optionnalDate?.toISOString()).toEqual(testDate.toISOString()); - }); - }); - - describe('Nullable Optional Date', () => { - beforeEach(() => { - state.nullableOptionnalDate = testDate; - }); - - it('should set the value', () => { - const newDate = new Date('2024-03-01'); - state.nullableOptionnalDate = newDate; - expect(state.nullableOptionnalDate?.toISOString()).toEqual(newDate.toISOString()); - }); - - it('should set the value to null', () => { - state.nullableOptionnalDate = null; - expect(state.nullableOptionnalDate).toBe(null); - }); - - it('should set the value to undefined', () => { - state.nullableOptionnalDate = undefined; - expect(state.nullableOptionnalDate).toBe(undefined); - }); - - it('should not set the value to a string', () => { - state.nullableOptionnalDate = '2024-03-01' as any; - expect(state.nullableOptionnalDate?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to a number', () => { - state.nullableOptionnalDate = 123 as any; - expect(state.nullableOptionnalDate?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to an object', () => { - state.nullableOptionnalDate = {} as any; - expect(state.nullableOptionnalDate?.toISOString()).toEqual(testDate.toISOString()); - }); - }); - - describe('Date With Default', () => { - beforeEach(() => { - state.dateWithDefault = testDate; - }); - - it('should set the value', () => { - const newDate = new Date('2024-03-01'); - state.dateWithDefault = newDate; - expect(state.dateWithDefault?.toISOString()).toEqual(newDate.toISOString()); - }); - - it('should not set the value to null', () => { - state.dateWithDefault = null; - expect(state.dateWithDefault?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to undefined', () => { - state.dateWithDefault = undefined; - expect(state.dateWithDefault?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to a string', () => { - state.dateWithDefault = '2024-03-01' as any; - expect(state.dateWithDefault.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to a number', () => { - state.dateWithDefault = 123 as any; - expect(state.dateWithDefault.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to an object', () => { - state.dateWithDefault = {} as any; - expect(state.dateWithDefault.toISOString()).toEqual(testDate.toISOString()); - }); - }); - - describe('Date With Default And Optional', () => { - beforeEach(() => { - state.dateWithDefaultAndOptional = testDate; - }); - - it('should set the value', () => { - const newDate = new Date('2024-03-01'); - state.dateWithDefaultAndOptional = newDate; - expect(state.dateWithDefaultAndOptional?.toISOString()).toEqual(newDate.toISOString()); - }); - - it('should not set the value to null', () => { - state.dateWithDefaultAndOptional = null; - expect(state.dateWithDefaultAndOptional?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should set the value to undefined', () => { - state.dateWithDefaultAndOptional = undefined; - expect(state.dateWithDefaultAndOptional).toBe(undefined); - }); - - it('should not set the value to a string', () => { - state.dateWithDefaultAndOptional = '2024-03-01' as any; - expect(state.dateWithDefaultAndOptional?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to a number', () => { - state.dateWithDefaultAndOptional = 123 as any; - expect(state.dateWithDefaultAndOptional?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to an object', () => { - state.dateWithDefaultAndOptional = {} as any; - expect(state.dateWithDefaultAndOptional?.toISOString()).toEqual(testDate.toISOString()); - }); - }); - - describe('Date With Default And Nullable', () => { - beforeEach(() => { - state.dateWithDefaultAndNullable = testDate; - }); - - it('should set the value', () => { - const newDate = new Date('2024-03-01'); - state.dateWithDefaultAndNullable = newDate; - expect(state.dateWithDefaultAndNullable?.toISOString()).toEqual(newDate.toISOString()); - }); - - it('should set the value to null', () => { - state.dateWithDefaultAndNullable = null; - expect(state.dateWithDefaultAndNullable).toBe(null); - }); - - it('should not set the value to undefined', () => { - state.dateWithDefaultAndNullable = undefined; - expect(state.dateWithDefaultAndNullable?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to a string', () => { - state.dateWithDefaultAndNullable = '2024-03-01' as any; - expect(state.dateWithDefaultAndNullable?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to a number', () => { - state.dateWithDefaultAndNullable = 123 as any; - expect(state.dateWithDefaultAndNullable?.toISOString()).toEqual(testDate.toISOString()); - }); - - it('should not set the value to an object', () => { - state.dateWithDefaultAndNullable = {} as any; - expect(state.dateWithDefaultAndNullable?.toISOString()).toEqual(testDate.toISOString()); - }); - }); - - describe('Date With Default And Nullable And Optional', () => { - beforeEach(() => { - state.dateWithDefaultAndNullableAndOptional = testDate; - }); - - it('should set the value', () => { - const newDate = new Date('2024-03-01'); - state.dateWithDefaultAndNullableAndOptional = newDate; - expect(state.dateWithDefaultAndNullableAndOptional?.toISOString()).toEqual( - newDate.toISOString() - ); - }); - - it('should set the value to null', () => { - state.dateWithDefaultAndNullableAndOptional = null; - expect(state.dateWithDefaultAndNullableAndOptional).toBe(null); - }); - - it('should set the value to undefined', () => { - state.dateWithDefaultAndNullableAndOptional = undefined; - expect(state.dateWithDefaultAndNullableAndOptional).toBe(undefined); - }); - - it('should not set the value to a string', () => { - state.dateWithDefaultAndNullableAndOptional = '2024-03-01' as any; - expect(state.dateWithDefaultAndNullableAndOptional?.toISOString()).toEqual( - testDate.toISOString() - ); - }); - - it('should not set the value to a number', () => { - state.dateWithDefaultAndNullableAndOptional = 123 as any; - expect(state.dateWithDefaultAndNullableAndOptional?.toISOString()).toEqual( - testDate.toISOString() - ); - }); - - it('should not set the value to an object', () => { - state.dateWithDefaultAndNullableAndOptional = {} as any; - expect(state.dateWithDefaultAndNullableAndOptional?.toISOString()).toEqual( - testDate.toISOString() - ); - }); - }); - }); -}); diff --git a/package/src/tests/proxys/discriminatedUnion.proxy.test.ts b/package/src/tests/proxys/discriminatedUnion.proxy.test.ts deleted file mode 100644 index c75b022..0000000 --- a/package/src/tests/proxys/discriminatedUnion.proxy.test.ts +++ /dev/null @@ -1,355 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import * as Y from 'yjs'; -import { SyncedDiscriminatedUnion } from '../../lib/proxys/discriminatedUnion.svelte.js'; -import { y } from '../../lib/schemas/schema.js'; -import type { State } from '../../lib/proxys/syncroState.svelte.js'; - -// Mock State for testing -const createMockState = (): State => { - const doc = new Y.Doc(); - const transactionKey = 'test-transaction'; - - return { - doc, - awareness: null as any, - isInitialized: { value: true }, - isConnected: { value: true }, - transactionKey, - transaction: (fn: () => void) => { - doc.transact(fn, transactionKey); - }, - undo: () => {}, - redo: () => {}, - canUndo: { value: false }, - canRedo: { value: false }, - presence: null as any - }; -}; - -// Mock parent container -const createMockParent = (yDoc: Y.Doc) => ({ - yType: yDoc.getMap('root'), - deleteProperty: () => true -}); - -describe('SyncedDiscriminatedUnion', () => { - let yDoc: Y.Doc; - let mockState: State; - let mockParent: ReturnType; - - beforeEach(() => { - yDoc = new Y.Doc(); - mockState = createMockState(); - // IMPORTANT: Use the same document for state and yType! - mockState.doc = yDoc; - mockParent = createMockParent(yDoc); - }); - - describe('Basic functionality', () => { - it('should create a discriminated union proxy', () => { - const validator = y.discriminatedUnion('status', [ - y.object({ status: y.literal('success'), data: y.string() }), - y.object({ status: y.literal('failed'), error: y.string() }) - ]); - - const yType = yDoc.getMap('test'); - const syncedUnion = new SyncedDiscriminatedUnion({ - state: mockState, - validator, - yType, - parent: mockParent, - key: 'test', - observe: false - }); - - expect(syncedUnion).toBeDefined(); - expect(syncedUnion.validator).toBe(validator); - expect(syncedUnion.currentVariant).toBe(validator.$schema.variantValidators[0]); - }); - - it('should set and get success variant values', () => { - const validator = y.discriminatedUnion('status', [ - y.object({ status: y.literal('success'), data: y.string() }), - y.object({ status: y.literal('failed'), error: y.string() }) - ]); - - const yType = yDoc.getMap('test'); - const syncedUnion = new SyncedDiscriminatedUnion({ - state: mockState, - validator, - yType, - parent: mockParent, - key: 'test', - observe: false - }); - - const successValue = { status: 'success', data: 'Hello world' }; - syncedUnion.value = successValue; - - expect(syncedUnion.currentVariant).toBeTruthy(); - expect(syncedUnion.value.status).toBe('success'); - expect(syncedUnion.value.data).toBe('Hello world'); - }); - - it('should set and get failed variant values', () => { - const validator = y.discriminatedUnion('status', [ - y.object({ status: y.literal('success'), data: y.string() }), - y.object({ status: y.literal('failed'), error: y.string() }) - ]); - - const yType = yDoc.getMap('test'); - const syncedUnion = new SyncedDiscriminatedUnion({ - state: mockState, - validator, - yType, - parent: mockParent, - key: 'test', - observe: false - }); - - const failedValue = { status: 'failed', error: 'Something went wrong' }; - syncedUnion.value = failedValue; - - expect(syncedUnion.currentVariant).toBeTruthy(); - expect(syncedUnion.value.status).toBe('failed'); - expect(syncedUnion.value.error).toBe('Something went wrong'); - }); - }); - - describe('Variant switching', () => { - it('should switch variants when discriminant changes', () => { - const validator = y.discriminatedUnion('status', [ - y.object({ status: y.literal('success'), data: y.string() }), - y.object({ status: y.literal('failed'), error: y.string() }) - ]); - - const yType = yDoc.getMap('test'); - const syncedUnion = new SyncedDiscriminatedUnion({ - state: mockState, - validator, - yType, - parent: mockParent, - key: 'test', - observe: true - }); - - // Start with success - const successValue = { status: 'success', data: 'Hello world' }; - syncedUnion.value = successValue; - - expect(syncedUnion.value.status).toBe('success'); - expect(syncedUnion.value.data).toBe('Hello world'); - - // Switch to failed - const failedValue = { status: 'failed', error: 'Something went wrong' }; - syncedUnion.value = failedValue; - - // Check that the value changed correctly (most important) - expect(syncedUnion.value.status).toBe('failed'); - expect(syncedUnion.value.error).toBe('Something went wrong'); - // The discriminant should not have data property anymore - expect(syncedUnion.value.data).toBe(undefined); - }); - - it('should handle property updates within the same variant', () => { - const validator = y.discriminatedUnion('status', [ - y.object({ status: y.literal('success'), data: y.string() }), - y.object({ status: y.literal('failed'), error: y.string() }) - ]); - - const yType = yDoc.getMap('test'); - const syncedUnion = new SyncedDiscriminatedUnion({ - state: mockState, - validator, - yType, - parent: mockParent, - key: 'test', - observe: false - }); - - // Set initial value - syncedUnion.value = { status: 'success', data: 'Initial data' }; - const initialVariant = syncedUnion.currentVariant; - - // Update data property - syncedUnion.value.data = 'Updated data'; - - expect(syncedUnion.currentVariant).toBe(initialVariant); // Same variant - expect(syncedUnion.value.status).toBe('success'); - expect(syncedUnion.value.data).toBe('Updated data'); - }); - }); - - describe('Null and undefined handling', () => { - it('should handle null values for nullable unions', () => { - const validator = y - .discriminatedUnion('status', [ - y.object({ status: y.literal('success'), data: y.string() }), - y.object({ status: y.literal('failed'), error: y.string() }) - ]) - .nullable(); - - const yType = yDoc.getMap('test'); - const syncedUnion = new SyncedDiscriminatedUnion({ - state: mockState, - validator, - yType, - parent: mockParent, - key: 'test', - observe: false - }); - - syncedUnion.value = null; - expect(syncedUnion.isNull).toBe(true); - expect(syncedUnion.value).toBeNull(); - }); - - it('should handle undefined values for optional unions', () => { - const validator = y - .discriminatedUnion('status', [ - y.object({ status: y.literal('success'), data: y.string() }), - y.object({ status: y.literal('failed'), error: y.string() }) - ]) - .optional(); - - const yType = yDoc.getMap('test'); - const syncedUnion = new SyncedDiscriminatedUnion({ - state: mockState, - validator, - yType, - parent: mockParent, - key: 'test', - observe: false - }); - - // This should trigger parent deletion for undefined values - syncedUnion.value = undefined; - // Note: In a real scenario, this would delete the property from parent - }); - }); - - describe('JSON serialization', () => { - it('should serialize to JSON correctly', () => { - const validator = y.discriminatedUnion('status', [ - y.object({ status: y.literal('success'), data: y.string() }), - y.object({ status: y.literal('failed'), error: y.string() }) - ]); - - const yType = yDoc.getMap('test'); - const syncedUnion = new SyncedDiscriminatedUnion({ - state: mockState, - validator, - yType, - parent: mockParent, - key: 'test', - observe: true - }); - - const successValue = { status: 'success', data: 'Hello world' }; - syncedUnion.value = successValue; - - const json = syncedUnion.toJSON(); - expect(json).toEqual(successValue); - }); - - it('should return null for null unions', () => { - const validator = y - .discriminatedUnion('status', [ - y.object({ status: y.literal('success'), data: y.string() }), - y.object({ status: y.literal('failed'), error: y.string() }) - ]) - .nullable(); - - const yType = yDoc.getMap('test'); - const syncedUnion = new SyncedDiscriminatedUnion({ - state: mockState, - validator, - yType, - parent: mockParent, - key: 'test', - observe: false - }); - - syncedUnion.value = null; - const json = syncedUnion.toJSON(); - expect(json).toBeNull(); - }); - }); - - describe('Proxy behavior', () => { - it('should support property access through proxy', () => { - const validator = y.discriminatedUnion('status', [ - y.object({ status: y.literal('success'), data: y.string() }), - y.object({ status: y.literal('failed'), error: y.string() }) - ]); - - const yType = yDoc.getMap('test'); - const syncedUnion = new SyncedDiscriminatedUnion({ - state: mockState, - validator, - yType, - parent: mockParent, - key: 'test', - observe: false - }); - - syncedUnion.value = { status: 'success', data: 'Hello world' }; - - // Access through proxy - expect(syncedUnion.proxy.status).toBe('success'); - expect(syncedUnion.proxy.data).toBe('Hello world'); - }); - - it('should support property enumeration', () => { - const validator = y.discriminatedUnion('status', [ - y.object({ status: y.literal('success'), data: y.string() }), - y.object({ status: y.literal('failed'), error: y.string() }) - ]); - - const yType = yDoc.getMap('test'); - const syncedUnion = new SyncedDiscriminatedUnion({ - state: mockState, - validator, - yType, - parent: mockParent, - key: 'test', - observe: true - }); - - syncedUnion.value = { status: 'success', data: 'Hello world' }; - - const keys = Object.keys(syncedUnion.value); - - expect(keys).toContain('status'); - expect(keys).toContain('data'); - }); - }); - - describe('Cleanup', () => { - it('should cleanup resources on destroy', () => { - const validator = y - .discriminatedUnion('status', [ - y.object({ status: y.literal('success'), data: y.string() }), - y.object({ status: y.literal('failed'), error: y.string() }) - ]) - .default({ status: 'success', data: 'Hello world' }); - - const yType = yDoc.getMap('test'); - const syncedUnion = new SyncedDiscriminatedUnion({ - state: mockState, - validator, - yType, - parent: mockParent, - key: 'test', - observe: false - }); - - syncedUnion.value = { status: 'success', data: 'Hello world' }; - expect(syncedUnion.objectProxy).not.toBeNull(); - - syncedUnion.destroy(); - expect(syncedUnion.objectProxy).toBeNull(); - expect(syncedUnion.currentVariant).toBeFalsy(); - }); - }); -}); diff --git a/package/src/tests/proxys/enum.proxy.test.ts b/package/src/tests/proxys/enum.proxy.test.ts deleted file mode 100644 index 8f20705..0000000 --- a/package/src/tests/proxys/enum.proxy.test.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { syncroState, y } from '../../lib/index.js'; - -const state = syncroState({ - schema: { - enum: y.enum('a', 'b', 'c'), - nullableEnum: y.enum('a', 'b', 'c').nullable(), - optionnalEnum: y.enum('a', 'b', 'c').optional(), - nullableOptionnalEnum: y.enum('a', 'b', 'c').nullable().optional(), - enumWithDefault: y.enum('a', 'b', 'c').default('a'), - enumWithDefaultAndOptional: y.enum('a', 'b', 'c').default('a').optional(), - enumWithDefaultAndNullable: y.enum('a', 'b', 'c').default('a').nullable(), - enumWithDefaultAndNullableAndOptional: y.enum('a', 'b', 'c').default('a').nullable().optional() - } -}); - -describe('EnumProxy', () => { - describe('Initial values', () => { - it('should be a string', () => { - expect(state.enum).toBeTypeOf('string'); - }); - - it('should have null as default value for nullable enum', () => { - expect(state.nullableEnum).toBe(null); - }); - - it('should have undefined as default value for optional enum', () => { - expect(state.optionnalEnum).toBe(undefined); - }); - - it('should have undefined as default value for nullable optional enum', () => { - expect(state.nullableOptionnalEnum).toBe(undefined); - }); - - it('should have default value for enum with default', () => { - expect(state.enumWithDefault).toBe('a'); - }); - - it('should have default value for optional enum with default', () => { - expect(state.enumWithDefaultAndOptional).toBe('a'); - }); - - it('should have default value for nullable enum with default', () => { - expect(state.enumWithDefaultAndNullable).toBe('a'); - }); - - it('should have default value for nullable optional enum with default', () => { - expect(state.enumWithDefaultAndNullableAndOptional).toBe('a'); - }); - }); - - describe('Setters', () => { - describe('Enum', () => { - beforeEach(() => { - state.enum = 'a'; - }); - - it('should set the value to a valid enum value', () => { - state.enum = 'b'; - expect(state.enum).toBe('b'); - }); - - it('should not set the value to an invalid enum value', () => { - state.enum = 'd'; - expect(state.enum).toBe('a'); - }); - - it('should not set the value to null', () => { - state.enum = null; - expect(state.enum).toBe('a'); - }); - - it('should not set the value to undefined', () => { - state.enum = undefined; - expect(state.enum).toBe('a'); - }); - - it('should not set the value to a number', () => { - state.enum = 123; - expect(state.enum).toBe('a'); - }); - - it('should not set the value to an object', () => { - state.enum = {}; - expect(state.enum).toBe('a'); - }); - }); - - describe('Nullable Enum', () => { - beforeEach(() => { - state.nullableEnum = 'a'; - }); - - it('should set the value to a valid enum value', () => { - state.nullableEnum = 'b'; - expect(state.nullableEnum).toBe('b'); - }); - - it('should not set the value to an invalid enum value', () => { - state.nullableEnum = 'd'; - expect(state.nullableEnum).toBe('a'); - }); - - it('should set the value to null', () => { - state.nullableEnum = null; - expect(state.nullableEnum).toBe(null); - }); - - it('should not set the value to undefined', () => { - state.nullableEnum = undefined; - expect(state.nullableEnum).toBe('a'); - }); - - it('should not set the value to a number', () => { - state.nullableEnum = 123; - expect(state.nullableEnum).toBe('a'); - }); - - it('should not set the value to an object', () => { - state.nullableEnum = {}; - expect(state.nullableEnum).toBe('a'); - }); - }); - - describe('Optional Enum', () => { - beforeEach(() => { - state.optionnalEnum = 'a'; - }); - - it('should set the value to a valid enum value', () => { - state.optionnalEnum = 'b'; - expect(state.optionnalEnum).toBe('b'); - }); - - it('should not set the value to an invalid enum value', () => { - state.optionnalEnum = 'd'; - expect(state.optionnalEnum).toBe('a'); - }); - - it('should not set the value to null', () => { - state.optionnalEnum = null; - expect(state.optionnalEnum).toBe('a'); - }); - - it('should set the value to undefined', () => { - state.optionnalEnum = undefined; - expect(state.optionnalEnum).toBe(undefined); - }); - - it('should not set the value to a number', () => { - state.optionnalEnum = 123; - expect(state.optionnalEnum).toBe('a'); - }); - - it('should not set the value to an object', () => { - state.optionnalEnum = {}; - expect(state.optionnalEnum).toBe('a'); - }); - }); - - describe('Nullable Optional Enum', () => { - beforeEach(() => { - state.nullableOptionnalEnum = 'a'; - }); - - it('should set the value to a valid enum value', () => { - state.nullableOptionnalEnum = 'b'; - expect(state.nullableOptionnalEnum).toBe('b'); - }); - - it('should not set the value to an invalid enum value', () => { - state.nullableOptionnalEnum = 'd'; - expect(state.nullableOptionnalEnum).toBe('a'); - }); - - it('should set the value to null', () => { - state.nullableOptionnalEnum = null; - expect(state.nullableOptionnalEnum).toBe(null); - }); - - it('should set the value to undefined', () => { - state.nullableOptionnalEnum = undefined; - expect(state.nullableOptionnalEnum).toBe(undefined); - }); - - it('should not set the value to a number', () => { - state.nullableOptionnalEnum = 123; - expect(state.nullableOptionnalEnum).toBe('a'); - }); - - it('should not set the value to an object', () => { - state.nullableOptionnalEnum = {}; - expect(state.nullableOptionnalEnum).toBe('a'); - }); - }); - - describe('Enum With Default', () => { - beforeEach(() => { - state.enumWithDefault = 'b'; - }); - - it('should set the value to a valid enum value', () => { - state.enumWithDefault = 'c'; - expect(state.enumWithDefault).toBe('c'); - }); - - it('should not set the value to an invalid enum value', () => { - state.enumWithDefault = 'd'; - expect(state.enumWithDefault).toBe('b'); - }); - - it('should not set the value to null', () => { - state.enumWithDefault = null; - expect(state.enumWithDefault).toBe('b'); - }); - - it('should not set the value to undefined', () => { - state.enumWithDefault = undefined; - expect(state.enumWithDefault).toBe('b'); - }); - - it('should not set the value to a number', () => { - state.enumWithDefault = 123; - expect(state.enumWithDefault).toBe('b'); - }); - - it('should not set the value to an object', () => { - state.enumWithDefault = {}; - expect(state.enumWithDefault).toBe('b'); - }); - }); - - describe('Enum With Default And Optional', () => { - beforeEach(() => { - state.enumWithDefaultAndOptional = 'b'; - }); - - it('should set the value to a valid enum value', () => { - state.enumWithDefaultAndOptional = 'c'; - expect(state.enumWithDefaultAndOptional).toBe('c'); - }); - - it('should not set the value to an invalid enum value', () => { - state.enumWithDefaultAndOptional = 'd'; - expect(state.enumWithDefaultAndOptional).toBe('b'); - }); - - it('should not set the value to null', () => { - state.enumWithDefaultAndOptional = null; - expect(state.enumWithDefaultAndOptional).toBe('b'); - }); - - it('should set the value to undefined', () => { - state.enumWithDefaultAndOptional = undefined; - expect(state.enumWithDefaultAndOptional).toBe(undefined); - }); - - it('should not set the value to a number', () => { - state.enumWithDefaultAndOptional = 123; - expect(state.enumWithDefaultAndOptional).toBe('b'); - }); - - it('should not set the value to an object', () => { - state.enumWithDefaultAndOptional = {}; - expect(state.enumWithDefaultAndOptional).toBe('b'); - }); - }); - - describe('Enum With Default And Nullable', () => { - beforeEach(() => { - state.enumWithDefaultAndNullable = 'b'; - }); - - it('should set the value to a valid enum value', () => { - state.enumWithDefaultAndNullable = 'c'; - expect(state.enumWithDefaultAndNullable).toBe('c'); - }); - - it('should not set the value to an invalid enum value', () => { - state.enumWithDefaultAndNullable = 'd'; - expect(state.enumWithDefaultAndNullable).toBe('b'); - }); - - it('should set the value to null', () => { - state.enumWithDefaultAndNullable = null; - expect(state.enumWithDefaultAndNullable).toBe(null); - }); - - it('should not set the value to undefined', () => { - state.enumWithDefaultAndNullable = undefined; - expect(state.enumWithDefaultAndNullable).toBe('b'); - }); - - it('should not set the value to a number', () => { - state.enumWithDefaultAndNullable = 123; - expect(state.enumWithDefaultAndNullable).toBe('b'); - }); - - it('should not set the value to an object', () => { - state.enumWithDefaultAndNullable = {}; - expect(state.enumWithDefaultAndNullable).toBe('b'); - }); - }); - - describe('Enum With Default And Nullable And Optional', () => { - beforeEach(() => { - state.enumWithDefaultAndNullableAndOptional = 'b'; - }); - - it('should set the value to a valid enum value', () => { - state.enumWithDefaultAndNullableAndOptional = 'c'; - expect(state.enumWithDefaultAndNullableAndOptional).toBe('c'); - }); - - it('should not set the value to an invalid enum value', () => { - state.enumWithDefaultAndNullableAndOptional = 'd'; - expect(state.enumWithDefaultAndNullableAndOptional).toBe('b'); - }); - - it('should set the value to null', () => { - state.enumWithDefaultAndNullableAndOptional = null; - expect(state.enumWithDefaultAndNullableAndOptional).toBe(null); - }); - - it('should set the value to undefined', () => { - state.enumWithDefaultAndNullableAndOptional = undefined; - expect(state.enumWithDefaultAndNullableAndOptional).toBe(undefined); - }); - - it('should not set the value to a number', () => { - state.enumWithDefaultAndNullableAndOptional = 123; - expect(state.enumWithDefaultAndNullableAndOptional).toBe('b'); - }); - - it('should not set the value to an object', () => { - state.enumWithDefaultAndNullableAndOptional = {}; - expect(state.enumWithDefaultAndNullableAndOptional).toBe('b'); - }); - }); - }); -}); diff --git a/package/src/tests/proxys/map.proxy.test.ts b/package/src/tests/proxys/map.proxy.test.ts deleted file mode 100644 index edd9847..0000000 --- a/package/src/tests/proxys/map.proxy.test.ts +++ /dev/null @@ -1,307 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { syncroState, y } from '../../lib/index.js'; - -type StringMap = Map; -type ObjectMap = Map; - -const state = syncroState({ - schema: { - map: y.map(y.string()), - nullableMap: y.map(y.string()).nullable(), - optionalMap: y.map(y.string()).optional(), - nullableOptionalMap: y.map(y.string()).nullable().optional(), - mapWithDefault: y.map(y.string()).default(new Map([['key', 'default']])), - mapWithDefaultAndOptional: y - .map(y.string()) - .default(new Map([['key', 'default']])) - .optional(), - mapWithDefaultAndNullable: y - .map(y.string()) - .default(new Map([['key', 'default']])) - .nullable(), - mapWithDefaultAndNullableAndOptional: y - .map(y.string()) - .default(new Map([['key', 'default']])) - .nullable() - .optional(), - mapWithObject: y.map( - y.object({ - name: y.string(), - age: y.number() - }) - ) - } -}); - -describe('MapProxy', () => { - describe('Initial values', () => { - it('should be a Map', () => { - expect(state.map instanceof Map).toBe(true); - expect(state.map.size).toBe(0); - }); - - it('should have null as default value for nullable map', () => { - expect(state.nullableMap).toBe(null); - }); - - it('should have undefined as default value for optional map', () => { - expect(state.optionalMap).toBe(undefined); - }); - - it('should have undefined as default value for nullable optional map', () => { - expect(state.nullableOptionalMap).toBe(undefined); - }); - - it('should have default value for map with default', () => { - expect(state.mapWithDefault instanceof Map).toBe(true); - expect(state.mapWithDefault.get('key')).toBe('default'); - }); - - it('should have default value for optional map with default', () => { - expect(state.mapWithDefaultAndOptional instanceof Map).toBe(true); - expect(state.mapWithDefaultAndOptional.get('key')).toBe('default'); - }); - - it('should have default value for nullable map with default', () => { - expect(state.mapWithDefaultAndNullable instanceof Map).toBe(true); - expect(state.mapWithDefaultAndNullable.get('key')).toBe('default'); - }); - - it('should have default value for nullable optional map with default', () => { - expect(state.mapWithDefaultAndNullableAndOptional instanceof Map).toBe(true); - expect(state.mapWithDefaultAndNullableAndOptional.get('key')).toBe('default'); - }); - }); - - describe('Setters', () => { - describe('Map', () => { - beforeEach(() => { - state.map = new Map([['test', 'value']]) as StringMap; - }); - - it('should set the value', () => { - state.map = new Map([ - ['hello', 'world'], - ['foo', 'bar'] - ]) as StringMap; - expect(Array.from(state.map.entries())).toEqual([ - ['hello', 'world'], - ['foo', 'bar'] - ]); - }); - - it('should not set invalid values', () => { - const initialValue = Array.from(state.map.entries()); - - // Test invalid primitive values - const invalidValues = [null, undefined, 'invalid', 123, true, {}]; - for (const value of invalidValues) { - try { - (state.map as any) = value; - } catch {} - expect(Array.from(state.map.entries())).toEqual(initialValue); - } - - // Test invalid map values - const invalidMapValue = new Map(); - invalidMapValue.set('key1', 123); - invalidMapValue.set('key2', true); - - try { - (state.map as any) = invalidMapValue; - } catch {} - expect(Array.from(state.map.entries())).toEqual(initialValue); - }); - - it('should support map methods', () => { - state.map.set('key', 'value'); - expect(state.map.get('key')).toBe('value'); - - state.map.delete('key'); - expect(state.map.has('key')).toBe(false); - - state.map.clear(); - expect(state.map.size).toBe(0); - }); - }); - - describe('Nullable Map', () => { - beforeEach(() => { - state.nullableMap = new Map([['test', 'value']]) as StringMap; - }); - - it('should set the value', () => { - state.nullableMap = new Map([ - ['hello', 'world'], - ['foo', 'bar'] - ]) as StringMap; - expect(Array.from(state.nullableMap!.entries())).toEqual([ - ['hello', 'world'], - ['foo', 'bar'] - ]); - }); - - it('should set the value to null', () => { - (state.nullableMap as any) = null; - expect(state.nullableMap).toBe(null); - }); - - it('should not set invalid values', () => { - const initialValue = Array.from(state.nullableMap!.entries()); - - // Test invalid primitive values - const invalidValues = [undefined, 'invalid', 123, true, {}]; - for (const value of invalidValues) { - try { - (state.nullableMap as any) = value; - } catch {} - expect(Array.from(state.nullableMap!.entries())).toEqual(initialValue); - } - - // Test invalid map values - const invalidMapValue = new Map(); - invalidMapValue.set('key1', 123); - invalidMapValue.set('key2', true); - - try { - (state.nullableMap as any) = invalidMapValue; - } catch {} - expect(Array.from(state.nullableMap!.entries())).toEqual(initialValue); - }); - }); - - describe('Optional Map', () => { - beforeEach(() => { - state.optionalMap = new Map([['test', 'value']]) as StringMap; - }); - - it('should set the value', () => { - state.optionalMap = new Map([ - ['hello', 'world'], - ['foo', 'bar'] - ]) as StringMap; - expect(Array.from(state.optionalMap!.entries())).toEqual([ - ['hello', 'world'], - ['foo', 'bar'] - ]); - }); - - it('should set the value to undefined', () => { - (state.optionalMap as any) = undefined; - expect(state.optionalMap).toBe(undefined); - }); - - it('should not set invalid values', () => { - const initialValue = Array.from(state.optionalMap!.entries()); - - // Test invalid primitive values - const invalidValues = [null, 'invalid', 123, true, {}]; - for (const value of invalidValues) { - try { - (state.optionalMap as any) = value; - } catch {} - expect(Array.from(state.optionalMap!.entries())).toEqual(initialValue); - } - - // Test invalid map values - const invalidMapValue = new Map(); - invalidMapValue.set('key1', 123); - invalidMapValue.set('key2', true); - - try { - (state.optionalMap as any) = invalidMapValue; - } catch {} - expect(Array.from(state.optionalMap!.entries())).toEqual(initialValue); - }); - }); - - describe('Nullable Optional Map', () => { - beforeEach(() => { - state.nullableOptionalMap = new Map([['test', 'value']]) as StringMap; - }); - - it('should set the value', () => { - state.nullableOptionalMap = new Map([ - ['hello', 'world'], - ['foo', 'bar'] - ]) as StringMap; - expect(Array.from(state.nullableOptionalMap!.entries())).toEqual([ - ['hello', 'world'], - ['foo', 'bar'] - ]); - }); - - it('should set the value to null', () => { - (state.nullableOptionalMap as any) = null; - expect(state.nullableOptionalMap).toBe(null); - }); - - it('should set the value to undefined', () => { - (state.nullableOptionalMap as any) = undefined; - expect(state.nullableOptionalMap).toBe(undefined); - }); - - it('should not set invalid values', () => { - const initialValue = Array.from(state.nullableOptionalMap!.entries()); - - // Test invalid primitive values - const invalidValues = ['invalid', 123, true, {}]; - for (const value of invalidValues) { - try { - (state.nullableOptionalMap as any) = value; - } catch {} - expect(Array.from(state.nullableOptionalMap!.entries())).toEqual(initialValue); - } - - // Test invalid map values - const invalidMapValue = new Map(); - invalidMapValue.set('key1', 123); - invalidMapValue.set('key2', true); - - try { - (state.nullableOptionalMap as any) = invalidMapValue; - } catch {} - expect(Array.from(state.nullableOptionalMap!.entries())).toEqual(initialValue); - }); - }); - - describe('Map With Object Values', () => { - beforeEach(() => { - state.mapWithObject = new Map([['test', { name: 'John', age: 30 }]]) as ObjectMap; - }); - - it('should set valid object values', () => { - state.mapWithObject = new Map([ - ['user1', { name: 'John', age: 30 }], - ['user2', { name: 'Jane', age: 25 }] - ]) as ObjectMap; - expect(Array.from(state.mapWithObject.entries())).toEqual([ - ['user1', { name: 'John', age: 30 }], - ['user2', { name: 'Jane', age: 25 }] - ]); - }); - - it('should not set invalid object values', () => { - const initialValue = Array.from(state.mapWithObject.entries()); - - // Test invalid map values - const invalidMapValue = new Map(); - invalidMapValue.set('user1', { name: 123, age: 'invalid' }); - invalidMapValue.set('user2', { name: 'Jane', age: true }); - - try { - (state.mapWithObject as any) = invalidMapValue; - } catch {} - expect(Array.from(state.mapWithObject.entries())).toEqual(initialValue); - }); - - it('should support partial updates through map methods', () => { - state.mapWithObject.set('user1', { name: 'John Doe', age: 31 }); - expect(state.mapWithObject.get('user1')).toEqual({ name: 'John Doe', age: 31 }); - - state.mapWithObject.delete('user1'); - expect(state.mapWithObject.has('user1')).toBe(false); - }); - }); - }); -}); diff --git a/package/src/tests/proxys/number.proxy.test.ts b/package/src/tests/proxys/number.proxy.test.ts deleted file mode 100644 index 328f74b..0000000 --- a/package/src/tests/proxys/number.proxy.test.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { syncroState, y } from '../../lib/index.js'; - -const state = syncroState({ - schema: { - number: y.number(), - nullableNumber: y.number().nullable(), - optionnalNumber: y.number().optional(), - nullableOptionnalNumber: y.number().nullable().optional(), - numberWithDefault: y.number().default(0), - numberWithDefaultAndOptional: y.number().default(0).optional(), - numberWithDefaultAndNullable: y.number().default(0).nullable(), - numberWithDefaultAndNullableAndOptional: y.number().default(0).nullable().optional() - } -}); - -describe('NumberProxy', () => { - describe('Initial values', () => { - it('should be a number', () => { - expect(state.number).toBeTypeOf('number'); - }); - - it('should have null as default value for nullable number', () => { - expect(state.nullableNumber).toBe(null); - }); - - it('should have undefined as default value for optional number', () => { - expect(state.optionnalNumber).toBe(undefined); - }); - - it('should have undefined as default value for nullable optional number', () => { - expect(state.nullableOptionnalNumber).toBe(undefined); - }); - - it('should have default value for number with default', () => { - expect(state.numberWithDefault).toBe(0); - }); - - it('should have default value for optional number with default', () => { - expect(state.numberWithDefaultAndOptional).toBe(0); - }); - - it('should have default value for nullable number with default', () => { - expect(state.numberWithDefaultAndNullable).toBe(0); - }); - - it('should have default value for nullable optional number with default', () => { - expect(state.numberWithDefaultAndNullableAndOptional).toBe(0); - }); - }); - - describe('Setters', () => { - describe('Number', () => { - beforeEach(() => { - state.number = 42; - }); - - it('should set the value', () => { - state.number = 123; - expect(state.number).toBe(123); - }); - - it('should not set the value to null', () => { - state.number = null; - expect(state.number).toBe(42); - }); - - it('should not set the value to undefined', () => { - state.number = undefined; - expect(state.number).toBe(42); - }); - - it('should not set the value to a string', () => { - state.number = '123'; - expect(state.number).toBe(42); - }); - - it('should not set the value to a boolean', () => { - state.number = true; - expect(state.number).toBe(42); - }); - - it('should not set the value to an object', () => { - state.number = {}; - expect(state.number).toBe(42); - }); - }); - - describe('Nullable Number', () => { - beforeEach(() => { - state.nullableNumber = 42; - }); - - it('should set the value', () => { - state.nullableNumber = 123; - expect(state.nullableNumber).toBe(123); - }); - - it('should set the value to null', () => { - state.nullableNumber = null; - expect(state.nullableNumber).toBe(null); - }); - - it('should not set the value to undefined', () => { - state.nullableNumber = undefined; - expect(state.nullableNumber).toBe(42); - }); - - it('should not set the value to a string', () => { - state.nullableNumber = '123'; - expect(state.nullableNumber).toBe(42); - }); - - it('should not set the value to a boolean', () => { - state.nullableNumber = true; - expect(state.nullableNumber).toBe(42); - }); - - it('should not set the value to an object', () => { - state.nullableNumber = {}; - expect(state.nullableNumber).toBe(42); - }); - }); - - describe('Optional Number', () => { - beforeEach(() => { - state.optionnalNumber = 42; - }); - - it('should set the value', () => { - state.optionnalNumber = 123; - expect(state.optionnalNumber).toBe(123); - }); - - it('should not set the value to null', () => { - state.optionnalNumber = null; - expect(state.optionnalNumber).toBe(42); - }); - - it('should set the value to undefined', () => { - state.optionnalNumber = undefined; - expect(state.optionnalNumber).toBe(undefined); - }); - - it('should not set the value to a string', () => { - state.optionnalNumber = '123'; - expect(state.optionnalNumber).toBe(42); - }); - - it('should not set the value to a boolean', () => { - state.optionnalNumber = true; - expect(state.optionnalNumber).toBe(42); - }); - - it('should not set the value to an object', () => { - state.optionnalNumber = {}; - expect(state.optionnalNumber).toBe(42); - }); - }); - - describe('Nullable Optional Number', () => { - beforeEach(() => { - state.nullableOptionnalNumber = 42; - }); - - it('should set the value', () => { - state.nullableOptionnalNumber = 123; - expect(state.nullableOptionnalNumber).toBe(123); - }); - - it('should set the value to null', () => { - state.nullableOptionnalNumber = null; - expect(state.nullableOptionnalNumber).toBe(null); - }); - - it('should set the value to undefined', () => { - state.nullableOptionnalNumber = undefined; - expect(state.nullableOptionnalNumber).toBe(undefined); - }); - - it('should not set the value to a string', () => { - state.nullableOptionnalNumber = '123'; - expect(state.nullableOptionnalNumber).toBe(42); - }); - - it('should not set the value to a boolean', () => { - state.nullableOptionnalNumber = true; - expect(state.nullableOptionnalNumber).toBe(42); - }); - - it('should not set the value to an object', () => { - state.nullableOptionnalNumber = {}; - expect(state.nullableOptionnalNumber).toBe(42); - }); - }); - - describe('Number With Default', () => { - beforeEach(() => { - state.numberWithDefault = 42; - }); - - it('should set the value', () => { - state.numberWithDefault = 123; - expect(state.numberWithDefault).toBe(123); - }); - - it('should not set the value to null', () => { - state.numberWithDefault = null; - expect(state.numberWithDefault).toBe(42); - }); - - it('should not set the value to undefined', () => { - state.numberWithDefault = undefined; - expect(state.numberWithDefault).toBe(42); - }); - - it('should not set the value to a string', () => { - state.numberWithDefault = '123'; - expect(state.numberWithDefault).toBe(42); - }); - - it('should not set the value to a boolean', () => { - state.numberWithDefault = true; - expect(state.numberWithDefault).toBe(42); - }); - - it('should not set the value to an object', () => { - state.numberWithDefault = {}; - expect(state.numberWithDefault).toBe(42); - }); - }); - - describe('Number With Default And Optional', () => { - beforeEach(() => { - state.numberWithDefaultAndOptional = 42; - }); - - it('should set the value', () => { - state.numberWithDefaultAndOptional = 123; - expect(state.numberWithDefaultAndOptional).toBe(123); - }); - - it('should not set the value to null', () => { - state.numberWithDefaultAndOptional = null; - expect(state.numberWithDefaultAndOptional).toBe(42); - }); - - it('should set the value to undefined', () => { - state.numberWithDefaultAndOptional = undefined; - expect(state.numberWithDefaultAndOptional).toBe(undefined); - }); - - it('should not set the value to a string', () => { - state.numberWithDefaultAndOptional = '123'; - expect(state.numberWithDefaultAndOptional).toBe(42); - }); - - it('should not set the value to a boolean', () => { - state.numberWithDefaultAndOptional = true; - expect(state.numberWithDefaultAndOptional).toBe(42); - }); - - it('should not set the value to an object', () => { - state.numberWithDefaultAndOptional = {}; - expect(state.numberWithDefaultAndOptional).toBe(42); - }); - }); - - describe('Number With Default And Nullable', () => { - beforeEach(() => { - state.numberWithDefaultAndNullable = 42; - }); - - it('should set the value', () => { - state.numberWithDefaultAndNullable = 123; - expect(state.numberWithDefaultAndNullable).toBe(123); - }); - - it('should set the value to null', () => { - state.numberWithDefaultAndNullable = null; - expect(state.numberWithDefaultAndNullable).toBe(null); - }); - - it('should not set the value to undefined', () => { - state.numberWithDefaultAndNullable = undefined; - expect(state.numberWithDefaultAndNullable).toBe(42); - }); - - it('should not set the value to a string', () => { - state.numberWithDefaultAndNullable = '123'; - expect(state.numberWithDefaultAndNullable).toBe(42); - }); - - it('should not set the value to a boolean', () => { - state.numberWithDefaultAndNullable = true; - expect(state.numberWithDefaultAndNullable).toBe(42); - }); - - it('should not set the value to an object', () => { - state.numberWithDefaultAndNullable = {}; - expect(state.numberWithDefaultAndNullable).toBe(42); - }); - }); - - describe('Number With Default And Nullable And Optional', () => { - beforeEach(() => { - state.numberWithDefaultAndNullableAndOptional = 42; - }); - - it('should set the value', () => { - state.numberWithDefaultAndNullableAndOptional = 123; - expect(state.numberWithDefaultAndNullableAndOptional).toBe(123); - }); - - it('should set the value to null', () => { - state.numberWithDefaultAndNullableAndOptional = null; - expect(state.numberWithDefaultAndNullableAndOptional).toBe(null); - }); - - it('should set the value to undefined', () => { - state.numberWithDefaultAndNullableAndOptional = undefined; - expect(state.numberWithDefaultAndNullableAndOptional).toBe(undefined); - }); - - it('should not set the value to a string', () => { - state.numberWithDefaultAndNullableAndOptional = '123'; - expect(state.numberWithDefaultAndNullableAndOptional).toBe(42); - }); - - it('should not set the value to a boolean', () => { - state.numberWithDefaultAndNullableAndOptional = true; - expect(state.numberWithDefaultAndNullableAndOptional).toBe(42); - }); - - it('should not set the value to an object', () => { - state.numberWithDefaultAndNullableAndOptional = {}; - expect(state.numberWithDefaultAndNullableAndOptional).toBe(42); - }); - }); - }); -}); diff --git a/package/src/tests/proxys/object.proxy.test.ts b/package/src/tests/proxys/object.proxy.test.ts deleted file mode 100644 index 313ea26..0000000 --- a/package/src/tests/proxys/object.proxy.test.ts +++ /dev/null @@ -1,392 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { syncroState, y } from '../../lib/index.js'; - -const state = syncroState({ - schema: { - object: y.object({ - name: y.string(), - age: y.number() - }), - nullableObject: y - .object({ - name: y.string(), - age: y.number() - }) - .nullable(), - optionalObject: y - .object({ - name: y.string(), - age: y.number() - }) - .optional(), - nullableOptionalObject: y - .object({ - name: y.string(), - age: y.number() - }) - .nullable() - .optional(), - objectWithDefault: y - .object({ - name: y.string(), - age: y.number() - }) - .default({ name: 'default', age: 0 }), - objectWithDefaultAndOptional: y - .object({ - name: y.string(), - age: y.number() - }) - .default({ name: 'default', age: 0 }) - .optional(), - objectWithDefaultAndNullable: y - .object({ - name: y.string(), - age: y.number() - }) - .default({ name: 'default', age: 0 }) - .nullable(), - objectWithDefaultAndNullableAndOptional: y - .object({ - name: y.string(), - age: y.number() - }) - .default({ name: 'default', age: 0 }) - .nullable() - .optional() - } -}); - -describe('ObjectProxy', () => { - describe('Initial values', () => { - it('should be an object', () => { - expect(state.object).toBeTypeOf('object'); - expect(state.object.name).toBeTypeOf('string'); - expect(state.object.age).toBeTypeOf('number'); - }); - - it('should have null as default value for nullable object', () => { - expect(state.nullableObject).toBe(null); - }); - - it('should have undefined as default value for optional object', () => { - expect(state.optionalObject).toBe(undefined); - }); - - it('should have undefined as default value for nullable optional object', () => { - expect(state.nullableOptionalObject).toBe(undefined); - }); - - it('should have default value for object with default', () => { - expect(state.objectWithDefault).toEqual({ name: 'default', age: 0 }); - }); - - it('should have default value for optional object with default', () => { - expect(state.objectWithDefaultAndOptional).toEqual({ name: 'default', age: 0 }); - }); - - it('should have default value for nullable object with default', () => { - expect(state.objectWithDefaultAndNullable).toEqual({ name: 'default', age: 0 }); - }); - - it('should have default value for nullable optional object with default', () => { - expect(state.objectWithDefaultAndNullableAndOptional).toEqual({ name: 'default', age: 0 }); - }); - }); - - describe('Setters', () => { - describe('Object', () => { - beforeEach(() => { - state.object = { name: 'test', age: 25 }; - }); - - it('should set the value', () => { - state.object = { name: 'hello world', age: 30 }; - expect(state.object).toEqual({ name: 'hello world', age: 30 }); - }); - - it('should not set the value to null', () => { - (state.object as any) = null; - expect(state.object).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set the value to undefined', () => { - (state.object as any) = undefined; - expect(state.object).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set the value to a string', () => { - (state.object as any) = 'invalid'; - expect(state.object).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set the value to a number', () => { - (state.object as any) = 123; - expect(state.object).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set invalid object properties', () => { - (state.object as any) = { name: 123, age: 'invalid' }; - expect(state.object).toEqual({ name: 'test', age: 25 }); - }); - }); - - describe('Nullable Object', () => { - beforeEach(() => { - state.nullableObject = { name: 'test', age: 25 }; - }); - - it('should set the value', () => { - state.nullableObject = { name: 'hello world', age: 30 }; - expect(state.nullableObject).toEqual({ name: 'hello world', age: 30 }); - }); - - it('should set the value to null', () => { - state.nullableObject = null; - expect(state.nullableObject).toBe(null); - }); - - it('should not set the value to undefined', () => { - (state.nullableObject as any) = undefined; - expect(JSON.parse(JSON.stringify(state.nullableObject))).toStrictEqual({ - name: 'test', - age: 25 - }); - }); - - it('should not set the value to a string', () => { - (state.nullableObject as any) = 'invalid'; - expect(state.nullableObject).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set the value to a number', () => { - (state.nullableObject as any) = 123; - expect(state.nullableObject).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set invalid object properties', () => { - (state.nullableObject as any) = { name: 123, age: 'invalid' }; - expect(state.nullableObject).toEqual({ name: 'test', age: 25 }); - }); - }); - - describe('Optional Object', () => { - beforeEach(() => { - state.optionalObject = { name: 'test', age: 25 }; - }); - - it('should set the value', () => { - state.optionalObject = { name: 'hello world', age: 30 }; - expect(state.optionalObject).toEqual({ name: 'hello world', age: 30 }); - }); - - it('should not set the value to null', () => { - (state.optionalObject as any) = null; - expect(state.optionalObject).toEqual({ name: 'test', age: 25 }); - }); - - it('should set the value to undefined', () => { - state.optionalObject = undefined; - expect(state.optionalObject).toBe(undefined); - }); - - it('should not set the value to a string', () => { - (state.optionalObject as any) = 'invalid'; - expect(state.optionalObject).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set the value to a number', () => { - (state.optionalObject as any) = 123; - expect(state.optionalObject).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set invalid object properties', () => { - (state.optionalObject as any) = { name: 123, age: 'invalid' }; - expect(state.optionalObject).toEqual({ name: 'test', age: 25 }); - }); - }); - - describe('Nullable Optional Object', () => { - beforeEach(() => { - state.nullableOptionalObject = { name: 'test', age: 25 }; - }); - - it('should set the value', () => { - state.nullableOptionalObject = { name: 'hello world', age: 30 }; - expect(state.nullableOptionalObject).toEqual({ name: 'hello world', age: 30 }); - }); - - it('should set the value to null', () => { - state.nullableOptionalObject = null; - expect(state.nullableOptionalObject).toBe(null); - }); - - it('should set the value to undefined', () => { - state.nullableOptionalObject = undefined; - expect(state.nullableOptionalObject).toBe(undefined); - }); - - it('should not set the value to a string', () => { - (state.nullableOptionalObject as any) = 'invalid'; - expect(state.nullableOptionalObject).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set the value to a number', () => { - (state.nullableOptionalObject as any) = 123; - expect(state.nullableOptionalObject).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set invalid object properties', () => { - (state.nullableOptionalObject as any) = { name: 123, age: 'invalid' }; - expect(state.nullableOptionalObject).toEqual({ name: 'test', age: 25 }); - }); - }); - - describe('Object With Default', () => { - beforeEach(() => { - state.objectWithDefault = { name: 'test', age: 25 }; - }); - - it('should set the value', () => { - state.objectWithDefault = { name: 'hello world', age: 30 }; - expect(state.objectWithDefault).toEqual({ name: 'hello world', age: 30 }); - }); - - it('should not set the value to null', () => { - (state.objectWithDefault as any) = null; - expect(state.objectWithDefault).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set the value to undefined', () => { - (state.objectWithDefault as any) = undefined; - expect(state.objectWithDefault).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set the value to a string', () => { - (state.objectWithDefault as any) = 'invalid'; - expect(state.objectWithDefault).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set the value to a number', () => { - (state.objectWithDefault as any) = 123; - expect(state.objectWithDefault).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set invalid object properties', () => { - (state.objectWithDefault as any) = { name: 123, age: 'invalid' }; - expect(state.objectWithDefault).toEqual({ name: 'test', age: 25 }); - }); - }); - - describe('Object With Default And Optional', () => { - beforeEach(() => { - state.objectWithDefaultAndOptional = { name: 'test', age: 25 }; - }); - - it('should set the value', () => { - state.objectWithDefaultAndOptional = { name: 'hello world', age: 30 }; - expect(state.objectWithDefaultAndOptional).toEqual({ name: 'hello world', age: 30 }); - }); - - it('should not set the value to null', () => { - (state.objectWithDefaultAndOptional as any) = null; - expect(state.objectWithDefaultAndOptional).toEqual({ name: 'test', age: 25 }); - }); - - it('should set the value to undefined', () => { - state.objectWithDefaultAndOptional = undefined; - expect(state.objectWithDefaultAndOptional).toBe(undefined); - }); - - it('should not set the value to a string', () => { - (state.objectWithDefaultAndOptional as any) = 'invalid'; - expect(state.objectWithDefaultAndOptional).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set the value to a number', () => { - (state.objectWithDefaultAndOptional as any) = 123; - expect(state.objectWithDefaultAndOptional).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set invalid object properties', () => { - (state.objectWithDefaultAndOptional as any) = { name: 123, age: 'invalid' }; - expect(state.objectWithDefaultAndOptional).toEqual({ name: 'test', age: 25 }); - }); - }); - - describe('Object With Default And Nullable', () => { - beforeEach(() => { - state.objectWithDefaultAndNullable = { name: 'test', age: 25 }; - }); - - it('should set the value', () => { - state.objectWithDefaultAndNullable = { name: 'hello world', age: 30 }; - expect(state.objectWithDefaultAndNullable).toEqual({ name: 'hello world', age: 30 }); - }); - - it('should set the value to null', () => { - state.objectWithDefaultAndNullable = null; - expect(state.objectWithDefaultAndNullable).toBe(null); - }); - - it('should not set the value to undefined', () => { - (state.objectWithDefaultAndNullable as any) = undefined; - expect(state.objectWithDefaultAndNullable).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set the value to a string', () => { - (state.objectWithDefaultAndNullable as any) = 'invalid'; - expect(state.objectWithDefaultAndNullable).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set the value to a number', () => { - (state.objectWithDefaultAndNullable as any) = 123; - expect(state.objectWithDefaultAndNullable).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set invalid object properties', () => { - (state.objectWithDefaultAndNullable as any) = { name: 123, age: 'invalid' }; - expect(state.objectWithDefaultAndNullable).toEqual({ name: 'test', age: 25 }); - }); - }); - - describe('Object With Default And Nullable And Optional', () => { - beforeEach(() => { - state.objectWithDefaultAndNullableAndOptional = { name: 'test', age: 25 }; - }); - - it('should set the value', () => { - state.objectWithDefaultAndNullableAndOptional = { name: 'hello world', age: 30 }; - expect(state.objectWithDefaultAndNullableAndOptional).toEqual({ - name: 'hello world', - age: 30 - }); - }); - - it('should set the value to null', () => { - state.objectWithDefaultAndNullableAndOptional = null; - expect(state.objectWithDefaultAndNullableAndOptional).toBe(null); - }); - - it('should set the value to undefined', () => { - state.objectWithDefaultAndNullableAndOptional = undefined; - expect(state.objectWithDefaultAndNullableAndOptional).toBe(undefined); - }); - - it('should not set the value to a string', () => { - (state.objectWithDefaultAndNullableAndOptional as any) = 'invalid'; - expect(state.objectWithDefaultAndNullableAndOptional).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set the value to a number', () => { - (state.objectWithDefaultAndNullableAndOptional as any) = 123; - expect(state.objectWithDefaultAndNullableAndOptional).toEqual({ name: 'test', age: 25 }); - }); - - it('should not set invalid object properties', () => { - (state.objectWithDefaultAndNullableAndOptional as any) = { name: 123, age: 'invalid' }; - expect(state.objectWithDefaultAndNullableAndOptional).toEqual({ name: 'test', age: 25 }); - }); - }); - }); -}); diff --git a/package/src/tests/proxys/set.proxy.test.ts b/package/src/tests/proxys/set.proxy.test.ts deleted file mode 100644 index cf6b410..0000000 --- a/package/src/tests/proxys/set.proxy.test.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { describe, it, expect, beforeEach, beforeAll } from 'vitest'; -import { syncroState, y } from '../../lib/index.js'; -import { SvelteSet } from 'svelte/reactivity'; - -let createDocument = () => - syncroState({ - schema: { - set: y.set(y.string()), - nullableSet: y.set(y.string()).nullable(), - optionalSet: y.set(y.string()).optional(), - nullableOptionalSet: y.set(y.string()).nullable().optional(), - setWithDefault: y.set(y.string()).default(new SvelteSet(['default'])), - setWithDefaultAndOptional: y - .set(y.string()) - .default(new Set(['default'])) - .optional(), - setWithDefaultAndNullable: y - .set(y.string()) - .default(new Set(['default'])) - .nullable(), - setWithDefaultAndNullableAndOptional: y - .set(y.string()) - .default(new Set(['default'])) - .nullable() - .optional() - } - }); - -let state = createDocument(); - -describe('SetProxy', () => { - describe('Initial values', () => { - it('should be a Set', () => { - expect(state.set instanceof Set).toBe(true); - expect(state.set.size).toBe(0); - }); - - it('should have null as default value for nullable set', () => { - expect(state.nullableSet).toBe(null); - }); - - it('should have undefined as default value for optional set', () => { - expect(state.optionalSet).toBe(undefined); - }); - - it('should have undefined as default value for nullable optional set', () => { - expect(state.nullableOptionalSet).toBe(undefined); - }); - - it('should have default value for set with default', () => { - console.log('state.setWithDefault', Array.from(state.setWithDefault)); - expect(Array.from(state.setWithDefault)).toEqual(['default']); - }); - - it('should have default value for optional set with default', () => { - expect(Array.from(state.setWithDefaultAndOptional)).toEqual(['default']); - }); - - it('should have default value for nullable set with default', () => { - expect(Array.from(state.setWithDefaultAndNullable)).toEqual(['default']); - }); - - it('should have default value for nullable optional set with default', () => { - expect(Array.from(state.setWithDefaultAndNullableAndOptional)).toEqual(['default']); - }); - }); - - // const expectEqual = (a: Set, b: any) => { - // expect([...Array.from(a)]).toStrictEqual(b); - // }; - describe('Setters', () => { - // describe('Set', () => { - // beforeEach(() => { - // state.set = new Set(['test']); - // }); - - // it('should set the value', () => { - // state.set = new Set(['hello', 'world']); - // expect(Array.from(state.set)).toEqual(['hello', 'world']); - // }); - - // it('should not set the value to null', () => { - // (state.set as any) = null; - // expect(Array.from(state.set)).toEqual(['test']); - // }); - - // it('should not set the value to undefined', () => { - // (state.set as any) = undefined; - // expect(Array.from(state.set)).toEqual(['test']); - // }); - - // it('should not set the value to a string', () => { - // (state.set as any) = 'invalid'; - // expect(Array.from(state.set)).toEqual(['test']); - // }); - - // it('should not set the value to a number', () => { - // (state.set as any) = 123; - // expect(Array.from(state.set)).toEqual(['test']); - // }); - - // it('should not set invalid set items', () => { - // (state.set as any) = new Set([123, true, {}]); - // expect(Array.from(state.set)).toEqual(['test']); - // }); - - // it('should support set methods', () => { - // state.set.add('world'); - // console.log(state.set); - // expect(Array.from(state.set)).toEqual(['test', 'world']); - - // state.set.delete('world'); - // expect(Array.from(state.set)).toEqual(['test']); - - // state.set.add('hello'); - // expect(Array.from(state.set)).toEqual(['test', 'hello']); - - // expect(state.set.has('test')).toBe(true); - // expect(state.set.has('world')).toBe(false); - // state.set.clear(); - // expect(Array.from(state.set)).toEqual([]); - // }); - // }); - - // describe('Nullable Set', () => { - // beforeEach(() => { - // state.nullableSet = new Set(['test']); - // }); - - // it('should set the value', () => { - // state.nullableSet = new Set(['hello', 'world']); - // expect(Array.from(state.nullableSet)).toEqual(['hello', 'world']); - // }); - - // it('should set the value to null', () => { - // state.nullableSet = null; - // expect(state.nullableSet).toBe(null); - // }); - - // it('should not set the value to undefined', () => { - // (state.nullableSet as any) = undefined; - // expect(Array.from(state.nullableSet)).toEqual(['test']); - // }); - - // it('should not set the value to a string', () => { - // (state.nullableSet as any) = 'invalid'; - // expect(Array.from(state.nullableSet)).toEqual(['test']); - // }); - - // it('should not set the value to a number', () => { - // (state.nullableSet as any) = 123; - // expect(Array.from(state.nullableSet)).toEqual(['test']); - // }); - - // it('should not set invalid set items', () => { - // (state.nullableSet as any) = new Set([123, true, {}]); - // expect(Array.from(state.nullableSet)).toEqual(['test']); - // }); - // }); - - describe('Optional Set', () => { - beforeAll(() => { - state.optionalSet = new Set(['test']); - }); - - beforeEach(() => { - state.optionalSet = new Set(['test']); - }); - - it('should set the value', () => { - state.optionalSet = new Set(['hello', 'world']); - expect(Array.from(state.optionalSet)).toEqual(['hello', 'world']); - }); - - // it('should set the value to undefined', () => { - // state.optionalSet = undefined; - // expect(state.optionalSet).toBe(undefined); - // }); - - it('should not set the value to null', () => { - (state.optionalSet as any) = null; - console.log('state.optionalSet', state.optionalSet); - expect(Array.from(state.optionalSet)).toEqual(['test']); - }); - - it('should not set the value to a string', () => { - (state.optionalSet as any) = 'invalid'; - expect(Array.from(state.optionalSet)).toEqual(['test']); - }); - - it('should not set the value to a number', () => { - (state.optionalSet as any) = 123; - expect(Array.from(state.optionalSet)).toEqual(['test']); - }); - - it('should not set invalid set items', () => { - (state.optionalSet as any) = new Set([123, true, {}]); - expect(Array.from(state.optionalSet)).toEqual(['test']); - }); - }); - - describe('Nullable Optional Set', () => { - beforeAll(() => { - state.nullableOptionalSet = new Set(['test']); - }); - - beforeEach(() => { - state.nullableOptionalSet = new Set(['test']); - }); - - it('should set the value', () => { - state.nullableOptionalSet = new Set(['hello', 'world']); - expect(Array.from(state.nullableOptionalSet)).toEqual(['hello', 'world']); - }); - - it('should set the value to null', () => { - state.nullableOptionalSet = null; - expect(state.nullableOptionalSet).toBe(null); - }); - - // it('should set the value to undefined', () => { - // state.nullableOptionalSet = undefined; - // expect(state.nullableOptionalSet).toBe(undefined); - // }); - - it('should not set the value to a string', () => { - (state.nullableOptionalSet as any) = 'invalid'; - expect(Array.from(state.nullableOptionalSet)).toEqual(['test']); - }); - - it('should not set the value to a number', () => { - (state.nullableOptionalSet as any) = 123; - expect(Array.from(state.nullableOptionalSet)).toEqual(['test']); - }); - - it('should not set invalid set items', () => { - (state.nullableOptionalSet as any) = new Set([123, true, {}]); - expect(Array.from(state.nullableOptionalSet)).toEqual(['test']); - }); - }); - }); -}); diff --git a/package/src/tests/proxys/string.proxy.test.ts b/package/src/tests/proxys/string.proxy.test.ts deleted file mode 100644 index d48e61d..0000000 --- a/package/src/tests/proxys/string.proxy.test.ts +++ /dev/null @@ -1,332 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { syncroState, y } from '../../lib/index.js'; - -const state = syncroState({ - schema: { - string: y.string(), - nullableString: y.string().nullable(), - optionnalString: y.string().optional(), - nullableOptionnalString: y.string().nullable().optional(), - stringWithDefault: y.string().default('default'), - stringWithDefaultAndOptional: y.string().default('default').optional(), - stringWithDefaultAndNullable: y.string().default('default').nullable(), - stringWithDefaultAndNullableAndOptional: y.string().default('default').nullable().optional() - } -}); - -describe('StringProxy', () => { - describe('Initial values', () => { - it('should be a string', () => { - expect(state.string).toBeTypeOf('string'); - }); - it('should have null as default value for nullable string', () => { - expect(state.nullableString).toBe(null); - }); - - it('should have undefined as default value for optional string', () => { - expect(state.optionnalString).toBe(undefined); - }); - - it('should have undefined as default value for nullable optional string', () => { - expect(state.nullableOptionnalString).toBe(undefined); - }); - - it('should have default value for string with default', () => { - expect(state.stringWithDefault).toBe('default'); - }); - - it('should have default value for optional string with default', () => { - expect(state.stringWithDefaultAndOptional).toBe('default'); - }); - - it('should have default value for nullable string with default', () => { - expect(state.stringWithDefaultAndNullable).toBe('default'); - }); - - it('should have default value for nullable optional string with default', () => { - expect(state.stringWithDefaultAndNullableAndOptional).toBe('default'); - }); - }); - - describe('Setters', () => { - describe('String', () => { - beforeEach(() => { - state.string = 'test'; - }); - it('should set the value', () => { - state.string = 'hello world'; - expect(state.string).toBe('hello world'); - }); - - it('should set the value to empty string', () => { - state.string = ''; - expect(state.string).toBe(''); - }); - - it('should not set the value to null', () => { - state.string = null; - expect(state.string).toBe('test'); - }); - - it('should not set the value to undefined', () => { - state.string = undefined; - expect(state.string).toBe('test'); - }); - - it('should not set the value to a number', () => { - state.string = 123; - expect(state.string).toBe('test'); - }); - - it('should not set the value to an object', () => { - state.string = {}; - expect(state.string).toBe('test'); - }); - }); - - describe('Nullable String', () => { - beforeEach(() => { - state.nullableString = 'test'; - }); - it('should set the value', () => { - state.nullableString = 'hello world'; - expect(state.nullableString).toBe('hello world'); - }); - - it('should set the value to empty string', () => { - state.nullableString = ''; - expect(state.nullableString).toBe(''); - }); - - it('should set the value to null', () => { - state.nullableString = null; - expect(state.nullableString).toBe(null); - }); - - it('should not set the value to undefined', () => { - state.nullableString = undefined; - expect(state.nullableString).toBe('test'); - }); - - it('should not set the value to a number', () => { - state.nullableString = 123; - expect(state.nullableString).toBe('test'); - }); - - it('should not set the value to an object', () => { - state.nullableString = {}; - expect(state.nullableString).toBe('test'); - }); - }); - - describe('Optional String', () => { - beforeEach(() => { - state.optionnalString = 'test'; - }); - it('should set the value', () => { - state.optionnalString = 'hello world'; - expect(state.optionnalString).toBe('hello world'); - }); - - it('should set the value to empty string', () => { - state.optionnalString = ''; - expect(state.optionnalString).toBe(''); - }); - - it('should not set the value to null', () => { - state.optionnalString = null; - expect(state.optionnalString).toBe('test'); - }); - - it('should set the value to undefined', () => { - state.optionnalString = undefined; - expect(state.optionnalString).toBe(undefined); - }); - - it('should not set the value to a number', () => { - state.optionnalString = 123; - expect(state.optionnalString).toBe('test'); - }); - - it('should not set the value to an object', () => { - state.optionnalString = {}; - expect(state.optionnalString).toBe('test'); - }); - }); - - describe('Nullable Optional String', () => { - beforeEach(() => { - state.nullableOptionnalString = 'test'; - }); - it('should set the value', () => { - state.nullableOptionnalString = 'hello world'; - expect(state.nullableOptionnalString).toBe('hello world'); - }); - - it('should set the value to empty string', () => { - state.nullableOptionnalString = ''; - expect(state.nullableOptionnalString).toBe(''); - }); - - it('should set the value to null', () => { - state.nullableOptionnalString = null; - expect(state.nullableOptionnalString).toBe(null); - }); - - it('should set the value to undefined', () => { - state.nullableOptionnalString = undefined; - expect(state.nullableOptionnalString).toBe(undefined); - }); - - it('should not set the value to a number', () => { - state.nullableOptionnalString = 123; - expect(state.nullableOptionnalString).toBe('test'); - }); - - it('should not set the value to an object', () => { - state.nullableOptionnalString = {}; - expect(state.nullableOptionnalString).toBe('test'); - }); - }); - - describe('String With Default', () => { - beforeEach(() => { - state.stringWithDefault = 'test'; - }); - it('should set the value', () => { - state.stringWithDefault = 'hello world'; - expect(state.stringWithDefault).toBe('hello world'); - }); - - it('should set the value to empty string', () => { - state.stringWithDefault = ''; - expect(state.stringWithDefault).toBe(''); - }); - - it('should not set the value to null', () => { - state.stringWithDefault = null; - expect(state.stringWithDefault).toBe('test'); - }); - - it('should not set the value to undefined', () => { - state.stringWithDefault = undefined; - expect(state.stringWithDefault).toBe('test'); - }); - - it('should not set the value to a number', () => { - state.stringWithDefault = 123; - expect(state.stringWithDefault).toBe('test'); - }); - - it('should not set the value to an object', () => { - state.stringWithDefault = {}; - expect(state.stringWithDefault).toBe('test'); - }); - }); - - describe('String With Default And Optional', () => { - beforeEach(() => { - state.stringWithDefaultAndOptional = 'test'; - }); - it('should set the value', () => { - state.stringWithDefaultAndOptional = 'hello world'; - expect(state.stringWithDefaultAndOptional).toBe('hello world'); - }); - - it('should set the value to empty string', () => { - state.stringWithDefaultAndOptional = ''; - expect(state.stringWithDefaultAndOptional).toBe(''); - }); - - it('should not set the value to null', () => { - state.stringWithDefaultAndOptional = null; - expect(state.stringWithDefaultAndOptional).toBe('test'); - }); - - it('should set the value to undefined', () => { - state.stringWithDefaultAndOptional = undefined; - expect(state.stringWithDefaultAndOptional).toBe(undefined); - }); - - it('should not set the value to a number', () => { - state.stringWithDefaultAndOptional = 123; - expect(state.stringWithDefaultAndOptional).toBe('test'); - }); - - it('should not set the value to an object', () => { - state.stringWithDefaultAndOptional = {}; - expect(state.stringWithDefaultAndOptional).toBe('test'); - }); - }); - - describe('String With Default And Nullable', () => { - beforeEach(() => { - state.stringWithDefaultAndNullable = 'test'; - }); - it('should set the value', () => { - state.stringWithDefaultAndNullable = 'hello world'; - expect(state.stringWithDefaultAndNullable).toBe('hello world'); - }); - - it('should set the value to empty string', () => { - state.stringWithDefaultAndNullable = ''; - expect(state.stringWithDefaultAndNullable).toBe(''); - }); - - it('should set the value to null', () => { - state.stringWithDefaultAndNullable = null; - expect(state.stringWithDefaultAndNullable).toBe(null); - }); - - it('should not set the value to undefined', () => { - state.stringWithDefaultAndNullable = undefined; - expect(state.stringWithDefaultAndNullable).toBe('test'); - }); - - it('should not set the value to a number', () => { - state.stringWithDefaultAndNullable = 123; - expect(state.stringWithDefaultAndNullable).toBe('test'); - }); - - it('should not set the value to an object', () => { - state.stringWithDefaultAndNullable = {}; - expect(state.stringWithDefaultAndNullable).toBe('test'); - }); - }); - - describe('String With Default And Nullable And Optional', () => { - beforeEach(() => { - state.stringWithDefaultAndNullableAndOptional = 'test'; - }); - it('should set the value', () => { - state.stringWithDefaultAndNullableAndOptional = 'hello world'; - expect(state.stringWithDefaultAndNullableAndOptional).toBe('hello world'); - }); - - it('should set the value to empty string', () => { - state.stringWithDefaultAndNullableAndOptional = ''; - expect(state.stringWithDefaultAndNullableAndOptional).toBe(''); - }); - - it('should set the value to null', () => { - state.stringWithDefaultAndNullableAndOptional = null; - expect(state.stringWithDefaultAndNullableAndOptional).toBe(null); - }); - - it('should set the value to undefined', () => { - state.stringWithDefaultAndNullableAndOptional = undefined; - expect(state.stringWithDefaultAndNullableAndOptional).toBe(undefined); - }); - - it('should not set the value to a number', () => { - state.stringWithDefaultAndNullableAndOptional = 123; - expect(state.stringWithDefaultAndNullableAndOptional).toBe('test'); - }); - - it('should not set the value to an object', () => { - state.stringWithDefaultAndNullableAndOptional = {}; - expect(state.stringWithDefaultAndNullableAndOptional).toBe('test'); - }); - }); - }); -}); diff --git a/package/src/tests/schemas/boolean.test.ts b/package/src/tests/schemas/boolean.test.ts deleted file mode 100644 index 10b65b4..0000000 --- a/package/src/tests/schemas/boolean.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { y } from '../../lib/schemas/schema.js'; -import { NULL } from '$lib/constants.js'; - -describe('BooleanValidator', () => { - describe('basic validation', () => { - const schema = y.boolean(); - - it('should validate boolean values', () => { - expect(schema.isValid(true)).toBe(true); - expect(schema.isValid(false)).toBe(true); - }); - - it('should reject non-boolean values', () => { - expect(schema.isValid('true')).toBe(false); - expect(schema.isValid('false')).toBe(false); - expect(schema.isValid(1)).toBe(false); - expect(schema.isValid(0)).toBe(false); - expect(schema.isValid({})).toBe(false); - expect(schema.isValid([])).toBe(false); - expect(schema.isValid(null)).toBe(false); - expect(schema.isValid(undefined)).toBe(false); - }); - }); - - describe('optional', () => { - const schema = y.boolean().optional(); - - it('should allow undefined when optional', () => { - expect(schema.isValid(undefined)).toBe(true); - expect(schema.isValid(true)).toBe(true); - expect(schema.isValid(false)).toBe(true); - }); - }); - - describe('nullable', () => { - const schema = y.boolean().nullable(); - - it('should allow null when nullable', () => { - expect(schema.isValid(null)).toBe(true); - expect(schema.isValid(true)).toBe(true); - expect(schema.isValid(false)).toBe(true); - }); - }); - - describe('parse', () => { - const schema = y.boolean(); - - it('should parse valid boolean strings', () => { - expect(schema.parse('true')).toEqual({ isValid: true, value: true }); - expect(schema.parse('false')).toEqual({ isValid: true, value: false }); - }); - - it('should handle invalid inputs', () => { - expect(schema.parse('invalid')).toEqual({ isValid: false, value: null }); - expect(schema.parse(null)).toEqual({ isValid: false, value: null }); - }); - }); - - describe('coercion', () => { - const schema = y.boolean(); - - it('should coerce string representations', () => { - expect(schema.coerce('true')).toBe(true); - expect(schema.coerce('false')).toBe(false); - }); - - it('should handle null and invalid inputs', () => { - expect(schema.coerce(null)).toBe(null); - expect(schema.coerce('invalid')).toBe(null); - expect(schema.coerce('2')).toBe(null); - }); - }); - - describe('stringify', () => { - const schema = y.boolean(); - - it('should stringify boolean values correctly', () => { - expect(schema.stringify(true)).toBe('true'); - expect(schema.stringify(false)).toBe('false'); - }); - - it('should handle null and invalid inputs', () => { - expect(schema.stringify(null)).toBe(NULL); - expect(schema.stringify('invalid')).toBe(NULL); - }); - }); -}); diff --git a/package/src/tests/schemas/date.test.ts b/package/src/tests/schemas/date.test.ts deleted file mode 100644 index e46aaf0..0000000 --- a/package/src/tests/schemas/date.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { y } from '../../lib/schemas/schema.js'; -import { NULL } from '$lib/constants.js'; - -describe('DateValidator', () => { - describe('basic validation', () => { - const schema = y.date(); - const testDate = new Date('2024-01-07T12:00:00Z'); - - it('should validate Date objects', () => { - expect(schema.isValid(testDate)).toBe(true); - expect(schema.isValid(new Date())).toBe(true); - }); - - it('should reject invalid dates', () => { - expect(schema.isValid('invalid-date')).toBe(false); - expect(schema.isValid('2024-13-45')).toBe(false); - expect(schema.isValid({})).toBe(false); - expect(schema.isValid([])).toBe(false); - expect(schema.isValid(null)).toBe(false); - expect(schema.isValid(undefined)).toBe(false); - }); - }); - - describe('min/max constraints', () => { - const minDate = new Date('2024-01-01'); - const maxDate = new Date('2024-12-31'); - const schema = y.date().min(minDate).max(maxDate); - - it('should validate dates within range', () => { - expect(schema.isValid(new Date('2024-06-15'))).toBe(true); - expect(schema.isValid(minDate)).toBe(true); - expect(schema.isValid(maxDate)).toBe(true); - }); - - it('should reject dates outside range', () => { - expect(schema.isValid(new Date('2023-12-31'))).toBe(false); - expect(schema.isValid(new Date('2025-01-01'))).toBe(false); - }); - }); - - describe('optional', () => { - const schema = y.date().optional(); - - it('should allow undefined when optional', () => { - expect(schema.isValid(undefined)).toBe(true); - expect(schema.isValid(new Date())).toBe(true); - }); - }); - - describe('nullable', () => { - const schema = y.date().nullable(); - - it('should allow null when nullable', () => { - expect(schema.isValid(null)).toBe(true); - expect(schema.isValid(new Date())).toBe(true); - }); - }); - - describe('parse', () => { - const schema = y.date(); - const testDate = new Date('2024-01-07T12:00:00Z'); - - it('should parse valid date strings', () => { - const result = schema.parse('2024-01-07T12:00:00Z'); - expect(result.isValid).toBe(true); - expect(result.value?.toISOString()).toBe(testDate.toISOString()); - }); - - it('should handle invalid inputs', () => { - expect(schema.parse('invalid-date')).toEqual({ isValid: false, value: null }); - expect(schema.parse(null)).toEqual({ isValid: false, value: null }); - }); - }); - - describe('coercion', () => { - const schema = y.date(); - const testDate = new Date('2024-01-07T12:00:00Z'); - - it('should coerce valid date strings', () => { - const result = schema.coerce('2024-01-07T12:00:00Z'); - expect(result?.toISOString()).toBe(testDate.toISOString()); - }); - - it('should handle null and invalid inputs', () => { - expect(schema.coerce(null)).toBe(null); - expect(schema.coerce('invalid-date')).toBe(null); - }); - }); - - describe('stringify', () => { - const schema = y.date(); - const testDate = new Date('2024-01-07T12:00:00Z'); - - it('should stringify dates correctly', () => { - expect(schema.stringify(testDate)).toBe(testDate.toISOString()); - }); - - it('should handle null and invalid inputs', () => { - expect(schema.stringify(null)).toBe(NULL); - expect(schema.stringify('invalid-date')).toBe(NULL); - }); - }); -}); diff --git a/package/src/tests/schemas/enum.test.ts b/package/src/tests/schemas/enum.test.ts deleted file mode 100644 index bea03ba..0000000 --- a/package/src/tests/schemas/enum.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { y } from '../../lib/schemas/schema.js'; -import { NULL } from '$lib/constants.js'; - -describe('EnumValidator', () => { - describe('basic validation', () => { - const schema = y.enum('red', 'green', 'blue'); - const numSchema = y.enum(1, 2, 3); - - it('should validate string enum values', () => { - expect(schema.isValid('red')).toBe(true); - expect(schema.isValid('green')).toBe(true); - expect(schema.isValid('blue')).toBe(true); - }); - - it('should validate number enum values', () => { - expect(numSchema.isValid(1)).toBe(true); - expect(numSchema.isValid(2)).toBe(true); - expect(numSchema.isValid(3)).toBe(true); - }); - - it('should reject invalid enum values', () => { - expect(schema.isValid('yellow')).toBe(false); - expect(schema.isValid('')).toBe(false); - expect(schema.isValid(123)).toBe(false); - expect(schema.isValid({})).toBe(false); - expect(schema.isValid([])).toBe(false); - expect(schema.isValid(null)).toBe(false); - expect(schema.isValid(undefined)).toBe(false); - - expect(numSchema.isValid(4)).toBe(false); - expect(numSchema.isValid(0)).toBe(false); - expect(numSchema.isValid('1')).toBe(false); - }); - }); - - describe('optional', () => { - const schema = y.enum('red', 'green', 'blue').optional(); - - it('should allow undefined when optional', () => { - expect(schema.isValid(undefined)).toBe(true); - expect(schema.isValid('red')).toBe(true); - }); - }); - - describe('nullable', () => { - const schema = y.enum('red', 'green', 'blue').nullable(); - - it('should allow null when nullable', () => { - expect(schema.isValid(null)).toBe(true); - expect(schema.isValid('red')).toBe(true); - }); - }); - - describe('parse', () => { - const schema = y.enum('red', 'green', 'blue'); - - it('should parse valid enum strings', () => { - expect(schema.parse('red')).toEqual({ isValid: true, value: 'red' }); - expect(schema.parse('green')).toEqual({ isValid: true, value: 'green' }); - }); - - it('should handle invalid inputs', () => { - expect(schema.parse('yellow')).toEqual({ isValid: false, value: null }); - expect(schema.parse(null)).toEqual({ isValid: false, value: null }); - }); - }); - - describe('coercion', () => { - const schema = y.enum('red', 'green', 'blue'); - - it('should coerce valid enum values', () => { - expect(schema.coerce('red')).toBe('red'); - expect(schema.coerce('green')).toBe('green'); - }); - - it('should handle null and invalid inputs', () => { - expect(schema.coerce(null)).toBe(null); - expect(schema.coerce('yellow')).toBe(null); - }); - }); - - describe('stringify', () => { - const schema = y.enum('red', 'green', 'blue'); - - it('should stringify enum values correctly', () => { - expect(schema.stringify('red')).toBe('red'); - expect(schema.stringify('green')).toBe('green'); - }); - - it('should handle null and invalid inputs', () => { - expect(schema.stringify(null)).toBe(NULL); - expect(schema.stringify('yellow')).toBe(''); - }); - }); -}); diff --git a/package/src/tests/schemas/number.test.ts b/package/src/tests/schemas/number.test.ts deleted file mode 100644 index 630a90d..0000000 --- a/package/src/tests/schemas/number.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { y } from '../../lib/schemas/schema.js'; -import { NULL } from '$lib/constants.js'; - -describe('NumberValidator', () => { - describe('basic validation', () => { - const schema = y.number(); - - it('should validate number values', () => { - expect(schema.isValid(123)).toBe(true); - expect(schema.isValid(0)).toBe(true); - expect(schema.isValid(-123)).toBe(true); - expect(schema.isValid(123.456)).toBe(true); - }); - - it('should reject non-number values', () => { - expect(schema.isValid('123')).toBe(false); - expect(schema.isValid({})).toBe(false); - expect(schema.isValid([])).toBe(false); - expect(schema.isValid(null)).toBe(false); - expect(schema.isValid(undefined)).toBe(false); - expect(schema.isValid(NaN)).toBe(false); - }); - }); - - describe('optional', () => { - const schema = y.number().optional(); - - it('should allow undefined when optional', () => { - expect(schema.isValid(undefined)).toBe(true); - expect(schema.isValid(123)).toBe(true); - }); - }); - - describe('nullable', () => { - const schema = y.number().nullable(); - - it('should allow null when nullable', () => { - expect(schema.isValid(null)).toBe(true); - expect(schema.isValid(123)).toBe(true); - }); - }); - - describe('parse', () => { - const schema = y.number(); - - it('should parse valid number strings', () => { - expect(schema.parse('123')).toEqual({ isValid: true, value: 123 }); - expect(schema.parse('-123.456')).toEqual({ isValid: true, value: -123.456 }); - }); - - it('should handle invalid inputs', () => { - expect(schema.parse('abc')).toEqual({ isValid: false, value: null }); - expect(schema.parse(null)).toEqual({ isValid: false, value: null }); - }); - }); - - describe('coercion', () => { - const schema = y.number(); - - it('should coerce valid number strings', () => { - expect(schema.coerce('123')).toBe(123); - expect(schema.coerce('-123.456')).toBe(-123.456); - }); - - it('should handle null and invalid inputs', () => { - expect(schema.coerce(null)).toBe(null); - expect(schema.coerce('abc')).toBe(null); - }); - }); - - describe('stringify', () => { - const schema = y.number(); - - it('should stringify numbers correctly', () => { - expect(schema.stringify(123)).toBe('123'); - expect(schema.stringify(-123.456)).toBe('-123.456'); - }); - - it('should handle null and invalid inputs', () => { - expect(schema.stringify(null)).toBe(NULL); - expect(schema.stringify('abc')).toBe(NULL); - }); - }); -}); diff --git a/package/src/tests/schemas/string.test.ts b/package/src/tests/schemas/string.test.ts deleted file mode 100644 index 00da403..0000000 --- a/package/src/tests/schemas/string.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { y } from '../../lib/schemas/schema.js'; -import { NULL } from '$lib/constants.js'; - -describe('StringValidator', () => { - describe('basic validation', () => { - const schema = y.string(); - - it('should validate string values', () => { - expect(schema.isValid('hello')).toBe(true); - expect(schema.isValid('')).toBe(true); - }); - - it('should reject non-string values', () => { - expect(schema.isValid(123)).toBe(false); - expect(schema.isValid({})).toBe(false); - expect(schema.isValid([])).toBe(false); - expect(schema.isValid(null)).toBe(false); - expect(schema.isValid(undefined)).toBe(false); - }); - }); - - describe('optional', () => { - const schema = y.string().optional(); - - it('should allow undefined when optional', () => { - expect(schema.isValid(undefined)).toBe(true); - // expect(schema.isValid('test')).toBe(true); - }); - }); - - describe('nullable', () => { - const schema = y.string().nullable(); - - it('should allow null when nullable', () => { - expect(schema.isValid(null)).toBe(true); - expect(schema.isValid('test')).toBe(true); - }); - }); - - describe('min length', () => { - const schema = y.string().min(3); - - it('should validate strings with minimum length', () => { - expect(schema.isValid('abc')).toBe(true); - expect(schema.isValid('abcd')).toBe(true); - expect(schema.isValid('ab')).toBe(false); - }); - }); - - describe('max length', () => { - const schema = y.string().max(3); - - it('should validate strings with maximum length', () => { - expect(schema.isValid('abc')).toBe(true); - expect(schema.isValid('ab')).toBe(true); - expect(schema.isValid('abcd')).toBe(false); - }); - }); - - describe('pattern', () => { - const schema = y.string().pattern(/^[A-Z]+$/); - - it('should validate strings matching the pattern', () => { - expect(schema.isValid('ABC')).toBe(true); - expect(schema.isValid('abc')).toBe(false); - expect(schema.isValid('123')).toBe(false); - }); - }); - - describe('coercion', () => { - const schema = y.string(); - - it('should coerce values correctly', () => { - expect(schema.coerce('test')).toBe('test'); - expect(schema.coerce(null as any)).toBe(null); - }); - }); - - describe('stringify', () => { - const schema = y.string(); - - it('should stringify values correctly', () => { - expect(schema.stringify('test')).toBe('test'); - expect(schema.stringify(null)).toBe(NULL); - }); - }); -}); diff --git a/package/src/tests/syncroState.test.ts b/package/src/tests/syncroState.test.ts deleted file mode 100644 index 35c369a..0000000 --- a/package/src/tests/syncroState.test.ts +++ /dev/null @@ -1,278 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { syncroState, y } from '../lib/index.js'; -import { Doc } from 'yjs'; - -const schema = { - text: y.string().optional().nullable().default('default'), - number: y.number().optional().nullable().default(1), - boolean: y.boolean().optional().nullable().default(false), - array: y.array(y.string()).optional().nullable().default(['default']), - date: y.date().optional().nullable().default(new Date()), - enum: y.enum('a', 'b', 'c').optional().nullable().default('a'), - object: y - .object({ - text: y.string().optional().nullable().default('default'), - number: y.number().optional().nullable().default(1), - boolean: y.boolean().optional().nullable().default(false) - }) - .optional() - .nullable() - .default({ text: 'default', number: 1, boolean: false }), - set: y.set(y.string()).optional().nullable().default(['default']), - map: y - .map(y.string()) - .optional() - .nullable() - .default(new Map([['key', 'value']])), - arrayOfObjects: y - .array(y.object({ text: y.string() })) - .optional() - .nullable() - .default([{ text: 'default' }]), - discriminatedUnion: y.discriminatedUnion('type', [ - y.object({ type: y.literal('a'), value: y.string() }).default({ type: 'a', value: 'hello' }), - y.object({ type: y.literal('b'), value: y.number() }).default({ type: 'b', value: 1 }) - ]) -}; - -const doc = new Doc(); -const state1 = syncroState({ schema, doc }); -const state2 = syncroState({ schema, doc }); - -describe('SyncroState', () => { - describe('text field', () => { - it('should sync regular text mutation', () => { - state1.text = 'world'; - expect(state1.text).toBe('world'); - expect(state2.text).toBe('world'); - }); - - it('should sync null text mutation', () => { - state1.text = null; - expect(state1.text).toBe(null); - expect(state2.text).toBe(null); - }); - - it('should sync undefined text mutation', () => { - state1.text = undefined; - expect(state1.text).toBe(undefined); - expect(state2.text).toBe(undefined); - }); - }); - - describe('number field', () => { - it('should sync regular number mutation', () => { - state1.number = 42; - expect(state1.number).toBe(42); - expect(state2.number).toBe(42); - }); - - it('should sync null number mutation', () => { - state1.number = null; - expect(state1.number).toBe(null); - expect(state2.number).toBe(null); - }); - - it('should sync undefined number mutation', () => { - state1.number = undefined; - expect(state1.number).toBe(undefined); - expect(state2.number).toBe(undefined); - }); - }); - - describe('boolean field', () => { - it('should sync regular boolean mutation', () => { - state1.boolean = true; - expect(state1.boolean).toBe(true); - expect(state2.boolean).toBe(true); - }); - - it('should sync null boolean mutation', () => { - state1.boolean = null; - expect(state1.boolean).toBe(null); - expect(state2.boolean).toBe(null); - }); - - it('should sync undefined boolean mutation', () => { - state1.boolean = undefined; - expect(state1.boolean).toBe(undefined); - expect(state2.boolean).toBe(undefined); - }); - }); - - describe('date field', () => { - it('should sync regular date mutation', () => { - const newDate = new Date('2024-01-01'); - state1.date = newDate; - expect(state1.date?.toISOString()).toEqual(newDate.toISOString()); - expect(state2.date?.toISOString()).toEqual(newDate.toISOString()); - }); - - it('should sync null date mutation', () => { - state1.date = null; - expect(state1.date).toBe(null); - expect(state2.date).toBe(null); - }); - - it('should sync undefined date mutation', () => { - state1.date = undefined; - expect(state1.date).toBe(undefined); - expect(state2.date).toBe(undefined); - }); - }); - - describe('enum field', () => { - it('should sync regular enum mutation', () => { - state1.enum = 'b'; - expect(state1.enum).toBe('b'); - expect(state2.enum).toBe('b'); - }); - - it('should sync null enum mutation', () => { - state1.enum = null; - expect(state1.enum).toBe(null); - expect(state2.enum).toBe(null); - }); - - it('should sync undefined enum mutation', () => { - state1.enum = undefined; - expect(state1.enum).toBe(undefined); - expect(state2.enum).toBe(undefined); - }); - }); - - describe('object field', () => { - it('should sync regular object mutation', () => { - state1.object = { text: 'hello', number: 42, boolean: true }; - expect(state1.object).toEqual({ text: 'hello', number: 42, boolean: true }); - expect(state2.object).toEqual({ text: 'hello', number: 42, boolean: true }); - }); - - it('should sync null object mutation', () => { - (state1.object as any) = null; - expect(state1.object).toBe(null); - expect(state2.object).toBe(null); - }); - - it('should sync undefined object mutation', () => { - (state1.object as any) = undefined; - expect(state1.object).toBe(undefined); - expect(state2.object).toBe(undefined); - }); - }); - - describe('array field', () => { - it('should sync regular array mutation', () => { - state1.array = ['hello', 'world']; - expect(state1.array).toEqual(['hello', 'world']); - expect(state2.array).toEqual(['hello', 'world']); - }); - - it('should sync null array mutation', () => { - (state1.array as any) = null; - expect(state1.array).toBe(null); - expect(state2.array).toBe(null); - }); - - it('should sync undefined array mutation', () => { - (state1.array as any) = undefined; - expect(state1.array).toBe(undefined); - expect(state2.array).toBe(undefined); - }); - }); - - describe('set field', () => { - it('should sync regular set mutation', () => { - state1.set = new Set(['hello', 'world']); - expect(Array.from(state1.set)).toEqual(['hello', 'world']); - expect(Array.from(state2.set)).toEqual(['hello', 'world']); - }); - - it('should sync null set mutation', () => { - (state1.set as any) = null; - expect(state1.set).toBe(null); - expect(state2.set).toBe(null); - }); - - it('should sync undefined set mutation', () => { - (state1.set as any) = undefined; - expect(state1.set).toBe(undefined); - expect(state2.set).toBe(undefined); - }); - }); - - describe('map field', () => { - it('should sync regular map mutation', () => { - state1.map = new Map([ - ['hello', 'world'], - ['test', 'value'] - ]); - - expect(Array.from(state1.map.entries())).toEqual([ - ['hello', 'world'], - ['test', 'value'] - ]); - expect(Array.from(state2.map.entries())).toEqual([ - ['hello', 'world'], - ['test', 'value'] - ]); - }); - - it('should sync null map mutation', () => { - (state1.map as any) = null; - expect(state1.map).toBe(null); - expect(state2.map).toBe(null); - }); - - it('should sync undefined map mutation', () => { - (state1.map as any) = undefined; - expect(state1.map).toBe(undefined); - expect(state2.map).toBe(undefined); - }); - - it('should sync map operations', () => { - state1.map = new Map(); - state1.map.set('key1', 'value1'); - expect(state1.map.get('key1')).toBe('value1'); - expect(state2.map.get('key1')).toBe('value1'); - - state1.map.delete('key1'); - expect(state1.map.has('key1')).toBe(false); - expect(state2.map.has('key1')).toBe(false); - - state1.map.set('key2', 'value2'); - state1.map.clear(); - expect(state1.map.size).toBe(0); - expect(state2.map.size).toBe(0); - }); - }); - - describe('discriminatedUnion field', () => { - it('should sync regular discriminatedUnion mutation', () => { - expect(state1.discriminatedUnion).toEqual({ type: 'a', value: 'hello' }); - expect(state2.discriminatedUnion).toEqual({ type: 'a', value: 'hello' }); - }); - - it('should sync null discriminatedUnion mutation', () => { - (state1.discriminatedUnion as any) = null; - expect(state1.discriminatedUnion).toEqual({ type: 'a', value: 'hello' }); - expect(state2.discriminatedUnion).toEqual({ type: 'a', value: 'hello' }); - }); - - it('should sync undefined discriminatedUnion mutation', () => { - (state1.discriminatedUnion as any) = undefined; - expect(state1.discriminatedUnion).toEqual({ type: 'a', value: 'hello' }); - expect(state2.discriminatedUnion).toEqual({ type: 'a', value: 'hello' }); - }); - - it('should sync discriminatedUnion mutation with different type', async () => { - state1.discriminatedUnion = { type: 'b', value: 42 }; - - await wait(100); - expect(state1.discriminatedUnion).toEqual({ type: 'b', value: 42 }); - expect(state2.discriminatedUnion).toEqual({ type: 'b', value: 42 }); - }); - }); -}); - -const wait = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/package/tailwind.config.ts b/package/tailwind.config.ts deleted file mode 100644 index 182234c..0000000 --- a/package/tailwind.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import typography from '@tailwindcss/typography'; -import type { Config } from 'tailwindcss'; - -export default { - content: ['./src/**/*.{html,js,svelte,ts}'], - - theme: { - extend: {} - }, - daisyui: { - themes: ['black'] - }, - plugins: [typography, require('daisyui')] -} as Config; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8a1ca29..45a4fc7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,37 @@ settings: importers: + core: + dependencies: + '@vitest/coverage-v8': + specifier: ^3.2.4 + version: 3.2.4(vitest@3.2.4) + esm-env: + specifier: ^1.2.2 + version: 1.2.2 + y-protocols: + specifier: ^1.0.6 + version: 1.0.6(yjs@13.6.27) + yjs: + specifier: ^13.6.27 + version: 13.6.27 + devDependencies: + '@vitest/ui': + specifier: ^3.2.4 + version: 3.2.4(vitest@3.2.4) + alien-signals: + specifier: ^3.0.0 + version: 3.0.0 + tsdown: + specifier: ^0.15.5 + version: 0.15.5(publint@0.3.13)(typescript@5.9.2) + typescript: + specifier: ^5.9.2 + version: 5.9.2 + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/node@24.6.0)(@vitest/ui@3.2.4)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0) + demo: dependencies: '@liveblocks/client': @@ -14,9 +45,12 @@ importers: '@liveblocks/yjs': specifier: ^2.15.1 version: 2.24.4(yjs@13.6.27) + '@syncrostate/svelte': + specifier: workspace:* + version: link:../svelte '@tailwindcss/typography': specifier: ^0.5.15 - version: 0.5.16(tailwindcss@3.4.17) + version: 0.5.19(tailwindcss@3.4.17) canvas: specifier: ^3.0.1 version: 3.2.0 @@ -34,38 +68,35 @@ importers: version: 4.6.6 svelte-konva: specifier: ^0.3.1 - version: 0.3.1(konva@9.3.22)(svelte@5.38.3) - syncrostate: - specifier: workspace:* - version: link:../package + version: 0.3.1(konva@9.3.22)(svelte@5.39.6) y-partyserver: specifier: ^0.0.31 version: 0.0.31(@cloudflare/workers-types@4.20241230.0)(partyserver@0.0.59(@cloudflare/workers-types@4.20241230.0))(yjs@13.6.27) devDependencies: '@eslint/compat': specifier: ^1.2.3 - version: 1.3.2(eslint@9.34.0(jiti@1.21.7)) + version: 1.4.0(eslint@9.36.0(jiti@2.6.0)) '@sveltejs/adapter-cloudflare': specifier: ^4.8.0 - version: 4.9.0(@sveltejs/kit@2.36.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)))(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)))(wrangler@3.99.0(@cloudflare/workers-types@4.20241230.0)) + version: 4.9.0(@sveltejs/kit@2.43.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)))(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)))(wrangler@3.99.0(@cloudflare/workers-types@4.20241230.0)) '@sveltejs/kit': specifier: ^2.0.0 - version: 2.36.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)))(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)) + version: 2.43.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)))(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)) '@sveltejs/vite-plugin-svelte': specifier: ^4.0.0 - version: 4.0.4(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)) + version: 4.0.4(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)) autoprefixer: specifier: ^10.4.20 version: 10.4.21(postcss@8.4.49) eslint: specifier: ^9.7.0 - version: 9.34.0(jiti@1.21.7) + version: 9.36.0(jiti@2.6.0) eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.2(eslint@9.34.0(jiti@1.21.7)) + version: 9.1.2(eslint@9.36.0(jiti@2.6.0)) eslint-plugin-svelte: specifier: ^2.36.0 - version: 2.46.1(eslint@9.34.0(jiti@1.21.7))(svelte@5.38.3) + version: 2.46.1(eslint@9.36.0(jiti@2.6.0))(svelte@5.39.6) globals: specifier: ^15.0.0 version: 15.15.0 @@ -74,16 +105,16 @@ importers: version: 3.6.2 prettier-plugin-svelte: specifier: ^3.2.6 - version: 3.4.0(prettier@3.6.2)(svelte@5.38.3) + version: 3.4.0(prettier@3.6.2)(svelte@5.39.6) prettier-plugin-tailwindcss: specifier: ^0.6.5 - version: 0.6.14(prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.38.3))(prettier@3.6.2) + version: 0.6.14(prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.39.6))(prettier@3.6.2) svelte: specifier: ^5.0.0 - version: 5.38.3 + version: 5.39.6 svelte-check: specifier: ^4.0.0 - version: 4.3.1(picomatch@4.0.3)(svelte@5.38.3)(typescript@5.9.2) + version: 4.3.2(picomatch@4.0.3)(svelte@5.39.6)(typescript@5.9.2) tailwindcss: specifier: ^3.4.9 version: 3.4.17 @@ -92,47 +123,44 @@ importers: version: 5.9.2 typescript-eslint: specifier: ^8.0.0 - version: 8.41.0(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2) + version: 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) vite: specifier: ^5.4.11 - version: 5.4.19(@types/node@24.3.0) + version: 5.4.20(@types/node@24.6.0)(lightningcss@1.30.1) - package: + svelte: dependencies: + '@syncrostate/core': + specifier: workspace:* + version: link:../core esm-env: specifier: ^1.2.2 version: 1.2.2 - highlight.js: - specifier: ^11.11.1 - version: 11.11.1 - y-protocols: - specifier: ^1.0.6 - version: 1.0.6(yjs@13.6.27) - yjs: - specifier: ^13.6.27 - version: 13.6.27 devDependencies: '@liveblocks/client': - specifier: ^3.4.0 - version: 3.4.0(@types/json-schema@7.0.15) + specifier: ^3.8.0 + version: 3.8.0(@types/json-schema@7.0.15) '@liveblocks/yjs': - specifier: ^3.4.0 - version: 3.4.0(@types/json-schema@7.0.15)(yjs@13.6.27) + specifier: ^3.8.0 + version: 3.8.0(@types/json-schema@7.0.15)(yjs@13.6.27) '@sveltejs/adapter-auto': specifier: ^6.1.0 - version: 6.1.0(@sveltejs/kit@2.36.2(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)))(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0))) + version: 6.1.0(@sveltejs/kit@2.43.5(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)))(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0))) '@sveltejs/kit': - specifier: ^2.36.2 - version: 2.36.2(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)))(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)) + specifier: ^2.43.5 + version: 2.43.5(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)))(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)) '@sveltejs/package': - specifier: ^2.5.0 - version: 2.5.0(svelte@5.38.3)(typescript@5.9.2) + specifier: ^2.5.4 + version: 2.5.4(svelte@5.39.6)(typescript@5.9.2) '@sveltejs/vite-plugin-svelte': - specifier: ^6.1.3 - version: 6.1.3(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)) + specifier: ^6.2.1 + version: 6.2.1(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)) '@tailwindcss/typography': - specifier: ^0.5.16 - version: 0.5.16(tailwindcss@4.1.12) + specifier: ^0.5.19 + version: 0.5.19(tailwindcss@4.1.13) + '@tailwindcss/vite': + specifier: ^4.1.13 + version: 4.1.13(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)) '@types/eslint': specifier: ^9.6.1 version: 9.6.1 @@ -146,53 +174,69 @@ importers: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.6) daisyui: - specifier: ^5.0.51 - version: 5.0.51 + specifier: ^5.1.25 + version: 5.1.25 eslint: - specifier: ^9.34.0 - version: 9.34.0(jiti@1.21.7) + specifier: ^9.36.0 + version: 9.36.0(jiti@2.6.0) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.34.0(jiti@1.21.7)) + version: 10.1.8(eslint@9.36.0(jiti@2.6.0)) eslint-plugin-svelte: - specifier: ^3.11.0 - version: 3.11.0(eslint@9.34.0(jiti@1.21.7))(svelte@5.38.3) + specifier: ^3.12.4 + version: 3.12.4(eslint@9.36.0(jiti@2.6.0))(svelte@5.39.6) globals: - specifier: ^16.3.0 - version: 16.3.0 + specifier: ^16.4.0 + version: 16.4.0 prettier: specifier: ^3.6.2 version: 3.6.2 prettier-plugin-svelte: specifier: ^3.4.0 - version: 3.4.0(prettier@3.6.2)(svelte@5.38.3) + version: 3.4.0(prettier@3.6.2)(svelte@5.39.6) publint: - specifier: ^0.3.12 - version: 0.3.12 + specifier: ^0.3.13 + version: 0.3.13 svelte: - specifier: ^5.38.3 - version: 5.38.3 + specifier: ^5.39.6 + version: 5.39.6 svelte-check: - specifier: ^4.3.1 - version: 4.3.1(picomatch@4.0.3)(svelte@5.38.3)(typescript@5.9.2) + specifier: ^4.3.2 + version: 4.3.2(picomatch@4.0.3)(svelte@5.39.6)(typescript@5.9.2) svelte-inspect-value: - specifier: ^0.9.1 - version: 0.9.1(svelte@5.38.3) + specifier: ^0.9.2 + version: 0.9.2(svelte@5.39.6) tailwindcss: - specifier: ^4.1.12 - version: 4.1.12 + specifier: ^4.1.13 + version: 4.1.13 typescript: specifier: ^5.9.2 version: 5.9.2 typescript-eslint: - specifier: ^8.41.0 - version: 8.41.0(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2) + specifier: ^8.45.0 + version: 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) vite: - specifier: ^7.1.3 - version: 7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0) + specifier: ^7.1.7 + version: 7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@24.3.0)(@vitest/ui@3.2.4)(jiti@1.21.7)(yaml@2.7.0) + version: 3.2.4(@types/node@24.6.0)(@vitest/ui@3.2.4)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0) + + vanilla: + dependencies: + '@syncrostate/core': + specifier: workspace:* + version: link:../core + alien-signals: + specifier: ^3.0.0 + version: 3.0.0 + typescript: + specifier: ^5 + version: 5.9.2 + devDependencies: + tsdown: + specifier: ^0.15.5 + version: 0.15.5(publint@0.3.13)(typescript@5.9.2) packages: @@ -204,6 +248,10 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -212,13 +260,13 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.3': - resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/types@7.28.2': - resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@1.0.2': @@ -266,6 +314,15 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@emnapi/core@1.5.0': + resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} + + '@emnapi/runtime@1.5.0': + resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} + + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@esbuild-plugins/node-globals-polyfill@0.2.3': resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} peerDependencies: @@ -288,8 +345,8 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.25.9': - resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} + '@esbuild/aix-ppc64@0.25.10': + resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -312,8 +369,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.25.9': - resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + '@esbuild/android-arm64@0.25.10': + resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -336,8 +393,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.25.9': - resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} + '@esbuild/android-arm@0.25.10': + resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -360,8 +417,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.25.9': - resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} + '@esbuild/android-x64@0.25.10': + resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -384,8 +441,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.25.9': - resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + '@esbuild/darwin-arm64@0.25.10': + resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -408,8 +465,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.25.9': - resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} + '@esbuild/darwin-x64@0.25.10': + resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -432,8 +489,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.25.9': - resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} + '@esbuild/freebsd-arm64@0.25.10': + resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -456,8 +513,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.9': - resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + '@esbuild/freebsd-x64@0.25.10': + resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -480,8 +537,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.25.9': - resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + '@esbuild/linux-arm64@0.25.10': + resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -504,8 +561,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.25.9': - resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} + '@esbuild/linux-arm@0.25.10': + resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -528,8 +585,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.25.9': - resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} + '@esbuild/linux-ia32@0.25.10': + resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -552,8 +609,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.25.9': - resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + '@esbuild/linux-loong64@0.25.10': + resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -576,8 +633,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.25.9': - resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + '@esbuild/linux-mips64el@0.25.10': + resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -600,8 +657,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.25.9': - resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + '@esbuild/linux-ppc64@0.25.10': + resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -624,8 +681,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.25.9': - resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + '@esbuild/linux-riscv64@0.25.10': + resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -648,8 +705,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.25.9': - resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} + '@esbuild/linux-s390x@0.25.10': + resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -672,8 +729,8 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.25.9': - resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + '@esbuild/linux-x64@0.25.10': + resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -684,8 +741,8 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.25.9': - resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} + '@esbuild/netbsd-arm64@0.25.10': + resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] @@ -708,8 +765,8 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.9': - resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + '@esbuild/netbsd-x64@0.25.10': + resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] @@ -720,8 +777,8 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.25.9': - resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} + '@esbuild/openbsd-arm64@0.25.10': + resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -744,14 +801,14 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.9': - resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + '@esbuild/openbsd-x64@0.25.10': + resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.9': - resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + '@esbuild/openharmony-arm64@0.25.10': + resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] @@ -774,8 +831,8 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.25.9': - resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + '@esbuild/sunos-x64@0.25.10': + resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -798,8 +855,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.25.9': - resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + '@esbuild/win32-arm64@0.25.10': + resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -822,8 +879,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.25.9': - resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} + '@esbuild/win32-ia32@0.25.10': + resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -846,8 +903,8 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.25.9': - resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} + '@esbuild/win32-x64@0.25.10': + resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -858,8 +915,8 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/eslint-utils@4.7.0': - resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -868,8 +925,8 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/compat@1.3.2': - resolution: {integrity: sha512-jRNwzTbd6p2Rw4sZ1CgWRS8YMtqG15YyZf7zvb6gY2rB2u6n+2Z+ELW0GtL0fQgyl0pr4Y/BzBfng/BdsereRA==} + '@eslint/compat@1.4.0': + resolution: {integrity: sha512-DEzm5dKeDBPm3r08Ixli/0cmxr8LkRdwxMRUIJBlSCpAwSrvFEJpVBzV+66JhDxiaqKxnRzCXhtiMiczF7Hglg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.40 || 9 @@ -889,12 +946,16 @@ packages: resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.16.0': + resolution: {integrity: sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.3.1': resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.34.0': - resolution: {integrity: sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==} + '@eslint/js@9.36.0': + resolution: {integrity: sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.6': @@ -913,18 +974,14 @@ packages: resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} - engines: {node: '>=18.18'} - '@humanwhocodes/retry@0.4.3': resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} @@ -933,6 +990,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@istanbuljs/schema@0.1.3': resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} @@ -961,8 +1022,8 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.30': - resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} @@ -970,14 +1031,14 @@ packages: '@liveblocks/client@2.24.4': resolution: {integrity: sha512-jT6kpapuSWuQlg8m/r3N5R/i7XlQAG2UkeaQi1DkyCc2+U4PWgKNVVXVWUUKM+OcbG3yKoJJ93Pw4AZE0hHWDg==} - '@liveblocks/client@3.4.0': - resolution: {integrity: sha512-0IVSmoCrSW+27Ir30ndD9nZwFD8mmVc9Yl7W5EHRuSvP0ARvCtdAI/dzn4cJnCvXmN+HQUk4CkyXX/h4Y79g3w==} + '@liveblocks/client@3.8.0': + resolution: {integrity: sha512-uBQwe5n7VEhRG1Q1AGEmeub5tQYSxRLiY+ku9NBfeyRaO/ofG2lz+PvE6pMcYmWE+jHqr4XJVNGL7Yf9/O/Qqg==} '@liveblocks/core@2.24.4': resolution: {integrity: sha512-Os2Fs/RgggyrISpqHIefWo864auKIkpw48BG5fvPubcXkLU9i8wUcK5oczWEqJ0JvtyTfqREl/tkEhGf124DCw==} - '@liveblocks/core@3.4.0': - resolution: {integrity: sha512-oMuBWclDLu1Tb6aqRn+wBq+AtaEenpQc6RQxDgU55wJhNZm7Nadv/kPfEQwulJnjWdZ6/PJp22y1PuiQCfRVoQ==} + '@liveblocks/core@3.8.0': + resolution: {integrity: sha512-d0SQUOEmfSn26o6KqMjp70eVOd0HtNH7CE8BHu0sNTxMDP9vSrlvHIajgg3To978ewxBFKM9s/HbUCnrKZ1TAw==} peerDependencies: '@types/json-schema': ^7 @@ -986,11 +1047,14 @@ packages: peerDependencies: yjs: ^13.6.1 - '@liveblocks/yjs@3.4.0': - resolution: {integrity: sha512-JUmuA2GslqKD1egdACvqly/VB1T+xKd8E22wt1sipIVT4B7lB2OVrvHb3TVX4VaDT52ehzaPAvzOQrCVXlk/Ww==} + '@liveblocks/yjs@3.8.0': + resolution: {integrity: sha512-nSFlVL1qz3hNXQ8h1lsw1Kr6bGPpUre3OMgFEz4WzW12Vtmouy4KdMNWR0YnDEU6aXXcvluHRHAjUN85IYzQig==} peerDependencies: yjs: ^13.6.1 + '@napi-rs/wasm-runtime@1.0.5': + resolution: {integrity: sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==} + '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} @@ -1007,6 +1071,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@oxc-project/types@0.93.0': + resolution: {integrity: sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1018,13 +1085,102 @@ packages: resolution: {integrity: sha512-S+9ANAvUmjutrshV4jZjaiG8XQyuJIZ8a4utWmN/vW1sgQ9IfBnPndwkmQYw53QmouOIytT874u65HEmu6H5jw==} engines: {node: '>=18'} + '@quansync/fs@0.1.5': + resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==} + + '@rolldown/binding-android-arm64@1.0.0-beta.41': + resolution: {integrity: sha512-Edflndd9lU7JVhVIvJlZhdCj5DkhYDJPIRn4Dx0RUdfc8asP9xHOI5gMd8MesDDx+BJpdIT/uAmVTearteU/mQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-beta.41': + resolution: {integrity: sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-beta.41': + resolution: {integrity: sha512-Ho6lIwGJed98zub7n0xcRKuEtnZgbxevAmO4x3zn3C3N4GVXZD5xvCvTVxSMoeBJwTcIYzkVDRTIhylQNsTgLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-beta.41': + resolution: {integrity: sha512-ijAZETywvL+gACjbT4zBnCp5ez1JhTRs6OxRN4J+D6AzDRbU2zb01Esl51RP5/8ZOlvB37xxsRQ3X4YRVyYb3g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.41': + resolution: {integrity: sha512-EgIOZt7UildXKFEFvaiLNBXm+4ggQyGe3E5Z1QP9uRcJJs9omihOnm897FwOBQdCuMvI49iBgjFrkhH+wMJ2MA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.41': + resolution: {integrity: sha512-F8bUwJq8v/JAU8HSwgF4dztoqJ+FjdyjuvX4//3+Fbe2we9UktFeZ27U4lRMXF1vxWtdV4ey6oCSqI7yUrSEeg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.41': + resolution: {integrity: sha512-MioXcCIX/wB1pBnBoJx8q4OGucUAfC1+/X1ilKFsjDK05VwbLZGRgOVD5OJJpUQPK86DhQciNBrfOKDiatxNmg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.41': + resolution: {integrity: sha512-m66M61fizvRCwt5pOEiZQMiwBL9/y0bwU/+Kc4Ce/Pef6YfoEkR28y+DzN9rMdjo8Z28NXjsDPq9nH4mXnAP0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.41': + resolution: {integrity: sha512-yRxlSfBvWnnfrdtJfvi9lg8xfG5mPuyoSHm0X01oiE8ArmLRvoJGHUTJydCYz+wbK2esbq5J4B4Tq9WAsOlP1Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.41': + resolution: {integrity: sha512-PHVxYhBpi8UViS3/hcvQQb9RFqCtvFmFU1PvUoTRiUdBtgHA6fONNHU4x796lgzNlVSD3DO/MZNk1s5/ozSMQg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.41': + resolution: {integrity: sha512-OAfcO37ME6GGWmj9qTaDT7jY4rM0T2z0/8ujdQIJQ2x2nl+ztO32EIwURfmXOK0U1tzkyuaKYvE34Pug/ucXlQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.41': + resolution: {integrity: sha512-NIYGuCcuXaq5BC4Q3upbiMBvmZsTsEPG9k/8QKQdmrch+ocSy5Jv9tdpdmXJyighKqm182nh/zBt+tSJkYoNlg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.41': + resolution: {integrity: sha512-kANdsDbE5FkEOb5NrCGBJBCaZ2Sabp3D7d4PRqMYJqyLljwh9mDyYyYSv5+QNvdAmifj+f3lviNEUUuUZPEFPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.41': + resolution: {integrity: sha512-UlpxKmFdik0Y2VjZrgUCgoYArZJiZllXgIipdBRV1hw6uK45UbQabSTW6Kp6enuOu7vouYWftwhuxfpE8J2JAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-beta.41': + resolution: {integrity: sha512-ycMEPrS3StOIeb87BT3/+bu+blEtyvwQ4zmo2IcJQy0Rd1DAAhKksA0iUZ3MYSpJtjlPhg0Eo6mvVS6ggPhRbw==} + '@rollup/rollup-android-arm-eabi@4.30.1': resolution: {integrity: sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm-eabi@4.48.1': - resolution: {integrity: sha512-rGmb8qoG/zdmKoYELCBwu7vt+9HxZ7Koos3pD0+sH5fR3u3Wb/jGcpnqxcnWsPEKDUyzeLSqksN8LJtgXjqBYw==} + '@rollup/rollup-android-arm-eabi@4.52.3': + resolution: {integrity: sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==} cpu: [arm] os: [android] @@ -1033,8 +1189,8 @@ packages: cpu: [arm64] os: [android] - '@rollup/rollup-android-arm64@4.48.1': - resolution: {integrity: sha512-4e9WtTxrk3gu1DFE+imNJr4WsL13nWbD/Y6wQcyku5qadlKHY3OQ3LJ/INrrjngv2BJIHnIzbqMk1GTAC2P8yQ==} + '@rollup/rollup-android-arm64@4.52.3': + resolution: {integrity: sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==} cpu: [arm64] os: [android] @@ -1043,8 +1199,8 @@ packages: cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-arm64@4.48.1': - resolution: {integrity: sha512-+XjmyChHfc4TSs6WUQGmVf7Hkg8ferMAE2aNYYWjiLzAS/T62uOsdfnqv+GHRjq7rKRnYh4mwWb4Hz7h/alp8A==} + '@rollup/rollup-darwin-arm64@4.52.3': + resolution: {integrity: sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==} cpu: [arm64] os: [darwin] @@ -1053,8 +1209,8 @@ packages: cpu: [x64] os: [darwin] - '@rollup/rollup-darwin-x64@4.48.1': - resolution: {integrity: sha512-upGEY7Ftw8M6BAJyGwnwMw91rSqXTcOKZnnveKrVWsMTF8/k5mleKSuh7D4v4IV1pLxKAk3Tbs0Lo9qYmii5mQ==} + '@rollup/rollup-darwin-x64@4.52.3': + resolution: {integrity: sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==} cpu: [x64] os: [darwin] @@ -1063,8 +1219,8 @@ packages: cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-arm64@4.48.1': - resolution: {integrity: sha512-P9ViWakdoynYFUOZhqq97vBrhuvRLAbN/p2tAVJvhLb8SvN7rbBnJQcBu8e/rQts42pXGLVhfsAP0k9KXWa3nQ==} + '@rollup/rollup-freebsd-arm64@4.52.3': + resolution: {integrity: sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==} cpu: [arm64] os: [freebsd] @@ -1073,8 +1229,8 @@ packages: cpu: [x64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.48.1': - resolution: {integrity: sha512-VLKIwIpnBya5/saccM8JshpbxfyJt0Dsli0PjXozHwbSVaHTvWXJH1bbCwPXxnMzU4zVEfgD1HpW3VQHomi2AQ==} + '@rollup/rollup-freebsd-x64@4.52.3': + resolution: {integrity: sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==} cpu: [x64] os: [freebsd] @@ -1083,8 +1239,8 @@ packages: cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-gnueabihf@4.48.1': - resolution: {integrity: sha512-3zEuZsXfKaw8n/yF7t8N6NNdhyFw3s8xJTqjbTDXlipwrEHo4GtIKcMJr5Ed29leLpB9AugtAQpAHW0jvtKKaQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.52.3': + resolution: {integrity: sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==} cpu: [arm] os: [linux] @@ -1093,8 +1249,8 @@ packages: cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.48.1': - resolution: {integrity: sha512-leo9tOIlKrcBmmEypzunV/2w946JeLbTdDlwEZ7OnnsUyelZ72NMnT4B2vsikSgwQifjnJUbdXzuW4ToN1wV+Q==} + '@rollup/rollup-linux-arm-musleabihf@4.52.3': + resolution: {integrity: sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==} cpu: [arm] os: [linux] @@ -1103,8 +1259,8 @@ packages: cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.48.1': - resolution: {integrity: sha512-Vy/WS4z4jEyvnJm+CnPfExIv5sSKqZrUr98h03hpAMbE2aI0aD2wvK6GiSe8Gx2wGp3eD81cYDpLLBqNb2ydwQ==} + '@rollup/rollup-linux-arm64-gnu@4.52.3': + resolution: {integrity: sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==} cpu: [arm64] os: [linux] @@ -1113,18 +1269,18 @@ packages: cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.48.1': - resolution: {integrity: sha512-x5Kzn7XTwIssU9UYqWDB9VpLpfHYuXw5c6bJr4Mzv9kIv242vmJHbI5PJJEnmBYitUIfoMCODDhR7KoZLot2VQ==} + '@rollup/rollup-linux-arm64-musl@4.52.3': + resolution: {integrity: sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.30.1': - resolution: {integrity: sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==} + '@rollup/rollup-linux-loong64-gnu@4.52.3': + resolution: {integrity: sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.48.1': - resolution: {integrity: sha512-yzCaBbwkkWt/EcgJOKDUdUpMHjhiZT/eDktOPWvSRpqrVE04p0Nd6EGV4/g7MARXXeOqstflqsKuXVM3H9wOIQ==} + '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + resolution: {integrity: sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==} cpu: [loong64] os: [linux] @@ -1133,8 +1289,8 @@ packages: cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.48.1': - resolution: {integrity: sha512-UK0WzWUjMAJccHIeOpPhPcKBqax7QFg47hwZTp6kiMhQHeOYJeaMwzeRZe1q5IiTKsaLnHu9s6toSYVUlZ2QtQ==} + '@rollup/rollup-linux-ppc64-gnu@4.52.3': + resolution: {integrity: sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==} cpu: [ppc64] os: [linux] @@ -1143,13 +1299,13 @@ packages: cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.48.1': - resolution: {integrity: sha512-3NADEIlt+aCdCbWVZ7D3tBjBX1lHpXxcvrLt/kdXTiBrOds8APTdtk2yRL2GgmnSVeX4YS1JIf0imFujg78vpw==} + '@rollup/rollup-linux-riscv64-gnu@4.52.3': + resolution: {integrity: sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.48.1': - resolution: {integrity: sha512-euuwm/QTXAMOcyiFCcrx0/S2jGvFlKJ2Iro8rsmYL53dlblp3LkUQVFzEidHhvIPPvcIsxDhl2wkBE+I6YVGzA==} + '@rollup/rollup-linux-riscv64-musl@4.52.3': + resolution: {integrity: sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==} cpu: [riscv64] os: [linux] @@ -1158,8 +1314,8 @@ packages: cpu: [s390x] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.48.1': - resolution: {integrity: sha512-w8mULUjmPdWLJgmTYJx/W6Qhln1a+yqvgwmGXcQl2vFBkWsKGUBRbtLRuKJUln8Uaimf07zgJNxOhHOvjSQmBQ==} + '@rollup/rollup-linux-s390x-gnu@4.52.3': + resolution: {integrity: sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==} cpu: [s390x] os: [linux] @@ -1168,8 +1324,8 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.48.1': - resolution: {integrity: sha512-90taWXCWxTbClWuMZD0DKYohY1EovA+W5iytpE89oUPmT5O1HFdf8cuuVIylE6vCbrGdIGv85lVRzTcpTRZ+kA==} + '@rollup/rollup-linux-x64-gnu@4.52.3': + resolution: {integrity: sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==} cpu: [x64] os: [linux] @@ -1178,18 +1334,23 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.48.1': - resolution: {integrity: sha512-2Gu29SkFh1FfTRuN1GR1afMuND2GKzlORQUP3mNMJbqdndOg7gNsa81JnORctazHRokiDzQ5+MLE5XYmZW5VWg==} + '@rollup/rollup-linux-x64-musl@4.52.3': + resolution: {integrity: sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==} cpu: [x64] os: [linux] + '@rollup/rollup-openharmony-arm64@4.52.3': + resolution: {integrity: sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==} + cpu: [arm64] + os: [openharmony] + '@rollup/rollup-win32-arm64-msvc@4.30.1': resolution: {integrity: sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.48.1': - resolution: {integrity: sha512-6kQFR1WuAO50bxkIlAVeIYsz3RUx+xymwhTo9j94dJ+kmHe9ly7muH23sdfWduD0BA8pD9/yhonUvAjxGh34jQ==} + '@rollup/rollup-win32-arm64-msvc@4.52.3': + resolution: {integrity: sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==} cpu: [arm64] os: [win32] @@ -1198,26 +1359,31 @@ packages: cpu: [ia32] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.48.1': - resolution: {integrity: sha512-RUyZZ/mga88lMI3RlXFs4WQ7n3VyU07sPXmMG7/C1NOi8qisUg57Y7LRarqoGoAiopmGmChUhSwfpvQ3H5iGSQ==} + '@rollup/rollup-win32-ia32-msvc@4.52.3': + resolution: {integrity: sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==} cpu: [ia32] os: [win32] + '@rollup/rollup-win32-x64-gnu@4.52.3': + resolution: {integrity: sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==} + cpu: [x64] + os: [win32] + '@rollup/rollup-win32-x64-msvc@4.30.1': resolution: {integrity: sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.48.1': - resolution: {integrity: sha512-8a/caCUN4vkTChxkaIJcMtwIVcBhi4X2PQRoT+yCK3qRYaZ7cURrmJFL5Ux9H9RaMIXj9RuihckdmkBX3zZsgg==} + '@rollup/rollup-win32-x64-msvc@4.52.3': + resolution: {integrity: sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==} cpu: [x64] os: [win32] '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - '@sveltejs/acorn-typescript@1.0.5': - resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} + '@sveltejs/acorn-typescript@1.0.6': + resolution: {integrity: sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==} peerDependencies: acorn: ^8.9.0 @@ -1232,8 +1398,8 @@ packages: '@sveltejs/kit': ^2.0.0 wrangler: ^3.28.4 - '@sveltejs/kit@2.36.2': - resolution: {integrity: sha512-WlBGY060nHe4UE5QrDAJAbls5hOsG6mljtrDGkM8jJCDQ4JEcAEH04XrTVmQ0Ex1CU8nzoZto0EE75aiLA3G8Q==} + '@sveltejs/kit@2.43.5': + resolution: {integrity: sha512-44Mm5csR4mesKx2Eyhtk8UVrLJ4c04BT2wMTfYGKJMOkUqpHP5KLL2DPV0hXUA4t4+T3ZYe0aBygd42lVYv2cA==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -1245,8 +1411,8 @@ packages: '@opentelemetry/api': optional: true - '@sveltejs/package@2.5.0': - resolution: {integrity: sha512-qpB91oWEraOXA4l1ldpGtMc/rLCthbf1ACw/1oroKxvT3sd2NXPd/+NLhIk5FCvd0IUSEZGYa86K+D94GWW2Zw==} + '@sveltejs/package@2.5.4': + resolution: {integrity: sha512-8+1hccAt0M3PPkHVPKH54Wc+cc1PNxRqCrICZiv/hEEto8KwbQVRghxNgTB4htIPyle+4CIB8RayTQH5zRQh9A==} engines: {node: ^16.14 || >=18} hasBin: true peerDependencies: @@ -1275,18 +1441,111 @@ packages: svelte: ^5.0.0-next.96 || ^5.0.0 vite: ^5.0.0 - '@sveltejs/vite-plugin-svelte@6.1.3': - resolution: {integrity: sha512-3pppgIeIZs6nrQLazzKcdnTJ2IWiui/UucEPXKyFG35TKaHQrfkWBnv6hyJcLxFuR90t+LaoecrqTs8rJKWfSQ==} + '@sveltejs/vite-plugin-svelte@6.2.1': + resolution: {integrity: sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==} engines: {node: ^20.19 || ^22.12 || >=24} peerDependencies: svelte: ^5.0.0 vite: ^6.3.0 || ^7.0.0 - '@tailwindcss/typography@0.5.16': - resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==} + '@tailwindcss/node@4.1.13': + resolution: {integrity: sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==} + + '@tailwindcss/oxide-android-arm64@4.1.13': + resolution: {integrity: sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.13': + resolution: {integrity: sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.13': + resolution: {integrity: sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.13': + resolution: {integrity: sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': + resolution: {integrity: sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': + resolution: {integrity: sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.13': + resolution: {integrity: sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.13': + resolution: {integrity: sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.13': + resolution: {integrity: sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.13': + resolution: {integrity: sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': + resolution: {integrity: sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.13': + resolution: {integrity: sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.13': + resolution: {integrity: sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==} + engines: {node: '>= 10'} + + '@tailwindcss/typography@0.5.19': + resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==} peerDependencies: tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + '@tailwindcss/vite@4.1.13': + resolution: {integrity: sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} @@ -1311,66 +1570,66 @@ packages: '@types/node-forge@1.3.14': resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} - '@types/node@24.3.0': - resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==} + '@types/node@24.6.0': + resolution: {integrity: sha512-F1CBxgqwOMc4GKJ7eY22hWhBVQuMYTtqI8L0FcszYcpYX0fzfDGpez22Xau8Mgm7O9fI+zA/TYIdq3tGWfweBA==} - '@typescript-eslint/eslint-plugin@8.41.0': - resolution: {integrity: sha512-8fz6oa6wEKZrhXWro/S3n2eRJqlRcIa6SlDh59FXJ5Wp5XRZ8B9ixpJDcjadHq47hMx0u+HW6SNa6LjJQ6NLtw==} + '@typescript-eslint/eslint-plugin@8.45.0': + resolution: {integrity: sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.41.0 + '@typescript-eslint/parser': ^8.45.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.41.0': - resolution: {integrity: sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==} + '@typescript-eslint/parser@8.45.0': + resolution: {integrity: sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.41.0': - resolution: {integrity: sha512-b8V9SdGBQzQdjJ/IO3eDifGpDBJfvrNTp2QD9P2BeqWTGrRibgfgIlBSw6z3b6R7dPzg752tOs4u/7yCLxksSQ==} + '@typescript-eslint/project-service@8.45.0': + resolution: {integrity: sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.41.0': - resolution: {integrity: sha512-n6m05bXn/Cd6DZDGyrpXrELCPVaTnLdPToyhBoFkLIMznRUQUEQdSp96s/pcWSQdqOhrgR1mzJ+yItK7T+WPMQ==} + '@typescript-eslint/scope-manager@8.45.0': + resolution: {integrity: sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.41.0': - resolution: {integrity: sha512-TDhxYFPUYRFxFhuU5hTIJk+auzM/wKvWgoNYOPcOf6i4ReYlOoYN8q1dV5kOTjNQNJgzWN3TUUQMtlLOcUgdUw==} + '@typescript-eslint/tsconfig-utils@8.45.0': + resolution: {integrity: sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.41.0': - resolution: {integrity: sha512-63qt1h91vg3KsjVVonFJWjgSK7pZHSQFKH6uwqxAH9bBrsyRhO6ONoKyXxyVBzG1lJnFAJcKAcxLS54N1ee1OQ==} + '@typescript-eslint/type-utils@8.45.0': + resolution: {integrity: sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.41.0': - resolution: {integrity: sha512-9EwxsWdVqh42afLbHP90n2VdHaWU/oWgbH2P0CfcNfdKL7CuKpwMQGjwev56vWu9cSKU7FWSu6r9zck6CVfnag==} + '@typescript-eslint/types@8.45.0': + resolution: {integrity: sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.41.0': - resolution: {integrity: sha512-D43UwUYJmGhuwHfY7MtNKRZMmfd8+p/eNSfFe6tH5mbVDto+VQCayeAt35rOx3Cs6wxD16DQtIKw/YXxt5E0UQ==} + '@typescript-eslint/typescript-estree@8.45.0': + resolution: {integrity: sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.41.0': - resolution: {integrity: sha512-udbCVstxZ5jiPIXrdH+BZWnPatjlYwJuJkDA4Tbo3WyYLh8NvB+h/bKeSZHDOFKfphsZYJQqaFtLeXEqurQn1A==} + '@typescript-eslint/utils@8.45.0': + resolution: {integrity: sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.41.0': - resolution: {integrity: sha512-+GeGMebMCy0elMNg67LRNoVnUFPIm37iu5CmHESVx56/9Jsfdpsvbv605DQ81Pi/x11IdKUsS5nzgTYbCQU9fg==} + '@typescript-eslint/visitor-keys@8.45.0': + resolution: {integrity: sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitest/coverage-v8@3.2.4': @@ -1433,22 +1692,29 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + alien-signals@3.0.0: + resolution: {integrity: sha512-JHoRJf18Y6HN4/KZALr3iU+0vW9LKG+8FMThQlbn4+gv8utsLIkwpomjElGPccGeNwh0FI2HN6BLnyFLo6OyLQ==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.2.0: - resolution: {integrity: sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -1473,8 +1739,12 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - ast-v8-to-istanbul@0.3.4: - resolution: {integrity: sha512-cxrAnZNLBnQwBPByK4CeDaw5sWZtMilJE/Q3iDA0aamgaIVNDF9T6K2/8DfYDZEejZ2jNnDrG9m8MY72HFd0KA==} + ast-kit@2.1.2: + resolution: {integrity: sha512-cl76xfBQM6pztbrFWRnxbrDm9EOqDr1BF6+qQnnDZG2Co2LjyUktkN9GTJfBAfdae+DbT2nJf2nCGAdDDN7W2g==} + engines: {node: '>=20.18.0'} + + ast-v8-to-istanbul@0.3.5: + resolution: {integrity: sha512-9SdXjNheSiE8bALAQCQQuT6fgQaoxJh7IRYrRGZ8/9nv8WhJeC1aXAwN8TbaOssGOukUvyvnkgD9+Yuykvl1aA==} autoprefixer@10.4.21: resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} @@ -1493,10 +1763,17 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.8.9: + resolution: {integrity: sha512-hY/u2lxLrbecMEWSB0IpGzGyDyeoMFQhCvZd2jGFSE5I17Fh01sYUBPCJtkWERw7zrac9+cIghxm/ytJa2X8iA==} + hasBin: true + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + birpc@2.6.1: + resolution: {integrity: sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -1513,8 +1790,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.25.3: - resolution: {integrity: sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==} + browserslist@4.26.2: + resolution: {integrity: sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1533,8 +1810,8 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001737: - resolution: {integrity: sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==} + caniuse-lite@1.0.30001746: + resolution: {integrity: sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==} canvas@3.2.0: resolution: {integrity: sha512-jk0GxrLtUEmW/TmFsk2WghvgHe8B0pxGilqCL21y8lHkPUGa6FTsnCNtHPOzT8O3y+N+m3espawV80bbBlgfTA==} @@ -1566,6 +1843,10 @@ packages: chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -1612,8 +1893,8 @@ packages: resolution: {integrity: sha512-JYg9fhQHOfXyLadrBrEqCDM6D5dWCSSiM6eTNCRrBRzx/VlOCrLS8eDfIw9RVvs64v2mJdLooKXY8EwQzoszAA==} engines: {node: '>=16.9.0'} - daisyui@5.0.51: - resolution: {integrity: sha512-lhB0BBOjt43/t5S1my0XChMy3ClXfmGlDU/XmSlx+N0h2y7cyWF+cnheeemguxNHb9TjqI66mxKI9qiFsOU3mA==} + daisyui@5.1.25: + resolution: {integrity: sha512-LYOGVIzTCCucEFkKmdj0fxbHHPZ83fpkYD7jXYF3/7UwrUu68TtXkIdGtEXadzeqUT361hCe6cj5tBB/7mvszw==} data-uri-to-buffer@2.0.2: resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==} @@ -1630,8 +1911,8 @@ packages: supports-color: optional: true - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -1668,20 +1949,33 @@ packages: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} - devalue@5.1.1: - resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} + devalue@5.3.2: + resolution: {integrity: sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==} didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dts-resolver@2.1.2: + resolution: {integrity: sha512-xeXHBQkn2ISSXxbJWD828PFjtyg+/UrMDo7W4Ffcs7+YWCquxU8YjV1KoxuiL+eJ5pg3ll+bC6flVv61L3LKZg==} + engines: {node: '>=20.18.0'} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.209: - resolution: {integrity: sha512-Xoz0uMrim9ZETCQt8UgM5FxQF9+imA7PBpokoGcZloA1uw2LeHzTlip5cb5KOAsXZLjh/moN2vReN3ZjJmjI9A==} + electron-to-chromium@1.5.227: + resolution: {integrity: sha512-ITxuoPfJu3lsNWUi2lBM2PaBPYgH3uqmxut5vmBxgYvyI4AlJ6P3Cai1O76mOrkJCBzq0IxWg/NtqOrpu/0gKA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1689,9 +1983,17 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} @@ -1710,8 +2012,8 @@ packages: engines: {node: '>=18'} hasBin: true - esbuild@0.25.9: - resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + esbuild@0.25.10: + resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} engines: {node: '>=18'} hasBin: true @@ -1751,8 +2053,8 @@ packages: svelte: optional: true - eslint-plugin-svelte@3.11.0: - resolution: {integrity: sha512-KliWlkieHyEa65aQIkRwUFfHzT5Cn4u3BQQsu3KlkJOs7c1u7ryn84EWaOjEzilbKgttT4OfBURA8Uc4JBSQIw==} + eslint-plugin-svelte@3.12.4: + resolution: {integrity: sha512-hD7wPe+vrPgx3U2X2b/wyTMtWobm660PygMGKrWWYTc9lvtY8DpNFDaU2CJQn1szLjGbn/aJ3g8WiXuKakrEkw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.1 || ^9.0.0 @@ -1777,8 +2079,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.34.0: - resolution: {integrity: sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==} + eslint@9.36.0: + resolution: {integrity: sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1906,6 +2208,9 @@ packages: get-source@2.0.12: resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -1932,10 +2237,13 @@ packages: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} - globals@16.3.0: - resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==} + globals@16.4.0: + resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} engines: {node: '>=18'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -1954,6 +2262,9 @@ packages: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -2041,6 +2352,10 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true + jiti@2.6.0: + resolution: {integrity: sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==} + hasBin: true + js-base64@3.7.7: resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} @@ -2054,6 +2369,11 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -2093,6 +2413,70 @@ packages: engines: {node: '>=16'} hasBin: true + lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -2111,24 +2495,15 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash.castarray@4.4.0: - resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} - lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -2138,8 +2513,8 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - magic-string@0.30.18: - resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==} + magic-string@0.30.19: + resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} @@ -2195,6 +2570,10 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -2231,8 +2610,8 @@ packages: engines: {node: ^18 || >=20} hasBin: true - nanoid@5.1.5: - resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} + nanoid@5.1.6: + resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} engines: {node: ^18 || >=20} hasBin: true @@ -2242,9 +2621,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - node-abi@3.75.0: resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==} engines: {node: '>=10'} @@ -2256,8 +2632,8 @@ packages: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + node-releases@2.0.21: + resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -2308,9 +2684,6 @@ packages: peerDependencies: '@cloudflare/workers-types': ^4.20240729.0 - pascal-case@3.1.2: - resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} - path-data-parser@0.1.0: resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} @@ -2534,8 +2907,8 @@ packages: printable-characters@1.0.42: resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} - publint@0.3.12: - resolution: {integrity: sha512-1w3MMtL9iotBjm1mmXtG3Nk06wnq9UhGNRpQ2j6n1Zq7YAD6gnxMMZMIxlRPAydVjVbjSm+n0lhwqsD1m4LD5w==} + publint@0.3.13: + resolution: {integrity: sha512-NC+lph09+BRO9LJgKlIy3WQXyu6/6WDQ0dCA60KALUwdKVf3PfGuC6fY8I+oKB/5kEPh50aOSUz+6yWy1n4EfA==} engines: {node: '>=18'} hasBin: true @@ -2546,6 +2919,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2576,6 +2952,9 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -2585,6 +2964,30 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rolldown-plugin-dts@0.16.11: + resolution: {integrity: sha512-9IQDaPvPqTx3RjG2eQCK5GYZITo203BxKunGI80AGYicu1ySFTUyugicAaTZWRzFWh9DSnzkgNeMNbDWBbSs0w==} + engines: {node: '>=20.18.0'} + peerDependencies: + '@ts-macro/tsc': ^0.3.6 + '@typescript/native-preview': '>=7.0.0-dev.20250601.1' + rolldown: ^1.0.0-beta.9 + typescript: ^5.0.0 + vue-tsc: ~3.1.0 + peerDependenciesMeta: + '@ts-macro/tsc': + optional: true + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.0.0-beta.41: + resolution: {integrity: sha512-U+NPR0Bkg3wm61dteD2L4nAM1U9dtaqVrpDXwC36IKRHpEO/Ubpid4Nijpa2imPchcVNHfxVFwSSMJdwdGFUbg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup-plugin-inject@3.0.2: resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==} deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject. @@ -2600,8 +3003,8 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rollup@4.48.1: - resolution: {integrity: sha512-jVG20NvbhTYDkGAty2/Yh7HK6/q3DGSRH4o8ALKGArmMuaauM9kLfoMZ+WliPwA5+JHr2lTn3g557FxBV87ifg==} + rollup@4.52.3: + resolution: {integrity: sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2618,6 +3021,9 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + selfsigned@2.4.1: resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} engines: {node: '>=10'} @@ -2656,8 +3062,8 @@ packages: simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - sirv@3.0.1: - resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} source-map-js@1.2.1: @@ -2700,8 +3106,8 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} strip-json-comments@2.0.1: @@ -2712,8 +3118,8 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strip-literal@3.0.0: - resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} @@ -2728,8 +3134,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte-check@4.3.1: - resolution: {integrity: sha512-lkh8gff5gpHLjxIV+IaApMxQhTGnir2pNUAqcNgeKkvK5bT/30Ey/nzBxNLDlkztCH4dP7PixkMt9SWEKFPBWg==} + svelte-check@4.3.2: + resolution: {integrity: sha512-71udP5w2kaSTcX8iV0hn3o2FWlabQHhJTJLIQrCqMsrcOeDUO2VhCQKKCA8AMVHSPwdxLEWkUWh9OKxns5PD9w==} engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: @@ -2745,8 +3151,8 @@ packages: svelte: optional: true - svelte-eslint-parser@1.3.1: - resolution: {integrity: sha512-0Iztj5vcOVOVkhy1pbo5uA9r+d3yaVoE5XPc9eABIWDOSJZ2mOsZ4D+t45rphWCOr0uMw3jtSG2fh2e7GvKnPg==} + svelte-eslint-parser@1.3.3: + resolution: {integrity: sha512-oTrDR8Z7Wnguut7QH3YKh7JR19xv1seB/bz4dxU5J/86eJtZOU4eh0/jZq4dy6tAlz/KROxnkRQspv5ZEt7t+Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 @@ -2754,8 +3160,8 @@ packages: svelte: optional: true - svelte-inspect-value@0.9.1: - resolution: {integrity: sha512-2pwVa306EOyuyOmcg/GLhriLKRRprm4hVUDJhmO4WhxEQeKDOIhmkhjwpTWXwQVpj55EUf1F3EH7s2s4QFFvnw==} + svelte-inspect-value@0.9.2: + resolution: {integrity: sha512-uGUYNuYCGwZmKBpEvMCHfl7pY5AXrCWdVVisLzmwB89ySn5i8hS784c2o2/wfTWRMcUmWg9fKbzkCLgBJtqfKQ==} peerDependencies: svelte: ^5.29.0 @@ -2765,14 +3171,14 @@ packages: konva: 8 - 9 svelte: 3 - 4 - svelte2tsx@0.7.42: - resolution: {integrity: sha512-PSNrKS16aVdAajoFjpF5M0t6TA7ha7GcKbBajD9RG3M+vooAuvLnWAGUSC6eJL4zEOVbOWKtcS2BuY4rxPljoA==} + svelte2tsx@0.7.44: + resolution: {integrity: sha512-opuH+bCboss0/ncxnfAO+qt0IAprxc8OqwuC7otafWeO5CHjJ6UAAwvQmu/+xjpCSarX8pQKydXQuoJmbCDcTg==} peerDependencies: svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 typescript: ^4.9.4 || ^5.0.0 - svelte@5.38.3: - resolution: {integrity: sha512-ldbPzKdjUy7IALMBn15jzBM/TNxdXMxKeQZ538zzdABUjLg7e7/OIwnlaMQ+OR6s91W7DbDmJYjxHThHH7r9xA==} + svelte@5.39.6: + resolution: {integrity: sha512-bOJXmuwLNaoqPCTWO8mPu/fwxI5peGE5Efe7oo6Cakpz/G60vsnVF6mxbGODaxMUFUKEnjm6XOwHEqOht6cbvw==} engines: {node: '>=18'} tailwindcss@3.4.17: @@ -2780,8 +3186,12 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - tailwindcss@4.1.12: - resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==} + tailwindcss@4.1.13: + resolution: {integrity: sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==} + + tapable@2.2.3: + resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} + engines: {node: '>=6'} tar-fs@2.1.3: resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==} @@ -2790,6 +3200,10 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} + tar@7.5.1: + resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} + engines: {node: '>=18'} + test-exclude@7.0.1: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} @@ -2807,8 +3221,11 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} tinypool@1.1.1: @@ -2819,8 +3236,8 @@ packages: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} - tinyspy@4.0.3: - resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} to-regex-range@5.0.1: @@ -2831,6 +3248,10 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -2840,6 +3261,28 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tsdown@0.15.5: + resolution: {integrity: sha512-2UP5hDBVYGHnnQSIYtDxMDjePmut7EDpvW5E2kVnjpZ17JgiPvRJPHwk5Dm045bC75+q8yxAlw/CMIELymTWaw==} + engines: {node: '>=20.19.0'} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + publint: ^0.3.0 + typescript: ^5.0.0 + unplugin-lightningcss: ^0.4.0 + unplugin-unused: ^0.5.0 + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + publint: + optional: true + typescript: + optional: true + unplugin-lightningcss: + optional: true + unplugin-unused: + optional: true + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -2850,18 +3293,13 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.41.0: - resolution: {integrity: sha512-n66rzs5OBXW3SFSnZHr2T685q1i4ODm2nulFJhMZBotaTavsS8TrI3d7bDlRSs9yWo7HmyWrN9qDu14Qv7Y0Dw==} + typescript-eslint@8.45.0: + resolution: {integrity: sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - typescript@5.7.2: - resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} - engines: {node: '>=14.17'} - hasBin: true - typescript@5.9.2: resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} @@ -2870,8 +3308,11 @@ packages: ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - undici-types@7.10.0: - resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + unconfig@7.3.3: + resolution: {integrity: sha512-QCkQoOnJF8L107gxfHL0uavn7WD9b3dpBcFX6HtfQYmjw2YzWxGuFQ0N0J6tE9oguCBJn9KOvfqYDCMPHIZrBA==} + + undici-types@7.13.0: + resolution: {integrity: sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==} undici@5.29.0: resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} @@ -2897,8 +3338,8 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite@5.4.19: - resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} + vite@5.4.20: + resolution: {integrity: sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2928,8 +3369,8 @@ packages: terser: optional: true - vite@7.1.3: - resolution: {integrity: sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==} + vite@7.1.7: + resolution: {integrity: sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3090,6 +3531,10 @@ packages: peerDependencies: yjs: ^13.0.0 + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -3099,10 +3544,6 @@ packages: engines: {node: '>= 14'} hasBin: true - yjs@13.6.21: - resolution: {integrity: sha512-/fzzyeCAfr3Qwx1D71zvumm64x+Q5MEFel6EhWlA1IBFxWPb7tei4J2a8CJyjpYHfVrRij5q3RJTK9W2Iqjouw==} - engines: {node: '>=16.0.0', npm: '>=8.0.0'} - yjs@13.6.27: resolution: {integrity: sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} @@ -3114,8 +3555,8 @@ packages: youch@3.3.4: resolution: {integrity: sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==} - zimmerframe@1.1.2: - resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + zimmerframe@1.1.4: + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -3127,17 +3568,25 @@ snapshots: '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 + + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {} - '@babel/parser@7.28.3': + '@babel/parser@7.28.4': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 - '@babel/types@7.28.2': + '@babel/types@7.28.4': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 @@ -3169,6 +3618,22 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@emnapi/core@1.5.0': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.5.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.17.19)': dependencies: esbuild: 0.17.19 @@ -3185,7 +3650,7 @@ snapshots: '@esbuild/aix-ppc64@0.24.2': optional: true - '@esbuild/aix-ppc64@0.25.9': + '@esbuild/aix-ppc64@0.25.10': optional: true '@esbuild/android-arm64@0.17.19': @@ -3197,7 +3662,7 @@ snapshots: '@esbuild/android-arm64@0.24.2': optional: true - '@esbuild/android-arm64@0.25.9': + '@esbuild/android-arm64@0.25.10': optional: true '@esbuild/android-arm@0.17.19': @@ -3209,7 +3674,7 @@ snapshots: '@esbuild/android-arm@0.24.2': optional: true - '@esbuild/android-arm@0.25.9': + '@esbuild/android-arm@0.25.10': optional: true '@esbuild/android-x64@0.17.19': @@ -3221,7 +3686,7 @@ snapshots: '@esbuild/android-x64@0.24.2': optional: true - '@esbuild/android-x64@0.25.9': + '@esbuild/android-x64@0.25.10': optional: true '@esbuild/darwin-arm64@0.17.19': @@ -3233,7 +3698,7 @@ snapshots: '@esbuild/darwin-arm64@0.24.2': optional: true - '@esbuild/darwin-arm64@0.25.9': + '@esbuild/darwin-arm64@0.25.10': optional: true '@esbuild/darwin-x64@0.17.19': @@ -3245,7 +3710,7 @@ snapshots: '@esbuild/darwin-x64@0.24.2': optional: true - '@esbuild/darwin-x64@0.25.9': + '@esbuild/darwin-x64@0.25.10': optional: true '@esbuild/freebsd-arm64@0.17.19': @@ -3257,7 +3722,7 @@ snapshots: '@esbuild/freebsd-arm64@0.24.2': optional: true - '@esbuild/freebsd-arm64@0.25.9': + '@esbuild/freebsd-arm64@0.25.10': optional: true '@esbuild/freebsd-x64@0.17.19': @@ -3269,7 +3734,7 @@ snapshots: '@esbuild/freebsd-x64@0.24.2': optional: true - '@esbuild/freebsd-x64@0.25.9': + '@esbuild/freebsd-x64@0.25.10': optional: true '@esbuild/linux-arm64@0.17.19': @@ -3281,7 +3746,7 @@ snapshots: '@esbuild/linux-arm64@0.24.2': optional: true - '@esbuild/linux-arm64@0.25.9': + '@esbuild/linux-arm64@0.25.10': optional: true '@esbuild/linux-arm@0.17.19': @@ -3293,7 +3758,7 @@ snapshots: '@esbuild/linux-arm@0.24.2': optional: true - '@esbuild/linux-arm@0.25.9': + '@esbuild/linux-arm@0.25.10': optional: true '@esbuild/linux-ia32@0.17.19': @@ -3305,7 +3770,7 @@ snapshots: '@esbuild/linux-ia32@0.24.2': optional: true - '@esbuild/linux-ia32@0.25.9': + '@esbuild/linux-ia32@0.25.10': optional: true '@esbuild/linux-loong64@0.17.19': @@ -3317,7 +3782,7 @@ snapshots: '@esbuild/linux-loong64@0.24.2': optional: true - '@esbuild/linux-loong64@0.25.9': + '@esbuild/linux-loong64@0.25.10': optional: true '@esbuild/linux-mips64el@0.17.19': @@ -3329,7 +3794,7 @@ snapshots: '@esbuild/linux-mips64el@0.24.2': optional: true - '@esbuild/linux-mips64el@0.25.9': + '@esbuild/linux-mips64el@0.25.10': optional: true '@esbuild/linux-ppc64@0.17.19': @@ -3341,7 +3806,7 @@ snapshots: '@esbuild/linux-ppc64@0.24.2': optional: true - '@esbuild/linux-ppc64@0.25.9': + '@esbuild/linux-ppc64@0.25.10': optional: true '@esbuild/linux-riscv64@0.17.19': @@ -3353,7 +3818,7 @@ snapshots: '@esbuild/linux-riscv64@0.24.2': optional: true - '@esbuild/linux-riscv64@0.25.9': + '@esbuild/linux-riscv64@0.25.10': optional: true '@esbuild/linux-s390x@0.17.19': @@ -3365,7 +3830,7 @@ snapshots: '@esbuild/linux-s390x@0.24.2': optional: true - '@esbuild/linux-s390x@0.25.9': + '@esbuild/linux-s390x@0.25.10': optional: true '@esbuild/linux-x64@0.17.19': @@ -3377,13 +3842,13 @@ snapshots: '@esbuild/linux-x64@0.24.2': optional: true - '@esbuild/linux-x64@0.25.9': + '@esbuild/linux-x64@0.25.10': optional: true '@esbuild/netbsd-arm64@0.24.2': optional: true - '@esbuild/netbsd-arm64@0.25.9': + '@esbuild/netbsd-arm64@0.25.10': optional: true '@esbuild/netbsd-x64@0.17.19': @@ -3395,13 +3860,13 @@ snapshots: '@esbuild/netbsd-x64@0.24.2': optional: true - '@esbuild/netbsd-x64@0.25.9': + '@esbuild/netbsd-x64@0.25.10': optional: true '@esbuild/openbsd-arm64@0.24.2': optional: true - '@esbuild/openbsd-arm64@0.25.9': + '@esbuild/openbsd-arm64@0.25.10': optional: true '@esbuild/openbsd-x64@0.17.19': @@ -3413,10 +3878,10 @@ snapshots: '@esbuild/openbsd-x64@0.24.2': optional: true - '@esbuild/openbsd-x64@0.25.9': + '@esbuild/openbsd-x64@0.25.10': optional: true - '@esbuild/openharmony-arm64@0.25.9': + '@esbuild/openharmony-arm64@0.25.10': optional: true '@esbuild/sunos-x64@0.17.19': @@ -3428,7 +3893,7 @@ snapshots: '@esbuild/sunos-x64@0.24.2': optional: true - '@esbuild/sunos-x64@0.25.9': + '@esbuild/sunos-x64@0.25.10': optional: true '@esbuild/win32-arm64@0.17.19': @@ -3440,7 +3905,7 @@ snapshots: '@esbuild/win32-arm64@0.24.2': optional: true - '@esbuild/win32-arm64@0.25.9': + '@esbuild/win32-arm64@0.25.10': optional: true '@esbuild/win32-ia32@0.17.19': @@ -3452,7 +3917,7 @@ snapshots: '@esbuild/win32-ia32@0.24.2': optional: true - '@esbuild/win32-ia32@0.25.9': + '@esbuild/win32-ia32@0.25.10': optional: true '@esbuild/win32-x64@0.17.19': @@ -3464,29 +3929,31 @@ snapshots: '@esbuild/win32-x64@0.24.2': optional: true - '@esbuild/win32-x64@0.25.9': + '@esbuild/win32-x64@0.25.10': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.34.0(jiti@1.21.7))': + '@eslint-community/eslint-utils@4.4.1(eslint@9.36.0(jiti@2.6.0))': dependencies: - eslint: 9.34.0(jiti@1.21.7) + eslint: 9.36.0(jiti@2.6.0) eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.7.0(eslint@9.34.0(jiti@1.21.7))': + '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0(jiti@2.6.0))': dependencies: - eslint: 9.34.0(jiti@1.21.7) + eslint: 9.36.0(jiti@2.6.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/compat@1.3.2(eslint@9.34.0(jiti@1.21.7))': + '@eslint/compat@1.4.0(eslint@9.36.0(jiti@2.6.0))': + dependencies: + '@eslint/core': 0.16.0 optionalDependencies: - eslint: 9.34.0(jiti@1.21.7) + eslint: 9.36.0(jiti@2.6.0) '@eslint/config-array@0.21.0': dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.4.1 + debug: 4.4.3 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -3497,10 +3964,14 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 + '@eslint/core@0.16.0': + dependencies: + '@types/json-schema': 7.0.15 + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.1 + debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -3511,7 +3982,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.34.0': {} + '@eslint/js@9.36.0': {} '@eslint/object-schema@2.1.6': {} @@ -3524,43 +3995,45 @@ snapshots: '@humanfs/core@0.19.1': {} - '@humanfs/node@0.16.6': + '@humanfs/node@0.16.7': dependencies: '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.1 + '@humanwhocodes/retry': 0.4.3 '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/retry@0.3.1': {} - '@humanwhocodes/retry@0.4.3': {} '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + '@istanbuljs/schema@0.1.3': {} '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/remapping@2.3.5': dependencies: '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/resolve-uri@3.1.2': {} @@ -3570,7 +4043,7 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.30': + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 @@ -3584,15 +4057,15 @@ snapshots: dependencies: '@liveblocks/core': 2.24.4 - '@liveblocks/client@3.4.0(@types/json-schema@7.0.15)': + '@liveblocks/client@3.8.0(@types/json-schema@7.0.15)': dependencies: - '@liveblocks/core': 3.4.0(@types/json-schema@7.0.15) + '@liveblocks/core': 3.8.0(@types/json-schema@7.0.15) transitivePeerDependencies: - '@types/json-schema' '@liveblocks/core@2.24.4': {} - '@liveblocks/core@3.4.0(@types/json-schema@7.0.15)': + '@liveblocks/core@3.8.0(@types/json-schema@7.0.15)': dependencies: '@types/json-schema': 7.0.15 @@ -3604,10 +4077,10 @@ snapshots: y-indexeddb: 9.0.12(yjs@13.6.27) yjs: 13.6.27 - '@liveblocks/yjs@3.4.0(@types/json-schema@7.0.15)(yjs@13.6.27)': + '@liveblocks/yjs@3.8.0(@types/json-schema@7.0.15)(yjs@13.6.27)': dependencies: - '@liveblocks/client': 3.4.0(@types/json-schema@7.0.15) - '@liveblocks/core': 3.4.0(@types/json-schema@7.0.15) + '@liveblocks/client': 3.8.0(@types/json-schema@7.0.15) + '@liveblocks/core': 3.8.0(@types/json-schema@7.0.15) '@noble/hashes': 1.8.0 js-base64: 3.7.8 y-indexeddb: 9.0.12(yjs@13.6.27) @@ -3615,6 +4088,13 @@ snapshots: transitivePeerDependencies: - '@types/json-schema' + '@napi-rs/wasm-runtime@1.0.5': + dependencies: + '@emnapi/core': 1.5.0 + '@emnapi/runtime': 1.5.0 + '@tybys/wasm-util': 0.10.1 + optional: true + '@noble/hashes@1.8.0': {} '@nodelib/fs.scandir@2.1.5': @@ -3629,6 +4109,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.18.0 + '@oxc-project/types@0.93.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -3636,249 +4118,374 @@ snapshots: '@publint/pack@0.1.2': {} + '@quansync/fs@0.1.5': + dependencies: + quansync: 0.2.11 + + '@rolldown/binding-android-arm64@1.0.0-beta.41': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-beta.41': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-beta.41': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-beta.41': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.41': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.41': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.41': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.41': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.41': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.41': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.41': + dependencies: + '@napi-rs/wasm-runtime': 1.0.5 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.41': + optional: true + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.41': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.41': + optional: true + + '@rolldown/pluginutils@1.0.0-beta.41': {} + '@rollup/rollup-android-arm-eabi@4.30.1': optional: true - '@rollup/rollup-android-arm-eabi@4.48.1': + '@rollup/rollup-android-arm-eabi@4.52.3': optional: true '@rollup/rollup-android-arm64@4.30.1': optional: true - '@rollup/rollup-android-arm64@4.48.1': + '@rollup/rollup-android-arm64@4.52.3': optional: true '@rollup/rollup-darwin-arm64@4.30.1': optional: true - '@rollup/rollup-darwin-arm64@4.48.1': + '@rollup/rollup-darwin-arm64@4.52.3': optional: true '@rollup/rollup-darwin-x64@4.30.1': optional: true - '@rollup/rollup-darwin-x64@4.48.1': + '@rollup/rollup-darwin-x64@4.52.3': optional: true '@rollup/rollup-freebsd-arm64@4.30.1': optional: true - '@rollup/rollup-freebsd-arm64@4.48.1': + '@rollup/rollup-freebsd-arm64@4.52.3': optional: true '@rollup/rollup-freebsd-x64@4.30.1': optional: true - '@rollup/rollup-freebsd-x64@4.48.1': + '@rollup/rollup-freebsd-x64@4.52.3': optional: true '@rollup/rollup-linux-arm-gnueabihf@4.30.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.48.1': + '@rollup/rollup-linux-arm-gnueabihf@4.52.3': optional: true '@rollup/rollup-linux-arm-musleabihf@4.30.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.48.1': + '@rollup/rollup-linux-arm-musleabihf@4.52.3': optional: true '@rollup/rollup-linux-arm64-gnu@4.30.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.48.1': + '@rollup/rollup-linux-arm64-gnu@4.52.3': optional: true '@rollup/rollup-linux-arm64-musl@4.30.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.48.1': + '@rollup/rollup-linux-arm64-musl@4.52.3': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + '@rollup/rollup-linux-loong64-gnu@4.52.3': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.48.1': + '@rollup/rollup-linux-loongarch64-gnu@4.30.1': optional: true '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.48.1': + '@rollup/rollup-linux-ppc64-gnu@4.52.3': optional: true '@rollup/rollup-linux-riscv64-gnu@4.30.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.48.1': + '@rollup/rollup-linux-riscv64-gnu@4.52.3': optional: true - '@rollup/rollup-linux-riscv64-musl@4.48.1': + '@rollup/rollup-linux-riscv64-musl@4.52.3': optional: true '@rollup/rollup-linux-s390x-gnu@4.30.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.48.1': + '@rollup/rollup-linux-s390x-gnu@4.52.3': optional: true '@rollup/rollup-linux-x64-gnu@4.30.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.48.1': + '@rollup/rollup-linux-x64-gnu@4.52.3': optional: true '@rollup/rollup-linux-x64-musl@4.30.1': optional: true - '@rollup/rollup-linux-x64-musl@4.48.1': + '@rollup/rollup-linux-x64-musl@4.52.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.52.3': optional: true '@rollup/rollup-win32-arm64-msvc@4.30.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.48.1': + '@rollup/rollup-win32-arm64-msvc@4.52.3': optional: true '@rollup/rollup-win32-ia32-msvc@4.30.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.48.1': + '@rollup/rollup-win32-ia32-msvc@4.52.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.52.3': optional: true '@rollup/rollup-win32-x64-msvc@4.30.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.48.1': + '@rollup/rollup-win32-x64-msvc@4.52.3': optional: true '@standard-schema/spec@1.0.0': {} - '@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)': + '@sveltejs/acorn-typescript@1.0.6(acorn@8.15.0)': dependencies: acorn: 8.15.0 - '@sveltejs/adapter-auto@6.1.0(@sveltejs/kit@2.36.2(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)))(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)))': + '@sveltejs/adapter-auto@6.1.0(@sveltejs/kit@2.43.5(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)))(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)))': dependencies: - '@sveltejs/kit': 2.36.2(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)))(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)) + '@sveltejs/kit': 2.43.5(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)))(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)) - '@sveltejs/adapter-cloudflare@4.9.0(@sveltejs/kit@2.36.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)))(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)))(wrangler@3.99.0(@cloudflare/workers-types@4.20241230.0))': + '@sveltejs/adapter-cloudflare@4.9.0(@sveltejs/kit@2.43.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)))(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)))(wrangler@3.99.0(@cloudflare/workers-types@4.20241230.0))': dependencies: '@cloudflare/workers-types': 4.20241230.0 - '@sveltejs/kit': 2.36.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)))(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)) + '@sveltejs/kit': 2.43.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)))(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)) esbuild: 0.24.2 worktop: 0.8.0-next.18 wrangler: 3.99.0(@cloudflare/workers-types@4.20241230.0) - '@sveltejs/kit@2.36.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)))(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0))': + '@sveltejs/kit@2.43.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)))(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1))': dependencies: '@standard-schema/spec': 1.0.0 - '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)) + '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)) '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 - devalue: 5.1.1 + devalue: 5.3.2 esm-env: 1.2.2 kleur: 4.1.5 - magic-string: 0.30.18 + magic-string: 0.30.19 mrmime: 2.0.1 sade: 1.8.1 set-cookie-parser: 2.7.1 - sirv: 3.0.1 - svelte: 5.38.3 - vite: 5.4.19(@types/node@24.3.0) + sirv: 3.0.2 + svelte: 5.39.6 + vite: 5.4.20(@types/node@24.6.0)(lightningcss@1.30.1) - '@sveltejs/kit@2.36.2(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)))(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0))': + '@sveltejs/kit@2.43.5(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)))(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0))': dependencies: '@standard-schema/spec': 1.0.0 - '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.1.3(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)) + '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)) '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 - devalue: 5.1.1 + devalue: 5.3.2 esm-env: 1.2.2 kleur: 4.1.5 - magic-string: 0.30.18 + magic-string: 0.30.19 mrmime: 2.0.1 sade: 1.8.1 set-cookie-parser: 2.7.1 - sirv: 3.0.1 - svelte: 5.38.3 - vite: 7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0) + sirv: 3.0.2 + svelte: 5.39.6 + vite: 7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0) - '@sveltejs/package@2.5.0(svelte@5.38.3)(typescript@5.9.2)': + '@sveltejs/package@2.5.4(svelte@5.39.6)(typescript@5.9.2)': dependencies: chokidar: 4.0.3 kleur: 4.1.5 sade: 1.8.1 semver: 7.7.2 - svelte: 5.38.3 - svelte2tsx: 0.7.42(svelte@5.38.3)(typescript@5.9.2) + svelte: 5.39.6 + svelte2tsx: 0.7.44(svelte@5.39.6)(typescript@5.9.2) transitivePeerDependencies: - typescript - '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)))(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0))': + '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)))(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)) - debug: 4.4.0 - svelte: 5.38.3 - vite: 5.4.19(@types/node@24.3.0) + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)) + debug: 4.4.3 + svelte: 5.39.6 + vite: 5.4.20(@types/node@24.6.0)(lightningcss@1.30.1) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)))(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)))(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.1.3(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)) - debug: 4.4.1 - svelte: 5.38.3 - vite: 7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)) + debug: 4.4.3 + svelte: 5.39.6 + vite: 7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0))': + '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)))(svelte@5.38.3)(vite@5.4.19(@types/node@24.3.0)) + '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)))(svelte@5.39.6)(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)) debug: 4.4.0 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 - svelte: 5.38.3 - vite: 5.4.19(@types/node@24.3.0) - vitefu: 1.0.5(vite@5.4.19(@types/node@24.3.0)) + svelte: 5.39.6 + vite: 5.4.20(@types/node@24.6.0)(lightningcss@1.30.1) + vitefu: 1.0.5(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0))': + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)))(svelte@5.38.3)(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)) - debug: 4.4.1 + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)))(svelte@5.39.6)(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)) + debug: 4.4.3 deepmerge: 4.3.1 - kleur: 4.1.5 - magic-string: 0.30.18 - svelte: 5.38.3 - vite: 7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0) - vitefu: 1.1.1(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)) + magic-string: 0.30.19 + svelte: 5.39.6 + vite: 7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0) + vitefu: 1.1.1(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)) transitivePeerDependencies: - supports-color - '@tailwindcss/typography@0.5.16(tailwindcss@3.4.17)': + '@tailwindcss/node@4.1.13': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.3 + jiti: 2.6.0 + lightningcss: 1.30.1 + magic-string: 0.30.19 + source-map-js: 1.2.1 + tailwindcss: 4.1.13 + + '@tailwindcss/oxide-android-arm64@4.1.13': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.13': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.13': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.13': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.13': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.13': + optional: true + + '@tailwindcss/oxide@4.1.13': + dependencies: + detect-libc: 2.0.4 + tar: 7.5.1 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.13 + '@tailwindcss/oxide-darwin-arm64': 4.1.13 + '@tailwindcss/oxide-darwin-x64': 4.1.13 + '@tailwindcss/oxide-freebsd-x64': 4.1.13 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.13 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.13 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.13 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.13 + '@tailwindcss/oxide-linux-x64-musl': 4.1.13 + '@tailwindcss/oxide-wasm32-wasi': 4.1.13 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.13 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.13 + + '@tailwindcss/typography@0.5.19(tailwindcss@3.4.17)': dependencies: - lodash.castarray: 4.4.0 - lodash.isplainobject: 4.0.6 - lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 tailwindcss: 3.4.17 - '@tailwindcss/typography@0.5.16(tailwindcss@4.1.12)': + '@tailwindcss/typography@0.5.19(tailwindcss@4.1.13)': dependencies: - lodash.castarray: 4.4.0 - lodash.isplainobject: 4.0.6 - lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 - tailwindcss: 4.1.12 + tailwindcss: 4.1.13 + + '@tailwindcss/vite@4.1.13(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0))': + dependencies: + '@tailwindcss/node': 4.1.13 + '@tailwindcss/oxide': 4.1.13 + tailwindcss: 4.1.13 + vite: 7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0) + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true '@types/chai@5.2.2': dependencies: @@ -3901,21 +4508,21 @@ snapshots: '@types/node-forge@1.3.14': dependencies: - '@types/node': 24.3.0 + '@types/node': 24.6.0 - '@types/node@24.3.0': + '@types/node@24.6.0': dependencies: - undici-types: 7.10.0 + undici-types: 7.13.0 - '@typescript-eslint/eslint-plugin@8.41.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2))(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.41.0(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2) - '@typescript-eslint/scope-manager': 8.41.0 - '@typescript-eslint/type-utils': 8.41.0(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2) - '@typescript-eslint/utils': 8.41.0(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.41.0 - eslint: 9.34.0(jiti@1.21.7) + '@typescript-eslint/parser': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/scope-manager': 8.45.0 + '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.45.0 + eslint: 9.36.0(jiti@2.6.0) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -3924,57 +4531,57 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2)': + '@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': dependencies: - '@typescript-eslint/scope-manager': 8.41.0 - '@typescript-eslint/types': 8.41.0 - '@typescript-eslint/typescript-estree': 8.41.0(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.41.0 - debug: 4.4.1 - eslint: 9.34.0(jiti@1.21.7) + '@typescript-eslint/scope-manager': 8.45.0 + '@typescript-eslint/types': 8.45.0 + '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.45.0 + debug: 4.4.3 + eslint: 9.36.0(jiti@2.6.0) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.41.0(typescript@5.9.2)': + '@typescript-eslint/project-service@8.45.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.41.0(typescript@5.9.2) - '@typescript-eslint/types': 8.41.0 - debug: 4.4.1 + '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.2) + '@typescript-eslint/types': 8.45.0 + debug: 4.4.3 typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.41.0': + '@typescript-eslint/scope-manager@8.45.0': dependencies: - '@typescript-eslint/types': 8.41.0 - '@typescript-eslint/visitor-keys': 8.41.0 + '@typescript-eslint/types': 8.45.0 + '@typescript-eslint/visitor-keys': 8.45.0 - '@typescript-eslint/tsconfig-utils@8.41.0(typescript@5.9.2)': + '@typescript-eslint/tsconfig-utils@8.45.0(typescript@5.9.2)': dependencies: typescript: 5.9.2 - '@typescript-eslint/type-utils@8.41.0(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2)': + '@typescript-eslint/type-utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.41.0 - '@typescript-eslint/typescript-estree': 8.41.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.41.0(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2) - debug: 4.4.1 - eslint: 9.34.0(jiti@1.21.7) + '@typescript-eslint/types': 8.45.0 + '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + debug: 4.4.3 + eslint: 9.36.0(jiti@2.6.0) ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.41.0': {} + '@typescript-eslint/types@8.45.0': {} - '@typescript-eslint/typescript-estree@8.41.0(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@8.45.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/project-service': 8.41.0(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.41.0(typescript@5.9.2) - '@typescript-eslint/types': 8.41.0 - '@typescript-eslint/visitor-keys': 8.41.0 - debug: 4.4.1 + '@typescript-eslint/project-service': 8.45.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.2) + '@typescript-eslint/types': 8.45.0 + '@typescript-eslint/visitor-keys': 8.45.0 + debug: 4.4.3 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -3984,38 +4591,38 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.41.0(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2)': + '@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@1.21.7)) - '@typescript-eslint/scope-manager': 8.41.0 - '@typescript-eslint/types': 8.41.0 - '@typescript-eslint/typescript-estree': 8.41.0(typescript@5.9.2) - eslint: 9.34.0(jiti@1.21.7) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.0)) + '@typescript-eslint/scope-manager': 8.45.0 + '@typescript-eslint/types': 8.45.0 + '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) + eslint: 9.36.0(jiti@2.6.0) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.41.0': + '@typescript-eslint/visitor-keys@8.45.0': dependencies: - '@typescript-eslint/types': 8.41.0 + '@typescript-eslint/types': 8.45.0 eslint-visitor-keys: 4.2.1 '@vitest/coverage-v8@3.2.4(vitest@3.2.4)': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 - ast-v8-to-istanbul: 0.3.4 - debug: 4.4.1 + ast-v8-to-istanbul: 0.3.5 + debug: 4.4.3 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.2.0 - magic-string: 0.30.18 + magic-string: 0.30.19 magicast: 0.3.5 std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@24.3.0)(@vitest/ui@3.2.4)(jiti@1.21.7)(yaml@2.7.0) + vitest: 3.2.4(@types/node@24.6.0)(@vitest/ui@3.2.4)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0) transitivePeerDependencies: - supports-color @@ -4027,13 +4634,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0))': + '@vitest/mocker@3.2.4(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 - magic-string: 0.30.18 + magic-string: 0.30.19 optionalDependencies: - vite: 7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0) + vite: 7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0) '@vitest/pretty-format@3.2.4': dependencies: @@ -4043,17 +4650,17 @@ snapshots: dependencies: '@vitest/utils': 3.2.4 pathe: 2.0.3 - strip-literal: 3.0.0 + strip-literal: 3.1.0 '@vitest/snapshot@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 - magic-string: 0.30.18 + magic-string: 0.30.19 pathe: 2.0.3 '@vitest/spy@3.2.4': dependencies: - tinyspy: 4.0.3 + tinyspy: 4.0.4 '@vitest/ui@3.2.4(vitest@3.2.4)': dependencies: @@ -4061,10 +4668,10 @@ snapshots: fflate: 0.8.2 flatted: 3.3.3 pathe: 2.0.3 - sirv: 3.0.1 - tinyglobby: 0.2.14 + sirv: 3.0.2 + tinyglobby: 0.2.15 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@24.3.0)(@vitest/ui@3.2.4)(jiti@1.21.7)(yaml@2.7.0) + vitest: 3.2.4(@types/node@24.6.0)(@vitest/ui@3.2.4)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0) '@vitest/utils@3.2.4': dependencies: @@ -4089,15 +4696,19 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + alien-signals@3.0.0: {} + ansi-regex@5.0.1: {} - ansi-regex@6.2.0: {} + ansi-regex@6.2.2: {} ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - ansi-styles@6.2.1: {} + ansi-styles@6.2.3: {} + + ansis@4.2.0: {} any-promise@1.3.0: {} @@ -4118,16 +4729,21 @@ snapshots: assertion-error@2.0.1: {} - ast-v8-to-istanbul@0.3.4: + ast-kit@2.1.2: dependencies: - '@jridgewell/trace-mapping': 0.3.30 + '@babel/parser': 7.28.4 + pathe: 2.0.3 + + ast-v8-to-istanbul@0.3.5: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 js-tokens: 9.0.1 autoprefixer@10.4.21(postcss@8.4.49): dependencies: - browserslist: 4.25.3 - caniuse-lite: 1.0.30001737 + browserslist: 4.26.2 + caniuse-lite: 1.0.30001746 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -4136,8 +4752,8 @@ snapshots: autoprefixer@10.4.21(postcss@8.5.6): dependencies: - browserslist: 4.25.3 - caniuse-lite: 1.0.30001737 + browserslist: 4.26.2 + caniuse-lite: 1.0.30001746 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -4150,8 +4766,12 @@ snapshots: base64-js@1.5.1: {} + baseline-browser-mapping@2.8.9: {} + binary-extensions@2.3.0: {} + birpc@2.6.1: {} + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -4173,12 +4793,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.25.3: + browserslist@4.26.2: dependencies: - caniuse-lite: 1.0.30001737 - electron-to-chromium: 1.5.209 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.3) + baseline-browser-mapping: 2.8.9 + caniuse-lite: 1.0.30001746 + electron-to-chromium: 1.5.227 + node-releases: 2.0.21 + update-browserslist-db: 1.1.3(browserslist@4.26.2) buffer@5.7.1: dependencies: @@ -4191,7 +4812,7 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001737: {} + caniuse-lite@1.0.30001746: {} canvas@3.2.0: dependencies: @@ -4200,7 +4821,7 @@ snapshots: capnp-ts@0.7.0: dependencies: - debug: 4.4.1 + debug: 4.4.3 tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -4238,6 +4859,8 @@ snapshots: chownr@1.1.4: {} + chownr@3.0.0: {} + clsx@2.1.1: {} color-convert@2.0.1: @@ -4278,7 +4901,7 @@ snapshots: transitivePeerDependencies: - postcss - daisyui@5.0.51: {} + daisyui@5.1.25: {} data-uri-to-buffer@2.0.2: {} @@ -4288,7 +4911,7 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.1: + debug@4.4.3: dependencies: ms: 2.1.3 @@ -4310,24 +4933,35 @@ snapshots: detect-libc@2.0.4: {} - devalue@5.1.1: {} + devalue@5.3.2: {} didyoumean@1.2.2: {} + diff@8.0.2: {} + dlv@1.1.3: {} + dts-resolver@2.1.2: {} + eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.209: {} + electron-to-chromium@1.5.227: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + empathic@2.0.0: {} + end-of-stream@1.4.5: dependencies: once: 1.4.0 + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.3 + es-module-lexer@1.7.0: {} esbuild@0.17.19: @@ -4409,58 +5043,58 @@ snapshots: '@esbuild/win32-ia32': 0.24.2 '@esbuild/win32-x64': 0.24.2 - esbuild@0.25.9: + esbuild@0.25.10: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.9 - '@esbuild/android-arm': 0.25.9 - '@esbuild/android-arm64': 0.25.9 - '@esbuild/android-x64': 0.25.9 - '@esbuild/darwin-arm64': 0.25.9 - '@esbuild/darwin-x64': 0.25.9 - '@esbuild/freebsd-arm64': 0.25.9 - '@esbuild/freebsd-x64': 0.25.9 - '@esbuild/linux-arm': 0.25.9 - '@esbuild/linux-arm64': 0.25.9 - '@esbuild/linux-ia32': 0.25.9 - '@esbuild/linux-loong64': 0.25.9 - '@esbuild/linux-mips64el': 0.25.9 - '@esbuild/linux-ppc64': 0.25.9 - '@esbuild/linux-riscv64': 0.25.9 - '@esbuild/linux-s390x': 0.25.9 - '@esbuild/linux-x64': 0.25.9 - '@esbuild/netbsd-arm64': 0.25.9 - '@esbuild/netbsd-x64': 0.25.9 - '@esbuild/openbsd-arm64': 0.25.9 - '@esbuild/openbsd-x64': 0.25.9 - '@esbuild/openharmony-arm64': 0.25.9 - '@esbuild/sunos-x64': 0.25.9 - '@esbuild/win32-arm64': 0.25.9 - '@esbuild/win32-ia32': 0.25.9 - '@esbuild/win32-x64': 0.25.9 + '@esbuild/aix-ppc64': 0.25.10 + '@esbuild/android-arm': 0.25.10 + '@esbuild/android-arm64': 0.25.10 + '@esbuild/android-x64': 0.25.10 + '@esbuild/darwin-arm64': 0.25.10 + '@esbuild/darwin-x64': 0.25.10 + '@esbuild/freebsd-arm64': 0.25.10 + '@esbuild/freebsd-x64': 0.25.10 + '@esbuild/linux-arm': 0.25.10 + '@esbuild/linux-arm64': 0.25.10 + '@esbuild/linux-ia32': 0.25.10 + '@esbuild/linux-loong64': 0.25.10 + '@esbuild/linux-mips64el': 0.25.10 + '@esbuild/linux-ppc64': 0.25.10 + '@esbuild/linux-riscv64': 0.25.10 + '@esbuild/linux-s390x': 0.25.10 + '@esbuild/linux-x64': 0.25.10 + '@esbuild/netbsd-arm64': 0.25.10 + '@esbuild/netbsd-x64': 0.25.10 + '@esbuild/openbsd-arm64': 0.25.10 + '@esbuild/openbsd-x64': 0.25.10 + '@esbuild/openharmony-arm64': 0.25.10 + '@esbuild/sunos-x64': 0.25.10 + '@esbuild/win32-arm64': 0.25.10 + '@esbuild/win32-ia32': 0.25.10 + '@esbuild/win32-x64': 0.25.10 escalade@3.2.0: {} escape-string-regexp@4.0.0: {} - eslint-compat-utils@0.5.1(eslint@9.34.0(jiti@1.21.7)): + eslint-compat-utils@0.5.1(eslint@9.36.0(jiti@2.6.0)): dependencies: - eslint: 9.34.0(jiti@1.21.7) + eslint: 9.36.0(jiti@2.6.0) semver: 7.6.3 - eslint-config-prettier@10.1.8(eslint@9.34.0(jiti@1.21.7)): + eslint-config-prettier@10.1.8(eslint@9.36.0(jiti@2.6.0)): dependencies: - eslint: 9.34.0(jiti@1.21.7) + eslint: 9.36.0(jiti@2.6.0) - eslint-config-prettier@9.1.2(eslint@9.34.0(jiti@1.21.7)): + eslint-config-prettier@9.1.2(eslint@9.36.0(jiti@2.6.0)): dependencies: - eslint: 9.34.0(jiti@1.21.7) + eslint: 9.36.0(jiti@2.6.0) - eslint-plugin-svelte@2.46.1(eslint@9.34.0(jiti@1.21.7))(svelte@5.38.3): + eslint-plugin-svelte@2.46.1(eslint@9.36.0(jiti@2.6.0))(svelte@5.39.6): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.34.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.36.0(jiti@2.6.0)) '@jridgewell/sourcemap-codec': 1.5.0 - eslint: 9.34.0(jiti@1.21.7) - eslint-compat-utils: 0.5.1(eslint@9.34.0(jiti@1.21.7)) + eslint: 9.36.0(jiti@2.6.0) + eslint-compat-utils: 0.5.1(eslint@9.36.0(jiti@2.6.0)) esutils: 2.0.3 known-css-properties: 0.35.0 postcss: 8.4.49 @@ -4468,27 +5102,27 @@ snapshots: postcss-safe-parser: 6.0.0(postcss@8.4.49) postcss-selector-parser: 6.1.2 semver: 7.6.3 - svelte-eslint-parser: 0.43.0(svelte@5.38.3) + svelte-eslint-parser: 0.43.0(svelte@5.39.6) optionalDependencies: - svelte: 5.38.3 + svelte: 5.39.6 transitivePeerDependencies: - ts-node - eslint-plugin-svelte@3.11.0(eslint@9.34.0(jiti@1.21.7))(svelte@5.38.3): + eslint-plugin-svelte@3.12.4(eslint@9.36.0(jiti@2.6.0))(svelte@5.39.6): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.0)) '@jridgewell/sourcemap-codec': 1.5.5 - eslint: 9.34.0(jiti@1.21.7) + eslint: 9.36.0(jiti@2.6.0) esutils: 2.0.3 - globals: 16.3.0 + globals: 16.4.0 known-css-properties: 0.37.0 postcss: 8.5.6 postcss-load-config: 3.1.4(postcss@8.5.6) postcss-safe-parser: 7.0.1(postcss@8.5.6) semver: 7.7.2 - svelte-eslint-parser: 1.3.1(svelte@5.38.3) + svelte-eslint-parser: 1.3.3(svelte@5.39.6) optionalDependencies: - svelte: 5.38.3 + svelte: 5.39.6 transitivePeerDependencies: - ts-node @@ -4506,17 +5140,17 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.34.0(jiti@1.21.7): + eslint@9.36.0(jiti@2.6.0): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.0)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.0 '@eslint/config-helpers': 0.3.1 '@eslint/core': 0.15.2 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.34.0 + '@eslint/js': 9.36.0 '@eslint/plugin-kit': 0.3.5 - '@humanfs/node': 0.16.6 + '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 @@ -4524,7 +5158,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.1 + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -4544,7 +5178,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 1.21.7 + jiti: 2.6.0 transitivePeerDependencies: - supports-color @@ -4655,6 +5289,10 @@ snapshots: data-uri-to-buffer: 2.0.2 source-map: 0.6.1 + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + github-from-package@0.0.0: {} glob-parent@5.1.2: @@ -4680,7 +5318,9 @@ snapshots: globals@15.15.0: {} - globals@16.3.0: {} + globals@16.4.0: {} + + graceful-fs@4.2.11: {} graphemer@1.4.0: {} @@ -4694,6 +5334,8 @@ snapshots: highlight.js@11.11.1: {} + hookable@5.5.3: {} + html-escaper@2.0.2: {} ieee754@1.2.1: {} @@ -4749,8 +5391,8 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: - '@jridgewell/trace-mapping': 0.3.30 - debug: 4.4.1 + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -4770,6 +5412,8 @@ snapshots: jiti@1.21.7: {} + jiti@2.6.0: {} + js-base64@3.7.7: {} js-base64@3.7.8: {} @@ -4780,6 +5424,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -4811,6 +5457,51 @@ snapshots: dependencies: isomorphic.js: 0.2.5 + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + lilconfig@2.1.0: {} lilconfig@3.1.3: {} @@ -4823,20 +5514,12 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash.castarray@4.4.0: {} - lodash.debounce@4.0.8: {} - lodash.isplainobject@4.0.6: {} - lodash.merge@4.6.2: {} loupe@3.2.1: {} - lower-case@2.0.2: - dependencies: - tslib: 2.8.1 - lru-cache@10.4.3: {} magic-string@0.25.9: @@ -4847,14 +5530,14 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magic-string@0.30.18: + magic-string@0.30.19: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 magicast@0.3.5: dependencies: - '@babel/parser': 7.28.3 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 source-map-js: 1.2.1 make-dir@4.0.0: @@ -4911,6 +5594,10 @@ snapshots: minipass@7.1.2: {} + minizlib@3.1.0: + dependencies: + minipass: 7.1.2 + mkdirp-classic@0.5.3: {} mri@1.2.0: {} @@ -4933,17 +5620,12 @@ snapshots: nanoid@5.0.9: {} - nanoid@5.1.5: {} + nanoid@5.1.6: {} napi-build-utils@2.0.0: {} natural-compare@1.4.0: {} - no-case@3.0.4: - dependencies: - lower-case: 2.0.2 - tslib: 2.8.1 - node-abi@3.75.0: dependencies: semver: 7.7.2 @@ -4952,7 +5634,7 @@ snapshots: node-forge@1.3.1: {} - node-releases@2.0.19: {} + node-releases@2.0.21: {} normalize-path@3.0.0: {} @@ -4996,12 +5678,7 @@ snapshots: partyserver@0.0.59(@cloudflare/workers-types@4.20241230.0): dependencies: '@cloudflare/workers-types': 4.20241230.0 - nanoid: 5.1.5 - - pascal-case@3.1.2: - dependencies: - no-case: 3.0.4 - tslib: 2.8.1 + nanoid: 5.1.6 path-data-parser@0.1.0: {} @@ -5141,22 +5818,22 @@ snapshots: prelude-ls@1.2.1: {} - prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.38.3): + prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.39.6): dependencies: prettier: 3.6.2 - svelte: 5.38.3 + svelte: 5.39.6 - prettier-plugin-tailwindcss@0.6.14(prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.38.3))(prettier@3.6.2): + prettier-plugin-tailwindcss@0.6.14(prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.39.6))(prettier@3.6.2): dependencies: prettier: 3.6.2 optionalDependencies: - prettier-plugin-svelte: 3.4.0(prettier@3.6.2)(svelte@5.38.3) + prettier-plugin-svelte: 3.4.0(prettier@3.6.2)(svelte@5.39.6) prettier@3.6.2: {} printable-characters@1.0.42: {} - publint@0.3.12: + publint@0.3.13: dependencies: '@publint/pack': 0.1.2 package-manager-detector: 1.3.0 @@ -5170,6 +5847,8 @@ snapshots: punycode@2.3.1: {} + quansync@0.2.11: {} + queue-microtask@1.2.3: {} rc@1.2.8: @@ -5199,6 +5878,8 @@ snapshots: resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -5207,6 +5888,45 @@ snapshots: reusify@1.0.4: {} + rolldown-plugin-dts@0.16.11(rolldown@1.0.0-beta.41)(typescript@5.9.2): + dependencies: + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + ast-kit: 2.1.2 + birpc: 2.6.1 + debug: 4.4.3 + dts-resolver: 2.1.2 + get-tsconfig: 4.10.1 + magic-string: 0.30.19 + rolldown: 1.0.0-beta.41 + optionalDependencies: + typescript: 5.9.2 + transitivePeerDependencies: + - oxc-resolver + - supports-color + + rolldown@1.0.0-beta.41: + dependencies: + '@oxc-project/types': 0.93.0 + '@rolldown/pluginutils': 1.0.0-beta.41 + ansis: 4.2.0 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-beta.41 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.41 + '@rolldown/binding-darwin-x64': 1.0.0-beta.41 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.41 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.41 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.41 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.41 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.41 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.41 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.41 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.41 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.41 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.41 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.41 + rollup-plugin-inject@3.0.2: dependencies: estree-walker: 0.6.1 @@ -5246,30 +5966,32 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.30.1 fsevents: 2.3.3 - rollup@4.48.1: + rollup@4.52.3: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.48.1 - '@rollup/rollup-android-arm64': 4.48.1 - '@rollup/rollup-darwin-arm64': 4.48.1 - '@rollup/rollup-darwin-x64': 4.48.1 - '@rollup/rollup-freebsd-arm64': 4.48.1 - '@rollup/rollup-freebsd-x64': 4.48.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.48.1 - '@rollup/rollup-linux-arm-musleabihf': 4.48.1 - '@rollup/rollup-linux-arm64-gnu': 4.48.1 - '@rollup/rollup-linux-arm64-musl': 4.48.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.48.1 - '@rollup/rollup-linux-ppc64-gnu': 4.48.1 - '@rollup/rollup-linux-riscv64-gnu': 4.48.1 - '@rollup/rollup-linux-riscv64-musl': 4.48.1 - '@rollup/rollup-linux-s390x-gnu': 4.48.1 - '@rollup/rollup-linux-x64-gnu': 4.48.1 - '@rollup/rollup-linux-x64-musl': 4.48.1 - '@rollup/rollup-win32-arm64-msvc': 4.48.1 - '@rollup/rollup-win32-ia32-msvc': 4.48.1 - '@rollup/rollup-win32-x64-msvc': 4.48.1 + '@rollup/rollup-android-arm-eabi': 4.52.3 + '@rollup/rollup-android-arm64': 4.52.3 + '@rollup/rollup-darwin-arm64': 4.52.3 + '@rollup/rollup-darwin-x64': 4.52.3 + '@rollup/rollup-freebsd-arm64': 4.52.3 + '@rollup/rollup-freebsd-x64': 4.52.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.3 + '@rollup/rollup-linux-arm-musleabihf': 4.52.3 + '@rollup/rollup-linux-arm64-gnu': 4.52.3 + '@rollup/rollup-linux-arm64-musl': 4.52.3 + '@rollup/rollup-linux-loong64-gnu': 4.52.3 + '@rollup/rollup-linux-ppc64-gnu': 4.52.3 + '@rollup/rollup-linux-riscv64-gnu': 4.52.3 + '@rollup/rollup-linux-riscv64-musl': 4.52.3 + '@rollup/rollup-linux-s390x-gnu': 4.52.3 + '@rollup/rollup-linux-x64-gnu': 4.52.3 + '@rollup/rollup-linux-x64-musl': 4.52.3 + '@rollup/rollup-openharmony-arm64': 4.52.3 + '@rollup/rollup-win32-arm64-msvc': 4.52.3 + '@rollup/rollup-win32-ia32-msvc': 4.52.3 + '@rollup/rollup-win32-x64-gnu': 4.52.3 + '@rollup/rollup-win32-x64-msvc': 4.52.3 fsevents: 2.3.3 roughjs@4.6.6: @@ -5289,6 +6011,8 @@ snapshots: safe-buffer@5.2.1: {} + scule@1.3.0: {} + selfsigned@2.4.1: dependencies: '@types/node-forge': 1.3.14 @@ -5318,7 +6042,7 @@ snapshots: once: 1.4.0 simple-concat: 1.0.1 - sirv@3.0.1: + sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 mrmime: 2.0.1 @@ -5351,7 +6075,7 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 string_decoder@1.3.0: dependencies: @@ -5361,15 +6085,15 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + strip-ansi@7.1.2: dependencies: - ansi-regex: 6.2.0 + ansi-regex: 6.2.2 strip-json-comments@2.0.1: {} strip-json-comments@3.1.1: {} - strip-literal@3.0.0: + strip-literal@3.1.0: dependencies: js-tokens: 9.0.1 @@ -5389,19 +6113,19 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.3.1(picomatch@4.0.3)(svelte@5.38.3)(typescript@5.9.2): + svelte-check@4.3.2(picomatch@4.0.3)(svelte@5.39.6)(typescript@5.9.2): dependencies: - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 fdir: 6.5.0(picomatch@4.0.3) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.38.3 + svelte: 5.39.6 typescript: 5.9.2 transitivePeerDependencies: - picomatch - svelte-eslint-parser@0.43.0(svelte@5.38.3): + svelte-eslint-parser@0.43.0(svelte@5.39.6): dependencies: eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 @@ -5409,9 +6133,9 @@ snapshots: postcss: 8.4.49 postcss-scss: 4.0.9(postcss@8.4.49) optionalDependencies: - svelte: 5.38.3 + svelte: 5.39.6 - svelte-eslint-parser@1.3.1(svelte@5.38.3): + svelte-eslint-parser@1.3.3(svelte@5.39.6): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -5420,34 +6144,34 @@ snapshots: postcss-scss: 4.0.9(postcss@8.5.6) postcss-selector-parser: 7.1.0 optionalDependencies: - svelte: 5.38.3 + svelte: 5.39.6 - svelte-inspect-value@0.9.1(svelte@5.38.3): + svelte-inspect-value@0.9.2(svelte@5.39.6): dependencies: esm-env: 1.2.2 fast-deep-equal: 3.1.3 highlight.js: 11.11.1 many-keys-map: 2.0.1 memoize: 10.1.0 - svelte: 5.38.3 + svelte: 5.39.6 - svelte-konva@0.3.1(konva@9.3.22)(svelte@5.38.3): + svelte-konva@0.3.1(konva@9.3.22)(svelte@5.39.6): dependencies: konva: 9.3.22 - svelte: 5.38.3 + svelte: 5.39.6 - svelte2tsx@0.7.42(svelte@5.38.3)(typescript@5.9.2): + svelte2tsx@0.7.44(svelte@5.39.6)(typescript@5.9.2): dependencies: dedent-js: 1.0.1 - pascal-case: 3.1.2 - svelte: 5.38.3 + scule: 1.3.0 + svelte: 5.39.6 typescript: 5.9.2 - svelte@5.38.3: + svelte@5.39.6: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 - '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) + '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) '@types/estree': 1.0.8 acorn: 8.15.0 aria-query: 5.3.2 @@ -5457,8 +6181,8 @@ snapshots: esrap: 2.1.0 is-reference: 3.0.3 locate-character: 3.0.0 - magic-string: 0.30.18 - zimmerframe: 1.1.2 + magic-string: 0.30.19 + zimmerframe: 1.1.4 tailwindcss@3.4.17: dependencies: @@ -5487,7 +6211,9 @@ snapshots: transitivePeerDependencies: - ts-node - tailwindcss@4.1.12: {} + tailwindcss@4.1.13: {} + + tapable@2.2.3: {} tar-fs@2.1.3: dependencies: @@ -5504,6 +6230,14 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + tar@7.5.1: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 + test-exclude@7.0.1: dependencies: '@istanbuljs/schema': 0.1.3 @@ -5522,7 +6256,9 @@ snapshots: tinyexec@0.3.2: {} - tinyglobby@0.2.14: + tinyexec@1.0.1: {} + + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 @@ -5531,7 +6267,7 @@ snapshots: tinyrainbow@2.0.0: {} - tinyspy@4.0.3: {} + tinyspy@4.0.4: {} to-regex-range@5.0.1: dependencies: @@ -5539,12 +6275,40 @@ snapshots: totalist@3.0.1: {} + tree-kill@1.2.2: {} + ts-api-utils@2.1.0(typescript@5.9.2): dependencies: typescript: 5.9.2 ts-interface-checker@0.1.13: {} + tsdown@0.15.5(publint@0.3.13)(typescript@5.9.2): + dependencies: + ansis: 4.2.0 + cac: 6.7.14 + chokidar: 4.0.3 + debug: 4.4.3 + diff: 8.0.2 + empathic: 2.0.0 + hookable: 5.5.3 + rolldown: 1.0.0-beta.41 + rolldown-plugin-dts: 0.16.11(rolldown@1.0.0-beta.41)(typescript@5.9.2) + semver: 7.7.2 + tinyexec: 1.0.1 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 + unconfig: 7.3.3 + optionalDependencies: + publint: 0.3.13 + typescript: 5.9.2 + transitivePeerDependencies: + - '@ts-macro/tsc' + - '@typescript/native-preview' + - oxc-resolver + - supports-color + - vue-tsc + tslib@2.8.1: {} tunnel-agent@0.6.0: @@ -5555,24 +6319,29 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.41.0(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2): + typescript-eslint@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.41.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2))(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2) - '@typescript-eslint/parser': 8.41.0(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2) - '@typescript-eslint/typescript-estree': 8.41.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.41.0(eslint@9.34.0(jiti@1.21.7))(typescript@5.9.2) - eslint: 9.34.0(jiti@1.21.7) + '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/parser': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + eslint: 9.36.0(jiti@2.6.0) typescript: 5.9.2 transitivePeerDependencies: - supports-color - typescript@5.7.2: {} - typescript@5.9.2: {} ufo@1.6.1: {} - undici-types@7.10.0: {} + unconfig@7.3.3: + dependencies: + '@quansync/fs': 0.1.5 + defu: 6.1.4 + jiti: 2.6.0 + quansync: 0.2.11 + + undici-types@7.13.0: {} undici@5.29.0: dependencies: @@ -5585,9 +6354,9 @@ snapshots: pathe: 1.1.2 ufo: 1.6.1 - update-browserslist-db@1.1.3(browserslist@4.25.3): + update-browserslist-db@1.1.3(browserslist@4.26.2): dependencies: - browserslist: 4.25.3 + browserslist: 4.26.2 escalade: 3.2.0 picocolors: 1.1.1 @@ -5597,13 +6366,13 @@ snapshots: util-deprecate@1.0.2: {} - vite-node@3.2.4(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0): + vite-node@3.2.4(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0): dependencies: cac: 6.7.14 - debug: 4.4.1 + debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0) + vite: 7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0) transitivePeerDependencies: - '@types/node' - jiti @@ -5618,64 +6387,66 @@ snapshots: - tsx - yaml - vite@5.4.19(@types/node@24.3.0): + vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1): dependencies: esbuild: 0.21.5 postcss: 8.4.49 rollup: 4.30.1 optionalDependencies: - '@types/node': 24.3.0 + '@types/node': 24.6.0 fsevents: 2.3.3 + lightningcss: 1.30.1 - vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0): + vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0): dependencies: - esbuild: 0.25.9 + esbuild: 0.25.10 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.48.1 - tinyglobby: 0.2.14 + rollup: 4.52.3 + tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.3.0 + '@types/node': 24.6.0 fsevents: 2.3.3 - jiti: 1.21.7 + jiti: 2.6.0 + lightningcss: 1.30.1 yaml: 2.7.0 - vitefu@1.0.5(vite@5.4.19(@types/node@24.3.0)): + vitefu@1.0.5(vite@5.4.20(@types/node@24.6.0)(lightningcss@1.30.1)): optionalDependencies: - vite: 5.4.19(@types/node@24.3.0) + vite: 5.4.20(@types/node@24.6.0)(lightningcss@1.30.1) - vitefu@1.1.1(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)): + vitefu@1.1.1(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)): optionalDependencies: - vite: 7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0) + vite: 7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0) - vitest@3.2.4(@types/node@24.3.0)(@vitest/ui@3.2.4)(jiti@1.21.7)(yaml@2.7.0): + vitest@3.2.4(@types/node@24.6.0)(@vitest/ui@3.2.4)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0)) + '@vitest/mocker': 3.2.4(vite@7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 chai: 5.3.3 - debug: 4.4.1 + debug: 4.4.3 expect-type: 1.2.2 - magic-string: 0.30.18 + magic-string: 0.30.19 pathe: 2.0.3 picomatch: 4.0.3 std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.3(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0) - vite-node: 3.2.4(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.7.0) + vite: 7.1.7(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0) + vite-node: 3.2.4(@types/node@24.6.0)(jiti@2.6.0)(lightningcss@1.30.1)(yaml@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 24.3.0 + '@types/node': 24.6.0 '@vitest/ui': 3.2.4(vitest@3.2.4) transitivePeerDependencies: - jiti @@ -5750,9 +6521,9 @@ snapshots: wrap-ansi@8.1.0: dependencies: - ansi-styles: 6.2.1 + ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 wrappy@1.0.2: {} @@ -5765,16 +6536,6 @@ snapshots: lib0: 0.2.114 yjs: 13.6.27 - y-partyserver@0.0.31(@cloudflare/workers-types@4.20241230.0)(partyserver@0.0.59(@cloudflare/workers-types@4.20241230.0))(yjs@13.6.21): - dependencies: - '@cloudflare/workers-types': 4.20241230.0 - lib0: 0.2.99 - lodash.debounce: 4.0.8 - nanoid: 5.0.9 - partyserver: 0.0.59(@cloudflare/workers-types@4.20241230.0) - y-protocols: 1.0.6(yjs@13.6.21) - yjs: 13.6.21 - y-partyserver@0.0.31(@cloudflare/workers-types@4.20241230.0)(partyserver@0.0.59(@cloudflare/workers-types@4.20241230.0))(yjs@13.6.27): dependencies: '@cloudflare/workers-types': 4.20241230.0 @@ -5785,24 +6546,17 @@ snapshots: y-protocols: 1.0.6(yjs@13.6.27) yjs: 13.6.27 - y-protocols@1.0.6(yjs@13.6.21): - dependencies: - lib0: 0.2.99 - yjs: 13.6.21 - y-protocols@1.0.6(yjs@13.6.27): dependencies: lib0: 0.2.114 yjs: 13.6.27 + yallist@5.0.0: {} + yaml@1.10.2: {} yaml@2.7.0: {} - yjs@13.6.21: - dependencies: - lib0: 0.2.99 - yjs@13.6.27: dependencies: lib0: 0.2.114 @@ -5815,6 +6569,6 @@ snapshots: mustache: 4.2.0 stacktracey: 2.1.8 - zimmerframe@1.1.2: {} + zimmerframe@1.1.4: {} zod@3.25.76: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1b7ca4f..8d9fcea 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,5 @@ packages: - "demo" - - "package" + - "core" + - "svelte" + - "vanilla" diff --git a/package/.gitignore b/svelte/.gitignore similarity index 100% rename from package/.gitignore rename to svelte/.gitignore diff --git a/package/.npmrc b/svelte/.npmrc similarity index 100% rename from package/.npmrc rename to svelte/.npmrc diff --git a/package/.prettierignore b/svelte/.prettierignore similarity index 100% rename from package/.prettierignore rename to svelte/.prettierignore diff --git a/package/.prettierrc b/svelte/.prettierrc similarity index 100% rename from package/.prettierrc rename to svelte/.prettierrc diff --git a/package/debug-variant.test.ts b/svelte/debug-variant.test.ts similarity index 100% rename from package/debug-variant.test.ts rename to svelte/debug-variant.test.ts diff --git a/package/eslint.config.js b/svelte/eslint.config.js similarity index 100% rename from package/eslint.config.js rename to svelte/eslint.config.js diff --git a/package/package.json b/svelte/package.json similarity index 60% rename from package/package.json rename to svelte/package.json index c8e440d..7208e4b 100644 --- a/package/package.json +++ b/svelte/package.json @@ -1,7 +1,8 @@ { - "name": "syncrostate", - "version": "0.0.8", + "name": "@syncrostate/svelte", + "version": "0.0.11", "description": "A reactive and real-time state management solution integrating Yjs with Svelte 5's runes for seamless collaborative applications", + "license": "MIT", "publishConfig": { "access": "public" }, @@ -16,7 +17,10 @@ "format": "prettier --write .", "lint": "prettier --check . && eslint .", "test:unit": "vitest --ui --coverage", - "test": "vitest --run" + "test": "vitest --run", + "publish:before": "node publish/fixVersionBeforePublication.js", + "publish:after": "node publish/fixVersionAfterPublication.js", + "patch": "npm version patch && npm run build && npm run publish:before && npm publish && npm run publish:after" }, "files": [ "dist", @@ -40,38 +44,37 @@ "svelte": "^5.0.0" }, "devDependencies": { - "@liveblocks/client": "^3.4.0", - "@liveblocks/yjs": "^3.4.0", + "@liveblocks/client": "^3.8.0", + "@liveblocks/yjs": "^3.8.0", "@sveltejs/adapter-auto": "^6.1.0", - "@sveltejs/kit": "^2.36.2", - "@sveltejs/package": "^2.5.0", - "@sveltejs/vite-plugin-svelte": "^6.1.3", - "@tailwindcss/typography": "^0.5.16", + "@sveltejs/kit": "^2.43.5", + "@sveltejs/package": "^2.5.4", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.1.13", "@types/eslint": "^9.6.1", "@vitest/coverage-v8": "^3.2.4", "@vitest/ui": "^3.2.4", "autoprefixer": "^10.4.21", - "daisyui": "^5.0.51", - "eslint": "^9.34.0", + "daisyui": "^5.1.25", + "eslint": "^9.36.0", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-svelte": "^3.11.0", - "globals": "^16.3.0", + "eslint-plugin-svelte": "^3.12.4", + "globals": "^16.4.0", "prettier": "^3.6.2", "prettier-plugin-svelte": "^3.4.0", - "publint": "^0.3.12", - "svelte": "^5.38.3", - "svelte-check": "^4.3.1", - "svelte-inspect-value": "^0.9.1", - "tailwindcss": "^4.1.12", + "publint": "^0.3.13", + "svelte": "^5.39.6", + "svelte-check": "^4.3.2", + "svelte-inspect-value": "^0.9.2", + "tailwindcss": "^4.1.13", "typescript": "^5.9.2", - "typescript-eslint": "^8.41.0", - "vite": "^7.1.3", + "typescript-eslint": "^8.45.0", + "vite": "^7.1.7", "vitest": "^3.2.4" }, "dependencies": { "esm-env": "^1.2.2", - "highlight.js": "^11.11.1", - "y-protocols": "^1.0.6", - "yjs": "^13.6.27" + "@syncrostate/core": "workspace:*" } } diff --git a/package/pnpm-lock.yaml b/svelte/pnpm-lock.yaml similarity index 100% rename from package/pnpm-lock.yaml rename to svelte/pnpm-lock.yaml diff --git a/svelte/publish/fixVersionAfterPublication.js b/svelte/publish/fixVersionAfterPublication.js new file mode 100644 index 0000000..d891557 --- /dev/null +++ b/svelte/publish/fixVersionAfterPublication.js @@ -0,0 +1,5 @@ +import fs from 'fs'; + +let packageJson = fs.readFileSync('package.json', 'utf8'); +packageJson = packageJson.replace('latest', 'workspace:*'); +fs.writeFileSync('package.json', packageJson); diff --git a/svelte/publish/fixVersionBeforePublication.js b/svelte/publish/fixVersionBeforePublication.js new file mode 100644 index 0000000..4505b07 --- /dev/null +++ b/svelte/publish/fixVersionBeforePublication.js @@ -0,0 +1,5 @@ +import fs from 'fs'; + +let packageJson = fs.readFileSync('package.json', 'utf8'); +packageJson = packageJson.replace('workspace:*', 'latest'); +fs.writeFileSync('package.json', packageJson); diff --git a/svelte/src/app.css b/svelte/src/app.css new file mode 100644 index 0000000..39855e7 --- /dev/null +++ b/svelte/src/app.css @@ -0,0 +1,2 @@ +@import 'tailwindcss'; +@plugin "daisyui"; diff --git a/package/src/app.d.ts b/svelte/src/app.d.ts similarity index 100% rename from package/src/app.d.ts rename to svelte/src/app.d.ts diff --git a/package/src/app.html b/svelte/src/app.html similarity index 100% rename from package/src/app.html rename to svelte/src/app.html diff --git a/svelte/src/lib/index.ts b/svelte/src/lib/index.ts new file mode 100644 index 0000000..2d528aa --- /dev/null +++ b/svelte/src/lib/index.ts @@ -0,0 +1,22 @@ +import { + syncroState as coreSyncroState, + type SyncroStateOptions, + usePresence as coreUsePresence, + type ObjectShape +} from '@syncrostate/core'; +import { getContext } from 'svelte'; +import { SvelteProvider } from './provider.svelte.js'; +export { y } from '@syncrostate/core'; + +export const syncroState = ( + options: Omit, 'provider'> +) => { + return coreSyncroState({ + ...options, + provider: new SvelteProvider() + }); +}; + +export const usePresence = () => { + return coreUsePresence(getContext); +}; diff --git a/svelte/src/lib/provider.svelte.ts b/svelte/src/lib/provider.svelte.ts new file mode 100644 index 0000000..7676a28 --- /dev/null +++ b/svelte/src/lib/provider.svelte.ts @@ -0,0 +1,48 @@ +import { SvelteDate, SvelteMap, SvelteSet } from 'svelte/reactivity'; +import { onMount, setContext, untrack } from 'svelte'; +import { onDestroy } from 'svelte'; +import { getContext } from 'svelte'; +import type { Provider } from '@syncrostate/core'; + +const svelteState = (initialValue: T) => { + let state = $state(initialValue); + return { + get value() { + return state; + }, + set value(value: T) { + state = value; + } + }; +}; + +const svelteEffect = (cb: () => void, deps?: any[]) => { + $effect(() => { + deps; + untrack(() => { + cb(); + }); + }); +}; + +const svelteDerived = (cb: () => T) => { + const derivedValue = $derived.by(cb); + return { + get value() { + return derivedValue; + } + }; +}; + +export class SvelteProvider implements Provider { + state = svelteState; + effect = svelteEffect; + derived = svelteDerived; + Map = SvelteMap; + Set = SvelteSet; + onMount = onMount; + onDestroy = onDestroy; + Date = SvelteDate as any; + getContext = getContext; + setContext = setContext; +} diff --git a/package/src/routes/+layout.svelte b/svelte/src/routes/+layout.svelte similarity index 100% rename from package/src/routes/+layout.svelte rename to svelte/src/routes/+layout.svelte diff --git a/package/src/routes/+page.svelte b/svelte/src/routes/+page.svelte similarity index 86% rename from package/src/routes/+page.svelte rename to svelte/src/routes/+page.svelte index 714225f..dc8b11b 100644 --- a/package/src/routes/+page.svelte +++ b/svelte/src/routes/+page.svelte @@ -1,24 +1,13 @@ {#if document.getState?.().synced} @@ -177,7 +166,7 @@

- +
@@ -211,13 +200,7 @@
- -
-
- -
-
- +
diff --git a/package/src/routes/proxy/+page.svelte b/svelte/src/routes/proxy/+page.svelte similarity index 100% rename from package/src/routes/proxy/+page.svelte rename to svelte/src/routes/proxy/+page.svelte diff --git a/package/src/routes/proxy/Test.svelte b/svelte/src/routes/proxy/Test.svelte similarity index 100% rename from package/src/routes/proxy/Test.svelte rename to svelte/src/routes/proxy/Test.svelte diff --git a/package/src/routes/proxy/proxy.svelte.ts b/svelte/src/routes/proxy/proxy.svelte.ts similarity index 100% rename from package/src/routes/proxy/proxy.svelte.ts rename to svelte/src/routes/proxy/proxy.svelte.ts diff --git a/package/static/favicon.png b/svelte/static/favicon.png similarity index 100% rename from package/static/favicon.png rename to svelte/static/favicon.png diff --git a/package/svelte.config.js b/svelte/svelte.config.js similarity index 100% rename from package/svelte.config.js rename to svelte/svelte.config.js diff --git a/package/tsconfig.json b/svelte/tsconfig.json similarity index 100% rename from package/tsconfig.json rename to svelte/tsconfig.json diff --git a/package/vite.config.ts b/svelte/vite.config.ts similarity index 92% rename from package/vite.config.ts rename to svelte/vite.config.ts index abe8b31..8e95007 100644 --- a/package/vite.config.ts +++ b/svelte/vite.config.ts @@ -1,5 +1,6 @@ import { defineConfig } from 'vitest/config'; import { sveltekit } from '@sveltejs/kit/vite'; +import tailwindcss from '@tailwindcss/vite'; export default defineConfig({ plugins: [sveltekit()], diff --git a/vanilla/.gitignore b/vanilla/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/vanilla/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/vanilla/README.md b/vanilla/README.md new file mode 100644 index 0000000..4cb280c --- /dev/null +++ b/vanilla/README.md @@ -0,0 +1,15 @@ +# vanilla + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.2.13. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/vanilla/lib/index.ts b/vanilla/lib/index.ts new file mode 100644 index 0000000..285011d --- /dev/null +++ b/vanilla/lib/index.ts @@ -0,0 +1,18 @@ +import { + syncroState as coreSyncroState, + type SyncroStateOptions, + usePresence as coreUsePresence, + type ObjectShape, +} from "@syncrostate/core"; +import { VanillaProvider } from "./provider"; + +export { y } from "@syncrostate/core"; + +export const syncroState = ( + options: Omit, "provider"> +) => { + return coreSyncroState({ + ...options, + provider: new VanillaProvider(), + }); +}; diff --git a/vanilla/lib/provider.ts b/vanilla/lib/provider.ts new file mode 100644 index 0000000..ff407b1 --- /dev/null +++ b/vanilla/lib/provider.ts @@ -0,0 +1,56 @@ +import { computed, effect, signal } from "alien-signals"; +import type { Provider } from "@syncrostate/core"; + +const vanillaState = (initialValue: T) => { + let state = signal(initialValue); + return { + get value() { + return state(); + }, + set value(value: T) { + state(value); + }, + }; +}; + +const vanillaEffect = (cb: () => void, deps?: any[]) => { + effect(() => { + cb(); + }); +}; + +const vanillaDerived = (cb: () => T) => { + const derivedValue = computed(cb); + return { + get value() { + return derivedValue(); + }, + }; +}; + +export class VanillaProvider implements Provider { + private context = new Map(); + + getContext = (key: string): T => { + if (!this.context.has(key)) { + throw new Error(`Context key "${key}" not found`); + } + return this.context.get(key) as T; + }; + + setContext = (key: string, value: T): void => { + this.context.set(key, value); + }; + state = vanillaState; + effect = vanillaEffect; + derived = vanillaDerived; + Map = Map; + Set = Set; + onMount = (cb: () => void) => { + cb(); + }; + onDestroy = (cb: () => void) => { + // cb(); + }; + Date = Date; +} diff --git a/vanilla/package.json b/vanilla/package.json new file mode 100644 index 0000000..5c52e98 --- /dev/null +++ b/vanilla/package.json @@ -0,0 +1,38 @@ +{ + "name": "@syncrostate/vanilla", + "version": "0.0.2", + "module": "index.ts", + "type": "module", + "publishConfig": { + "access": "public" + }, + "license": "MIT", + "peerDependencies": { + "typescript": "^5" + }, + "scripts": { + "build": "tsdown", + "build:watch": "tsdown --watch", + "publish:before": "node publish/fixVersionBeforePublication.js", + "publish:after": "node publish/fixVersionAfterPublication.js", + "patch": "npm version patch && npm run build && npm run publish:before && npm publish && npm run publish:after" + }, + "dependencies": { + "@syncrostate/core": "workspace:*", + "alien-signals": "^3.0.0" + }, + "devDependencies": { + "tsdown": "^0.15.5" + }, + "files": [ + "dist" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/vanilla/publish/fixVersionAfterPublication.js b/vanilla/publish/fixVersionAfterPublication.js new file mode 100644 index 0000000..d891557 --- /dev/null +++ b/vanilla/publish/fixVersionAfterPublication.js @@ -0,0 +1,5 @@ +import fs from 'fs'; + +let packageJson = fs.readFileSync('package.json', 'utf8'); +packageJson = packageJson.replace('latest', 'workspace:*'); +fs.writeFileSync('package.json', packageJson); diff --git a/vanilla/publish/fixVersionBeforePublication.js b/vanilla/publish/fixVersionBeforePublication.js new file mode 100644 index 0000000..4505b07 --- /dev/null +++ b/vanilla/publish/fixVersionBeforePublication.js @@ -0,0 +1,5 @@ +import fs from 'fs'; + +let packageJson = fs.readFileSync('package.json', 'utf8'); +packageJson = packageJson.replace('workspace:*', 'latest'); +fs.writeFileSync('package.json', packageJson); diff --git a/vanilla/tsconfig.json b/vanilla/tsconfig.json new file mode 100644 index 0000000..9c62f74 --- /dev/null +++ b/vanilla/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/vanilla/tsdown.config.ts b/vanilla/tsdown.config.ts new file mode 100644 index 0000000..491cdfd --- /dev/null +++ b/vanilla/tsdown.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + entry: ["./lib/index.ts"], + outDir: "./dist", + format: "esm", + sourcemap: true, + clean: true, + dts: true, + tsconfig: "./tsconfig.json", +}); From e3a8a2cd9ea1c8a63115e32d55525b78d4d35248 Mon Sep 17 00:00:00 2001 From: beynar Date: Tue, 30 Sep 2025 17:49:11 +0200 Subject: [PATCH 2/2] update coderabbit --- .coderabbit.yml | 14 +++----------- core/package.json | 5 +++++ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.coderabbit.yml b/.coderabbit.yml index 10b8f12..faeb74d 100644 --- a/.coderabbit.yml +++ b/.coderabbit.yml @@ -1,14 +1,6 @@ reviews: path_filters: # Include these paths - - "package/src/**" - - "package/tests/**" - - # Exclude these paths (prefix with !) - - "!node_modules/**" - - "!package/node_modules/**" - - "!package/dist/**" - - "!package/.svelte-kit/**" - - "!package/build/**" - - "!package/*.min.js" - - "!demo/**" + - "core/**" + - "svelte/src/lib/**" + - "vanilla/lib/**" diff --git a/core/package.json b/core/package.json index 4ae7753..43868cb 100644 --- a/core/package.json +++ b/core/package.json @@ -2,6 +2,11 @@ "name": "@syncrostate/core", "module": "index.ts", "type": "module", + "version": "2.0.0-beta.1", + "publishConfig": { + "access": "public" + }, + "license": "MIT", "exports": { ".": { "types": "./dist/index.d.ts",