From f7c65aed2e760affb24cfbea54f050773f1d6168 Mon Sep 17 00:00:00 2001 From: Andrew Zolotukhin Date: Sun, 6 Sep 2026 21:46:44 +0000 Subject: [PATCH 1/5] feat: harden cache keys and schema form lifecycle --- .changeset/cache-form-correctness.md | 9 + docs/assets/form-lifecycle.png | Bin 0 -> 77934 bytes docs/cache-form-migration.md | 184 ++++++ libs/client/README.md | 18 +- libs/client/src/middlewares/cacheTags.test.ts | 166 +++++- libs/client/src/middlewares/cacheTags.ts | 102 ++-- .../src/middlewares/externalCacheTags.test.ts | 64 ++- .../src/middlewares/externalCacheTags.ts | 18 +- libs/react-form/README.md | 11 +- libs/react-form/src/FormStore.ts | 217 +++++-- libs/react-form/src/contexts.ts | 1 + libs/react-form/src/hooks.ts | 533 +++++++++--------- libs/react-form/src/index.ts | 10 + libs/react-form/src/lifecycle.test.tsx | 439 +++++++++++++++ libs/react-form/src/react.test.tsx | 4 +- libs/react-form/src/system.test-d.tsx | 148 +++++ libs/react-form/src/system.test.tsx | 121 ++++ libs/react-form/src/system.tsx | 135 +++++ libs/react-form/src/types.ts | 43 +- libs/react-form/tsconfig.build.json | 2 + libs/react-form/tsconfig.json | 2 + libs/react-form/tsconfig.typecheck.json | 9 + libs/react-form/vitest.config.mts | 7 +- libs/server/README.md | 14 +- libs/server/src/CacheTag.ts | 47 +- libs/server/src/Endpoint.ts | 2 +- libs/server/src/cacheKey.test.ts | 125 ++++ libs/server/src/cacheKey.ts | 128 +++++ libs/server/src/contract.ts | 1 + .../src/middlewares/ResponseCache.test.ts | 146 +++++ libs/server/src/middlewares/ResponseCache.ts | 105 ++-- libs/server/tests/CacheTag.test.ts | 30 +- .../docs/app/client/sections/cacheTags.tsx | 34 +- .../app/react-form/LifecycleDemo.module.css | 23 + .../docs/app/react-form/LifecycleDemo.tsx | 181 ++++++ websites/docs/app/react-form/page.tsx | 110 +++- 36 files changed, 2643 insertions(+), 546 deletions(-) create mode 100644 .changeset/cache-form-correctness.md create mode 100644 docs/assets/form-lifecycle.png create mode 100644 docs/cache-form-migration.md create mode 100644 libs/react-form/src/lifecycle.test.tsx create mode 100644 libs/react-form/src/system.test-d.tsx create mode 100644 libs/react-form/src/system.test.tsx create mode 100644 libs/react-form/src/system.tsx create mode 100644 libs/react-form/tsconfig.typecheck.json create mode 100644 libs/server/src/cacheKey.test.ts create mode 100644 libs/server/src/cacheKey.ts create mode 100644 libs/server/src/middlewares/ResponseCache.test.ts create mode 100644 websites/docs/app/react-form/LifecycleDemo.module.css create mode 100644 websites/docs/app/react-form/LifecycleDemo.tsx diff --git a/.changeset/cache-form-correctness.md b/.changeset/cache-form-correctness.md new file mode 100644 index 00000000..c95ab01a --- /dev/null +++ b/.changeset/cache-form-correctness.md @@ -0,0 +1,9 @@ +--- +'@cleverbrush/server': major +'@cleverbrush/client': major +'@cleverbrush/react-form': minor +--- + +Use one browser-safe, deterministic `ct2:` cache key encoder across server and client helpers, response caches, and external invalidation. Selected properties and plain-object keys are sorted, values retain their types, and dates retain millisecond precision. Unsupported values fail explicitly. **Breaking:** every computed key changes, including property-free tags. Upgrade external cache writers and invalidators together, and flush or expire previous entries; literal base invalidation names and TTL settings remain unchanged. Successful mutations invalidate cached aliases and prevent older in-flight reads from refilling them; failed writes preserve entries. + +Synchronize mounted schema-form fields with form/field setters and reset baselines, using descriptor identities and stable external-store subscriptions. Discard obsolete async validation after value changes or reset. Add `handleSubmit`, reactive `submitting`/`error`, duplicate-submit protection, explicit success/failure results and opt-in error translation. Add `defineFieldRenderer` and `createFormSystem` for typed renderer values, variants, props and composable registries while retaining existing form APIs. See the cache/form migration guide for semantics and examples. diff --git a/docs/assets/form-lifecycle.png b/docs/assets/form-lifecycle.png new file mode 100644 index 0000000000000000000000000000000000000000..6871823cb79ed725dcdffa2d366571c66ca9ec4a GIT binary patch literal 77934 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 X#zkN8X^1MZ%*d#xRNCqW1j6oghCGk2?wqM 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 bjR%^uUaueOw+Gn%K_K}TDl!$%O+x+yOS|pA literal 0 HcmV?d00001 diff --git a/docs/cache-form-migration.md b/docs/cache-form-migration.md new file mode 100644 index 00000000..c4c427cd --- /dev/null +++ b/docs/cache-form-migration.md @@ -0,0 +1,184 @@ +# Cache keys and schema-form lifecycle + +These changes are application-agnostic. Libraries do not choose an application's +auth scope, UI kit, notifications, navigation behavior, or cache backend. + +## Cache key migration (breaking) + +Previously, selected values were converted to strings and joined with delimiters. +Numeric `42` and string `"42"` could share `record:id=42`; server-side date keys +could collapse different times on the same day. The server helper, client helper, +and response middleware did not agree on date precision. + +All computed keys now use one browser-safe encoder exported as `computeCacheKey` +from `@cleverbrush/server/contract` (also from the server entry point) and as +`computeCacheTagKey` from `@cleverbrush/client/cache`: + +```ts +import { computeCacheKey } from '@cleverbrush/server/contract'; + +const tag = { + name: 'record', + properties: { + id: { getValue: (root: { params: { id?: unknown } }) => ({ + success: true, value: root.params.id + }) } + } +}; +const root = { params: { id: 42 }, query: {}, headers: {}, body: undefined }; +computeCacheKey(tag, root); +// ct2:["record",[["id",["number","42"]]]] +computeCacheKey({ name: 'records', properties: {} }, root); +// ct2:["records",[]] — NOT the literal base label "records" +``` + +- Property names and plain-object keys are sorted by code units. Types are tagged; + delimiters are escaped by JSON. Arrays preserve order; dates preserve milliseconds. + Bigints, non-finite numbers and negative zero remain distinguishable. +- Failed accessors and top-level `undefined` selections are omitted, as before. + Nested `undefined` and `null` are distinct. Array holes encode as `undefined`. +- Cycles, invalid dates, functions, symbols, accessor properties, maps, sets, class + instances and named array properties throw `TypeError`; select plain data instead. +- Treat helper output as opaque. Do not parse or hand-build computed keys. + +Upgrade **every writer and invalidator sharing an external cache together**. Flush +old entries (or let them expire before enabling upgraded readers). There is no +legacy-format fallback. `externalCacheTags` sends the literal base label **and** +the new computed key by default; `invalidateBaseTags: false` sends computed keys +only, including for property-free tags. Ensure the backend supports their length +and characters; adapters can apply the same hash on both sides. + +In-memory response caches retain TTL configuration and tag-name-prefix invalidation +coverage, using metadata instead of encoded-key prefixes. A successful 2xx mutation +advances tag generations; a read started before it cannot refill any alias afterward. +Failed HTTP responses or thrown mutations do not invalidate. A multi-tag cached +response becomes unusable if any of its tags changes. External writers need their +own concurrency controls; invalidation callbacks alone cannot prevent stale refills. + +**Scope remains the consumer's responsibility.** Use distinct names for distinct +response shapes (`records-list` versus `record-detail`) and select every value that +changes the response. Keys do not automatically include URL, principal, tenant, +locale or headers. Keep authenticated caches request/session-scoped unless every +isolation dimension is encoded. Sharing a base invalidation label does not make +different response shapes interchangeable. + +## Form values and reset + +Previously edit forms could require remount keys or manual field setters to display +new values. Now mounted fields read the same values as the form: + +```tsx +const form = useSchemaForm(ProfileSchema); +const city = form.useField(t => t.address.city); +useEffect(() => { + form.reset(profile); // mounted fields update; new clean baseline +}, [form, profile]); // controller identity stays stable + +form.setValue({ address: { city: 'Berlin' } }); // shallow merge; city becomes dirty +city.setValue('Paris'); // central values and parent fields update too +form.reset(); // clears values, NOT restoration of the previous baseline +``` + +`reset(values)` clones plain input data into the new `initialValue` baseline. It +clears errors, touched/dirty/validating flags and submission error. Returning to the +baseline clears dirty, including arrays and nested values. Setters do not mark +fields touched. `createMissingStructure: false` rejects writes to absent parents; +the field no longer displays a value that was not saved. Treat snapshots returned +by `getValue()`/fields as read-only and use setters. Keep the schema fixed for the +form's lifetime (normally define it outside render). + +Validation is discarded immediately when values change, even if the next validation +is still debounced. Reset/unmount cancels scheduled validation and ignores stale +async results. Stable `useSyncExternalStore` snapshots support SSR and Strict Mode. + +## Submission lifecycle + +The local documentation demo shows a successful save clearing mounted fields: + +![Successful save with cleared text, select and checkbox controls](assets/form-lifecycle.png) + +Previously each form repeated validation, pending state, duplicate-submit guards, +error conversion and success handling. The new API supplies these mechanics: + +```tsx +const form = useSchemaForm(ProfileSchema); +const submit = form.handleSubmit( + async values => { + const response = await saveProfile(values); + if (!response.ok) return { ok: false, error: 'Could not save profile' }; + return { ok: true, data: response.data }; // success data inferred + }, + { + onSuccess: profile => { + showConfirmation('Saved'); // notifications/navigation remain app-owned + closeDialog(); + }, + onError: error => { + if (isExpectedNetworkError(error)) return 'Please try again'; + throw error; // preserve unexpected/control-flow exceptions + } + } +); +return
+ {/* fields */} + {form.error &&

{form.error}

} + +
; +``` + +`onValid` accepts synchronous/async `void` (success), `{ ok: true, data? }`, or +`{ ok: false, error: string }`. `onError` is optional: without it exceptions reject +the handler promise; returning a string translates an exception to `form.error`, +and rethrowing preserves it. Exceptions from `onSuccess` always propagate and are +not misreported as failed writes. The awaitable handler calls `preventDefault` and +locks duplicates from validation through success/error handling. Failures retain +inputs; the next attempt clears submission error. `submitting` and `error` are +reactive, read-only flags. `submit()`/`validate()` still return `ValidationResult`. + +Reset/unmount suppress callbacks/results from older submissions, but do not cancel +network operations. Reset keeps the submission lock until that operation settles. +A value change during async validation prevents dispatch of stale validated values. + +## Typed renderers without a UI dependency + +Legacy `Field`, `FieldRenderer`, `FormSystemProvider` and nested providers remain +supported. For new code, capture renderer types once instead of casting custom props: + +```tsx +import { createFormSystem, defineFieldRenderer, useSchemaForm } from '@cleverbrush/react-form'; +import { object, string } from '@cleverbrush/schema'; + +const text = defineFieldRenderer(props => ( + props.onChange(e.target.value)} onBlur={props.onBlur} /> +)); +const select = defineFieldRenderer(props => ( + +)); +const basic = createFormSystem({ renderers: { string: text } }); +const ui = createFormSystem({ renderers: { + ...basic.renderers, 'string:select': select +} }); +const ProfileSchema = object({ name: string(), role: string() }); +function ProfileForm() { + const form = useSchemaForm(ProfileSchema); + return t.role} variant="select" + fieldProps={{ options: ['reader', 'editor'] }} />; + // Unknown variant, missing options, or numeric options: compile-time errors. +} +``` + +The factory returns `Field`, `Provider`, and a composable `renderers` registry. +Typed fields use their factory's registry without needing a provider; an untyped +ancestor cannot replace a renderer with incompatible props. `ui.Provider` also +configures legacy descendants and supports normal nesting. Required custom props +make `fieldProps` required at the call site. + +Use `boolean` for checkboxes, `string[]` for multi-selects, `number | undefined` +for numeric inputs that can be cleared, and `string | null` for nullable strings. +Values can initially be `undefined`; optional/nested fields retain their types. +No UI-kit dependency or application-specific field vocabulary is introduced. diff --git a/libs/client/README.md b/libs/client/README.md index a4d8649a..3e1dcbd4 100644 --- a/libs/client/README.md +++ b/libs/client/README.md @@ -256,7 +256,7 @@ const client = createClient(api, { middlewares: [cacheTags({ defaultTtl: 5000 })], }); -// Populates cache entries for 'todo-list' and 'todo:id=1' tags. +// Populates versioned keys for the 'todo-list' and 'todo' tag definitions. await client.todos.list({ query: { page: 1 } }); await client.todos.get({ params: { id: 1 } }); @@ -307,8 +307,20 @@ externalCacheTags({ invalidateTag: revalidateTag }); ``` The middleware runs after successful `POST`, `PUT`, `PATCH`, and `DELETE` -responses. Dynamic tags invalidate both the base tag name and the computed key -by default, for example `expense` and `expense:id=42`. +responses. Tags invalidate both the literal base tag name and the versioned computed +key by default, for example `expense` and `ct2:["expense",[["id",["number","42"]]]]`. +Property-free tags also have a computed key: `ct2:["expense",[]]`. + +Computed keys share a deterministic, type-tagged encoder with the server; +property/object key order is normalized and dates retain millisecond precision. +Only successful mutations invalidate the in-memory cache; older in-flight reads +cannot refill invalidated entries or aliases. Tag-name-prefix coverage and TTLs +are unchanged. Unsupported selected values throw `TypeError`. + +**Breaking migration:** upgrade all external cache writers and invalidators together +and retire old entries. Base invalidation labels remain literal names, but all +computed keys changed. Endpoint and auth/tenant scope are still consumer-defined. +See the [migration guide](../../docs/cache-form-migration.md#cache-key-migration-breaking). ## Per-Call Overrides diff --git a/libs/client/src/middlewares/cacheTags.test.ts b/libs/client/src/middlewares/cacheTags.test.ts index 14545185..9c905d85 100644 --- a/libs/client/src/middlewares/cacheTags.test.ts +++ b/libs/client/src/middlewares/cacheTags.test.ts @@ -1,6 +1,7 @@ +import { computeCacheKey } from '@cleverbrush/server/contract'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import type { EndpointMeta, FetchLike } from '../middleware.js'; -import { cacheTags } from './cacheTags.js'; +import { cacheTags, computeCacheTagKey } from './cacheTags.js'; // --------------------------------------------------------------------------- // Helpers @@ -495,3 +496,166 @@ describe('cacheTags middleware', () => { expect(r1).not.toBe(r2); }); }); + +describe('cache invalidation races', () => { + const tag = (name = 'records') => ({ name, properties: {} }); + function deferred() { + let resolve!: (value: T) => void; + const promise = new Promise(done => { + resolve = done; + }); + return { promise, resolve }; + } + test.each([ + 400, 500 + ])('failed mutation %s preserves cached reads', async status => { + const fetch = vi + .fn() + .mockResolvedValueOnce(new Response('old')) + .mockResolvedValueOnce(new Response('failed', { status })); + const mw = cacheTags({ defaultTtl: 5000 })(fetch); + const meta = makeTagMeta([tag()]); + await mw('/records', makeInit(meta)); + await mw('/records', makeInit(meta, 'PATCH')); + expect(await (await mw('/records', makeInit(meta))).text()).toBe('old'); + expect(fetch).toHaveBeenCalledTimes(2); + }); + test('transport rejection preserves cached reads', async () => { + const fetch = vi + .fn() + .mockResolvedValueOnce(new Response('old')) + .mockRejectedValueOnce(new Error('offline')); + const mw = cacheTags({ defaultTtl: 5000 })(fetch); + const meta = makeTagMeta([tag()]); + await mw('/records', makeInit(meta)); + await expect(mw('/records', makeInit(meta, 'DELETE'))).rejects.toThrow( + 'offline' + ); + expect(await (await mw('/records', makeInit(meta))).text()).toBe('old'); + expect(fetch).toHaveBeenCalledTimes(2); + }); + test('does not clear entries until the write succeeds', async () => { + const write = deferred(); + const fetch = vi + .fn() + .mockResolvedValueOnce(new Response('old')) + .mockReturnValueOnce(write.promise) + .mockResolvedValueOnce(new Response('new')); + const mw = cacheTags({ defaultTtl: 5000 })(fetch); + const meta = makeTagMeta([tag()]); + await mw('/records', makeInit(meta)); + const pending = mw('/records', makeInit(meta, 'PATCH')); + expect(await (await mw('/records', makeInit(meta))).text()).toBe('old'); + write.resolve(new Response(null, { status: 204 })); + await pending; + expect(await (await mw('/records', makeInit(meta))).text()).toBe('new'); + }); + test.each([ + false, + true + ])('rejects old fills, read during write: %s', async duringWrite => { + const oldRead = deferred(); + const write = deferred(); + const fetch = vi + .fn() + .mockImplementation(async (_url, init) => { + if (init?.method === 'PATCH') return write.promise; + if ( + fetch.mock.calls.filter(([, i]) => i?.method === 'GET') + .length === 1 + ) { + return oldRead.promise; + } + return new Response('new'); + }); + const mw = cacheTags({ defaultTtl: 5000 })(fetch); + const meta = makeTagMeta([tag(), tag('other')]); + let pendingWrite: Promise; + let pendingRead: Promise; + if (duringWrite) { + pendingWrite = mw( + '/records', + makeInit(makeTagMeta([tag()]), 'PATCH') + ); + pendingRead = mw('/records', makeInit(meta)); + } else { + pendingRead = mw('/records', makeInit(meta)); + pendingWrite = mw( + '/records', + makeInit(makeTagMeta([tag()]), 'PATCH') + ); + } + write.resolve(new Response(null, { status: 204 })); + await pendingWrite; + oldRead.resolve(new Response('old')); + expect(await (await pendingRead).text()).toBe('old'); + // No stale body may survive under a second tag either. + expect( + await ( + await mw('/records', makeInit(makeTagMeta([tag('other')]))) + ).text() + ).toBe('new'); + }); + test('invalidates every alias of a previously cached response', async () => { + const fetch = vi + .fn() + .mockResolvedValueOnce(new Response('old')) + .mockResolvedValueOnce(new Response(null, { status: 204 })) + .mockResolvedValueOnce(new Response('new')); + const mw = cacheTags({ defaultTtl: 5000 })(fetch); + await mw('/records', makeInit(makeTagMeta([tag(), tag('alias')]))); + await mw('/records', makeInit(makeTagMeta([tag()]), 'PATCH')); + expect( + await ( + await mw('/alias', makeInit(makeTagMeta([tag('alias')]))) + ).text() + ).toBe('new'); + }); + test('freezes selected values at dispatch', async () => { + const response = deferred(); + const fetch = vi + .fn() + .mockReturnValueOnce(response.promise) + .mockResolvedValueOnce(new Response('second')); + const mw = cacheTags({ defaultTtl: 5000 })(fetch); + const meta = makeTagMeta( + [ + { + name: 'records', + properties: { + id: { + getValue: root => ({ + success: true, + value: root.params.id + }) + } + } + } + ], + { params: { id: 1 } } + ); + const pending = mw('/records', makeInit(meta)); + (meta.params as any).id = 2; + response.resolve(new Response('first')); + await pending; + expect(await (await mw('/records', makeInit(meta))).text()).toBe( + 'second' + ); + expect(fetch).toHaveBeenCalledTimes(2); + }); + test('client and server helpers use exactly the same encoding', () => { + const definition = { + name: 'records', + properties: { + value: makeConstAccessor({ + date: new Date(1234), + filter: 'x,y=z' + }) + } + }; + const root = { params: {}, body: undefined, query: {}, headers: {} }; + expect(computeCacheTagKey(definition, root)).toBe( + computeCacheKey(definition, root) + ); + }); +}); diff --git a/libs/client/src/middlewares/cacheTags.ts b/libs/client/src/middlewares/cacheTags.ts index d6ad5b09..fa4cf7c4 100644 --- a/libs/client/src/middlewares/cacheTags.ts +++ b/libs/client/src/middlewares/cacheTags.ts @@ -3,7 +3,8 @@ * * Caches successful GET responses keyed by endpoint-defined cache tags. * Mutating requests (POST, PUT, DELETE, PATCH) invalidate all cache - * entries whose key starts with each of the endpoint's tag names. + * entries whose tag name starts with each of the endpoint's tag names, only + * after a successful response. Older in-flight reads cannot refill those tags. * * @example * ```ts @@ -18,6 +19,7 @@ * @module */ +import { computeCacheKey } from '@cleverbrush/server/contract'; import type { EndpointMeta, Middleware } from '../middleware.js'; // --------------------------------------------------------------------------- @@ -82,6 +84,7 @@ export interface SerializedCacheTag { interface CacheEntry { response: Response; expiresAt: number; + generations: ReadonlyArray; } /** @@ -94,35 +97,15 @@ export function isMutatingMethod(method: string): boolean { /** * Computes a deterministic cache key from a tag and request data. * - * - Tags with no properties produce just the tag name. - * - Tags with properties produce `name:key1=val1,key2=val2` where - * keys are sorted alphabetically for determinism. + * Uses the shared, versioned `ct2:` encoding, including property-free tags. + * Selected values are type-tagged and retain date precision. Unsupported values + * throw TypeError. External cache writers and invalidators must upgrade together. */ export function computeCacheTagKey( tag: SerializedCacheTag, root: CacheTagRoot ): string { - const entries = Object.entries(tag.properties); - - if (entries.length === 0) { - return tag.name; - } - - const parts: string[] = []; - for (const [key, accessor] of entries.sort(([a], [b]) => - a.localeCompare(b) - )) { - const result = accessor.getValue(root); - if (result.success && result.value !== undefined) { - parts.push(`${key}=${String(result.value)}`); - } - } - - if (parts.length === 0) { - return tag.name; - } - - return `${tag.name}:${parts.join(',')}`; + return computeCacheKey(tag, root); } /** @@ -148,8 +131,9 @@ export function createCacheTagRoot(meta: EndpointMeta): CacheTagRoot { * compute cache keys. If a valid (non-expired) cache entry exists, the * cached response is returned immediately (cloned). * - * On mutating requests (POST, PUT, DELETE, PATCH), all cache entries whose - * key starts with any of the endpoint's tag names are invalidated. + * Successful mutations (POST, PUT, DELETE, PATCH) invalidate entries whose + * tag names start with any of the endpoint's tag names, including all aliases. + * Generations also prevent older in-flight reads from repopulating them. * * @param options - Cache configuration. * @returns A {@link Middleware} that caches and invalidates by tag. @@ -162,42 +146,58 @@ export function cacheTags(options: CacheTagMiddlewareOptions = {}): Middleware { } = options; const cache = new Map(); + const generations = new Map(); + const isCurrent = (snapshot: CacheEntry['generations']) => + snapshot.every( + ([name, generation]) => generations.get(name) === generation + ); return next => (url, init) => { const meta = (init as any).__endpointMeta as EndpointMeta | undefined; const tags: readonly SerializedCacheTag[] | undefined = meta?.cacheTags; const method = (init.method ?? 'GET').toUpperCase(); - // -- Invalidation on mutating requests -- + // Invalidate only after success, including reads still in flight. if (isMutatingMethod(method) && meta && tags && tags.length > 0) { - const root = createCacheTagRoot(meta); - - for (const tag of tags) { - const tagKey = computeCacheTagKey(tag, root); - // Invalidate the exact key and any prefixed variants - // (tag name prefix match handles dynamic property variants - // when the mutation didn't provide the same properties). - for (const [cachedKey] of cache) { - if ( - cachedKey === tagKey || - cachedKey.startsWith(tag.name) - ) { - cache.delete(cachedKey); + const names = tags.map(tag => tag.name); + return next(url, init).then(response => { + if (response.ok) { + for (const [name, generation] of generations) { + if (names.some(prefix => name.startsWith(prefix))) { + generations.set(name, generation + 1); + } + } + for (const [key, entry] of cache) { + if (!isCurrent(entry.generations)) cache.delete(key); } } - } + return response; + }); } // -- Cache lookup for GET requests -- if (method === 'GET' && meta && tags && tags.length > 0) { const root = createCacheTagRoot(meta); + // Resolve keys once: caller-owned request objects can change while + // awaiting the response. A fill must belong to the original read. + const keys = tags.map(tag => ({ + name: tag.name, + key: computeCacheTagKey(tag, root) + })); + const snapshot = keys.map(({ name }) => { + if (!generations.has(name)) generations.set(name, 0); + return [name, generations.get(name)!] as const; + }); let foundEntry: CacheEntry | undefined; - for (const tag of tags) { - const cacheKey = computeCacheTagKey(tag, root); + for (const { key: cacheKey } of keys) { const entry = cache.get(cacheKey); - if (entry && entry.expiresAt > Date.now()) { + if ( + entry && + entry.expiresAt > Date.now() && + isCurrent(entry.generations) + ) { foundEntry = entry; break; } @@ -211,17 +211,17 @@ export function cacheTags(options: CacheTagMiddlewareOptions = {}): Middleware { } return next(url, init).then(response => { - if (condition(response)) { - for (const tag of tags) { - const cacheKey = computeCacheTagKey(tag, root); + if (isCurrent(snapshot) && condition(response)) { + for (const { name, key: cacheKey } of keys) { const ttl = - ttlByTag[tag.name] !== undefined - ? ttlByTag[tag.name] + ttlByTag[name] !== undefined + ? ttlByTag[name] : defaultTtl; if (ttl > 0) { cache.set(cacheKey, { response: response.clone(), - expiresAt: Date.now() + ttl + expiresAt: Date.now() + ttl, + generations: snapshot }); } } diff --git a/libs/client/src/middlewares/externalCacheTags.test.ts b/libs/client/src/middlewares/externalCacheTags.test.ts index 8a6ddfc8..8d360009 100644 --- a/libs/client/src/middlewares/externalCacheTags.test.ts +++ b/libs/client/src/middlewares/externalCacheTags.test.ts @@ -37,6 +37,62 @@ function makeMeta(overrides: Partial = {}): EndpointMeta { } describe('externalCacheTags middleware', () => { + test('property-free tags retain the base label and use a versioned computed key', async () => { + const fetch = vi + .fn() + .mockResolvedValue(new Response(null, { status: 204 })); + const invalidateTag = vi.fn(); + await externalCacheTags({ invalidateTag })(fetch)('/records', { + method: 'POST', + __endpointMeta: makeMeta({ + cacheTags: [{ name: 'records', properties: {} }] + }) + } as any); + expect(invalidateTag.mock.calls).toEqual([ + ['records'], + ['ct2:["records",[]]'] + ]); + }); + + test('freezes keys before dispatch and rejects unsupported values before a write', async () => { + const meta = makeMeta(); + const invalidateTag = vi.fn(); + const fetch = vi.fn().mockImplementation(async () => { + (meta.params as any).id = 99; + return new Response(null, { status: 204 }); + }); + const middleware = externalCacheTags({ invalidateTag })(fetch); + await middleware('/records', { + method: 'POST', + __endpointMeta: meta + } as any); + expect(invalidateTag).toHaveBeenCalledWith( + 'ct2:["expense",[["id",["number","42"]]]]' + ); + (meta.params as any).id = new Map(); + await expect( + middleware('/records', { + method: 'POST', + __endpointMeta: meta + } as any) + ).rejects.toThrow(TypeError); + expect(fetch).toHaveBeenCalledTimes(1); + }); + + test('a thrown mutation never calls the invalidator', async () => { + const fetch = vi + .fn() + .mockRejectedValue(new Error('offline')); + const invalidateTag = vi.fn(); + await expect( + externalCacheTags({ invalidateTag })(fetch)('/records', { + method: 'POST', + __endpointMeta: makeMeta() + } as any) + ).rejects.toThrow('offline'); + expect(invalidateTag).not.toHaveBeenCalled(); + }); + test('invalidates base and dynamic tags after a successful mutation', async () => { const fetch = vi .fn() @@ -52,7 +108,9 @@ describe('externalCacheTags middleware', () => { expect(response.status).toBe(204); expect(fetch).toHaveBeenCalledTimes(1); expect(invalidateTag).toHaveBeenCalledWith('expense'); - expect(invalidateTag).toHaveBeenCalledWith('expense:id=42'); + expect(invalidateTag).toHaveBeenCalledWith( + 'ct2:["expense",[["id",["number","42"]]]]' + ); expect(invalidateTag).toHaveBeenCalledTimes(2); }); @@ -102,7 +160,9 @@ describe('externalCacheTags middleware', () => { __endpointMeta: makeMeta() } as any); - expect(invalidateTag).toHaveBeenCalledWith('expense:id=42'); + expect(invalidateTag).toHaveBeenCalledWith( + 'ct2:["expense",[["id",["number","42"]]]]' + ); expect(invalidateTag).toHaveBeenCalledTimes(1); }); }); diff --git a/libs/client/src/middlewares/externalCacheTags.ts b/libs/client/src/middlewares/externalCacheTags.ts index cbc161af..fa47aaef 100644 --- a/libs/client/src/middlewares/externalCacheTags.ts +++ b/libs/client/src/middlewares/externalCacheTags.ts @@ -67,19 +67,12 @@ export function externalCacheTags( } = options; return next => async (url, init) => { - const response = await next(url, init); const meta = (init as any).__endpointMeta as EndpointMeta | undefined; const method = (init.method ?? meta?.method ?? 'GET').toUpperCase(); const tags: readonly SerializedCacheTag[] | undefined = meta?.cacheTags; - if ( - !meta || - !tags || - tags.length === 0 || - !isMutatingMethod(method) || - !condition(response) - ) { - return response; + if (!meta || !tags || tags.length === 0 || !isMutatingMethod(method)) { + return next(url, init); } const root = createCacheTagRoot(meta); @@ -91,7 +84,12 @@ export function externalCacheTags( tagKeys.add(dynamicKey); } - await Promise.all([...tagKeys].map(tag => invalidateTag(tag))); + // Freeze keys before dispatch and reject invalid selectors before a + // write is sent, not after the server has already committed it. + const response = await next(url, init); + if (condition(response)) { + await Promise.all([...tagKeys].map(tag => invalidateTag(tag))); + } return response; }; } diff --git a/libs/react-form/README.md b/libs/react-form/README.md index 8734102c..28150d30 100644 --- a/libs/react-form/README.md +++ b/libs/react-form/README.md @@ -54,6 +54,10 @@ npm install @cleverbrush/react-form ## Quick Start +For typed renderer props/variants and managed submission, see the new +[consumer examples and migration guide](../../docs/cache-form-migration.md). +The provider-based APIs below remain supported. + ```tsx import { object, string, number } from '@cleverbrush/schema'; import { useSchemaForm, FormSystemProvider, Field } from '@cleverbrush/react-form'; @@ -316,9 +320,12 @@ const form = useSchemaForm(UserSchema, { | `form.useField(forProperty)` | Bind a field by PropertyDescriptor selector | | `form.submit()` | Validate and return `ValidationResult` (includes `result.object` on success) | | `form.validate()` | Run validation, propagate errors to fields | -| `form.reset(values?)` | Reset all fields; optionally set new initial values | +| `form.reset(values?)` | Clear values, or establish supplied values as a clean baseline; synchronize mounted fields and clear errors/touched/dirty/validation | +| `form.handleSubmit(onValid, options?)` | Awaitable event handler: validate, prevent duplicate submits, handle success/failure results | +| `form.submitting` | Reactive, read-only pending state from validation through callbacks | +| `form.error` | Reactive, read-only submission error; cleared on reset or the next attempt | | `form.getValue()` | Get current form values as plain object | -| `form.setValue(values)` | Merge values into form state | +| `form.setValue(values)` | Shallow-merge values and synchronize mounted fields without marking touched | ## useField diff --git a/libs/react-form/src/FormStore.ts b/libs/react-form/src/FormStore.ts index 7dd2d432..ca588c27 100644 --- a/libs/react-form/src/FormStore.ts +++ b/libs/react-form/src/FormStore.ts @@ -1,21 +1,90 @@ -import type { FieldState } from './types.js'; +import type { FieldState, FormSubmissionState } from './types.js'; -/** - * Internal form store — manages field state and notification. - * Does not depend on React; used by hooks for state management. - */ +type Binding = { + getValue: (values: any) => { success: boolean; value?: any }; + setValue: (values: any, value: any, options: any) => boolean; +}; + +function isPlain(value: any): value is Record { + return ( + value != null && + typeof value === 'object' && + (Object.getPrototypeOf(value) === Object.prototype || + Object.getPrototypeOf(value) === null) + ); +} + +/** Copy editable data; opaque values such as Files retain their identity. */ +function copy(value: any, seen = new WeakMap()): any { + if (value instanceof Date) return new Date(value.getTime()); + if (!Array.isArray(value) && !isPlain(value)) return value; + if (seen.has(value)) return seen.get(value); + const result: any = Array.isArray(value) ? new Array(value.length) : {}; + seen.set(value, result); + for (const key of Object.keys(value)) { + Object.defineProperty(result, key, { + value: copy(Reflect.get(value, key), seen), + enumerable: true, + writable: true, + configurable: true + }); + } + return result; +} + +function equal(a: any, b: any, seen = new WeakMap()): boolean { + if (Object.is(a, b)) return true; + if (a instanceof Date && b instanceof Date) { + return Object.is(a.getTime(), b.getTime()); + } + if (Array.isArray(a) !== Array.isArray(b)) return false; + if (Array.isArray(a) && a.length !== b.length) return false; + if ( + !(Array.isArray(a) && Array.isArray(b)) && + !(isPlain(a) && isPlain(b)) + ) { + return false; + } + if (seen.has(a)) return seen.get(a) === b; + seen.set(a, b); + const keys = Object.keys(a); + return ( + keys.length === Object.keys(b).length && + keys.every( + key => + Object.hasOwn(b, key) && + equal(Reflect.get(a, key), Reflect.get(b, key), seen) + ) + ); +} + +/** Internal immutable snapshots shared by all bindings of a form. */ export function createFormStore(initialValues: any) { - let values: any = initialValues != null ? { ...initialValues } : {}; + let values = copy(initialValues ?? {}); + let baseline = values; + let revision = 0; + let submission: FormSubmissionState = { + submitting: false, + error: undefined + }; + const bindings = new Map(); const fieldStates = new Map(); const listeners = new Map void>>(); const globalListeners = new Set<() => void>(); - function ensureFieldState(path: string): FieldState { + function read(binding: Binding | undefined, source: any) { + const result = binding?.getValue(source); + return result?.success ? result.value : undefined; + } + function getFieldState(path: string): FieldState { if (!fieldStates.has(path)) { + const binding = bindings.get(path); + const value = read(binding, values); + const initialValue = read(binding, baseline); fieldStates.set(path, { - value: undefined, - initialValue: undefined, - dirty: false, + value, + initialValue, + dirty: !equal(value, initialValue), touched: false, error: undefined, validating: false @@ -23,83 +92,123 @@ export function createFormStore(initialValues: any) { } return fieldStates.get(path)!; } - - function getFieldState(path: string): FieldState { - return ensureFieldState(path); - } - - function updateFieldState(path: string, patch: Partial) { - const current = ensureFieldState(path); - const updated = { ...current, ...patch }; - fieldStates.set(path, updated); - notifyPath(path); + function registerField(path: string, binding: Binding) { + if (!bindings.has(path)) bindings.set(path, binding); + return getFieldState(path); } - function notifyPath(path: string) { - const pathListeners = listeners.get(path); - if (pathListeners) { - for (const listener of pathListeners) { - listener(); - } - } + for (const listener of listeners.get(path) ?? []) listener(); } - function notifyAll() { - for (const [, pathListeners] of listeners) { - for (const listener of pathListeners) { - listener(); - } - } - for (const listener of globalListeners) { - listener(); + for (const path of listeners.keys()) notifyPath(path); + for (const listener of globalListeners) listener(); + } + function updateFieldState(path: string, patch: Partial) { + const current = getFieldState(path); + if ( + Object.entries(patch).every(([key, value]) => + Object.is(current[key as keyof FieldState], value) + ) + ) + return; + fieldStates.set(path, { ...current, ...patch }); + notifyPath(path); + } + function syncFields(reset: boolean) { + for (const [path, binding] of bindings) { + const value = read(binding, values); + const initialValue = read(binding, baseline); + updateFieldState(path, { + value, + initialValue, + dirty: !equal(value, initialValue), + validating: false, + ...(reset ? { touched: false, error: undefined } : {}) + }); } } - function subscribe(path: string, listener: () => void): () => void { - if (!listeners.has(path)) { - listeners.set(path, new Set()); + let set = listeners.get(path); + if (!set) { + set = new Set(); + listeners.set(path, set); } - listeners.get(path)!.add(listener); + set.add(listener); return () => { - listeners.get(path)?.delete(listener); + set.delete(listener); + if (set.size === 0) listeners.delete(path); }; } - function subscribeGlobal(listener: () => void): () => void { globalListeners.add(listener); return () => { globalListeners.delete(listener); }; } - - function getValues(): any { + function getSubmissionState() { + return submission; + } + function setSubmissionState(patch: Partial) { + const next = { ...submission, ...patch }; + if ( + next.submitting === submission.submitting && + next.error === submission.error + ) + return; + submission = next; + for (const listener of globalListeners) listener(); + } + function getValues() { return values; } - + function getRevision() { + return revision; + } function setValues(newValues: any) { - values = newValues != null ? { ...newValues } : {}; + values = copy(newValues ?? {}); + revision++; + syncFields(false); + } + function setFieldValue( + path: string, + value: any, + createMissingStructure: boolean + ) { + const next = copy(values); + if ( + bindings + .get(path) + ?.setValue(next, copy(value), { createMissingStructure }) + ) { + setValues(next); + } } - function resetAll(newInitialValues?: any) { - values = newInitialValues != null ? { ...newInitialValues } : {}; - fieldStates.clear(); - notifyAll(); + values = copy(newInitialValues ?? {}); + baseline = values; + revision++; + syncFields(true); + setSubmissionState({ error: undefined }); } - - function getAllFieldPaths(): string[] { - return Array.from(fieldStates.keys()); + function getAllFieldPaths() { + return Array.from(bindings.keys()); } return { + registerField, getFieldState, updateFieldState, subscribe, subscribeGlobal, getValues, setValues, + setFieldValue, resetAll, - notifyAll, - getAllFieldPaths + getAllFieldPaths, + getRevision, + getSubmissionState, + setSubmissionState, + notifyAll }; } diff --git a/libs/react-form/src/contexts.ts b/libs/react-form/src/contexts.ts index ac3da588..ddea0016 100644 --- a/libs/react-form/src/contexts.ts +++ b/libs/react-form/src/contexts.ts @@ -22,6 +22,7 @@ export type FormContextValue = { schema: ObjectSchemaBuilder; options: UseSchemaFormOptions; pathMap: Map, string>; + triggerValidation?: (markTouched: boolean) => Promise; }; /** diff --git a/libs/react-form/src/hooks.ts b/libs/react-form/src/hooks.ts index 019997a8..1159433c 100644 --- a/libs/react-form/src/hooks.ts +++ b/libs/react-form/src/hooks.ts @@ -10,31 +10,33 @@ import { ObjectSchemaBuilder, SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR } from '@cleverbrush/schema'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { + useCallback, + useEffect, + useMemo, + useRef, + useSyncExternalStore +} from 'react'; import type { FormContextValue } from './contexts.js'; -import { debounce } from './debounce.js'; import type { FormStore } from './FormStore.js'; import { createFormStore } from './FormStore.js'; import { buildDescriptorPathMap, - buildSelectorFromPath, ensureNestedStructure, - getDescriptorPath, getSchemaType } from './helpers.js'; import type { FieldRenderer, + FormSubmissionState, + FormSubmitHandler, + FormSubmitOptions, + FormSubmitResult, FormSystemConfig, UseFieldResult, UseSchemaFormOptions } from './types.js'; -// ─── SchemaFormInstance ────────────────────────────────────────────────────── - -/** - * Return type for useSchemaForm — fully typed for IntelliSense. - * The `useField` method infers the field value type from the schema via PropertyDescriptor. - */ +/** A stable form controller with reactive field and submission subscriptions. */ export type SchemaFormInstance< TSchema extends ObjectSchemaBuilder > = { @@ -45,36 +47,33 @@ export type SchemaFormInstance< ) => UseFieldResult>; submit: () => Promise>>; validate: () => Promise>>; + /** Clear values, or establish supplied values as the new clean baseline. */ reset: (values?: Partial>) => void; + /** Read the current snapshot. Use setters rather than mutating it. */ getValue: () => InferType; + /** Shallow-merge values without marking fields touched. */ setValue: (values: Partial>) => void; - /** @internal — Used by FormProvider and Field to access internal context */ + /** Validate and submit once; repeated calls while pending are ignored. */ + handleSubmit: ( + onValid: ( + values: InferType + ) => FormSubmitResult | Promise>, + options?: FormSubmitOptions, TData> + ) => FormSubmitHandler; + /** @internal — Used by FormProvider and Field to access internal context. */ _getFormContext: () => FormContextValue; -}; - -// ─── useSchemaForm ─────────────────────────────────────────────────────────── +} & FormSubmissionState; -/** - * Hook that binds a schema to a form instance. - * Provides field binding API, form-level validation, submit, reset. - */ +/** Bind a schema to independently subscribed fields and a stable form instance. */ export function useSchemaForm< TSchema extends ObjectSchemaBuilder >( schema: TSchema, options?: UseSchemaFormOptions ): SchemaFormInstance { - const resolvedOptions: UseSchemaFormOptions = { - createMissingStructure: true, - ...options - }; - const storeRef = useRef(null); - if (!storeRef.current) { - storeRef.current = createFormStore({}); - } + if (!storeRef.current) storeRef.current = createFormStore({}); const store = storeRef.current; - const descriptorTreeRef = useRef; } const descriptorTree = descriptorTreeRef.current; - const pathMapRef = useRef, string @@ -94,206 +92,238 @@ export function useSchemaForm< pathMapRef.current = buildDescriptorPathMap(descriptorTree, schema); } const pathMap = pathMapRef.current; - + for (const [descriptor] of pathMap) { + store.registerField(descriptor.toJsonPointer(), descriptor); + } const schemaRef = useRef(schema); - const optionsRef = useRef(resolvedOptions); - optionsRef.current = resolvedOptions; - - const formContextValue = useMemo( - () => ({ - store, - descriptorTree, - schema: schemaRef.current, - options: optionsRef.current, - pathMap - }), - [store, descriptorTree, pathMap] - ); - - const formContextRef = useRef(formContextValue); - formContextRef.current = formContextValue; - - // Generation counter to discard stale validation results from concurrent runs + const optionsRef = useRef({}); + optionsRef.current = { createMissingStructure: true, ...options }; + const mountedRef = useRef(true); + const epochRef = useRef(0); const validationGenRef = useRef(0); + const submissionLockRef = useRef(false); + const timerRef = useRef | null>(null); + const cancelScheduledValidation = useCallback(() => { + if (timerRef.current !== null) clearTimeout(timerRef.current); + timerRef.current = null; + }, []); + useEffect(() => { + mountedRef.current = true; + return () => { + mountedRef.current = false; + epochRef.current++; + validationGenRef.current++; + cancelScheduledValidation(); + }; + }, [cancelScheduledValidation]); - /** - * Runs full schema validation using getErrorsFor to extract per-field errors. - * Optionally marks all fields as touched (used by submit/explicit validate). - */ const runValidation = useCallback( async ( markTouched: boolean ): Promise>> => { + cancelScheduledValidation(); const gen = ++validationGenRef.current; - const values = store.getValues(); - // Ensure all nested object structures exist to prevent - // ObjectSchemaBuilder.validateAsync() from throwing on undefined nested objects - const safeValues = ensureNestedStructure(values, schemaRef.current); + const revision = store.getRevision(); + for (const path of store.getAllFieldPaths()) { + store.updateFieldState(path, { validating: true }); + } + const safeValues = ensureNestedStructure( + store.getValues(), + schemaRef.current + ); let result: ValidationResult>; try { result = (await schemaRef.current.validateAsync(safeValues, { doNotStopOnFirstError: true })) as ValidationResult>; } catch { - // If validation itself throws, treat as invalid - return { valid: false } as ValidationResult>; - } - - // Discard results if a newer validation has started since this one began - if (gen !== validationGenRef.current) { - return result as ValidationResult>; + result = { valid: false } as ValidationResult< + InferType + >; } - - // Clear all existing field errors - const allPaths = store.getAllFieldPaths(); - for (const p of allPaths) { - const patch: Partial<{ - error: string | undefined; - touched: boolean; - }> = { error: undefined }; - if (markTouched) { - patch.touched = true; - } - store.updateFieldState(p, patch); + // A value update invalidates validation immediately, even while the next + // validation is only scheduled (debounced) and has not started yet. + if ( + !mountedRef.current || + gen !== validationGenRef.current || + revision !== store.getRevision() + ) + return result; + + for (const path of store.getAllFieldPaths()) { + store.updateFieldState(path, { + error: undefined, + validating: false, + ...(markTouched ? { touched: true } : {}) + }); } - - // Use getErrorsFor to extract per-field errors via tree selectors - const resultWithErrors = result as ValidationResult< - InferType - > & { - getErrorsFor?: (selector: (t: any) => any) => { - errors: ReadonlyArray; - isValid: boolean; - }; - errors?: ReadonlyArray<{ message: string; path?: string }>; - }; - - // Build a map of errors found via getErrorsFor so we can detect gaps - const fieldsWithErrors = new Set(); - - if (typeof resultWithErrors.getErrorsFor === 'function') { - const getErrorsFor = resultWithErrors.getErrorsFor; - - // Extract per-field errors by building selectors from field paths - for (const [, path] of pathMap) { + const getErrorsFor = (result as any).getErrorsFor; + if (typeof getErrorsFor === 'function') { + for (const [descriptor] of pathMap) { + const path = descriptor.toJsonPointer(); + const parts = path + .slice(1) + .split('/') + .map(part => + part.replace(/~1/g, '/').replace(/~0/g, '~') + ); try { - const selector = buildSelectorFromPath(path); - const fieldResult = getErrorsFor(selector); + const field = getErrorsFor((tree: any) => + parts.reduce( + (value: any, key) => value?.[key], + tree + ) + ); if ( - fieldResult && - Array.isArray(fieldResult.errors) && - fieldResult.errors.length > 0 + Array.isArray(field?.errors) && + field.errors.length ) { - const errorMessage = fieldResult.errors[0]; - const patch: Partial<{ - error: string | undefined; - touched: boolean; - }> = { error: errorMessage }; - if (markTouched) { - patch.touched = true; - } - store.updateFieldState(path, patch); - fieldsWithErrors.add(path); + store.updateFieldState(path, { + error: field.errors[0] + }); } } catch { - // If getErrorsFor fails for this path, skip + // A missing error selector must not prevent other fields + // from receiving their validation results. } } } - - return result as ValidationResult>; + return result; }, - [store, pathMap] + [cancelScheduledValidation, store, pathMap] ); - const validate = useCallback(async (): Promise< - ValidationResult> - > => { - return runValidation(true); - }, [runValidation]); - - const submit = useCallback(async (): Promise< - ValidationResult> - > => { - return validate(); - }, [validate]); - + const validate = useCallback(() => runValidation(true), [runValidation]); + const submit = useCallback(() => validate(), [validate]); const reset = useCallback( (values?: Partial>) => { - const newValues = values ?? {}; - store.resetAll(newValues); + epochRef.current++; + validationGenRef.current++; + cancelScheduledValidation(); + store.resetAll(values); }, + [store, cancelScheduledValidation] + ); + const getValue = useCallback( + (): InferType => store.getValues(), [store] ); - - const getValue = useCallback((): InferType => { - return store.getValues(); - }, [store]); - - const setValueFn = useCallback( + const setValue = useCallback( (values: Partial>) => { - const currentValues = store.getValues(); - const merged = { ...currentValues, ...values }; - store.setValues(merged); - store.notifyAll(); + cancelScheduledValidation(); + store.setValues({ ...store.getValues(), ...values }); }, - [store] + [store, cancelScheduledValidation] ); - - const _getFormContext = useCallback(() => formContextRef.current, []); - - // Validate on mount when requested — runs once after first render - const validateOnMountRef = useRef(resolvedOptions.validateOnMount); - // biome-ignore lint/correctness/useExhaustiveDependencies: We only want to check validateOnMount on the initial mount, ignoring changes to it after that - useEffect(() => { - if (validateOnMountRef.current) { - runValidation(true); - } - }, []); - - // Create a debounced version of runValidation for onChange triggers. - // validate(), submit(), and validateOnMount always use runValidation directly. - const debouncedValidationRef = useRef< - ((markTouched: boolean) => void) | null - >(null); - if ( - resolvedOptions.validationDebounceMs != null && - resolvedOptions.validationDebounceMs > 0 && - !debouncedValidationRef.current - ) { - debouncedValidationRef.current = debounce((markTouched: boolean) => { - runValidation(markTouched); - }, resolvedOptions.validationDebounceMs); - } - const triggerValidation = useCallback( (markTouched: boolean) => { - if (debouncedValidationRef.current) { - debouncedValidationRef.current(markTouched); - return Promise.resolve( - undefined as unknown as ValidationResult> - ); + cancelScheduledValidation(); + const delay = optionsRef.current.validationDebounceMs; + if (delay != null && delay > 0) { + timerRef.current = setTimeout(() => { + timerRef.current = null; + void runValidation(markTouched); + }, delay); + return Promise.resolve(); } return runValidation(markTouched); }, - [runValidation] + [runValidation, cancelScheduledValidation] + ); + + const handleSubmit = useCallback( + ( + onValid: ( + values: InferType + ) => FormSubmitResult | Promise>, + submissionOptions?: FormSubmitOptions, TData> + ): FormSubmitHandler => + async event => { + event?.preventDefault(); + if (submissionLockRef.current || !mountedRef.current) return; + submissionLockRef.current = true; + const epoch = epochRef.current; + const current = () => + mountedRef.current && epoch === epochRef.current; + store.setSubmissionState({ + submitting: true, + error: undefined + }); + try { + const revision = store.getRevision(); + const result = await runValidation(true); + if ( + !current() || + revision !== store.getRevision() || + !result.valid || + result.object === undefined + ) + return; + const values = result.object; + let outcome: FormSubmitResult; + try { + outcome = await onValid(values); + } catch (error) { + if (!current()) return; + if (!submissionOptions?.onError) throw error; + const message = await submissionOptions.onError(error); + if (current()) + store.setSubmissionState({ error: message }); + return; + } + if (!current()) return; + if (outcome && !outcome.ok) { + store.setSubmissionState({ error: outcome.error }); + return; + } + // Errors from success callbacks (including redirects) propagate; + // they are not translated as failures of a completed submission. + await submissionOptions?.onSuccess?.(outcome?.data, values); + } finally { + submissionLockRef.current = false; + if (mountedRef.current) + store.setSubmissionState({ submitting: false }); + } + }, + [runValidation, store] ); + const formContextValue = useMemo( + () => ({ + store, + descriptorTree, + schema: schemaRef.current, + pathMap, + get options() { + return optionsRef.current; + }, + triggerValidation + }), + [store, descriptorTree, pathMap, triggerValidation] + ); + const formContextRef = useRef(formContextValue); + formContextRef.current = formContextValue; + const _getFormContext = useCallback(() => formContextRef.current, []); const useFieldHook = useCallback( >( - forProperty: ( + selector: ( tree: PropertyDescriptorTree ) => PropertyDescriptor - ): UseFieldResult> => { - return useFieldFromContext( - formContextRef.current, - forProperty, - triggerValidation - ) as UseFieldResult>; - }, - [triggerValidation] + ): UseFieldResult> => + useFieldFromContext(formContextRef.current, selector), + [] ); + const validateOnMountRef = useRef(options?.validateOnMount); + useEffect(() => { + if (validateOnMountRef.current) void runValidation(true); + }, [runValidation]); + // Subscribe to submission state without changing the controller's identity. + useSyncExternalStore( + store.subscribeGlobal, + store.getSubmissionState, + store.getSubmissionState + ); return useMemo( () => ({ useField: useFieldHook, @@ -301,8 +331,15 @@ export function useSchemaForm< validate, reset, getValue, - setValue: setValueFn, - _getFormContext + setValue, + handleSubmit, + _getFormContext, + get submitting() { + return store.getSubmissionState().submitting; + }, + get error() { + return store.getSubmissionState().error; + } }), [ useFieldHook, @@ -310,112 +347,63 @@ export function useSchemaForm< validate, reset, getValue, - setValueFn, - _getFormContext + setValue, + handleSubmit, + _getFormContext, + store ] ); } -// ─── useField (from context) ───────────────────────────────────────────────── - -/** - * Internal useField implementation, requires FormContextValue. - * Returns UseFieldResult with untyped values — callers should cast to the proper generic type. - */ +/** Internal field binding shared by direct and context-based hooks. */ export function useFieldFromContext( formContext: FormContextValue, forProperty: (tree: any) => any, - triggerValidation?: (markTouched: boolean) => Promise + triggerValidation = formContext.triggerValidation ): UseFieldResult { - const { store, descriptorTree, options, pathMap } = formContext; - - const descriptor = forProperty(descriptorTree as any); - const inner = descriptor[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]; - const path = getDescriptorPath(inner, pathMap); - const fieldSchema = inner.getSchema(); - - // Initialize field state from current values on first access - const initializedRef = useRef(null); - - if (initializedRef.current !== path) { - const values = store.getValues(); - const { success, value } = inner.getValue(values); - const currentState = store.getFieldState(path); - if (currentState.initialValue === undefined && !currentState.touched) { - const resolvedValue = success ? value : undefined; - store.updateFieldState(path, { - value: resolvedValue, - initialValue: resolvedValue, - dirty: false - }); - } - initializedRef.current = path; - } - - const [, setRenderTick] = useState(0); - - // Subscribe to field changes with proper cleanup on unmount/path change - useEffect(() => { - const unsub = store.subscribe(path, () => { - setRenderTick(c => c + 1); - }); - return unsub; - }, [store, path]); - + const { store, descriptorTree, options } = formContext; + const inner = + forProperty(descriptorTree)[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]; + const path = inner.toJsonPointer(); + store.registerField(path, inner); + const subscribe = useCallback( + (listener: () => void) => store.subscribe(path, listener), + [store, path] + ); + const getSnapshot = useCallback( + () => store.getFieldState(path), + [store, path] + ); + const fieldState = useSyncExternalStore( + subscribe, + getSnapshot, + getSnapshot + ); const onChange = useCallback( (value: any) => { - const values = store.getValues(); - inner.setValue(values, value, { - createMissingStructure: options.createMissingStructure !== false - }); - store.setValues(values); - const currentState = store.getFieldState(path); - store.updateFieldState(path, { + store.setFieldValue( + path, value, - dirty: value !== currentState.initialValue - }); - // Run validation on every field change (without marking all fields touched) - if (triggerValidation) { - triggerValidation(false); - } + options.createMissingStructure !== false + ); + void triggerValidation?.(false); }, - [store, inner, path, options, triggerValidation] + [store, path, options.createMissingStructure, triggerValidation] ); - - const onBlur = useCallback(() => { - store.updateFieldState(path, { touched: true }); - }, [store, path]); - - const setValue = useCallback( - (value: any) => { - onChange(value); - }, - [onChange] + const onBlur = useCallback( + () => store.updateFieldState(path, { touched: true }), + [store, path] ); - - const fieldState = store.getFieldState(path); - return { - value: fieldState.value, - initialValue: fieldState.initialValue, - dirty: fieldState.dirty, - touched: fieldState.touched, - error: fieldState.error, - validating: fieldState.validating, + ...fieldState, onChange, onBlur, - setValue, - schema: fieldSchema + setValue: onChange, + schema: inner.getSchema() }; } -/** - * Resolves a renderer from the FormSystem config based on schema type and optional variant. - * - * When `variant` is provided the registry is checked for `"type:variant"` first - * (e.g. `"string:password"`). If no match is found it falls back to the base - * `"type"` key (e.g. `"string"`). - */ +/** Resolve type:variant first, falling back to the base type. */ export function resolveRenderer( config: FormSystemConfig | null, schema: SchemaBuilder, @@ -423,9 +411,8 @@ export function resolveRenderer( ): FieldRenderer | undefined { if (!config?.renderers) return undefined; const type = getSchemaType(schema); - if (variant) { - const variantRenderer = config.renderers[`${type}:${variant}`]; - if (variantRenderer) return variantRenderer; - } - return config.renderers[type]; + return ( + (variant ? config.renderers[`${type}:${variant}`] : undefined) ?? + config.renderers[type] + ); } diff --git a/libs/react-form/src/index.ts b/libs/react-form/src/index.ts index eb479c21..30ed2420 100644 --- a/libs/react-form/src/index.ts +++ b/libs/react-form/src/index.ts @@ -18,10 +18,20 @@ export { export type { SchemaFormInstance } from './hooks.js'; // Hooks export { useSchemaForm } from './hooks.js'; +export type { + TypedFieldProps, + TypedFieldRenderer, + TypedRendererRegistry +} from './system.js'; +export { createFormSystem, defineFieldRenderer } from './system.js'; export type { FieldRenderer, FieldRenderProps, FieldState, + FormSubmissionState, + FormSubmitHandler, + FormSubmitOptions, + FormSubmitResult, FormSystemConfig, UseFieldResult, UseSchemaFormOptions diff --git a/libs/react-form/src/lifecycle.test.tsx b/libs/react-form/src/lifecycle.test.tsx new file mode 100644 index 00000000..38416931 --- /dev/null +++ b/libs/react-form/src/lifecycle.test.tsx @@ -0,0 +1,439 @@ +import { array, boolean, object, string } from '@cleverbrush/schema'; +import { + act, + cleanup, + fireEvent, + render, + renderHook, + screen +} from '@testing-library/react'; +import { StrictMode, useEffect } from 'react'; +import { renderToString } from 'react-dom/server'; +import { afterEach, describe, expect, test, vi } from 'vitest'; +import { FormProvider, useField, useSchemaForm } from './index.js'; + +afterEach(() => { + cleanup(); + vi.useRealTimers(); +}); +const schema = object({ + name: string().minLength(1), + active: boolean(), + tags: array(string()), + nested: object({ city: string() }) +}); +const baseline = { + name: 'Ada', + active: true, + tags: ['a'], + nested: { city: 'Paris' } +}; +function useBoundForm() { + const form = useSchemaForm(schema); + return { + form, + name: form.useField(t => t.name), + active: form.useField(t => t.active), + tags: form.useField(t => t.tags), + parent: form.useField(t => t.nested), + city: form.useField(t => t.nested.city) + }; +} +function deferred() { + let resolve!: (value: T) => void; + let reject!: (error: unknown) => void; + const promise = new Promise((done, fail) => { + resolve = done; + reject = fail; + }); + return { promise, resolve, reject }; +} + +describe('synchronized mounted fields', () => { + test('reset updates values and baselines without remounting', () => { + const { result } = renderHook(useBoundForm); + const instance = result.current.form; + act(() => result.current.form.reset(baseline)); + expect(result.current.form).toBe(instance); + expect(result.current.name.value).toBe('Ada'); + expect(result.current.name.initialValue).toBe('Ada'); + expect(result.current.active.value).toBe(true); + expect(result.current.tags.value).toEqual(['a']); + expect(result.current.city.value).toBe('Paris'); + expect(result.current.city.dirty).toBe(false); + act(() => { + result.current.name.onChange('Grace'); + result.current.name.onBlur(); + }); + act(() => result.current.form.reset(baseline)); + expect(result.current.name).toMatchObject({ + value: 'Ada', + dirty: false, + touched: false, + error: undefined + }); + act(() => result.current.form.reset()); + expect(result.current.form.getValue()).toEqual({}); + expect(result.current.city.value).toBeUndefined(); + }); + test('form and field setters synchronize parent, child and array bindings', () => { + const { result } = renderHook(useBoundForm); + act(() => result.current.form.reset(baseline)); + act(() => + result.current.form.setValue({ + nested: { city: 'Berlin' }, + tags: ['b'] + }) + ); + expect(result.current.city.value).toBe('Berlin'); + expect(result.current.parent.value).toEqual({ city: 'Berlin' }); + expect(result.current.city.touched).toBe(false); + expect(result.current.city.dirty).toBe(true); + expect(result.current.name.value).toBe('Ada'); + act(() => result.current.city.onChange('Rome')); + expect(result.current.parent.value).toEqual({ city: 'Rome' }); + expect(result.current.form.getValue().nested.city).toBe('Rome'); + expect(result.current.parent.initialValue).toEqual({ city: 'Paris' }); + act(() => result.current.tags.setValue(['a'])); + expect(result.current.tags.dirty).toBe(false); + }); + test('caller-owned reset objects cannot mutate the baseline', () => { + const { result } = renderHook(useBoundForm); + const values = { ...baseline, nested: { city: 'Paris' }, tags: ['a'] }; + act(() => result.current.form.reset(values)); + values.nested.city = 'Elsewhere'; + values.tags.push('b'); + expect(result.current.city.initialValue).toBe('Paris'); + expect(result.current.tags.value).toEqual(['a']); + }); + test('property names containing dots and slashes remain distinct', () => { + const special = object({ + 'a.b': string(), + 'a/b': string(), + a: object({ b: string() }) + }); + const { result } = renderHook(() => { + const form = useSchemaForm(special); + return { + form, + dotted: form.useField(t => t['a.b']), + slash: form.useField(t => t['a/b']), + nested: form.useField(t => t.a.b) + }; + }); + act(() => + result.current.form.reset({ + 'a.b': 'dot', + 'a/b': 'slash', + a: { b: 'nested' } + }) + ); + expect(result.current.dotted.value).toBe('dot'); + expect(result.current.slash.value).toBe('slash'); + expect(result.current.nested.value).toBe('nested'); + }); + test('text, select and checkbox controls follow programmatic updates in Strict Mode', () => { + function Controls() { + const { form, name, city, active } = useBoundForm(); + useEffect(() => form.reset(baseline), [form]); + return ( + <> + name.onChange(e.target.value)} + /> + + active.onChange(e.target.checked)} + /> + + + ); + } + render( + + + + ); + fireEvent.click(screen.getByText('Update')); + expect((screen.getByLabelText('Name') as HTMLInputElement).value).toBe( + 'Grace' + ); + expect((screen.getByLabelText('City') as HTMLSelectElement).value).toBe( + 'Berlin' + ); + expect( + (screen.getByLabelText('Active') as HTMLInputElement).checked + ).toBe(false); + }); + test('server rendering supplies a stable server snapshot', () => { + function ServerForm() { + const form = useSchemaForm(object({ name: string() })); + const field = form.useField(t => t.name); + return ( + + ); + } + expect(renderToString()).toContain('value=""'); + }); + test('context-based fields share values and validation with direct hooks', async () => { + const simple = object({ name: string().minLength(2) }); + function Child() { + const name = useField(t => t.name); + return ( + <> + name.onChange(e.target.value)} + /> + {name.error} + + ); + } + function Parent() { + const form = useSchemaForm(simple); + return ( + + + + ); + } + render(); + await act(async () => + fireEvent.change(screen.getByLabelText('Name'), { + target: { value: 'x' } + }) + ); + expect(screen.getByLabelText('Name').nextSibling?.textContent).not.toBe( + '' + ); + }); +}); + +describe('validation generations', () => { + test.each([ + 'reset', + 'setValue', + 'onChange' + ] as const)('ignores old async results after %s', async action => { + vi.useFakeTimers(); + const pending = deferred<{ + valid: boolean; + errors: { message: string }[]; + }>(); + const asyncSchema = object({ + name: string().addValidator(() => pending.promise) + }); + const { result } = renderHook(() => { + const form = useSchemaForm(asyncSchema, { + validationDebounceMs: 100 + }); + return { form, field: form.useField(t => t.name) }; + }); + act(() => result.current.form.reset({ name: 'old' })); + let validation!: ReturnType; + act(() => { + validation = result.current.form.validate(); + }); + act(() => { + if (action === 'onChange') result.current.field.onChange('new'); + else result.current.form[action]({ name: 'new' }); + }); + await act(async () => { + pending.resolve({ + valid: false, + errors: [{ message: 'obsolete' }] + }); + await validation; + }); + expect(result.current.field.value).toBe('new'); + expect(result.current.field.error).toBeUndefined(); + expect(result.current.field.validating).toBe(false); + }); + test('reset and unmount cancel scheduled validation', async () => { + vi.useFakeTimers(); + const validator = vi.fn(() => ({ valid: true })); + const simple = object({ name: string().addValidator(validator) }); + const { result, unmount } = renderHook(() => { + const form = useSchemaForm(simple, { validationDebounceMs: 100 }); + return { form, field: form.useField(t => t.name) }; + }); + act(() => { + result.current.field.onChange('one'); + result.current.form.reset(); + }); + await act(() => vi.advanceTimersByTimeAsync(101)); + expect(validator).not.toHaveBeenCalled(); + act(() => result.current.field.onChange('two')); + unmount(); + await vi.advanceTimersByTimeAsync(101); + expect(validator).not.toHaveBeenCalled(); + }); +}); + +describe('submission lifecycle', () => { + const simple = object({ name: string().minLength(1) }); + function ready() { + const hook = renderHook(() => useSchemaForm(simple)); + act(() => hook.result.current.reset({ name: 'Ada' })); + return hook; + } + test('exposes reactive state with stable identity and prevents double submits', async () => { + const { result } = ready(); + const form = result.current; + const pending = deferred<{ ok: true; data: number }>(); + const send = vi.fn(() => pending.promise); + const success = vi.fn(); + const handler = form.handleSubmit(send, { onSuccess: success }); + const preventDefault = vi.fn(); + let first!: Promise; + await act(async () => { + first = handler({ preventDefault }); + await handler(); + }); + expect(result.current).toBe(form); + expect(result.current.submitting).toBe(true); + expect(send).toHaveBeenCalledTimes(1); + expect(preventDefault).toHaveBeenCalledTimes(1); + await act(async () => { + pending.resolve({ ok: true, data: 42 }); + await first; + }); + expect(success).toHaveBeenCalledWith(42, { name: 'Ada' }); + expect(result.current.submitting).toBe(false); + }); + test('invalid values never reach the submit callback', async () => { + const { result } = ready(); + const send = vi.fn(); + act(() => result.current.setValue({ name: '' })); + await act(() => result.current.handleSubmit(send)()); + expect(send).not.toHaveBeenCalled(); + expect(result.current.submitting).toBe(false); + }); + test('explicit and translated failures preserve input; retry clears the error', async () => { + const { result } = ready(); + const success = vi.fn(); + await act(() => + result.current.handleSubmit( + () => ({ ok: false, error: 'Rejected' }), + { onSuccess: success } + )() + ); + expect(result.current.error).toBe('Rejected'); + expect(result.current.getValue()).toEqual({ name: 'Ada' }); + expect(success).not.toHaveBeenCalled(); + await act(() => + result.current.handleSubmit( + () => { + throw new Error('offline'); + }, + { onError: () => 'Try again' } + )() + ); + expect(result.current.error).toBe('Try again'); + await act(() => + result.current.handleSubmit(() => undefined, { + onSuccess: success + })() + ); + expect(result.current.error).toBeUndefined(); + expect(success).toHaveBeenCalledTimes(1); + }); + test('unhandled errors and callback rethrows propagate', async () => { + const { result } = ready(); + const redirect = new Error('application redirect'); + await act(async () => { + await expect( + result.current.handleSubmit(() => { + throw redirect; + })() + ).rejects.toBe(redirect); + await expect( + result.current.handleSubmit( + () => { + throw redirect; + }, + { + onError: error => { + throw error; + } + } + )() + ).rejects.toBe(redirect); + await expect( + result.current.handleSubmit(() => undefined, { + onSuccess: () => { + throw redirect; + } + })() + ).rejects.toBe(redirect); + }); + expect(result.current.submitting).toBe(false); + }); + test.each([ + 'reset', + 'unmount' + ] as const)('ignores obsolete success after %s', async action => { + const { result, unmount } = ready(); + const pending = deferred(); + const success = vi.fn(); + let submitted!: Promise; + await act(async () => { + submitted = result.current.handleSubmit(() => pending.promise, { + onSuccess: success + })(); + }); + if (action === 'reset') + act(() => result.current.reset({ name: 'New' })); + else unmount(); + await act(async () => { + pending.resolve(); + await submitted; + }); + expect(success).not.toHaveBeenCalled(); + if (action === 'reset') + expect(result.current.getValue()).toEqual({ name: 'New' }); + }); + test('does not dispatch values changed during asynchronous validation', async () => { + const pending = deferred<{ valid: true }>(); + const guarded = object({ + name: string().addValidator(() => pending.promise) + }); + const { result } = renderHook(() => useSchemaForm(guarded)); + act(() => result.current.reset({ name: 'Old' })); + const send = vi.fn(); + let submitted!: Promise; + act(() => { + submitted = result.current.handleSubmit(send)(); + }); + act(() => result.current.setValue({ name: 'New' })); + await act(async () => { + pending.resolve({ valid: true }); + await submitted; + }); + expect(send).not.toHaveBeenCalled(); + }); +}); diff --git a/libs/react-form/src/react.test.tsx b/libs/react-form/src/react.test.tsx index 403abb74..73c3b8f8 100644 --- a/libs/react-form/src/react.test.tsx +++ b/libs/react-form/src/react.test.tsx @@ -357,8 +357,8 @@ describe('nested fields', () => { result.current.cityField.onChange('Berlin'); }); - // The field value in state is still updated - expect(result.current.cityField.value).toBe('Berlin'); + // A rejected setter must not display a value missing from form data. + expect(result.current.cityField.value).toBeUndefined(); }); }); diff --git a/libs/react-form/src/system.test-d.tsx b/libs/react-form/src/system.test-d.tsx new file mode 100644 index 00000000..fac3a739 --- /dev/null +++ b/libs/react-form/src/system.test-d.tsx @@ -0,0 +1,148 @@ +import { array, boolean, number, object, string } from '@cleverbrush/schema'; +import { expectTypeOf, test } from 'vitest'; +import { + createFormSystem, + defineFieldRenderer, + Field, + type FieldRenderer, + type FieldRenderProps, + useSchemaForm +} from './index.js'; + +const schema = object({ + name: string(), + age: number(), + active: boolean(), + tags: array(string()), + optional: string().optional(), + nullable: string().nullable(), + nested: object({ name: string() }) +}); +const system = createFormSystem({ + renderers: { + string: defineFieldRenderer( + () => null + ), + 'string:select': defineFieldRenderer< + string, + { + options: readonly string[]; + disabled?: boolean; + } + >(() => null), + 'boolean:checkbox': defineFieldRenderer(() => null), + 'array:tags': defineFieldRenderer( + () => null + ), + 'string:nullable': defineFieldRenderer(() => null) + } +}); + +test('typed fields preserve schema and renderer props', () => { + const form = useSchemaForm(schema); + t.name} + fieldProps={{ placeholder: 'Name' }} + />; + t.optional} />; + t.nested.name} />; + t.name} + variant="select" + fieldProps={{ options: ['a'] }} + />; + t.active} variant="checkbox" />; + t.tags} + variant="tags" + fieldProps={{ choices: ['a'] }} + />; + t.nullable} + variant="nullable" + />; + t.name} + // @ts-expect-error misspelled custom prop + fieldProps={{ placehoder: 'Name' }} + />; + // @ts-expect-error required options missing + t.name} variant="select" />; + t.name} + variant="select" + // @ts-expect-error options must be strings + fieldProps={{ options: [1] }} + />; + // @ts-expect-error variant does not exist for strings + t.name} variant="checkbox" />; + // @ts-expect-error no numeric renderer registered + t.age} />; + // @ts-expect-error this renderer does not support null + t.nullable} />; + // @ts-expect-error invalid schema property + t.missing} />; +}); + +test('registry extensions and legacy integrations remain supported', () => { + const extended = createFormSystem({ + renderers: { + ...system.renderers, + 'number:slider': defineFieldRenderer( + () => null + ) + } + }); + const form = useSchemaForm(schema); + t.age} + variant="slider" + fieldProps={{ min: 0 }} + />; + t.name} />; + const legacy: FieldRenderer = (props: FieldRenderProps) => props.value; + t.name} + renderer={legacy} + fieldProps={{ custom: true }} + />; +}); + +test('renderer values and submission callbacks infer correctly', () => { + defineFieldRenderer(props => { + expectTypeOf(props.value).toEqualTypeOf(); + expectTypeOf(props.fieldProps).toEqualTypeOf< + { step: number } | undefined + >(); + props.onChange(1); + // @ts-expect-error renderer cannot write strings + props.onChange('1'); + return null; + }); + const form = useSchemaForm(schema); + form.handleSubmit( + async values => { + expectTypeOf(values.name).toEqualTypeOf(); + return { ok: true, data: { id: 1 } }; + }, + { + onSuccess: data => { + expectTypeOf(data).toEqualTypeOf<{ id: number } | undefined>(); + } + } + ); + form.handleSubmit(() => ({ ok: false, error: 'Try again' })); + form.handleSubmit(() => undefined); + // @ts-expect-error errors are displayable strings + form.handleSubmit(() => ({ ok: false, error: 123 })); + expectTypeOf(form.submitting).toEqualTypeOf(); + expectTypeOf(form.error).toEqualTypeOf(); +}); diff --git a/libs/react-form/src/system.test.tsx b/libs/react-form/src/system.test.tsx new file mode 100644 index 00000000..34149013 --- /dev/null +++ b/libs/react-form/src/system.test.tsx @@ -0,0 +1,121 @@ +import { boolean, object, string } from '@cleverbrush/schema'; +import { cleanup, fireEvent, render, screen } from '@testing-library/react'; +import { afterEach, expect, test } from 'vitest'; +import { Field, FormSystemProvider, useSchemaForm } from './index.js'; +import { createFormSystem, defineFieldRenderer } from './system.js'; + +afterEach(cleanup); +const text = defineFieldRenderer(props => ( + props.onChange(event.target.value)} + /> +)); +const base = createFormSystem({ renderers: { string: text } }); +const extended = createFormSystem({ + renderers: { + ...base.renderers, + 'string:select': defineFieldRenderer( + props => ( + + ) + ), + boolean: defineFieldRenderer(props => ( + props.onChange(event.target.checked)} + /> + )) + } +}); + +test('typed registries pass custom props and synchronize controls without a provider', () => { + function Demo() { + const form = useSchemaForm( + object({ name: string(), choice: string(), active: boolean() }) + ); + return ( + <> + t.name} + fieldProps={{ id: 'Name' }} + /> + t.choice} + variant="select" + fieldProps={{ options: ['one', 'two'] }} + /> + t.active} /> + + + ); + } + render(); + fireEvent.click(screen.getByText('Reset')); + expect((screen.getByLabelText('Name') as HTMLInputElement).value).toBe( + 'Ada' + ); + expect((screen.getByLabelText('Choice') as HTMLSelectElement).value).toBe( + 'two' + ); + expect((screen.getByLabelText('Active') as HTMLInputElement).checked).toBe( + true + ); +}); + +test('Provider supports legacy nesting without overriding a typed Field contract', () => { + function Demo() { + const form = useSchemaForm(object({ name: string() })); + return ( + + t.name} + fieldProps={{ id: 'Legacy' }} + /> + Inner legacy renderer + }} + > + t.name} /> + t.name} + fieldProps={{ id: 'Typed' }} + /> + + + ); + } + render(); + expect(screen.getByLabelText('Legacy')).toBeTruthy(); + expect(screen.getByText('Inner legacy renderer')).toBeTruthy(); + fireEvent.change(screen.getByLabelText('Typed'), { + target: { value: 'shared' } + }); + expect((screen.getByLabelText('Legacy') as HTMLInputElement).value).toBe( + 'shared' + ); +}); diff --git a/libs/react-form/src/system.tsx b/libs/react-form/src/system.tsx new file mode 100644 index 00000000..c54699c4 --- /dev/null +++ b/libs/react-form/src/system.tsx @@ -0,0 +1,135 @@ +import type { + InferType, + ObjectSchemaBuilder, + PropertyDescriptorTree, + SchemaBuilder +} from '@cleverbrush/schema'; +import { SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR } from '@cleverbrush/schema'; +import type { ReactNode } from 'react'; +import { Field, type FieldProps, FormSystemProvider } from './components.js'; +import { getSchemaType } from './helpers.js'; +import type { SchemaFormInstance } from './hooks.js'; +import type { FieldRenderer } from './types.js'; + +declare const rendererTypes: unique symbol; + +/** A renderer whose accepted values and custom props survive registration. */ +export type TypedFieldRenderer = FieldRenderer< + TValue, + TProps +> & { + readonly [rendererTypes]: { value: TValue; props: TProps }; +}; + +/** + * Attach type information without adding a UI dependency or runtime wrapper. + * Include null in TValue when the renderer supports nullable fields. + */ +export function defineFieldRenderer>( + renderer: FieldRenderer +): TypedFieldRenderer { + return renderer as TypedFieldRenderer; +} + +export type TypedRendererRegistry = Readonly< + Record> +>; + +type ValueOf = T extends TypedFieldRenderer ? V : never; +type PropsOf = T extends TypedFieldRenderer ? P : never; +type KindOf = 0 extends 1 & T + ? string + : [NonNullable] extends [string] + ? 'string' + : [NonNullable] extends [number] + ? 'number' + : [NonNullable] extends [boolean] + ? 'boolean' + : [NonNullable] extends [Date] + ? 'date' + : [NonNullable] extends [readonly unknown[]] + ? 'array' + : [NonNullable] extends [object] + ? 'object' + : never; +type CustomProps

= {} extends P ? { fieldProps?: P } : { fieldProps: P }; +type RendererChoice = { + [K in keyof R & string]: [Exclude] extends [ValueOf] + ? K extends `${KindOf}:${infer Variant}` + ? { variant: Variant } & CustomProps> + : K extends KindOf + ? { variant?: undefined } & CustomProps> + : never + : never; +}[keyof R & string]; + +type FieldDescriptor = { + readonly [SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]: { + getSchema: () => SchemaBuilder; + }; +}; +type DescriptorValue = InferType< + ReturnType +>; + +/** Props are inferred from the selected schema property and registered variant. */ +export type TypedFieldProps< + R extends TypedRendererRegistry, + TSchema extends ObjectSchemaBuilder, + TDescriptor extends FieldDescriptor +> = { + form: SchemaFormInstance; + forProperty: ( + tree: PropertyDescriptorTree + ) => TDescriptor; + label?: string; + name?: string; +} & RendererChoice>>; + +/** + * Create a typed Field and a provider for an application's renderer registry. + * The typed Field resolves from this factory's closed registry, so an untyped + * ancestor provider cannot silently replace a renderer with incompatible props. + * The Provider also configures legacy Field consumers and supports nesting. + * Extend a system with createFormSystem({ renderers: { ...system.renderers, + * 'string:custom': customRenderer } }). Existing global APIs remain available. + */ +export function createFormSystem< + const R extends TypedRendererRegistry +>(config: { renderers: R }) { + const renderers = Object.freeze({ ...config.renderers }); + function TypedField< + TSchema extends ObjectSchemaBuilder, + TDescriptor extends FieldDescriptor + >(props: TypedFieldProps): ReactNode { + const descriptor = props.forProperty( + props.form._getFormContext() + .descriptorTree as PropertyDescriptorTree + ); + const type = getSchemaType( + descriptor[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR].getSchema() + ); + const key = props.variant ? `${type}:${props.variant}` : type; + const renderer = renderers[key]; + if (!renderer) + throw new Error( + `No renderer registered for "${key}" in this form system.` + ); + // Field's legacy signature erases descriptor parent types. The factory + // has already checked the selected value and renderer-specific props. + return ( + )} + renderer={renderer} + /> + ); + } + function Provider({ children }: { children: ReactNode }): ReactNode { + return ( + + {children} + + ); + } + return { Field: TypedField, Provider, renderers }; +} diff --git a/libs/react-form/src/types.ts b/libs/react-form/src/types.ts index 5a2f2471..c3e4f3f6 100644 --- a/libs/react-form/src/types.ts +++ b/libs/react-form/src/types.ts @@ -1,24 +1,26 @@ import type { SchemaBuilder } from '@cleverbrush/schema'; -import type { ReactNode } from 'react'; +import type { FormEvent, ReactNode } from 'react'; /** * A renderer function that receives field state and returns a React node. */ -export type FieldRenderer = (props: FieldRenderProps) => ReactNode; +export type FieldRenderer> = ( + props: FieldRenderProps +) => ReactNode; /** * Props passed to a field renderer. */ -export type FieldRenderProps = { - value: any; - initialValue: any; +export type FieldRenderProps> = { + value: TValue | undefined; + initialValue: TValue | undefined; dirty: boolean; touched: boolean; error: string | undefined; validating: boolean; - onChange: (value: any) => void; + onChange: (value: TValue) => void; onBlur: () => void; - setValue: (value: any) => void; + setValue: (value: TValue) => void; schema: SchemaBuilder; /** * Rendering variant hint passed from the `Field` component. @@ -69,7 +71,7 @@ export type FieldRenderProps = { * /> * ``` */ - fieldProps?: Record; + fieldProps?: TProps; }; /** @@ -136,3 +138,28 @@ export type UseSchemaFormOptions = { */ validationDebounceMs?: number; }; + +/** Reactive state for handleSubmit; independent of any UI kit. */ +export type FormSubmissionState = { + readonly submitting: boolean; + readonly error: string | undefined; +}; + +/** Void denotes success without data; explicit failures preserve user input. */ +export type FormSubmitResult = + // biome-ignore lint/suspicious/noConfusingVoidType: callbacks returning void are valid successful submissions + void | { ok: true; data?: TData } | { ok: false; error: string }; + +/** Application-owned success UI and exception translation. Rethrows propagate. */ +export type FormSubmitOptions = { + onSuccess?: ( + data: TData | undefined, + values: TValues + ) => void | Promise; + onError?: (error: unknown) => string | Promise; +}; + +/** Awaitable handler for a form event or a programmatic invocation. */ +export type FormSubmitHandler = ( + event?: Pick +) => Promise; diff --git a/libs/react-form/tsconfig.build.json b/libs/react-form/tsconfig.build.json index fb65c726..024d11d0 100644 --- a/libs/react-form/tsconfig.build.json +++ b/libs/react-form/tsconfig.build.json @@ -16,6 +16,8 @@ }, "include": ["src/**/*.ts", "src/**/*.tsx"], "exclude": [ + "src/**/*.test-d.ts", + "src/**/*.test-d.tsx", "src/**/*.test.ts", "src/**/*.test.tsx", "src/**/*.spec.ts", diff --git a/libs/react-form/tsconfig.json b/libs/react-form/tsconfig.json index 4c936f39..901bc2a3 100644 --- a/libs/react-form/tsconfig.json +++ b/libs/react-form/tsconfig.json @@ -17,6 +17,8 @@ }, "include": ["src/**/*.ts", "src/**/*.tsx"], "exclude": [ + "src/**/*.test-d.ts", + "src/**/*.test-d.tsx", "src/**/*.test.ts", "src/**/*.test.tsx", "src/**/*.spec.ts", diff --git a/libs/react-form/tsconfig.typecheck.json b/libs/react-form/tsconfig.typecheck.json new file mode 100644 index 00000000..825abbda --- /dev/null +++ b/libs/react-form/tsconfig.typecheck.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.build.json", + "compilerOptions": { + "noEmit": true, + "strict": true + }, + "include": ["src/**/*.test-d.ts", "src/**/*.test-d.tsx"], + "exclude": [] +} diff --git a/libs/react-form/vitest.config.mts b/libs/react-form/vitest.config.mts index 7db16b24..e7e30cc3 100644 --- a/libs/react-form/vitest.config.mts +++ b/libs/react-form/vitest.config.mts @@ -4,6 +4,11 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { environment: 'jsdom', - include: ['src/**/*.{test,spec}.{ts,tsx}'] + include: ['src/**/*.{test,spec}.{ts,tsx}'], + typecheck: { + enabled: true, + include: ['src/**/*.test-d.{ts,tsx}'], + tsconfig: './tsconfig.typecheck.json' + } } }); diff --git a/libs/server/README.md b/libs/server/README.md index b4660b0d..52a224bf 100644 --- a/libs/server/README.md +++ b/libs/server/README.md @@ -147,7 +147,7 @@ const UpdateTodo = endpoint .patch('/api/todos/:id') .body(UpdateTodoBody) .clearsCacheTag('todo-list') // clears the collection cache - .clearsCacheTag('todo', p => ({ id: p.params.id })) // clears specific entity + .clearsCacheTag('todo', p => ({ id: p.params.id })) // entity label + external computed key .returns(TodoSchema); ``` @@ -160,6 +160,18 @@ const UpdateTodo = endpoint - **Immutability** — both methods return a new builder; the original is unchanged. +`computeCacheKey` (also exported from the browser-safe `@cleverbrush/server/contract`) +and the client's `computeCacheTagKey` use the same deterministic `ct2:` encoding. +It distinguishes types, normalizes property order and preserves date milliseconds. +`cacheResponse()` invalidates only after successful mutations and prevents older +in-flight reads from refilling any invalidated alias. In-memory invalidation retains +tag-name-prefix coverage; it is not limited to the mutation's selected entity ID. + +**Breaking:** computed keys changed, even for property-free tags. Upgrade external +writers/invalidators together and retire old entries. Literal base labels and TTLs +are unchanged. Endpoint response identity and auth/tenant isolation remain the +consumer's responsibility. See the [migration guide](../../docs/cache-form-migration.md#cache-key-migration-breaking). + ## Registering and Handling Endpoints ```ts diff --git a/libs/server/src/CacheTag.ts b/libs/server/src/CacheTag.ts index f6d44542..3376a86f 100644 --- a/libs/server/src/CacheTag.ts +++ b/libs/server/src/CacheTag.ts @@ -166,49 +166,4 @@ export function serializeTag( return { name, properties }; } -// --------------------------------------------------------------------------- -// Key computation (client-side) -// --------------------------------------------------------------------------- - -/** - * Computes a deterministic cache key from a tag definition and live - * request data. - * - * - Simple tags (no properties) produce just the tag name. - * - Tags with properties produce `name:key1=val1,key2=val2` where - * keys are sorted alphabetically for determinism. - * - * Properties whose `getValue` returns `success: false` are skipped - * (their value is not included in the key). - */ -export function computeCacheKey( - tag: CacheTagDefinition, - root: { - params: Record; - body: unknown; - query: Record; - headers: Record; - } -): string { - const entries = Object.entries(tag.properties); - - if (entries.length === 0) { - return tag.name; - } - - const parts: string[] = []; - for (const [key, accessor] of entries.sort(([a], [b]) => - a.localeCompare(b) - )) { - const result = accessor.getValue(root); - if (result.success && result.value !== undefined) { - parts.push(`${key}=${String(result.value)}`); - } - } - - if (parts.length === 0) { - return tag.name; - } - - return `${tag.name}:${parts.join(',')}`; -} +export { computeCacheKey } from './cacheKey.js'; diff --git a/libs/server/src/Endpoint.ts b/libs/server/src/Endpoint.ts index 0d00b238..4edc86ef 100644 --- a/libs/server/src/Endpoint.ts +++ b/libs/server/src/Endpoint.ts @@ -2206,7 +2206,7 @@ export class EndpointBuilder< * * @example * ```ts - * // PATCH — clears "todo-list" and "todo:id=42" on success + * // PATCH — clears "todo-list" / "todo" names and their computed ct2 keys * endpoint.patch('/api/todos/:id') * .clearsCacheTag('todo-list') * .clearsCacheTag('todo', p => ({ diff --git a/libs/server/src/cacheKey.test.ts b/libs/server/src/cacheKey.test.ts new file mode 100644 index 00000000..042ecb3d --- /dev/null +++ b/libs/server/src/cacheKey.test.ts @@ -0,0 +1,125 @@ +import { describe, expect, test } from 'vitest'; +import type { CacheTagDefinition } from './CacheTag.js'; +import { computeCacheKey } from './cacheKey.js'; + +const root = { params: {}, body: undefined, query: {}, headers: {} }; +function key(values: Record, name = 'records') { + const tag: CacheTagDefinition = { + name, + properties: Object.fromEntries( + Object.entries(values).map(([property, value]) => [ + property, + { getValue: () => ({ success: true, value }) } + ]) + ) + }; + return computeCacheKey(tag, root); +} + +describe('versioned cache keys', () => { + test('uses the same versioned envelope for simple and selected tags', () => { + expect(key({})).toBe('ct2:["records",[]]'); + expect(key({ id: 42 })).toBe( + 'ct2:["records",[["id",["number","42"]]]]' + ); + }); + test('sorts property and nested object keys deterministically', () => { + expect(key({ z: { b: 2, a: 1 }, a: true })).toBe( + key({ a: true, z: { a: 1, b: 2 } }) + ); + }); + test('separates strings containing delimiters from other properties', () => { + expect(key({ search: 'coffee,tagIds=1' })).not.toBe( + key({ search: 'coffee', tagIds: '1' }) + ); + expect(key({ 'x=y': 'z' })).not.toBe(key({ x: 'y=z' })); + expect(key({}, key({ id: 1 }))).not.toBe(key({ id: 1 })); + }); + test('distinguishes scalar types, nested undefined and dates', () => { + const values = [ + 1, + '1', + true, + 'true', + null, + 'null', + 1n, + ['a,b'], + ['a', 'b'], + { a: undefined }, + {}, + new Date('2026-01-01T00:00:00.001Z'), + new Date('2026-01-01T00:00:00.002Z'), + '2026-01-01T00:00:00.001Z', + -0, + 0, + NaN, + Infinity + ]; + expect(new Set(values.map(value => key({ value }))).size).toBe( + values.length + ); + }); + test('omits absent and undefined selected properties', () => { + expect(key({ missing: undefined })).toBe(key({})); + expect( + computeCacheKey( + { + name: 'records', + properties: { + absent: { + getValue: () => ({ success: false, value: 1 }) + } + } + }, + root + ) + ).toBe(key({})); + }); + test('allows repeated references that are not cycles', () => { + const child = { id: 1 }; + expect(key({ value: [child, child] })).toBe( + key({ value: [{ id: 1 }, { id: 1 }] }) + ); + }); + test.each([ + new Map(), + new Set(), + new Date(NaN), + Symbol(), + () => 1 + ])('rejects unsupported values explicitly: %s', value => { + expect(() => key({ value })).toThrow(TypeError); + }); + test('rejects cycles and getters', () => { + const cyclic: any = {}; + cyclic.self = cyclic; + expect(() => key({ cyclic })).toThrow(/cycles/); + expect(() => + key({ + value: { + get secret() { + throw new Error(); + } + } + }) + ).toThrow(/getters/); + }); + test('rejects array getters without invoking them and named array properties', () => { + let invoked = false; + const value = Object.defineProperty([], 0, { + get() { + invoked = true; + return 1; + } + }); + expect(() => key({ value })).toThrow(/getters/); + expect(invoked).toBe(false); + expect(() => key({ value: Object.assign([], { extra: 1 }) })).toThrow( + /named properties/ + ); + expect(key({ value: new Array(2) })).toBe( + key({ value: [undefined, undefined] }) + ); + }); +}); diff --git a/libs/server/src/cacheKey.ts b/libs/server/src/cacheKey.ts new file mode 100644 index 00000000..ca64130c --- /dev/null +++ b/libs/server/src/cacheKey.ts @@ -0,0 +1,128 @@ +import type { CacheTagDefinition } from './CacheTag.js'; + +type CacheRoot = Parameters< + CacheTagDefinition['properties'][string]['getValue'] +>[0]; + +/** Encode values without coercion, delimiter ambiguity, or lost date precision. */ +function encode(value: unknown, ancestors: Set): unknown { + if (value === null) return ['null']; + switch (typeof value) { + case 'undefined': + return ['undefined']; + case 'string': + case 'boolean': + return [typeof value, value]; + case 'number': + return ['number', Object.is(value, -0) ? '-0' : String(value)]; + case 'bigint': + return ['bigint', String(value)]; + case 'object': { + if (ancestors.has(value)) { + throw new TypeError( + 'Cache tag values must not contain cycles.' + ); + } + if (value instanceof Date) { + if (!Number.isFinite(value.getTime())) { + throw new TypeError('Cache tag dates must be valid.'); + } + return ['date', value.toISOString()]; + } + const prototype = Object.getPrototypeOf(value); + if ( + !Array.isArray(value) && + prototype !== Object.prototype && + prototype !== null + ) { + throw new TypeError('Unsupported cache tag object.'); + } + if (Object.getOwnPropertySymbols(value).length > 0) { + throw new TypeError( + 'Cache tag values cannot have symbol keys.' + ); + } + ancestors.add(value); + try { + if (Array.isArray(value)) { + if ( + Object.keys(value).some( + key => + !/^(0|[1-9]\d*)$/.test(key) || + Number(key) >= value.length + ) + ) { + throw new TypeError( + 'Cache tag arrays cannot have named properties.' + ); + } + return [ + 'array', + Array.from({ length: value.length }, (_, index) => { + const property = Object.getOwnPropertyDescriptor( + value, + index + ); + if (property && !('value' in property)) { + throw new TypeError( + 'Cache tag values cannot contain getters.' + ); + } + return encode(property?.value, ancestors); + }) + ]; + } + return [ + 'object', + Object.keys(value) + .sort() + .map(key => { + const property = Object.getOwnPropertyDescriptor( + value, + key + )!; + if (!('value' in property)) { + throw new TypeError( + 'Cache tag values cannot contain getters.' + ); + } + return [key, encode(property.value, ancestors)]; + }) + ]; + } finally { + ancestors.delete(value); + } + } + default: + throw new TypeError( + `Unsupported cache tag value: ${typeof value}.` + ); + } +} + +/** + * Compute a versioned, deterministic key from a tag and selected request values. + * + * Format: `ct2:` followed by JSON `[tagName, [[property, encodedValue], ...]]`. + * Properties and object keys are sorted by code units. Values are type-tagged; + * Dates retain millisecond precision. Missing/undefined selected properties are + * omitted. Even property-free tags use this format; base invalidation labels + * remain the original names. All cache writers/invalidators must use the same + * version and retire old entries when upgrading. + * + * @throws TypeError for cyclic values, invalid dates, functions, symbols, + * accessor properties, or objects other than arrays, dates and plain objects. + */ +export function computeCacheKey( + tag: CacheTagDefinition, + root: CacheRoot +): string { + const parts: Array<[string, unknown]> = []; + for (const key of Object.keys(tag.properties).sort()) { + const result = tag.properties[key].getValue(root); + if (result.success && result.value !== undefined) { + parts.push([key, encode(result.value, new Set())]); + } + } + return `ct2:${JSON.stringify([tag.name, parts])}`; +} diff --git a/libs/server/src/contract.ts b/libs/server/src/contract.ts index ac35f300..6fc5f109 100644 --- a/libs/server/src/contract.ts +++ b/libs/server/src/contract.ts @@ -31,6 +31,7 @@ export type { CacheTagDefinition, CacheTagPropertyAccessor } from './CacheTag.js'; +export { computeCacheKey } from './cacheKey.js'; export { type ActionContext, type AllowedResponseReturn, diff --git a/libs/server/src/middlewares/ResponseCache.test.ts b/libs/server/src/middlewares/ResponseCache.test.ts new file mode 100644 index 00000000..f77dbace --- /dev/null +++ b/libs/server/src/middlewares/ResponseCache.test.ts @@ -0,0 +1,146 @@ +import { createServer } from 'node:http'; +import { describe, expect, test, vi } from 'vitest'; +import type { RequestContext } from '../RequestContext.js'; +import { cacheResponse } from './ResponseCache.js'; + +function context(method = 'GET', names = ['records']) { + const response = { + statusCode: 200, + writeHead: vi.fn(function (this: any, status: number) { + this.statusCode = status; + return this; + }), + end: vi.fn() + }; + return { + method, + response, + pathParams: {}, + queryParams: {}, + headers: {}, + items: new Map([ + [ + '__endpoint_meta', + { + cacheTags: names.map(name => ({ name, properties: {} })) + } + ] + ]) + } as unknown as RequestContext; +} +function send(ctx: RequestContext, body: string, status = 200) { + ctx.response.writeHead(status, { 'content-type': 'text/plain' }); + ctx.response.end(body); +} + +describe('server response cache generations', () => { + test('preserves HTTP body, status and headers across cached responses', async () => { + const cache = cacheResponse(); + let reads = 0; + const server = createServer(async (request, response) => { + const ctx = Object.assign(context(request.method), { response }); + try { + await cache(ctx, async () => { + if (request.method === 'GET') { + reads++; + send(ctx, `read-${reads}`, 201); + } else send(ctx, '', request.url === '/failed' ? 500 : 204); + }); + } catch { + response.statusCode = 500; + response.end(); + } + }); + await new Promise(resolve => + server.listen(0, '127.0.0.1', resolve) + ); + const port = (server.address() as { port: number }).port; + const url = `http://127.0.0.1:${port}`; + try { + for (let i = 0; i < 2; i++) { + const response = await fetch(url); + expect(response.status).toBe(201); + expect(response.headers.get('content-type')).toBe('text/plain'); + expect(await response.text()).toBe('read-1'); + } + await (await fetch(`${url}/failed`, { method: 'PATCH' })).text(); + expect(await (await fetch(url)).text()).toBe('read-1'); + await (await fetch(url, { method: 'PATCH' })).text(); + expect(await (await fetch(url)).text()).toBe('read-2'); + } finally { + server.closeAllConnections(); + await new Promise((resolve, reject) => + server.close(error => (error ? reject(error) : resolve())) + ); + } + }); + + test('caches successful reads and invalidates on a successful write', async () => { + const cache = cacheResponse(); + const initial = context(); + await cache(initial, async () => send(initial, 'old')); + const hit = context(); + const handler = vi.fn(); + await cache(hit, handler); + expect(handler).not.toHaveBeenCalled(); + expect(hit.response.end).toHaveBeenCalledWith(Buffer.from('old')); + const write = context('PATCH'); + await cache(write, async () => send(write, '', 204)); + await cache(context(), handler); + expect(handler).toHaveBeenCalledTimes(1); + }); + test.each([ + 400, 500 + ])('failed write %s preserves cached entries', async status => { + const cache = cacheResponse(); + const initial = context(); + await cache(initial, async () => send(initial, 'old')); + const write = context('PATCH'); + await cache(write, async () => send(write, '', status)); + const handler = vi.fn(); + await cache(context(), handler); + expect(handler).not.toHaveBeenCalled(); + }); + test('a thrown write preserves entries', async () => { + const cache = cacheResponse(); + const initial = context(); + await cache(initial, async () => send(initial, 'old')); + await expect( + cache(context('PATCH'), async () => { + throw new Error('write failed'); + }) + ).rejects.toThrow('write failed'); + const handler = vi.fn(); + await cache(context(), handler); + expect(handler).not.toHaveBeenCalled(); + }); + test('in-flight reads cannot repopulate any alias after a write', async () => { + const cache = cacheResponse(); + let release!: () => void; + const gate = new Promise(resolve => { + release = resolve; + }); + const initial = context('GET', ['records', 'other']); + const pending = cache(initial, async () => { + await gate; + send(initial, 'old'); + }); + const write = context('PATCH'); + await cache(write, async () => send(write, '', 204)); + release(); + await pending; + const handler = vi.fn(); + await cache(context('GET', ['other']), handler); + expect(handler).toHaveBeenCalledTimes(1); + }); + test('a mutation invalidates cached aliases and name-prefix variants', async () => { + const cache = cacheResponse(); + const initial = context('GET', ['records-window', 'other']); + await cache(initial, async () => send(initial, 'old')); + const write = context('DELETE'); + await cache(write, async () => send(write, '', 204)); + const handler = vi.fn(); + await cache(context('GET', ['other']), handler); + expect(handler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/libs/server/src/middlewares/ResponseCache.ts b/libs/server/src/middlewares/ResponseCache.ts index 5286b25c..3edd4147 100644 --- a/libs/server/src/middlewares/ResponseCache.ts +++ b/libs/server/src/middlewares/ResponseCache.ts @@ -10,6 +10,8 @@ */ import type { ServerResponse } from 'node:http'; +import type { CacheTagDefinition } from '../CacheTag.js'; +import { computeCacheKey } from '../cacheKey.js'; import type { RequestContext } from '../RequestContext.js'; import type { Middleware } from '../types.js'; @@ -42,43 +44,13 @@ interface CacheEntry { headers: Record; body: Buffer; expiresAt: number; + generations: ReadonlyArray; } function isMutating(method: string): boolean { return ['POST', 'PUT', 'DELETE', 'PATCH'].includes(method.toUpperCase()); } -function computeKey( - tags: ReadonlyArray<{ - name: string; - properties: Readonly< - Record< - string, - { - getValue(root: any): { - value?: unknown; - success: boolean; - }; - } - > - >; - }>, - root: any -): string[] { - return tags.map(tag => { - const parts: string[] = []; - for (const [key, accessor] of Object.entries(tag.properties).sort( - ([a], [b]) => a.localeCompare(b) - )) { - const result = accessor.getValue(root); - if (result.success && result.value !== undefined) { - parts.push(`${key}=${String(result.value)}`); - } - } - return parts.length > 0 ? `${tag.name}:${parts.join(',')}` : tag.name; - }); -} - // --------------------------------------------------------------------------- // Middleware // --------------------------------------------------------------------------- @@ -94,8 +66,8 @@ function computeKey( * handler never executes. On cache miss, runs the handler and caches * the response. * - **Mutation (POST/PUT/PATCH/DELETE)**: Lets the handler run, then - * invalidates all cache entries whose key starts with any of the - * endpoint's cache tag names. + * invalidates all cache entries whose tag names start with any of the + * endpoint's cache tag names. Older in-flight reads cannot refill them. * * @param options - Cache configuration. * @returns A server-side {@link Middleware}. @@ -111,22 +83,22 @@ export function cacheResponse(options: ServerCacheOptions = {}): Middleware { const { ttlByTag = {}, defaultTtl = 60_000 } = options; const cache = new Map(); + const generations = new Map(); + const isCurrent = (snapshot: CacheEntry['generations']) => + snapshot.every( + ([name, generation]) => generations.get(name) === generation + ); return async (ctx: RequestContext, next: () => Promise) => { const meta = ctx.items.get('__endpoint_meta') as any; - const tags: ReadonlyArray<{ - name: string; - properties: Record< - string, - { getValue(root: any): { value?: unknown; success: boolean } } - >; - }> = meta?.cacheTags ?? []; + const tags: readonly CacheTagDefinition[] = meta?.cacheTags ?? []; if (tags.length === 0) { return next(); } if (isMutating(ctx.method)) { + const names = tags.map(tag => tag.name); // Run handler first (so cache is invalidated only on success) await next(); @@ -134,34 +106,14 @@ export function cacheResponse(options: ServerCacheOptions = {}): Middleware { (ctx.response as ServerResponse).statusCode >= 200 && (ctx.response as ServerResponse).statusCode < 300 ) { - // Build root for key computation - const rawBody = ctx.items.get('__raw_body') as - | unknown - | undefined; - const root = { - params: ctx.pathParams ?? {}, - body: rawBody, - query: ctx.queryParams ?? {}, - headers: ctx.headers ?? {} - }; - - const keys = computeKey(tags, root); - for (const tag of tags) { - for (const [cachedKey] of cache) { - if ( - keys.includes(cachedKey) || - keys.some(_k => cachedKey.startsWith(tag.name)) - ) { - cache.delete(cachedKey); - } - } - // Also delete by pure tag name prefix - for (const [cachedKey] of cache) { - if (cachedKey.startsWith(tag.name)) { - cache.delete(cachedKey); - } + for (const [name, generation] of generations) { + if (names.some(prefix => name.startsWith(prefix))) { + generations.set(name, generation + 1); } } + for (const [key, entry] of cache) { + if (!isCurrent(entry.generations)) cache.delete(key); + } } return; } @@ -175,12 +127,20 @@ export function cacheResponse(options: ServerCacheOptions = {}): Middleware { headers: ctx.headers ?? {} }; - const keys = computeKey(tags, root); + const keys = tags.map(tag => computeCacheKey(tag, root)); + const snapshot = tags.map(({ name }) => { + if (!generations.has(name)) generations.set(name, 0); + return [name, generations.get(name)!] as const; + }); // Check all keys — first valid cache hit wins for (const key of keys) { const entry = cache.get(key); - if (entry && entry.expiresAt > Date.now()) { + if ( + entry && + entry.expiresAt > Date.now() && + isCurrent(entry.generations) + ) { // Serve from cache const res = ctx.response as ServerResponse; res.writeHead(entry.status, entry.headers); @@ -234,7 +194,11 @@ export function cacheResponse(options: ServerCacheOptions = {}): Middleware { await next(); // Store in cache on success - if (capturedStatus >= 200 && capturedStatus < 300) { + if ( + capturedStatus >= 200 && + capturedStatus < 300 && + isCurrent(snapshot) + ) { const body = Buffer.concat(chunks); const ttl = tags.reduce((max, tag) => { const t = @@ -250,7 +214,8 @@ export function cacheResponse(options: ServerCacheOptions = {}): Middleware { status: capturedStatus, headers: capturedHeaders, body, - expiresAt: Date.now() + ttl + expiresAt: Date.now() + ttl, + generations: snapshot }); } } diff --git a/libs/server/tests/CacheTag.test.ts b/libs/server/tests/CacheTag.test.ts index edf3fc1f..2ca394ec 100644 --- a/libs/server/tests/CacheTag.test.ts +++ b/libs/server/tests/CacheTag.test.ts @@ -210,16 +210,20 @@ describe('computeCacheKey', () => { headers: {} }; - it('returns tag name for simple tags with no properties', () => { + it('uses the versioned format for simple tags with no properties', () => { const tag = makeTag('invalidate-all', {}); - expect(computeCacheKey(tag, emptyRoot)).toBe('invalidate-all'); + expect(computeCacheKey(tag, emptyRoot)).toBe( + 'ct2:["invalidate-all",[]]' + ); }); it('builds key with single property', () => { const tag = makeTag('todo', { id: makeConstAccessor(42) }); - expect(computeCacheKey(tag, emptyRoot)).toBe('todo:id=42'); + expect(computeCacheKey(tag, emptyRoot)).toBe( + 'ct2:["todo",[["id",["number","42"]]]]' + ); }); it('builds key with multiple properties sorted alphabetically', () => { @@ -229,7 +233,7 @@ describe('computeCacheKey', () => { m: makeConstAccessor('middle') }); expect(computeCacheKey(tag, emptyRoot)).toBe( - 'todo:a=first,m=middle,z=last' + 'ct2:["todo",[["a",["string","first"]],["m",["string","middle"]],["z",["string","last"]]]]' ); }); @@ -238,22 +242,24 @@ describe('computeCacheKey', () => { id: makeConstAccessor(42), optional: makeFailingAccessor() }); - expect(computeCacheKey(tag, emptyRoot)).toBe('todo:id=42'); + expect(computeCacheKey(tag, emptyRoot)).toBe( + 'ct2:["todo",[["id",["number","42"]]]]' + ); }); - it('returns tag name when all properties fail', () => { + it('uses the property-free versioned key when all properties fail', () => { const tag = makeTag('todo', { a: makeFailingAccessor(), b: makeFailingAccessor() }); - expect(computeCacheKey(tag, emptyRoot)).toBe('todo'); + expect(computeCacheKey(tag, emptyRoot)).toBe('ct2:["todo",[]]'); }); it('skips properties with undefined value', () => { const tag = makeTag('todo', { id: makeConstAccessor(undefined) }); - expect(computeCacheKey(tag, emptyRoot)).toBe('todo'); + expect(computeCacheKey(tag, emptyRoot)).toBe('ct2:["todo",[]]'); }); it('produces stable output for the same inputs', () => { @@ -296,7 +302,9 @@ describe('integration', () => { const key = computeCacheKey(definition, root); // Sorted: filter, orgId, userId - expect(key).toBe('resource:filter=active,orgId=10,userId=u1'); + expect(key).toBe( + 'ct2:["resource",[["filter",["string","active"]],["orgId",["number","10"]],["userId",["string","u1"]]]]' + ); }); it('end-to-end with params and headers', () => { @@ -326,6 +334,8 @@ describe('integration', () => { const key = computeCacheKey(definition, root); // Sorted: orgId, projectId, tenant - expect(key).toBe('project:orgId=42,projectId=p1,tenant=acme'); + expect(key).toBe( + 'ct2:["project",[["orgId",["number","42"]],["projectId",["string","p1"]],["tenant",["string","acme"]]]]' + ); }); }); diff --git a/websites/docs/app/client/sections/cacheTags.tsx b/websites/docs/app/client/sections/cacheTags.tsx index fdf9cdc8..73993976 100644 --- a/websites/docs/app/client/sections/cacheTags.tsx +++ b/websites/docs/app/client/sections/cacheTags.tsx @@ -108,8 +108,11 @@ externalCacheTags({ invalidateTag: revalidateTag });
  • On mutation (POST/PUT/PATCH/DELETE):{' '} - Invalidates all entries whose key starts with any of the - endpoint's tag names — no manual callbacks needed. + After a successful response, invalidates entries whose + tag names start with any of the endpoint's tag + names. Failed writes preserve entries; older in-flight + reads cannot refill an invalidated response or its + aliases.
  • Property-based keys: Tags with @@ -126,6 +129,33 @@ externalCacheTags({ invalidateTag: revalidateTag }); +
    +

    Versioned keys: breaking migration

    +

    + Server and client helpers, response caches and external + invalidation share the deterministic ct2:{' '} + format. Property and object key order is normalized, values + retain their types, and dates retain milliseconds. Even a + property-free tag computes to{' '} + {'ct2:["records",[]]'}. Unsupported selected + values throw TypeError. +

    +

    + Upgrade external cache writers and invalidators together and + flush or expire old entries. There is no legacy fallback. + Base invalidation labels remain literal names; TTLs are + unchanged. External invalidators send both base labels and + computed keys by default, including property-free tags. +

    +

    + Response shape and auth/tenant isolation remain + consumer-owned: use distinct list/detail names and select + every value that affects a response. URLs and identities are + not automatically included in keys. External caches need + their own protection against stale concurrent writers. +

    +
    +

    Options

    diff --git a/websites/docs/app/react-form/LifecycleDemo.module.css b/websites/docs/app/react-form/LifecycleDemo.module.css new file mode 100644 index 00000000..da377e79 --- /dev/null +++ b/websites/docs/app/react-form/LifecycleDemo.module.css @@ -0,0 +1,23 @@ +.form { + background: var(--bg-secondary); +} + +.form select { + width: 100%; + padding: 10px 14px; + border: 1px solid var(--border-subtle); + border-radius: 8px; + background: var(--bg-card); + color: var(--text-primary); + font: inherit; +} + +.form input[type="checkbox"] { + width: 1rem; + height: 1rem; +} + +.form button:disabled { + opacity: 0.65; + cursor: wait; +} diff --git a/websites/docs/app/react-form/LifecycleDemo.tsx b/websites/docs/app/react-form/LifecycleDemo.tsx new file mode 100644 index 00000000..b5e320a5 --- /dev/null +++ b/websites/docs/app/react-form/LifecycleDemo.tsx @@ -0,0 +1,181 @@ +'use client'; + +import { + createFormSystem, + defineFieldRenderer, + useSchemaForm +} from '@cleverbrush/react-form'; +import { boolean, object, string } from '@cleverbrush/schema'; +import { useState } from 'react'; +import styles from './LifecycleDemo.module.css'; + +const ProfileSchema = object({ + name: string() + .required('Name is required') + .minLength(2, 'Use at least two characters'), + role: string().required('Choose a role'), + active: boolean() +}); +const text = defineFieldRenderer(props => ( +
    + props.onChange(e.target.value)} + onBlur={props.onBlur} + aria-invalid={props.touched && !!props.error} + aria-describedby={ + props.error ? `${props.fieldProps?.id}-error` : undefined + } + /> + {props.touched && props.error && ( + + {props.error} + + )} +
    +)); +const basic = createFormSystem({ renderers: { string: text } }); +const ui = createFormSystem({ + renderers: { + ...basic.renderers, + 'string:select': defineFieldRenderer< + string, + { id: string; options: string[] } + >(props => ( +
    + + {props.touched && props.error && ( + {props.error} + )} +
    + )), + boolean: defineFieldRenderer(props => ( + props.onChange(e.target.checked)} + onBlur={props.onBlur} + /> + )) + } +}); + +/** Local-only demonstration: no user values are sent to a server. */ +export default function LifecycleDemo() { + const form = useSchemaForm(ProfileSchema); + const [fail, setFail] = useState(false); + const [confirmation, setConfirmation] = useState(''); + const submit = form.handleSubmit( + async values => { + setConfirmation(''); + await new Promise(resolve => setTimeout(resolve, 600)); + if (fail) + return { + ok: false, + error: 'Demo failure: your values are preserved. Try again.' + }; + return { ok: true, data: values }; + }, + { + onSuccess: values => { + setConfirmation( + `Saved ${values?.name}. The form was cleared without remounting.` + ); + form.reset(); + } + } + ); + + return ( +
    +
    + + t.name} + fieldProps={{ id: 'profile-name' }} + /> +
    +
    + + t.role} + variant="select" + fieldProps={{ + id: 'profile-role', + options: ['reader', 'editor'] + }} + /> +
    +
    + + t.active} + fieldProps={{ id: 'profile-active' }} + /> +
    +

    + +

    + {' '} + {' '} + + {form.error && ( +

    + {form.error} +

    + )} +

    {confirmation}

    +
    + ); +} diff --git a/websites/docs/app/react-form/page.tsx b/websites/docs/app/react-form/page.tsx index 24d6725f..22c9514a 100644 --- a/websites/docs/app/react-form/page.tsx +++ b/websites/docs/app/react-form/page.tsx @@ -10,6 +10,7 @@ import { number, object, string } from '@cleverbrush/schema'; import { InstallBanner } from '@cleverbrush/website-shared/components/InstallBanner'; import { highlightTS } from '@cleverbrush/website-shared/lib/highlight'; import { type InputHTMLAttributes, type ReactNode, useState } from 'react'; +import LifecycleDemo from './LifecycleDemo'; /* ── Live Quick-Start form ──────────────────────────────────────── */ @@ -298,6 +299,75 @@ function App() {
    +
    +

    Typed renderers and submission lifecycle

    +

    + Create a UI-agnostic typed registry once with{' '} + defineFieldRenderer<Value, Props> and{' '} + createFormSystem. Its Field{' '} + checks the selected value, variant and custom props. + Extend it by spreading system.renderers. + Its optional Provider also configures + legacy fields. +

    +
    +                        (props => (
    +  
    +));
    +const ui = createFormSystem({ renderers: { 'string:select': select } });
    +
    +function ProfileForm() {
    +  const form = useSchemaForm(ProfileSchema);
    +  const submit = form.handleSubmit(saveProfile, {
    +    onSuccess: saved => { showConfirmation('Saved'); form.reset(); },
    +    onError: error => {
    +      if (isExpectedNetworkError(error)) return 'Please try again';
    +      throw error;
    +    }
    +  });
    +  return 
    + t.role} variant="select" + fieldProps={{ options: ['reader', 'editor'] }} /> + {form.error &&

    {form.error}

    } + + ; +}`) + }} + /> +
    +

    + The submit callback returns void,{' '} + {'{ ok: true, data? }'}, or{' '} + {'{ ok: false, error: string }'}. Duplicate + submissions are locked from validation onward. Errors + propagate unless onError translates them; + exceptions in onSuccess always propagate. + Notifications and navigation remain application-owned. +

    +

    + reset(values) updates mounted text, select + and checkbox fields and establishes a clean baseline; + reset() clears them. The form controller + is stable. Value changes invalidate old async + validation. Reset/unmount suppresses old submission + callbacks without cancelling a network operation or + prematurely unlocking it. +

    +

    Try the lifecycle

    +

    + Load sample values, edit or clear them, and try a failed + save followed by a retry. A successful save clears the + mounted fields. This demo waits 600 ms locally and sends + no data to a server. +

    + +
    + {/* ── Why ──────────────────────────────────────────── */}

    💡 Why @cleverbrush/react-form?

    @@ -775,9 +845,10 @@ function App() { form.reset(values?) - Resets the form to initial values (or - provided values). Clears dirty/touched - state. + Clears values, or establishes supplied + values as a new clean baseline. Updates + mounted fields and clears errors, + dirty/touched/validating state. @@ -794,8 +865,9 @@ function App() { form.setValue(values) - Sets form values programmatically - (partial update). + Shallow-merges values and updates + mounted fields without marking them + touched. @@ -808,6 +880,29 @@ function App() { selector. + + + + form.handleSubmit(onValid, options?) + + + + Validates, locks duplicate submissions + and handles explicit results and + callbacks. + + + + + + form.submitting / form.error + + + + Reactive, read-only submission state on + a stable form controller. + +
    @@ -919,10 +1014,7 @@ function App() { () => void - - Mark the field as touched (triggers - validation) - + Mark the field as touched From ccea3ecb613559953101ab9ecae78c4383ed769e Mon Sep 17 00:00:00 2001 From: Andrew Zolotukhin Date: Tue, 8 Sep 2026 14:12:22 +0000 Subject: [PATCH 2/5] refactor: share form snapshots and equality through deep utilities --- .changeset/cache-form-correctness.md | 3 + docs/assets/form-lifecycle.png | Bin 77934 -> 70957 bytes docs/cache-form-migration.md | 42 ++++++ libs/deep/README.md | 60 ++++++++- libs/deep/src/data.ts | 12 ++ libs/deep/src/deepClone.test.ts | 138 ++++++++++++++++++++ libs/deep/src/deepClone.ts | 41 ++++++ libs/deep/src/deepEqual.test.ts | 180 +++++++++++++++++++++++++- libs/deep/src/deepEqual.ts | 178 ++++++++++++------------- libs/deep/src/index.ts | 10 +- libs/react-form/README.md | 9 ++ libs/react-form/package.json | 1 + libs/react-form/src/FormStore.test.ts | 74 +++++++++++ libs/react-form/src/FormStore.ts | 68 ++-------- libs/react-form/tsconfig.json | 2 +- package-lock.json | 77 +++++------ 16 files changed, 702 insertions(+), 193 deletions(-) create mode 100644 libs/deep/src/data.ts create mode 100644 libs/deep/src/deepClone.test.ts create mode 100644 libs/deep/src/deepClone.ts create mode 100644 libs/react-form/src/FormStore.test.ts diff --git a/.changeset/cache-form-correctness.md b/.changeset/cache-form-correctness.md index c95ab01a..60830213 100644 --- a/.changeset/cache-form-correctness.md +++ b/.changeset/cache-form-correctness.md @@ -1,9 +1,12 @@ --- '@cleverbrush/server': major '@cleverbrush/client': major +'@cleverbrush/deep': major '@cleverbrush/react-form': minor --- Use one browser-safe, deterministic `ct2:` cache key encoder across server and client helpers, response caches, and external invalidation. Selected properties and plain-object keys are sorted, values retain their types, and dates retain millisecond precision. Unsupported values fail explicitly. **Breaking:** every computed key changes, including property-free tags. Upgrade external cache writers and invalidators together, and flush or expire previous entries; literal base invalidation names and TTL settings remain unchanged. Successful mutations invalidate cached aliases and prevent older in-flight reads from refilling them; failed writes preserve entries. Synchronize mounted schema-form fields with form/field setters and reset baselines, using descriptor identities and stable external-store subscriptions. Discard obsolete async validation after value changes or reset. Add `handleSubmit`, reactive `submitting`/`error`, duplicate-submit protection, explicit success/failure results and opt-in error translation. Add `defineFieldRenderer` and `createFormSystem` for typed renderer values, variants, props and composable registries while retaining existing form APIs. See the cache/form migration guide for semantics and examples. + +Add reusable `deepClone` for plain objects, arrays and Dates with cycle/shared-reference preservation, sparse-array lengths, null prototypes and safe enumerable own string/symbol properties. React forms now use `deepClone` and `deepEqual` from `@cleverbrush/deep` for snapshots and dirty checks. **Breaking:** correct existing `deepEqual` semantics for null/type mismatches, equivalent cycles and shared references, signed zero, invalid Dates, symbol keys and sparse arrays. Opaque values such as Files, Maps, Sets and custom instances compare only by identity and remain references when cloned. Unordered arrays preserve duplicate/hole counts without hash-based matching or input mutation. Review consumers relying on the old outcomes; `deepExtend`, `HashObject` and `Transaction` are unchanged. diff --git a/docs/assets/form-lifecycle.png b/docs/assets/form-lifecycle.png index 6871823cb79ed725dcdffa2d366571c66ca9ec4a..6244d692d92d428cfed4012c7983ed808719e922 100644 GIT binary patch literal 70957 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ⅆ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%0D-;pBES)>@ATQaOHUGn}8WMk< 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

    !1-5|#&^T@!2#HeK(? zSazLdnbi`_Dt8mcNc=7nU6M{-C7@j6SC0M|EW-7lgZsgj`V|%xgM35+MJ?Ss*xqFJ z7wkRgW9uD>#WPLEjfPVN<3PYoM@w5;Q1Ai#k%oqbi>v7uKmHx03qv-I*+7Un6JHuV zCI`En?8o*m@eVxp6}m7xvgd{#j%09b&+?4BwI;!Pxz8qUDh4~mS7*I!-+NTIy|c6P z&)rCfjaB;3lRV4V+TJDPbP1%{&40Z4oYs8&c} zwr>!rn=nf0JyEbBfA;3kI%|D6UL57Sv5ZLPQS_ktUx#;EkD+gHW%KM)a0egLmoN4A z+dVd6ZN`&5VY92Yo8$>vJ%U4Bgb^_YC1e{r3RO2qbj@W{}(pk^p`v zs~MgSOh}mX3dmySaMbB|X}-n%2AB`%Jf|b~ZyxScJyg#=>69m&Sg$lU2nL{-q`6+- zVQZP@Q3a3jw7y~}Y#ERqdMLg6-*~1A^OfW24-AeEPVBdPW);E}S%h+miq+ruqH`tV zA-9{gX?R>NE;M^Q{+6+vLLU<8 zQ%bou>Gw9J;bkQyTN!(Mdx%7%gIs8FGH zl?&(F(9dCQdAGhR@d)_}2_!xv1$9N-qVa8rLIU}|x;U^IKnQx)x?x~mV*SMe?D5%q zlm<-K?E?ebqI~HF>V9vQr@@4aS;pb3!p&yF?c9FQ7!i6KK^uy^zxGFjc?h3=^trPC zTk{jurYu1=Eup);QRxeDdo65R`Gb#R;N`%2iwxd@!0kq>u0RH}!lSnjWY;#=ZGA%)VOGrL|s(U6K- ze%61@W8sjqw5hEB!aD%|1wpTCkR;a)rl8;%^OZ5)=R!#I7uVHrN0u@yTHTTRv0=Uz zs8UfGwRUIbJtdHlL;tTMmjZdhzt{;T&e+Q_i=m_2dt<~lF|V|np`UzSUqqDH*z_@lOYg2D ztL&~sKOA|!7EHY{+C&!?xM=zpAiJAb%pW5*K%I;e@`riG32GurvQheqkS%g*MOH=w zU44$DY##sUFWVL<$sAxp+2lYm{Il~FjRiV+F!Z5vZ&0R!z!@1=Og3anW(dw%#&HufK@`bOX z&tW99wcv}VsC+74t6-3a!%!e5w&z@Dv6-8rgNALETH`5H?+msQU3}17x%~_;hayZ6CZ;jDoecJjfRYf5D{l*#Z>yXxul#+| zl06i2oIB6Er1bn4kngeZYL1acR39TSUm!0D$!`#$30gJQlD?6L4SMfTW25~8tk5PM zE;Lm>Fb!;h6p?QWYQ{%ge=W-1ffjD-OG*HIbXo4GZXu)bi)V_!JL#L#{b3ls2umUV zW(ZTQczJ>RQ(mjI5R{cxJhiW-mn{NaHaKC&WjyJ5-fyqWWEO)gAy9l(w(TgPO za`r|xFP1!zTUl=_^0~LtAe*~T5{xwxo&5@Zp3QCF_Mf2(kK^sy>sbwd1Kx-a-WCrA zrj_`NWXA+&fxTUrorrnVN#ds}@`zNV`^2?8|4qCwy|G%vrJ2|Krf{Zf6kSiiVE**f hxV%3IGzR=LS}3zSW!bJ&PW!ic#D!&q$^`U&{vSG8EJ^?X literal 77934 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 X#zkN8X^1MZ%*d#xRNCqW1j6oghCGk2?wqM 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 bjR%^uUaueOw+Gn%K_K}TDl!$%O+x+yOS|pA diff --git a/docs/cache-form-migration.md b/docs/cache-form-migration.md index c4c427cd..5dd9e674 100644 --- a/docs/cache-form-migration.md +++ b/docs/cache-form-migration.md @@ -62,6 +62,48 @@ locale or headers. Keep authenticated caches request/session-scoped unless every isolation dimension is encoded. Sharing a base invalidation label does not make different response shapes interchangeable. +## Shared data snapshots and equality (breaking) + +`@cleverbrush/deep` now exports `deepClone(value: T): T`. React forms use it for +snapshots and reuse `deepEqual` for dirty checks instead of maintaining private +copies of these helpers: + +```ts +import { deepClone, deepEqual } from '@cleverbrush/deep'; + +const saved = { tags: ['a'], updatedAt: new Date(1) }; +const draft = deepClone(saved); +draft.tags.push('b'); // saved is unchanged +deepEqual(draft, saved); // false +draft.tags.pop(); +deepEqual(draft, saved); // true +``` + +The clone copies plain objects, arrays and Dates. It preserves cycles, shared +references, sparse-array lengths, null prototypes and enumerable own string/symbol +properties. Getters become data properties; non-enumerables/descriptors are not +copied. Special property names are copied without invoking prototype setters. +Files, Maps, Sets and other opaque instances remain references, not isolated copies. + +**Existing `deepEqual` behavior is corrected for every consumer**, without a legacy +mode. Object/null comparisons no longer throw; Dates cannot equal plain objects; +opaque objects compare only by identity. Dates compare timestamps, including two +invalid Dates as equal. Primitives follow `Object.is` (`NaN` equals itself; signed +zeros differ). Plain data compares structurally, including equivalent cycles and +shared references, without requiring identical sharing topology. Enumerable symbol +keys participate. Array holes differ from explicit `undefined`; unordered matching +preserves duplicate/hole counts and does not use hashes or mutate inputs. + +Audit code relying on previous results, particularly opaque-object content +comparisons. Project such values to relevant plain data when structural equality +is desired. See the [deep package README](../libs/deep/README.md) for the complete +supported-value contract. + +`deepExtend`, `HashObject` and schema `Transaction` behavior are unchanged. +`Transaction` tracks mutations through a proxy; it is not a structural comparison +against a reset snapshot, and adopting it here would require changes to its dirty +semantics (including equal replacements, falsy values and array removals). + ## Form values and reset Previously edit forms could require remount keys or manual field setters to display diff --git a/libs/deep/README.md b/libs/deep/README.md index 2149c114..7c70dc8e 100644 --- a/libs/deep/README.md +++ b/libs/deep/README.md @@ -6,7 +6,7 @@ ![Coverage](https://img.shields.io/badge/coverage-97.8%25-brightgreen) -A library for deep operations on JavaScript objects — deep equality, deep merge, and flattening. +A library for deep operations on JavaScript objects — cloning, equality, merging, and flattening. ## Installation @@ -17,14 +17,55 @@ npm install @cleverbrush/deep ## Usage ```typescript -import { deepEqual, deepExtend, deepFlatten } from '@cleverbrush/deep'; +import { deepClone, deepEqual, deepExtend, deepFlatten } from '@cleverbrush/deep'; ``` ## API +### `deepClone(value: T): T` + +Creates an isolated copy of plain data while preserving its TypeScript type. + +```typescript +import { deepClone } from '@cleverbrush/deep'; + +const input = { tags: ['a'], savedAt: new Date(1) }; +const snapshot = deepClone(input); +input.tags.push('b'); +input.savedAt.setTime(2); +// snapshot is still { tags: ['a'], savedAt: new Date(1) } +``` + +| Value | `deepClone` | `deepEqual` | +| --- | --- | --- | +| Primitives, functions | Returned unchanged | `Object.is` | +| Plain objects (including null prototypes) | Recursively cloned; prototype preserved | Enumerable own properties compared structurally; null and ordinary prototypes may compare equal | +| Arrays | Recursively cloned; length and holes preserved | Length, holes, elements and enumerable extra properties compared | +| Dates | Cloned by timestamp, including invalid dates | Timestamps compared with `Object.is`; two invalid dates compare equal | +| Files, Maps, Sets, typed arrays, custom instances and other objects | Returned by reference | Identity only | + +Cloning preserves cycles and repeated references, including shared Dates. Own +enumerable string and symbol properties are copied as writable data properties. +Getters are read once per copied property; accessor descriptors, non-enumerable +properties and frozen/sealed state are not preserved. Date timestamps and array +length are copied explicitly. Special keys such as `__proto__` are defined as own +properties without invoking inherited setters. This is a data snapshot utility, +not a clone of arbitrary object internals or a replacement for serialization. + ### `deepEqual(a, b, options?)` -Recursively compares two values and returns `true` if they are deeply equal. Supports nested objects, arrays, `Date` instances, and handles circular references. +Recursively compares supported data values according to the table above. It handles +nulls and cycles, and does not require identical reference-sharing topology: one +shared child can compare equal to two separate, structurally equal children. Object +key order does not matter; symbol keys participate by identity. Dates compare only +their timestamps, not extra properties. Other opaque objects are never traversed. + +Primitives follow `Object.is`: `NaN` equals `NaN`, but `0` differs from `-0`. +Array holes differ from explicit `undefined`. With `disregardArrayOrder`, each +element must match one unused element on the other side, preserving duplicate and +hole counts. Named/symbol array properties still compare by key. Inputs are not +sorted or mutated, and hash collisions cannot decide equality. Unordered matching +can require quadratic comparisons; prefer ordered equality for large arrays. **Parameters:** @@ -49,8 +90,21 @@ deepEqual({ a: { b: 1, c: 2 } }, { a: { b: 1 } }); // Array order can be ignored deepEqual([1, 2, 3], [3, 1, 2], { disregardArrayOrder: true }); // => true + +deepEqual([1, 1, 2], [1, 2, 2], { disregardArrayOrder: true }); +// => false (duplicate counts differ) + +deepEqual(new Map(), new Map()); +// => false (opaque objects compare by identity) ``` +**Breaking comparison corrections:** previous versions could throw for object/null +pairs, consider Dates equal to unrelated objects, compare distinct opaque objects +by enumerable shape, and reject equivalent cycles or repeated references. Signed +zero, invalid Dates, symbol keys and sparse arrays now follow the rules above. +Audit consumers relying on those outcomes. To compare Maps/Sets/custom instances +by content, explicitly project their relevant state into plain data first. + ### `deepExtend(...objects)` Deeply merges multiple objects. Works like `Object.assign`, but recursively merges nested objects instead of overwriting them. All arguments must be non-null objects. diff --git a/libs/deep/src/data.ts b/libs/deep/src/data.ts new file mode 100644 index 00000000..de5b6926 --- /dev/null +++ b/libs/deep/src/data.ts @@ -0,0 +1,12 @@ +/** The structural data types supported by cloning and equality. */ +export function isPlainObject(value: unknown): value is object { + if (value === null || typeof value !== 'object') return false; + const prototype = Object.getPrototypeOf(value); + return prototype === null || prototype === Object.prototype; +} + +export function enumerableKeys(value: object): (string | symbol)[] { + return Reflect.ownKeys(value).filter(key => + Object.prototype.propertyIsEnumerable.call(value, key) + ); +} diff --git a/libs/deep/src/deepClone.test.ts b/libs/deep/src/deepClone.test.ts new file mode 100644 index 00000000..903925e2 --- /dev/null +++ b/libs/deep/src/deepClone.test.ts @@ -0,0 +1,138 @@ +import { expect, expectTypeOf, test } from 'vitest'; +import { deepClone } from './index.js'; + +test('deepClone - isolates nested data and preserves the input type', () => { + const source = { profile: { name: 'Ada' }, tags: ['a'], date: new Date(1) }; + const result = deepClone(source); + expectTypeOf(result).toEqualTypeOf(source); + expect(result).toEqual(source); + expect(result).not.toBe(source); + expect(result.profile).not.toBe(source.profile); + expect(result.tags).not.toBe(source.tags); + expect(result.date).not.toBe(source.date); + result.profile.name = 'Grace'; + result.tags.push('b'); + result.date.setTime(2); + expect(source).toEqual({ + profile: { name: 'Ada' }, + tags: ['a'], + date: new Date(1) + }); +}); + +test('deepClone - primitives and opaque objects retain their identity', () => { + class RecordValue { + value = 1; + } + for (const value of [ + null, + undefined, + Number.NaN, + -0, + 1n, + true, + 'text', + Symbol('key'), + () => 1, + new Map(), + new Set(), + new RecordValue(), + new File(['text'], 'file.txt'), + /pattern/, + new Uint8Array([1]) + ]) { + expect(deepClone(value)).toBe(value); + expect(deepClone({ value }).value).toBe(value); + } +}); + +test('deepClone - preserves cycles and repeated objects, arrays and Dates', () => { + const child = { value: 1 }; + const date = new Date(Number.NaN); + const source: any = { + child, + again: child, + date, + againDate: date, + array: [] + }; + source.self = source; + source.array.push(source, source.array); + source.againArray = source.array; + date['owner'] = source; + const result = deepClone(source); + expect(result.self).toBe(result); + expect(result.child).toBe(result.again); + expect(result.child).not.toBe(child); + expect(result.date).toBe(result.againDate); + expect(result.date).not.toBe(date); + expect(result.date.getTime()).toBeNaN(); + expect(result.date.owner).toBe(result); + expect(result.array[0]).toBe(result); + expect(result.array[1]).toBe(result.array); + expect(result.againArray).toBe(result.array); +}); + +test('deepClone - preserves sparse array lengths and enumerable extra keys', () => { + const symbol = Symbol('metadata'); + const source = Object.assign(new Array(4), { label: { text: 'a' } }); + source[1] = { value: 1 }; + source[symbol] = { value: 2 }; + const result = deepClone(source); + expect(Array.isArray(result)).toBe(true); + expect(result.length).toBe(4); + expect(Object.keys(result)).toEqual(['1', 'label']); + expect(0 in result).toBe(false); + expect(3 in result).toBe(false); + expect(result[1]).toEqual({ value: 1 }); + expect(result[1]).not.toBe(source[1]); + expect(result.label).not.toBe(source.label); + expect(result[symbol]).toEqual({ value: 2 }); + expect(result[symbol]).not.toBe(source[symbol]); +}); + +test('deepClone - preserves null prototypes and safely copies special keys', () => { + for (const prototype of [null, Object.prototype]) { + const source = Object.assign(Object.create(prototype), { + child: Object.assign(Object.create(null), { value: 1 }) + }); + for (const key of ['__proto__', 'constructor', 'prototype']) { + Object.defineProperty(source, key, { + enumerable: true, + value: { cloned: true } + }); + } + const result = deepClone(source); + expect(Object.getPrototypeOf(result)).toBe(prototype); + expect(Object.getPrototypeOf(result.child)).toBeNull(); + expect(result.child).not.toBe(source.child); + for (const key of ['__proto__', 'constructor', 'prototype']) { + expect(Object.hasOwn(result, key)).toBe(true); + expect(result[key]).toEqual({ cloned: true }); + expect(result[key]).not.toBe(source[key]); + } + expect(Object.hasOwn(Object.prototype, 'cloned')).toBe(false); + } +}); + +test('deepClone - reads enumerable getters as data and omits non-enumerables', () => { + const child = { value: 1 }; + const symbol = Symbol('key'); + const source = { + get child() { + return child; + }, + [symbol]: child + }; + Object.defineProperty(source, 'hidden', { value: 1 }); + const result = deepClone(source); + expect(result.child).not.toBe(child); + expect(result[symbol]).toBe(result.child); + expect(Object.hasOwn(result, 'hidden')).toBe(false); + expect(Object.getOwnPropertyDescriptor(result, 'child')).toEqual({ + value: result.child, + enumerable: true, + configurable: true, + writable: true + }); +}); diff --git a/libs/deep/src/deepClone.ts b/libs/deep/src/deepClone.ts new file mode 100644 index 00000000..ba8f6452 --- /dev/null +++ b/libs/deep/src/deepClone.ts @@ -0,0 +1,41 @@ +import { enumerableKeys, isPlainObject } from './data.js'; + +/** + * Copies plain objects, arrays and Dates, preserving cycles, shared references, + * sparse arrays and null prototypes. Other values (including class instances, + * Files, Maps and Sets) retain their identity. + * + * Enumerable own string/symbol properties are read and copied as writable data + * properties; accessors, non-enumerable properties and descriptors are not cloned. + */ +export function deepClone(value: T): T { + const seen = new WeakMap(); + + function clone(source: any): any { + if ( + !Array.isArray(source) && + !(source instanceof Date) && + !isPlainObject(source) + ) { + return source; + } + if (seen.has(source)) return seen.get(source); + const result = Array.isArray(source) + ? new Array(source.length) + : source instanceof Date + ? new Date(source.getTime()) + : Object.create(Object.getPrototypeOf(source)); + seen.set(source, result); + for (const key of enumerableKeys(source)) { + Object.defineProperty(result, key, { + value: clone(Reflect.get(source, key)), + enumerable: true, + configurable: true, + writable: true + }); + } + return result; + } + + return clone(value); +} diff --git a/libs/deep/src/deepEqual.test.ts b/libs/deep/src/deepEqual.test.ts index 655393e9..6fdc71b5 100644 --- a/libs/deep/src/deepEqual.test.ts +++ b/libs/deep/src/deepEqual.test.ts @@ -109,13 +109,12 @@ test('deepEqual - array vs non-array returns false', () => { expect(deepEqual({ 0: 1 }, [1])).toEqual(false); }); -test('deepEqual - circular reference returns false', () => { +test('deepEqual - equivalent circular references compare equal', () => { const a: Record = {}; a['self'] = a; const b: Record = {}; b['self'] = b; - // cyclic comparison should not infinitely recurse and return false - expect(deepEqual(a, b)).toEqual(false); + expect(deepEqual(a, b)).toEqual(true); }); test('deepEqual - objects with different key order but same keys are equal', () => { @@ -125,3 +124,178 @@ test('deepEqual - objects with different key order but same keys are equal', () test('deepEqual - objects with different key names return false', () => { expect(deepEqual({ a: 1 }, { b: 1 })).toEqual(false); }); + +test.each([ + null, + undefined, + true, + 1, + 'a', + Symbol('a'), + () => 1 +])('deepEqual - object versus %s is symmetric and does not throw', value => { + expect(deepEqual({}, value)).toBe(false); + expect(deepEqual(value, {})).toBe(false); +}); + +test('deepEqual - primitives follow Object.is', () => { + expect(deepEqual(Number.NaN, Number.NaN)).toBe(true); + expect(deepEqual(0, -0)).toBe(false); + expect(deepEqual({ value: -0 }, { value: 0 })).toBe(false); + expect(deepEqual(1, '1')).toBe(false); + expect(deepEqual(1n, 1n)).toBe(true); + expect(deepEqual(Symbol('key'), Symbol('key'))).toBe(false); +}); + +test('deepEqual - Dates compare timestamps, including invalid dates', () => { + expect(deepEqual(new Date(1), new Date(1))).toBe(true); + expect(deepEqual(new Date(1), new Date(2))).toBe(false); + expect(deepEqual(new Date(Number.NaN), new Date(Number.NaN))).toBe(true); + expect(deepEqual(new Date(1), new Date(Number.NaN))).toBe(false); + expect(deepEqual(new Date(1), {})).toBe(false); + expect(deepEqual({}, new Date(1))).toBe(false); + expect(deepEqual(new Date(1), null)).toBe(false); +}); + +test('deepEqual - opaque objects compare by identity', () => { + class RecordValue { + value = 1; + } + for (const create of [ + () => new RecordValue(), + () => new Map([['a', 1]]), + () => new Set([1]), + () => /pattern/g, + () => new Uint8Array([1]), + () => new File(['text'], 'file.txt') + ]) { + const value = create(); + expect(deepEqual(value, value)).toBe(true); + expect(deepEqual(value, create())).toBe(false); + expect(deepEqual(value, {})).toBe(false); + expect(deepEqual({}, value)).toBe(false); + } +}); + +test('deepEqual - only enumerable own properties participate', () => { + const key = Symbol('key'); + const left = Object.assign(Object.create(null), { a: 1, [key]: 2 }); + expect(deepEqual(left, { a: 1, [key]: 2 })).toBe(true); + expect(deepEqual(left, { a: 1, [key]: 3 })).toBe(false); + expect(deepEqual(left, { a: 1, [Symbol('key')]: 2 })).toBe(false); + Object.defineProperty(left, 'hidden', { value: 3 }); + expect(deepEqual(left, { a: 1, [key]: 2 })).toBe(true); + expect(deepEqual({ value: undefined }, {})).toBe(false); + expect( + deepEqual({ a: 1 }, Object.defineProperty({}, 'a', { value: 1 })) + ).toBe(false); +}); + +test.each([ + false, + true +])('deepEqual - preserves array length, holes and extra properties (unordered=%s)', disregardArrayOrder => { + const options = { disregardArrayOrder }; + expect(deepEqual(new Array(3), new Array(3), options)).toBe(true); + expect(deepEqual(new Array(3), new Array(4), options)).toBe(false); + expect(deepEqual(new Array(1), [undefined], options)).toBe(false); + const key = Symbol('metadata'); + const left = Object.assign([1], { label: 'a', [key]: 2 }); + expect(deepEqual(left, [1], options)).toBe(false); + expect( + deepEqual(left, Object.assign([1], { label: 'a', [key]: 2 }), options) + ).toBe(true); + expect( + deepEqual(left, Object.assign([1], { label: 'a', [key]: 3 }), options) + ).toBe(false); +}); + +test('deepEqual - ordered holes retain their positions', () => { + const left = new Array(2); + left[0] = 1; + const right = new Array(2); + right[1] = 1; + expect(deepEqual(left, right)).toBe(false); + expect(deepEqual(left, right, { disregardArrayOrder: true })).toBe(true); +}); + +test.each([ + false, + true +])('deepEqual - cycles terminate and still detect mismatches (unordered=%s)', disregardArrayOrder => { + const options = { disregardArrayOrder }; + const left: any = { value: 1 }; + left.self = left; + const right: any = { value: 1 }; + right.self = right; + expect(deepEqual(left, right, options)).toBe(true); + right.value = 2; + expect(deepEqual(left, right, options)).toBe(false); + const leftArray: any[] = []; + leftArray.push(leftArray, { value: 1 }); + const rightArray: any[] = []; + rightArray.push(rightArray, { value: 1 }); + expect(deepEqual(leftArray, rightArray, options)).toBe(true); + rightArray[1].value = 2; + expect(deepEqual(leftArray, rightArray, options)).toBe(false); +}); + +test('deepEqual - sharing topology is not part of structural equality', () => { + const shared = { value: 1 }; + const left = { a: shared, b: shared }; + const right = { a: { value: 1 }, b: { value: 1 } }; + expect(deepEqual(left, right)).toBe(true); + expect(deepEqual(right, left)).toBe(true); + right.b.value = 2; + expect(deepEqual(left, right)).toBe(false); + const cycle: any = { value: 1 }; + cycle.next = cycle; + const twoCycle: any = { value: 1, next: { value: 1 } }; + twoCycle.next.next = twoCycle; + expect(deepEqual(cycle, twoCycle)).toBe(true); + expect(deepEqual(twoCycle, cycle)).toBe(true); + twoCycle.next.value = 2; + expect(deepEqual(cycle, twoCycle)).toBe(false); +}); + +test('deepEqual - unordered comparisons count duplicates without mutating inputs', () => { + const options = { disregardArrayOrder: true }; + const left = Object.freeze([{ value: 1 }, { value: 2 }, { value: 1 }]); + const right = Object.freeze([{ value: 1 }, { value: 1 }, { value: 2 }]); + expect(deepEqual(left, right, options)).toBe(true); + expect( + deepEqual(left, [{ value: 1 }, { value: 2 }, { value: 2 }], options) + ).toBe(false); + expect(deepEqual([0, -0, Number.NaN], [Number.NaN, -0, 0], options)).toBe( + true + ); + expect(deepEqual([0, 0], [0, -0], options)).toBe(false); + expect(deepEqual([[1, 2], [3]], [[3], [2, 1]], options)).toBe(true); + expect(left.map(value => value.value)).toEqual([1, 2, 1]); + expect(right.map(value => value.value)).toEqual([1, 1, 2]); +}); + +test('deepEqual - failed cyclic candidates do not poison unordered comparisons', () => { + function cycle(value: number) { + const result: any = {}; + result.self = result; + result.value = value; + return result; + } + const options = { disregardArrayOrder: true }; + const shared = cycle(1); + expect( + deepEqual( + [shared, cycle(2), shared], + [cycle(2), cycle(1), cycle(1)], + options + ) + ).toBe(true); + expect( + deepEqual( + [shared, cycle(2), shared], + [cycle(2), cycle(1), cycle(3)], + options + ) + ).toBe(false); +}); diff --git a/libs/deep/src/deepEqual.ts b/libs/deep/src/deepEqual.ts index 2e1fa033..71152482 100644 --- a/libs/deep/src/deepEqual.ts +++ b/libs/deep/src/deepEqual.ts @@ -1,113 +1,117 @@ -import { HashObject } from './hashObject.js'; +import { enumerableKeys, isPlainObject } from './data.js'; -const isBothNaN = (v1: any, v2: any) => Number.isNaN(v1) && Number.isNaN(v2); +function isArrayIndex(key: string | symbol): boolean { + if (typeof key !== 'string') return false; + const index = Number(key); + return ( + Number.isInteger(index) && + index >= 0 && + index < 2 ** 32 - 1 && + String(index) === key + ); +} /** - * Compares two objects and returns true if they - * have the same structure and values. Can work - * with arrays and objects, nested objects, - * recursive objects, dates, etc. - * @param p1 first object - * @param p2 second object - * @param options additional options - * @returns {boolean} `true` if the objects are equal, `false` otherwise + * Structurally compares plain objects and arrays (including cycles). Dates + * compare by timestamp; primitives and opaque objects use Object.is semantics. + * Only enumerable own string/symbol properties participate in structural + * comparisons. Shared-reference topology does not affect equality. */ export const deepEqual = ( p1: any, p2: any, options?: { - /** - * if true, the order of the elements in the array will be disregarded - * e.g. [1, 2] and [2, 1] will be considered equal - */ + /** Ignore array element order, but preserve duplicate and hole counts. */ disregardArrayOrder?: boolean; } ): boolean => { - const cache = new Map(); + // Track pairs on the current recursion path, not all objects ever visited. + // Removing pairs on return also isolates failed unordered-array candidates. + const active = new WeakMap>(); - const compare = (...args: any[]) => { - const arraysAreIdentical = (a1: any, a2: any) => { - if (a1.length !== a2.length) return false; - if (a1.length === 0 && a2.length === 0) return true; - - const c1 = options?.disregardArrayOrder - ? a1 - .map((t: any) => t) - .sort((l: any, r: any) => { - const hash1 = HashObject(l); - const hash2 = HashObject(r); - return hash1 < hash2 ? 1 : -1; - }) - : a1.map((t: any) => t); - const c2 = options?.disregardArrayOrder - ? a2 - .map((t: any) => t) - .sort((l: any, r: any) => { - const hash1 = HashObject(l); - const hash2 = HashObject(r); - return hash1 < hash2 ? 1 : -1; - }) - : a2.map((t: any) => t); - - for (let i = 0; i < c1.length; i++) { - if (!compare(c1[i], c2[i])) return false; - } - return true; - }; - - if (args.length !== 2) return false; - const [o1, o2] = args; - if (o1 === o2) return true; - if (typeof o1 !== typeof o2) return false; - if (isBothNaN(o1, o2)) return true; + function compareProperties( + left: object, + right: object, + leftKeys: (string | symbol)[], + rightKeys: (string | symbol)[] + ): boolean { + const rightKeySet = new Set(rightKeys); + return ( + leftKeys.length === rightKeys.length && + leftKeys.every( + key => + rightKeySet.has(key) && + compare(Reflect.get(left, key), Reflect.get(right, key)) + ) + ); + } + function compareUnordered( + left: object, + right: object, + leftKeys: (string | symbol)[], + rightKeys: (string | symbol)[] + ): boolean { + const leftIndices = leftKeys.filter(isArrayIndex); + const rightIndices = rightKeys.filter(isArrayIndex); + if (leftIndices.length !== rightIndices.length) return false; if ( - (Array.isArray(o1) && !Array.isArray(o2)) || - (!Array.isArray(o1) && Array.isArray(o2)) + !compareProperties( + left, + right, + leftKeys.filter(key => !isArrayIndex(key)), + rightKeys.filter(key => !isArrayIndex(key)) + ) ) { return false; } - if (Array.isArray(o1) && Array.isArray(o2)) { - return arraysAreIdentical(o1, o2); + + const unmatched = new Set(rightIndices); + for (const key of leftIndices) { + const value = Reflect.get(left, key); + const match = Array.from(unmatched).find(candidate => + compare(value, Reflect.get(right, candidate)) + ); + if (match === undefined) return false; + unmatched.delete(match); } + return true; + } - if (typeof o1 === 'object' && o1 !== null) { - if (cache.get(o1) === true) { + function compare(left: any, right: any): boolean { + if (Object.is(left, right)) return true; + if (left instanceof Date || right instanceof Date) { + return ( + left instanceof Date && + right instanceof Date && + Object.is(left.getTime(), right.getTime()) + ); + } + if (Array.isArray(left)) { + if (!Array.isArray(right) || left.length !== right.length) { return false; } - - cache.set(o1, true); - - if (o1 instanceof Date && o2 instanceof Date) { - return ( - // biome-ignore lint/suspicious/noGlobalIsNan: isNaN coerces Date to number, which is the intended behavior here - !isNaN(o1 as any) && - // biome-ignore lint/suspicious/noGlobalIsNan: isNaN coerces Date to number, which is the intended behavior here - !isNaN(o2 as any) && - o1.getTime() === o2.getTime() - ); - } - - const keys1 = Object.keys(o1); - const keys2 = Object.keys(o2); - if (keys1.length !== keys2.length) return false; - - keys1.sort(); - keys2.sort(); - for (let i = 0; i < keys1.length; i++) { - if (keys1[i] !== keys2[i]) { - return false; - } - - const v1 = o1[keys1[i]]; - const v2 = o2[keys1[i]]; - if (!compare(v1, v2)) return false; - } - return true; + } else if (!isPlainObject(left) || !isPlainObject(right)) { + return false; } - return false; - }; + let partners = active.get(left); + if (partners?.has(right)) return true; + if (!partners) { + partners = new Set(); + active.set(left, partners); + } + partners.add(right); + try { + const leftKeys = enumerableKeys(left); + const rightKeys = enumerableKeys(right); + return Array.isArray(left) && options?.disregardArrayOrder + ? compareUnordered(left, right, leftKeys, rightKeys) + : compareProperties(left, right, leftKeys, rightKeys); + } finally { + partners.delete(right); + } + } return compare(p1, p2); }; diff --git a/libs/deep/src/index.ts b/libs/deep/src/index.ts index 60599a9e..6d58eb19 100644 --- a/libs/deep/src/index.ts +++ b/libs/deep/src/index.ts @@ -1,5 +1,13 @@ +import { deepClone } from './deepClone.js'; import { deepEqual } from './deepEqual.js'; import { deepExtend, type Merge, type MergeTwo } from './deepExtend.js'; import { deepFlatten } from './deepFlatten.js'; -export { deepEqual, deepExtend, deepFlatten, type Merge, type MergeTwo }; +export { + deepClone, + deepEqual, + deepExtend, + deepFlatten, + type Merge, + type MergeTwo +}; diff --git a/libs/react-form/README.md b/libs/react-form/README.md index 28150d30..32e40d9c 100644 --- a/libs/react-form/README.md +++ b/libs/react-form/README.md @@ -327,6 +327,15 @@ const form = useSchemaForm(UserSchema, { | `form.getValue()` | Get current form values as plain object | | `form.setValue(values)` | Shallow-merge values and synchronize mounted fields without marking touched | +Form snapshots and dirty checks use `deepClone` and `deepEqual` from +[`@cleverbrush/deep`](../deep/README.md). Plain objects, arrays and Dates are cloned +on reset/setters; changing caller-owned input cannot change their baseline. Cycles, +shared references, sparse arrays and null prototypes are preserved. Replacing a +value with structurally equal data clears dirty, even with different sharing of +child references. Files and other opaque objects retain identity: a different File +is dirty even if its name/content match. Treat returned snapshots and opaque values +as read-only; update through setters rather than mutating them in place. + ## useField Binds a single field via PropertyDescriptor selector. Can be used via `form.useField()` or the context-based standalone `useField()`: diff --git a/libs/react-form/package.json b/libs/react-form/package.json index 66f3a316..14eb224a 100644 --- a/libs/react-form/package.json +++ b/libs/react-form/package.json @@ -5,6 +5,7 @@ "email": "andrew_zol@cleverbrush.com" }, "dependencies": { + "@cleverbrush/deep": "^4.4.2", "@cleverbrush/schema": "^4.4.2" }, "devDependencies": { diff --git a/libs/react-form/src/FormStore.test.ts b/libs/react-form/src/FormStore.test.ts new file mode 100644 index 00000000..013a83d4 --- /dev/null +++ b/libs/react-form/src/FormStore.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, test } from 'vitest'; +import { createFormStore } from './FormStore.js'; + +function setup(profile: any) { + const store = createFormStore({ profile }); + store.registerField('/profile', { + getValue: values => ({ success: true, value: values.profile }), + setValue: (values, value) => { + values.profile = value; + return true; + } + }); + return store; +} + +describe('shared deep utilities in the form store', () => { + test('reset isolates caller-owned nested objects, arrays and Dates', () => { + const store = setup(undefined); + const profile = { name: 'Ada', tags: ['a'], date: new Date(1) }; + store.resetAll({ profile }); + profile.name = 'Grace'; + profile.tags.push('b'); + profile.date.setTime(2); + expect(store.getFieldState('/profile')).toMatchObject({ + value: { name: 'Ada', tags: ['a'], date: new Date(1) }, + initialValue: { name: 'Ada', tags: ['a'], date: new Date(1) }, + dirty: false + }); + }); + + test('equal replacement clears dirty even with different reference sharing', () => { + const child = { value: 1 }; + const store = setup({ left: child, right: child }); + store.setFieldValue('/profile', { left: { value: 2 } }, true); + expect(store.getFieldState('/profile').dirty).toBe(true); + store.setFieldValue( + '/profile', + { left: { value: 1 }, right: { value: 1 } }, + true + ); + expect(store.getFieldState('/profile').dirty).toBe(false); + }); + + test('Files retain identity and distinct replacements become dirty', () => { + const original = new File(['text'], 'file.txt'); + const replacement = new File(['text'], 'file.txt'); + const store = setup(original); + expect(store.getFieldState('/profile').value).toBe(original); + store.setFieldValue('/profile', replacement, true); + expect(store.getFieldState('/profile')).toMatchObject({ + value: replacement, + initialValue: original, + dirty: true + }); + store.setFieldValue('/profile', original, true); + expect(store.getFieldState('/profile').dirty).toBe(false); + store.resetAll({ profile: replacement }); + expect(store.getFieldState('/profile').initialValue).toBe(replacement); + expect(store.getFieldState('/profile').dirty).toBe(false); + }); + + test('cyclic values and sparse array baselines survive setters', () => { + const profile: any = { entries: new Array(2) }; + profile.self = profile; + const store = setup(profile); + const replacement: any = { entries: new Array(2) }; + replacement.self = replacement; + store.setFieldValue('/profile', replacement, true); + expect(store.getFieldState('/profile').dirty).toBe(false); + replacement.entries[0] = undefined; + store.setFieldValue('/profile', replacement, true); + expect(store.getFieldState('/profile').dirty).toBe(true); + }); +}); diff --git a/libs/react-form/src/FormStore.ts b/libs/react-form/src/FormStore.ts index ca588c27..45971e0e 100644 --- a/libs/react-form/src/FormStore.ts +++ b/libs/react-form/src/FormStore.ts @@ -1,3 +1,4 @@ +import { deepClone, deepEqual } from '@cleverbrush/deep'; import type { FieldState, FormSubmissionState } from './types.js'; type Binding = { @@ -5,62 +6,9 @@ type Binding = { setValue: (values: any, value: any, options: any) => boolean; }; -function isPlain(value: any): value is Record { - return ( - value != null && - typeof value === 'object' && - (Object.getPrototypeOf(value) === Object.prototype || - Object.getPrototypeOf(value) === null) - ); -} - -/** Copy editable data; opaque values such as Files retain their identity. */ -function copy(value: any, seen = new WeakMap()): any { - if (value instanceof Date) return new Date(value.getTime()); - if (!Array.isArray(value) && !isPlain(value)) return value; - if (seen.has(value)) return seen.get(value); - const result: any = Array.isArray(value) ? new Array(value.length) : {}; - seen.set(value, result); - for (const key of Object.keys(value)) { - Object.defineProperty(result, key, { - value: copy(Reflect.get(value, key), seen), - enumerable: true, - writable: true, - configurable: true - }); - } - return result; -} - -function equal(a: any, b: any, seen = new WeakMap()): boolean { - if (Object.is(a, b)) return true; - if (a instanceof Date && b instanceof Date) { - return Object.is(a.getTime(), b.getTime()); - } - if (Array.isArray(a) !== Array.isArray(b)) return false; - if (Array.isArray(a) && a.length !== b.length) return false; - if ( - !(Array.isArray(a) && Array.isArray(b)) && - !(isPlain(a) && isPlain(b)) - ) { - return false; - } - if (seen.has(a)) return seen.get(a) === b; - seen.set(a, b); - const keys = Object.keys(a); - return ( - keys.length === Object.keys(b).length && - keys.every( - key => - Object.hasOwn(b, key) && - equal(Reflect.get(a, key), Reflect.get(b, key), seen) - ) - ); -} - /** Internal immutable snapshots shared by all bindings of a form. */ export function createFormStore(initialValues: any) { - let values = copy(initialValues ?? {}); + let values = deepClone(initialValues ?? {}); let baseline = values; let revision = 0; let submission: FormSubmissionState = { @@ -84,7 +32,7 @@ export function createFormStore(initialValues: any) { fieldStates.set(path, { value, initialValue, - dirty: !equal(value, initialValue), + dirty: !deepEqual(value, initialValue), touched: false, error: undefined, validating: false @@ -121,7 +69,7 @@ export function createFormStore(initialValues: any) { updateFieldState(path, { value, initialValue, - dirty: !equal(value, initialValue), + dirty: !deepEqual(value, initialValue), validating: false, ...(reset ? { touched: false, error: undefined } : {}) }); @@ -165,7 +113,7 @@ export function createFormStore(initialValues: any) { return revision; } function setValues(newValues: any) { - values = copy(newValues ?? {}); + values = deepClone(newValues ?? {}); revision++; syncFields(false); } @@ -174,17 +122,17 @@ export function createFormStore(initialValues: any) { value: any, createMissingStructure: boolean ) { - const next = copy(values); + const next = deepClone(values); if ( bindings .get(path) - ?.setValue(next, copy(value), { createMissingStructure }) + ?.setValue(next, deepClone(value), { createMissingStructure }) ) { setValues(next); } } function resetAll(newInitialValues?: any) { - values = copy(newInitialValues ?? {}); + values = deepClone(newInitialValues ?? {}); baseline = values; revision++; syncFields(true); diff --git a/libs/react-form/tsconfig.json b/libs/react-form/tsconfig.json index 901bc2a3..6f474178 100644 --- a/libs/react-form/tsconfig.json +++ b/libs/react-form/tsconfig.json @@ -24,7 +24,7 @@ "src/**/*.spec.ts", "src/**/*.spec.tsx" ], - "references": [{ "path": "../schema" }], + "references": [{ "path": "../deep" }, { "path": "../schema" }], "watchOptions": { "excludeDirectories": ["./dist"] } diff --git a/package-lock.json b/package-lock.json index 5382d67f..128ec903 100644 --- a/package-lock.json +++ b/package-lock.json @@ -149,7 +149,7 @@ }, "libs/async": { "name": "@cleverbrush/async", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "devDependencies": { "@types/node": "^25.4.0" @@ -167,10 +167,10 @@ }, "libs/auth": { "name": "@cleverbrush/auth", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "dependencies": { - "@cleverbrush/schema": "^4.3.2" + "@cleverbrush/schema": "^4.4.2" } }, "libs/benchmarks": { @@ -185,11 +185,11 @@ }, "libs/client": { "name": "@cleverbrush/client", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "dependencies": { - "@cleverbrush/schema": "^4.3.2", - "@cleverbrush/server": "^4.3.2" + "@cleverbrush/schema": "^4.4.2", + "@cleverbrush/server": "^4.4.2" }, "devDependencies": { "@tanstack/react-query": "^5.75.0", @@ -213,23 +213,23 @@ }, "libs/deep": { "name": "@cleverbrush/deep", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause" }, "libs/di": { "name": "@cleverbrush/di", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "dependencies": { - "@cleverbrush/schema": "^4.3.2" + "@cleverbrush/schema": "^4.4.2" } }, "libs/env": { "name": "@cleverbrush/env", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "dependencies": { - "@cleverbrush/deep": "^4.3.2" + "@cleverbrush/deep": "^4.4.2" }, "devDependencies": { "@types/node": "^25.4.0" @@ -250,11 +250,11 @@ }, "libs/knex-clickhouse": { "name": "@cleverbrush/knex-clickhouse", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "dependencies": { - "@cleverbrush/async": "^4.3.2", - "@cleverbrush/deep": "^4.3.2", + "@cleverbrush/async": "^4.4.2", + "@cleverbrush/deep": "^4.4.2", "@clickhouse/client": "^1.18.2" }, "peerDependencies": { @@ -263,10 +263,10 @@ }, "libs/knex-schema": { "name": "@cleverbrush/knex-schema", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "dependencies": { - "@cleverbrush/schema": "^4.3.2" + "@cleverbrush/schema": "^4.4.2" }, "peerDependencies": { "knex": ">=3.1.0" @@ -274,11 +274,11 @@ }, "libs/log": { "name": "@cleverbrush/log", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "dependencies": { - "@cleverbrush/async": "^4.3.2", - "@cleverbrush/schema": "^4.3.2" + "@cleverbrush/async": "^4.4.2", + "@cleverbrush/schema": "^4.4.2" }, "devDependencies": { "@types/node": "^25.4.0" @@ -312,19 +312,19 @@ }, "libs/mapper": { "name": "@cleverbrush/mapper", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "dependencies": { - "@cleverbrush/schema": "^4.3.2" + "@cleverbrush/schema": "^4.4.2" } }, "libs/orm": { "name": "@cleverbrush/orm", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "dependencies": { - "@cleverbrush/knex-schema": "^4.3.2", - "@cleverbrush/schema": "^4.3.2" + "@cleverbrush/knex-schema": "^4.4.2", + "@cleverbrush/schema": "^4.4.2" }, "peerDependencies": { "knex": ">=3.1.0" @@ -332,7 +332,7 @@ }, "libs/orm-cli": { "name": "@cleverbrush/orm-cli", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "dependencies": { "@cleverbrush/knex-schema": "*", @@ -347,7 +347,7 @@ }, "libs/otel": { "name": "@cleverbrush/otel", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "dependencies": { "@opentelemetry/api": "^1.9.0", @@ -419,10 +419,11 @@ }, "libs/react-form": { "name": "@cleverbrush/react-form", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "dependencies": { - "@cleverbrush/schema": "^4.3.2" + "@cleverbrush/deep": "^4.4.2", + "@cleverbrush/schema": "^4.4.2" }, "devDependencies": { "@types/react": "^19.0.0", @@ -434,10 +435,10 @@ }, "libs/scheduler": { "name": "@cleverbrush/scheduler", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "dependencies": { - "@cleverbrush/schema": "^4.3.2" + "@cleverbrush/schema": "^4.4.2" }, "devDependencies": { "@types/node": "^25.4.0" @@ -455,10 +456,10 @@ }, "libs/schema": { "name": "@cleverbrush/schema", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "devDependencies": { - "@cleverbrush/deep": "^4.3.2" + "@cleverbrush/deep": "^4.4.2" }, "peerDependencies": { "@standard-schema/spec": "^1.1.0" @@ -466,7 +467,7 @@ }, "libs/schema-json": { "name": "@cleverbrush/schema-json", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "peerDependencies": { "@cleverbrush/schema": "^4.0.0", @@ -475,12 +476,12 @@ }, "libs/server": { "name": "@cleverbrush/server", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "dependencies": { - "@cleverbrush/auth": "^4.3.2", - "@cleverbrush/di": "^4.3.2", - "@cleverbrush/schema": "^4.3.2", + "@cleverbrush/auth": "^4.4.2", + "@cleverbrush/di": "^4.4.2", + "@cleverbrush/schema": "^4.4.2", "@fastify/busboy": "^3.2.0", "ws": "^8.20.0" }, @@ -501,7 +502,7 @@ }, "libs/server-openapi": { "name": "@cleverbrush/server-openapi", - "version": "4.3.2", + "version": "4.4.2", "license": "BSD 3-Clause", "peerDependencies": { "@cleverbrush/auth": "^4.0.0", From 0d480e988da6ff2663785d849125dab2cc4a482a Mon Sep 17 00:00:00 2001 From: Andrew Zolotukhin Date: Tue, 8 Sep 2026 16:01:18 +0000 Subject: [PATCH 3/5] fix(react-form): support defaulted fields and declaration-safe exports --- .changeset/defaulted-form-fields.md | 5 + docs/cache-form-migration.md | 29 ++++ libs/react-form/README.md | 11 ++ libs/react-form/src/components.tsx | 6 +- libs/react-form/src/declarations.test.ts | 37 +++++ libs/react-form/src/defaults.test-d.tsx | 141 ++++++++++++++++++ libs/react-form/src/defaults.test.tsx | 72 +++++++++ libs/react-form/src/helpers.ts | 4 +- libs/react-form/src/hooks.ts | 6 +- libs/react-form/src/index.ts | 2 + libs/react-form/src/system.tsx | 19 ++- libs/react-form/src/types.ts | 4 +- .../test-fixtures/exported-system.tsx | 18 +++ 13 files changed, 341 insertions(+), 13 deletions(-) create mode 100644 .changeset/defaulted-form-fields.md create mode 100644 libs/react-form/src/declarations.test.ts create mode 100644 libs/react-form/src/defaults.test-d.tsx create mode 100644 libs/react-form/src/defaults.test.tsx create mode 100644 libs/react-form/test-fixtures/exported-system.tsx diff --git a/.changeset/defaulted-form-fields.md b/.changeset/defaulted-form-fields.md new file mode 100644 index 00000000..bf51a1e8 --- /dev/null +++ b/.changeset/defaulted-form-fields.md @@ -0,0 +1,5 @@ +--- +'@cleverbrush/react-form': patch +--- + +Accept defaulted schema properties in typed Field, headless useField, and renderer schema bounds without losing value inference. Return a named TypedFormSystem so shared UI packages can export inferred registries while emitting declarations. diff --git a/docs/cache-form-migration.md b/docs/cache-form-migration.md index 5dd9e674..7f767dcc 100644 --- a/docs/cache-form-migration.md +++ b/docs/cache-form-migration.md @@ -224,3 +224,32 @@ Use `boolean` for checkboxes, `string[]` for multi-selects, `number | undefined` for numeric inputs that can be cleared, and `string | null` for nullable strings. Values can initially be `undefined`; optional/nested fields retain their types. No UI-kit dependency or application-specific field vocabulary is introduced. + +### Defaulted properties and shared UI packages + +`form.useField()` and typed `Field` also support properties such as +`string().default('Untitled')`, `boolean().default(true)`, and +`array(string()).default(() => [])`. The default flag does not change which +renderer value types are compatible. Nullable fields still require nullable +renderers, enum setters still accept only their literals, and arrays retain +their element type. Defaults run during validation; initialize visible fields +with `reset(values)`. A bare `reset()` continues to clear values. + +An inferred registry and its field component can be exported from a shared +package compiled with `declaration: true`: + +```tsx +export const SharedFormSystem = createFormSystem({ + renderers: { string: text } +}); +export const SchemaField = SharedFormSystem.Field; +``` + +The public `TypedFormSystem` and `TypedFieldComponent` names keep these +exports declaration-safe; no consumer type assertion is required. + +When different value kinds reuse the same variant name (such as +`string:select` and `number:select`), TypeScript can require explicit parameter +types for inline callbacks in `fieldProps`. Annotate those parameters or pass +an already typed handler; the selected renderer still checks its callback +signature and rejects incompatible props. diff --git a/libs/react-form/README.md b/libs/react-form/README.md index 32e40d9c..25e8fc36 100644 --- a/libs/react-form/README.md +++ b/libs/react-form/README.md @@ -58,6 +58,17 @@ For typed renderer props/variants and managed submission, see the new [consumer examples and migration guide](../../docs/cache-form-migration.md). The provider-based APIs below remain supported. +Typed fields and headless `form.useField()` accept properties with schema +defaults, including enums, booleans, arrays, and nullable values. A default +does not erase the property's inferred type. Defaults are applied during +validation; call `form.reset(values)` to establish a visible clean baseline. +`reset()` still clears the store rather than restoring schema defaults. + +Shared UI packages can directly export `createFormSystem(...)` results and +`system.Field` while emitting TypeScript declarations. The named +`TypedFormSystem` and `TypedFieldComponent` types preserve the registry's +field/variant/props checks across package boundaries. + ```tsx import { object, string, number } from '@cleverbrush/schema'; import { useSchemaForm, FormSystemProvider, Field } from '@cleverbrush/react-form'; diff --git a/libs/react-form/src/components.tsx b/libs/react-form/src/components.tsx index 517c387d..1922fa0d 100644 --- a/libs/react-form/src/components.tsx +++ b/libs/react-form/src/components.tsx @@ -114,11 +114,7 @@ export function FormProvider< */ export function useField< TSchema extends ObjectSchemaBuilder, - TPropertySchema extends SchemaBuilder = SchemaBuilder< - any, - any, - any - > + TPropertySchema extends SchemaBuilder = any >( forProperty: ( tree: PropertyDescriptorTree diff --git a/libs/react-form/src/declarations.test.ts b/libs/react-form/src/declarations.test.ts new file mode 100644 index 00000000..32f9d9c1 --- /dev/null +++ b/libs/react-form/src/declarations.test.ts @@ -0,0 +1,37 @@ +// @vitest-environment node + +import { fileURLToPath } from 'node:url'; +import ts from 'typescript'; +import { expect, test } from 'vitest'; + +test('consumers can emit declarations for exported inferred form systems', () => { + const fixture = fileURLToPath( + new URL('../test-fixtures/exported-system.tsx', import.meta.url) + ); + const options: ts.CompilerOptions = { + declaration: true, + emitDeclarationOnly: true, + strict: true, + skipLibCheck: true, + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + jsx: ts.JsxEmit.ReactJSX + }; + const host = ts.createCompilerHost(options); + const output: string[] = []; + host.writeFile = (_fileName, content) => { + output.push(content); + }; + const program = ts.createProgram([fixture], options, host); + const diagnostics = [ + ...ts.getPreEmitDiagnostics(program), + ...program.emit().diagnostics + ]; + expect( + diagnostics.map(diagnostic => + ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n') + ) + ).toEqual([]); + expect(output.join('\n')).toContain('TypedFormSystem'); +}); diff --git a/libs/react-form/src/defaults.test-d.tsx b/libs/react-form/src/defaults.test-d.tsx new file mode 100644 index 00000000..6b00e011 --- /dev/null +++ b/libs/react-form/src/defaults.test-d.tsx @@ -0,0 +1,141 @@ +import { + array, + boolean, + date, + enumOf, + number, + object, + string +} from '@cleverbrush/schema'; +import { expectTypeOf, test } from 'vitest'; +import { + createFormSystem, + defineFieldRenderer, + Field, + type FieldRenderProps, + FormProvider, + useField, + useSchemaForm +} from './index.js'; + +const nameSchema = string().default('Untitled'); +const schema = object({ + name: nameSchema, + kind: enumOf('normal', 'offset').optional().default('normal'), + active: boolean().default(true), + count: number().default(1), + tags: array(string()).default(() => []), + when: date().default(() => new Date()), + optional: string().optional(), + nullable: string().nullable().default('Fallback') +}); +const system = createFormSystem({ + renderers: { + string: defineFieldRenderer(() => null), + 'string:select': defineFieldRenderer< + string, + { onSelect?: (value: string) => void } + >(() => null), + 'number:select': defineFieldRenderer< + number, + { onSelect?: (value: number) => void } + >(() => null), + boolean: defineFieldRenderer(() => null), + array: defineFieldRenderer(() => null), + date: defineFieldRenderer(() => null), + 'string:nullable': defineFieldRenderer(() => null) + } +}); + +test('defaulted properties retain their values in headless and rendered fields', () => { + const form = useSchemaForm(schema); + const name = form.useField(t => t.name); + const kind = form.useField(t => t.kind); + const active = form.useField(t => t.active); + const count = form.useField(t => t.count); + const tags = form.useField(t => t.tags); + const when = form.useField(t => t.when); + const optional = form.useField(t => t.optional); + const nullable = form.useField(t => t.nullable); + expectTypeOf(name.value).toEqualTypeOf(); + expectTypeOf(kind.value).toEqualTypeOf<'normal' | 'offset' | undefined>(); + expectTypeOf(active.value).toEqualTypeOf(); + expectTypeOf(count.value).toEqualTypeOf(); + expectTypeOf(tags.value).toEqualTypeOf(); + expectTypeOf(when.value).toEqualTypeOf(); + expectTypeOf(optional.value).toEqualTypeOf(); + expectTypeOf(nullable.value).toEqualTypeOf(); + name.setValue('New'); + kind.setValue('offset'); + active.setValue(false); + tags.setValue(['new']); + nullable.setValue(null); + // @ts-expect-error defaults must not erase the property's value type + name.setValue(1); + // @ts-expect-error enum remains narrow + kind.setValue('invalid'); + // @ts-expect-error array element types remain checked + tags.setValue([1]); + // @ts-expect-error default does not make a non-nullable string nullable + name.setValue(null); + t.name} />; + t.active} />; + t.tags} />; + t.when} />; + t.kind} />; + t.nullable} + variant="nullable" + />; + // @ts-expect-error nullable default requires a nullable renderer + t.nullable} />; + // @ts-expect-error numeric field cannot use a string variant + t.count} variant="nullable" />; + + t.name} /> + ; + useField(t => t.name); + const contextField = useField( + t => t.name + ); + expectTypeOf(contextField.value).toEqualTypeOf(); + form.handleSubmit(values => { + expectTypeOf(values.name).toEqualTypeOf(); + expectTypeOf(values.tags).toEqualTypeOf(); + expectTypeOf(values.active).toEqualTypeOf(); + }); + const rendererSchema: FieldRenderProps['schema'] = nameSchema; + expectTypeOf(rendererSchema).not.toBeNever(); +}); + +test('same-name variants check explicitly typed callbacks against the selected property', () => { + const form = useSchemaForm(schema); + t.name} + variant="select" + fieldProps={{ + onSelect: (value: string) => { + expectTypeOf(value).toEqualTypeOf(); + } + }} + />; + t.count} + variant="select" + fieldProps={{ + onSelect: (value: number) => { + expectTypeOf(value).toEqualTypeOf(); + } + }} + />; + t.name} + variant="select" + // @ts-expect-error callbacks must match the selected renderer's props + fieldProps={{ onSelect: (_value: number) => {} }} + />; +}); diff --git a/libs/react-form/src/defaults.test.tsx b/libs/react-form/src/defaults.test.tsx new file mode 100644 index 00000000..5eafcc24 --- /dev/null +++ b/libs/react-form/src/defaults.test.tsx @@ -0,0 +1,72 @@ +import { array, boolean, enumOf, object, string } from '@cleverbrush/schema'; +import { act, cleanup, renderHook } from '@testing-library/react'; +import { afterEach, expect, test, vi } from 'vitest'; +import { useSchemaForm } from './index.js'; + +afterEach(cleanup); +const schema = object({ + name: string().default('Untitled'), + kind: enumOf('normal', 'offset').optional().default('normal'), + active: boolean().default(true), + tags: array(string()).default(() => []) +}); + +test('validation applies schema defaults to submitted values without pre-filling the form store', async () => { + const { result } = renderHook(() => useSchemaForm(schema)); + expect(result.current.getValue()).toEqual({}); + const save = vi.fn(); + await act(async () => { + await result.current.handleSubmit(save)(); + }); + expect(save).toHaveBeenCalledOnce(); + expect(save).toHaveBeenCalledWith({ + name: 'Untitled', + kind: 'normal', + active: true, + tags: [] + }); + expect(result.current.submitting).toBe(false); + expect(result.current.error).toBeUndefined(); +}); + +test('headless defaulted properties remain synchronized through reset and submission', async () => { + const { result } = renderHook(() => { + const form = useSchemaForm(schema); + return { + form, + name: form.useField(t => t.name), + kind: form.useField(t => t.kind), + active: form.useField(t => t.active), + tags: form.useField(t => t.tags) + }; + }); + act(() => + result.current.form.reset({ + name: 'Existing', + kind: 'offset', + active: false, + tags: ['one'] + }) + ); + expect(result.current.name.value).toBe('Existing'); + expect(result.current.kind.value).toBe('offset'); + expect(result.current.active.value).toBe(false); + expect(result.current.tags.value).toEqual(['one']); + expect(result.current.name.dirty).toBe(false); + act(() => result.current.name.onChange('Edited')); + expect(result.current.name.dirty).toBe(true); + const save = vi.fn(); + await act(async () => { + await result.current.form.handleSubmit(save)(); + }); + expect(save).toHaveBeenCalledWith({ + name: 'Edited', + kind: 'offset', + active: false, + tags: ['one'] + }); + act(() => result.current.form.reset()); + expect(result.current.name.value).toBeUndefined(); + expect(result.current.name.dirty).toBe(false); + expect(result.current.active.value).toBeUndefined(); +}); diff --git a/libs/react-form/src/helpers.ts b/libs/react-form/src/helpers.ts index 428a8875..73f35c69 100644 --- a/libs/react-form/src/helpers.ts +++ b/libs/react-form/src/helpers.ts @@ -59,7 +59,9 @@ export function getDescriptorPath( /** * Returns the schema type string (e.g. "string", "number", "object"). */ -export function getSchemaType(schema: SchemaBuilder): string { +export function getSchemaType( + schema: SchemaBuilder +): string { const introspected = schema.introspect(); return introspected?.type ?? 'unknown'; } diff --git a/libs/react-form/src/hooks.ts b/libs/react-form/src/hooks.ts index 1159433c..1d77d4e6 100644 --- a/libs/react-form/src/hooks.ts +++ b/libs/react-form/src/hooks.ts @@ -40,7 +40,7 @@ import type { export type SchemaFormInstance< TSchema extends ObjectSchemaBuilder > = { - useField: >( + useField: >( forProperty: ( tree: PropertyDescriptorTree ) => PropertyDescriptor @@ -305,7 +305,7 @@ export function useSchemaForm< formContextRef.current = formContextValue; const _getFormContext = useCallback(() => formContextRef.current, []); const useFieldHook = useCallback( - >( + >( selector: ( tree: PropertyDescriptorTree ) => PropertyDescriptor @@ -406,7 +406,7 @@ export function useFieldFromContext( /** Resolve type:variant first, falling back to the base type. */ export function resolveRenderer( config: FormSystemConfig | null, - schema: SchemaBuilder, + schema: SchemaBuilder, variant?: string ): FieldRenderer | undefined { if (!config?.renderers) return undefined; diff --git a/libs/react-form/src/index.ts b/libs/react-form/src/index.ts index 30ed2420..60aeebc1 100644 --- a/libs/react-form/src/index.ts +++ b/libs/react-form/src/index.ts @@ -19,8 +19,10 @@ export type { SchemaFormInstance } from './hooks.js'; // Hooks export { useSchemaForm } from './hooks.js'; export type { + TypedFieldComponent, TypedFieldProps, TypedFieldRenderer, + TypedFormSystem, TypedRendererRegistry } from './system.js'; export { createFormSystem, defineFieldRenderer } from './system.js'; diff --git a/libs/react-form/src/system.tsx b/libs/react-form/src/system.tsx index c54699c4..67d3a85b 100644 --- a/libs/react-form/src/system.tsx +++ b/libs/react-form/src/system.tsx @@ -65,7 +65,7 @@ type RendererChoice = { type FieldDescriptor = { readonly [SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]: { - getSchema: () => SchemaBuilder; + getSchema: () => SchemaBuilder; }; }; type DescriptorValue = InferType< @@ -86,6 +86,21 @@ export type TypedFieldProps< name?: string; } & RendererChoice>>; +/** Named callable type also supports exporting system.Field from UI packages. */ +export type TypedFieldComponent = < + TSchema extends ObjectSchemaBuilder, + TDescriptor extends FieldDescriptor +>( + props: TypedFieldProps +) => ReactNode; + +/** Named return type keeps exported consumer registries declaration-safe. */ +export type TypedFormSystem = { + Field: TypedFieldComponent; + Provider: (props: { children: ReactNode }) => ReactNode; + renderers: Readonly; +}; + /** * Create a typed Field and a provider for an application's renderer registry. * The typed Field resolves from this factory's closed registry, so an untyped @@ -96,7 +111,7 @@ export type TypedFieldProps< */ export function createFormSystem< const R extends TypedRendererRegistry ->(config: { renderers: R }) { +>(config: { renderers: R }): TypedFormSystem { const renderers = Object.freeze({ ...config.renderers }); function TypedField< TSchema extends ObjectSchemaBuilder, diff --git a/libs/react-form/src/types.ts b/libs/react-form/src/types.ts index c3e4f3f6..77f20191 100644 --- a/libs/react-form/src/types.ts +++ b/libs/react-form/src/types.ts @@ -21,7 +21,7 @@ export type FieldRenderProps> = { onChange: (value: TValue) => void; onBlur: () => void; setValue: (value: TValue) => void; - schema: SchemaBuilder; + schema: SchemaBuilder; /** * Rendering variant hint passed from the `Field` component. * Used by renderers to select a sub-variant of the base schema type @@ -107,7 +107,7 @@ export type UseFieldResult = { onChange: (value: T) => void; onBlur: () => void; setValue: (value: T) => void; - schema: SchemaBuilder; + schema: SchemaBuilder; }; /** diff --git a/libs/react-form/test-fixtures/exported-system.tsx b/libs/react-form/test-fixtures/exported-system.tsx new file mode 100644 index 00000000..e5a4704d --- /dev/null +++ b/libs/react-form/test-fixtures/exported-system.tsx @@ -0,0 +1,18 @@ +import { createFormSystem, defineFieldRenderer } from '@cleverbrush/react-form'; + +export const SharedFormSystem = createFormSystem({ + renderers: { + string: defineFieldRenderer( + () => null + ) + } +}); +export const WebFormSystem = createFormSystem({ + renderers: { + ...SharedFormSystem.renderers, + 'number:select': defineFieldRenderer( + () => null + ) + } +}); +export const SchemaField = WebFormSystem.Field; From 275444c5fdafdae9a92c99fc757111b3dc001550 Mon Sep 17 00:00:00 2001 From: Andrew Zolotukhin Date: Wed, 9 Sep 2026 21:13:51 +0000 Subject: [PATCH 4/5] fix: schedule cache and form changes as a minor release --- .changeset/cache-form-correctness.md | 6 +++--- .changeset/defaulted-form-fields.md | 2 +- docs/cache-form-migration.md | 6 ++++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.changeset/cache-form-correctness.md b/.changeset/cache-form-correctness.md index 60830213..a2f73b35 100644 --- a/.changeset/cache-form-correctness.md +++ b/.changeset/cache-form-correctness.md @@ -1,7 +1,7 @@ --- -'@cleverbrush/server': major -'@cleverbrush/client': major -'@cleverbrush/deep': major +'@cleverbrush/server': minor +'@cleverbrush/client': minor +'@cleverbrush/deep': minor '@cleverbrush/react-form': minor --- diff --git a/.changeset/defaulted-form-fields.md b/.changeset/defaulted-form-fields.md index bf51a1e8..e8a02108 100644 --- a/.changeset/defaulted-form-fields.md +++ b/.changeset/defaulted-form-fields.md @@ -1,5 +1,5 @@ --- -'@cleverbrush/react-form': patch +'@cleverbrush/react-form': minor --- Accept defaulted schema properties in typed Field, headless useField, and renderer schema bounds without losing value inference. Return a named TypedFormSystem so shared UI packages can export inferred registries while emitting declarations. diff --git a/docs/cache-form-migration.md b/docs/cache-form-migration.md index 7f767dcc..be9e262d 100644 --- a/docs/cache-form-migration.md +++ b/docs/cache-form-migration.md @@ -3,6 +3,12 @@ These changes are application-agnostic. Libraries do not choose an application's auth scope, UI kit, notifications, navigation behavior, or cache backend. +This batch is scheduled as a coordinated **minor release** of the fixed package +group. This release classification does not remove the compatibility changes +below: external-cache users must coordinate the key-format migration, and +consumers relying on previous `deepEqual` results must review those assumptions. +The existing public form APIs remain available. + ## Cache key migration (breaking) Previously, selected values were converted to strings and joined with delimiters. From 106fb447b162046550c410bb1d5dd94048cfdbd9 Mon Sep 17 00:00:00 2001 From: Andrew Zolotukhin Date: Wed, 9 Sep 2026 21:23:18 +0000 Subject: [PATCH 5/5] docs: v4.4.3 --- websites/docs/public/api-docs/index.html | 3 + .../docs/public/api-docs/v4.4.3/.nojekyll | 1 + .../api-docs/v4.4.3/assets/hierarchy.js | 1 + .../api-docs/v4.4.3/assets/highlight.css | 176 ++ .../public/api-docs/v4.4.3/assets/icons.js | 18 + .../public/api-docs/v4.4.3/assets/icons.svg | 1 + .../public/api-docs/v4.4.3/assets/main.js | 60 + .../api-docs/v4.4.3/assets/navigation.js | 1 + .../public/api-docs/v4.4.3/assets/search.js | 1 + .../public/api-docs/v4.4.3/assets/style.css | 1633 +++++++++++++++++ .../classes/_cleverbrush_async.Collector.html | 183 ++ .../_cleverbrush_async.TimeoutError.html | 38 + ...cleverbrush_auth.AuthorizationService.html | 16 + .../_cleverbrush_auth.PolicyBuilder.html | 12 + .../classes/_cleverbrush_auth.Principal.html | 17 + .../_cleverbrush_client..ApiError.html | 25 + .../_cleverbrush_client..NetworkError.html | 12 + .../_cleverbrush_client..OfflineError.html | 12 + .../_cleverbrush_client..TimeoutError.html | 13 + .../_cleverbrush_client..WebError.html | 12 + ..._cleverbrush_di.ScopedServiceProvider.html | 38 + .../_cleverbrush_di.ServiceCollection.html | 150 ++ .../_cleverbrush_di.ServiceProvider.html | 83 + .../classes/_cleverbrush_di.ServiceScope.html | 64 + .../_cleverbrush_env.EnvValidationError.html | 40 + ..._knex-clickhouse.ClickhouseKnexClient.html | 363 ++++ .../_cleverbrush_knex-schema..Entity.html | 56 + ...brush_knex-schema..SchemaQueryBuilder.html | 72 + .../_cleverbrush_log..BatchingSink.html | 18 + .../classes/_cleverbrush_log..Logger.html | 57 + .../classes/_cleverbrush_mapper.Mapper.html | 40 + ...brush_mapper.MapperConfigurationError.html | 6 + .../_cleverbrush_mapper.MappingRegistry.html | 24 + ...erbrush_mapper.PropertyMappingBuilder.html | 29 + .../_cleverbrush_orm.ConcurrencyError.html | 42 + .../classes/_cleverbrush_orm.Entity.html | 56 + .../_cleverbrush_orm.EntityNotFoundError.html | 39 + ...everbrush_orm.InvariantViolationError.html | 38 + .../_cleverbrush_orm.PendingChangesError.html | 39 + .../_cleverbrush_orm.SchemaQueryBuilder.html | 72 + .../_cleverbrush_scheduler.JobScheduler.html | 206 +++ ...verbrush_scheduler.ScheduleCalculator.html | 68 + .../_cleverbrush_schema.AnySchemaBuilder.html | 272 +++ ...cleverbrush_schema.ArraySchemaBuilder.html | 297 +++ ...everbrush_schema.BooleanSchemaBuilder.html | 303 +++ ..._cleverbrush_schema.DateSchemaBuilder.html | 354 ++++ ...leverbrush_schema.ExternSchemaBuilder.html | 292 +++ ...verbrush_schema.FunctionSchemaBuilder.html | 308 ++++ ...everbrush_schema.GenericSchemaBuilder.html | 325 ++++ ...rush_schema.IntersectionSchemaBuilder.html | 265 +++ ..._cleverbrush_schema.LazySchemaBuilder.html | 277 +++ ..._cleverbrush_schema.NullSchemaBuilder.html | 273 +++ ...leverbrush_schema.NumberSchemaBuilder.html | 351 ++++ ...leverbrush_schema.ObjectSchemaBuilder.html | 448 +++++ ...brush_schema.ParseStringSchemaBuilder.html | 293 +++ ...everbrush_schema.PromiseSchemaBuilder.html | 292 +++ ...leverbrush_schema.RecordSchemaBuilder.html | 281 +++ .../_cleverbrush_schema.SchemaBuilder.html | 310 ++++ ...verbrush_schema.SchemaValidationError.html | 10 + ...leverbrush_schema.StringSchemaBuilder.html | 353 ++++ ...cleverbrush_schema.TupleSchemaBuilder.html | 299 +++ ...cleverbrush_schema.UnionSchemaBuilder.html | 295 +++ ...erbrush_server-openapi.SchemaRegistry.html | 33 + .../_cleverbrush_server..ActionResult.html | 53 + .../_cleverbrush_server..BadRequestError.html | 40 + .../_cleverbrush_server..ConflictError.html | 40 + ...cleverbrush_server..ContentNegotiator.html | 17 + .../_cleverbrush_server..ContentResult.html | 49 + .../_cleverbrush_server..EndpointBuilder.html | 175 ++ .../_cleverbrush_server..FileResult.html | 49 + .../_cleverbrush_server..ForbiddenError.html | 40 + .../_cleverbrush_server..HttpError.html | 45 + .../_cleverbrush_server..JsonResult.html | 52 + .../_cleverbrush_server..NoContentResult.html | 46 + .../_cleverbrush_server..NotFoundError.html | 40 + .../_cleverbrush_server..RawResult.html | 47 + .../_cleverbrush_server..RedirectResult.html | 49 + .../_cleverbrush_server..RequestContext.html | 27 + .../classes/_cleverbrush_server..Server.html | 10 + .../_cleverbrush_server..ServerBuilder.html | 72 + ..._cleverbrush_server..StatusCodeResult.html | 48 + .../_cleverbrush_server..StreamResult.html | 49 + ...everbrush_server..SubscriptionBuilder.html | 51 + ...cleverbrush_server..UnauthorizedError.html | 40 + .../_cleverbrush_di.ServiceLifetime.html | 21 + .../_cleverbrush_async.debounce.html | 8 + .../functions/_cleverbrush_async.dedupe.html | 14 + .../functions/_cleverbrush_async.retry.html | 4 + .../_cleverbrush_async.throttle.html | 5 + .../_cleverbrush_async.withTimeout.html | 15 + ...verbrush_auth.authorizationCodeScheme.html | 7 + ...verbrush_auth.clientCredentialsScheme.html | 7 + .../_cleverbrush_auth.cookieScheme.html | 2 + .../_cleverbrush_auth.defineRoles.html | 7 + .../_cleverbrush_auth.jwtScheme.html | 2 + .../_cleverbrush_auth.oidcScheme.html | 8 + .../_cleverbrush_auth.parseCookies.html | 5 + .../_cleverbrush_auth.requireRole.html | 3 + .../_cleverbrush_auth.serializeCookie.html | 2 + .../functions/_cleverbrush_auth.signJwt.html | 3 + ...cleverbrush_client..composeMiddleware.html | 11 + .../_cleverbrush_client..createClient.html | 20 + ...cleverbrush_client..getPerCallOptions.html | 5 + .../_cleverbrush_client..isApiError.html | 5 + .../_cleverbrush_client..isNetworkError.html | 1 + .../_cleverbrush_client..isOfflineError.html | 5 + .../_cleverbrush_client..isTimeoutError.html | 5 + .../_cleverbrush_client..isWebError.html | 5 + ..._cleverbrush_client.batching.batching.html | 21 + .../_cleverbrush_client.cache.cacheTags.html | 9 + ...brush_client.cache.computeCacheTagKey.html | 7 + ...brush_client.cache.createCacheTagRoot.html | 2 + ...rbrush_client.cache.externalCacheTags.html | 11 + ...erbrush_client.cache.isMutatingMethod.html | 2 + ...verbrush_client.cache.throttlingCache.html | 10 + .../_cleverbrush_client.dedupe.dedupe.html | 10 + ...rbrush_client.idempotency.idempotency.html | 11 + ...ush_client.offline-queue.offlineQueue.html | 1 + ...nt.optimistic-update.optimisticUpdate.html | 1 + ...brush_client.react.buildGroupQueryKey.html | 10 + ...leverbrush_client.react.buildQueryKey.html | 11 + ...cleverbrush_client.react.createClient.html | 25 + ...sh_client.react.useOptimisticMutation.html | 1 + ...verbrush_client.react.useSubscription.html | 9 + .../_cleverbrush_client.retry.retry.html | 7 + .../_cleverbrush_client.timeout.timeout.html | 9 + .../_cleverbrush_deep.deepEqual.html | 11 + .../_cleverbrush_deep.deepFlatten.html | 5 + .../_cleverbrush_di.isAsyncDisposable.html | 8 + .../_cleverbrush_di.isDisposable.html | 8 + .../functions/_cleverbrush_env.env.html | 12 + .../_cleverbrush_env.envBoolean.html | 11 + .../functions/_cleverbrush_env.parseEnv.html | 37 + .../_cleverbrush_env.parseEnvFlat.html | 9 + .../functions/_cleverbrush_env.splitBy.html | 10 + ...ex-clickhouse.getClickhouseConnection.html | 8 + .../_cleverbrush_knex-schema..applyDiff.html | 10 + ...everbrush_knex-schema..buildColumnMap.html | 4 + .../_cleverbrush_knex-schema..clearRow.html | 13 + ..._cleverbrush_knex-schema..createQuery.html | 1 + ...cleverbrush_knex-schema..defineEntity.html | 6 + .../_cleverbrush_knex-schema..diffSchema.html | 10 + ...brush_knex-schema..entitiesToSnapshot.html | 10 + ...knex-schema..entitySchemaToTableState.html | 12 + ...hema..generateCreatePolymorphicTables.html | 12 + ...rush_knex-schema..generateCreateTable.html | 12 + ...nex-schema..generateCreateTableSource.html | 12 + ...rbrush_knex-schema..generateMigration.html | 10 + ...-schema..generateMigrationsForContext.html | 21 + ...ush_knex-schema..getPrimaryKeyColumns.html | 8 + ...rush_knex-schema..getRowVersionColumn.html | 3 + ...brush_knex-schema..introspectDatabase.html | 11 + ..._cleverbrush_knex-schema..isDiffEmpty.html | 3 + ...cleverbrush_knex-schema..loadSnapshot.html | 8 + .../_cleverbrush_knex-schema..mapObject.html | 10 + .../_cleverbrush_knex-schema..mapValue.html | 8 + .../_cleverbrush_knex-schema..query.html | 1 + .../_cleverbrush_knex-schema..rawQuery.html | 15 + ...erbrush_knex-schema..resolveColumnRef.html | 27 + ...brush_knex-schema..resolvePropertyKey.html | 3 + ...verbrush_knex-schema..tableExistsInDb.html | 8 + ...hema..validateEntitiesAgainstDatabase.html | 8 + ...leverbrush_knex-schema..writeSnapshot.html | 5 + ...chema.extension.applyVariantsToSchema.html | 6 + ...h_knex-schema.extension.getColumnName.html | 3 + ...xtension.getPolymorphicVariantSchemas.html | 4 + ..._knex-schema.extension.getProjections.html | 10 + ...sh_knex-schema.extension.getTableName.html | 3 + ...ush_knex-schema.extension.getVariants.html | 4 + ..._cleverbrush_log..applicationEnricher.html | 4 + .../_cleverbrush_log..callerEnricher.html | 5 + .../_cleverbrush_log..captureProperties.html | 7 + .../_cleverbrush_log..computeEventId.html | 5 + .../_cleverbrush_log..configureLogging.html | 9 + .../_cleverbrush_log..consoleSink.html | 12 + ...leverbrush_log..correlationIdEnricher.html | 4 + ...verbrush_log..correlationIdMiddleware.html | 9 + .../_cleverbrush_log..createLogEvent.html | 8 + .../_cleverbrush_log..createLogger.html | 14 + .../_cleverbrush_log..createSink.html | 7 + ..._cleverbrush_log..environmentEnricher.html | 4 + ...cleverbrush_log..extractCorrelationId.html | 11 + .../functions/_cleverbrush_log..fileSink.html | 8 + .../_cleverbrush_log..formatClef.html | 10 + .../_cleverbrush_log..formatClefBatch.html | 8 + ...leverbrush_log..generateCorrelationId.html | 4 + .../_cleverbrush_log..hostnameEnricher.html | 4 + .../_cleverbrush_log..levelToShortString.html | 2 + .../_cleverbrush_log..levelToString.html | 4 + .../_cleverbrush_log..parseLogLevel.html | 4 + .../_cleverbrush_log..parseTemplate.html | 6 + .../_cleverbrush_log..processIdEnricher.html | 4 + .../_cleverbrush_log..renderTemplate.html | 7 + ...erbrush_log..requestLoggingMiddleware.html | 11 + .../_cleverbrush_log..safeSerialize.html | 15 + .../_cleverbrush_log..samplingFilter.html | 9 + .../functions/_cleverbrush_log..seqSink.html | 10 + .../_cleverbrush_log..useLogging.html | 9 + ...erbrush_log.clickhouse.clickHouseSink.html | 9 + ...rbrush_log.clickhouse.createLogsTable.html | 8 + .../functions/_cleverbrush_mapper.mapper.html | 7 + .../_cleverbrush_orm-cli.defineConfig.html | 5 + .../functions/_cleverbrush_orm.applyDiff.html | 10 + .../_cleverbrush_orm.buildColumnMap.html | 4 + .../functions/_cleverbrush_orm.clearRow.html | 13 + .../functions/_cleverbrush_orm.createDb.html | 9 + .../_cleverbrush_orm.createQuery.html | 1 + .../_cleverbrush_orm.defineEntity.html | 6 + .../_cleverbrush_orm.diffSchema.html | 10 + .../_cleverbrush_orm.entitiesToSnapshot.html | 10 + ...verbrush_orm.entitySchemaToTableState.html | 12 + ...h_orm.generateCreatePolymorphicTables.html | 12 + .../_cleverbrush_orm.generateCreateTable.html | 12 + ...erbrush_orm.generateCreateTableSource.html | 12 + .../_cleverbrush_orm.generateMigration.html | 10 + ...rush_orm.generateMigrationsForContext.html | 21 + .../_cleverbrush_orm.getColumnName.html | 3 + ...rush_orm.getPolymorphicVariantSchemas.html | 4 + ..._cleverbrush_orm.getPrimaryKeyColumns.html | 8 + .../_cleverbrush_orm.getProjections.html | 10 + .../_cleverbrush_orm.getRowVersionColumn.html | 3 + .../_cleverbrush_orm.getTableName.html | 3 + .../_cleverbrush_orm.getVariants.html | 4 + .../_cleverbrush_orm.introspectDatabase.html | 11 + .../_cleverbrush_orm.isDiffEmpty.html | 3 + .../_cleverbrush_orm.loadSnapshot.html | 8 + .../functions/_cleverbrush_orm.mapObject.html | 10 + .../functions/_cleverbrush_orm.mapValue.html | 8 + .../functions/_cleverbrush_orm.query.html | 1 + .../functions/_cleverbrush_orm.rawQuery.html | 15 + .../_cleverbrush_orm.resolveColumnRef.html | 27 + .../_cleverbrush_orm.resolvePropertyKey.html | 3 + .../_cleverbrush_orm.tableExistsInDb.html | 8 + ...h_orm.validateEntitiesAgainstDatabase.html | 8 + .../_cleverbrush_orm.writeSnapshot.html | 5 + .../_cleverbrush_otel..configureOtel.html | 10 + .../_cleverbrush_otel..instrumentKnex.html | 18 + .../_cleverbrush_otel..otelLogSink.html | 26 + .../_cleverbrush_otel..setupOtel.html | 13 + .../_cleverbrush_otel..traceEnricher.html | 11 + .../_cleverbrush_otel..tracingMiddleware.html | 25 + .../_cleverbrush_otel..withSpan.html | 19 + ...h_otel.client.clientTracingMiddleware.html | 9 + ...ntations.outboundHttpInstrumentations.html | 12 + ..._otel.instrumentations.runtimeMetrics.html | 9 + .../_cleverbrush_react-form.Field.html | 13 + .../_cleverbrush_react-form.FormProvider.html | 2 + ...erbrush_react-form.FormSystemProvider.html | 6 + .../_cleverbrush_react-form.useField.html | 7 + ..._cleverbrush_react-form.useFormSystem.html | 2 + ..._cleverbrush_react-form.useSchemaForm.html | 3 + ...leverbrush_schema-json.fromJsonSchema.html | 128 ++ ..._cleverbrush_schema-json.toJsonSchema.html | 29 + ...sh_schema-json.withStandardJsonSchema.html | 14 + .../_cleverbrush_schema.defineExtension.html | 49 + .../functions/_cleverbrush_schema.enumOf.html | 33 + .../functions/_cleverbrush_schema.extern.html | 19 + .../_cleverbrush_schema.generic.html | 57 + .../_cleverbrush_schema.intersection.html | 8 + .../functions/_cleverbrush_schema.lazy.html | 13 + .../functions/_cleverbrush_schema.nul.html | 13 + .../_cleverbrush_schema.parseString.html | 18 + .../_cleverbrush_schema.withExtensions.html | 40 + ...verbrush_server-openapi.convertSchema.html | 9 + ..._server-openapi.createOpenApiEndpoint.html | 7 + ...h_server-openapi.generateAsyncApiSpec.html | 17 + ...sh_server-openapi.generateOpenApiSpec.html | 2 + ...h_server-openapi.mapOperationSecurity.html | 7 + ...ush_server-openapi.mapSecuritySchemes.html | 2 + ...leverbrush_server-openapi.resolvePath.html | 3 + ...verbrush_server-openapi.serveAsyncApi.html | 8 + ...everbrush_server-openapi.serveOpenApi.html | 4 + ...leverbrush_server-openapi.walkSchemas.html | 15 + ...brush_server-openapi.writeOpenApiSpec.html | 8 + .../_cleverbrush_server..cacheResponse.html | 18 + .../_cleverbrush_server..computeCacheKey.html | 10 + ...leverbrush_server..createCacheTagTree.html | 8 + .../_cleverbrush_server..createEndpoints.html | 7 + ...verbrush_server..createProblemDetails.html | 7 + .../_cleverbrush_server..createServer.html | 1 + ...erver..createValidationProblemDetails.html | 4 + .../_cleverbrush_server..defineWebhook.html | 4 + .../_cleverbrush_server..idempotency.html | 12 + .../_cleverbrush_server..mapHandlers.html | 15 + .../functions/_cleverbrush_server..route.html | 17 + .../_cleverbrush_server..serializeTag.html | 7 + .../_cleverbrush_server..tracked.html | 11 + ...cleverbrush_server.contract.defineApi.html | 17 + ...rbrush_server.contract.mergeContracts.html | 22 + ...leverbrush_server.contract.omitGroups.html | 13 + ...leverbrush_server.contract.pickGroups.html | 14 + .../public/api-docs/v4.4.3/hierarchy.html | 1 + .../docs/public/api-docs/v4.4.3/index.html | 168 ++ ...leverbrush_auth.AuthenticationContext.html | 7 + ...cleverbrush_auth.AuthenticationScheme.html | 11 + ...h_auth.AuthorizationCodeSchemeOptions.html | 15 + ..._cleverbrush_auth.AuthorizationPolicy.html | 6 + ...h_auth.ClientCredentialsSchemeOptions.html | 13 + .../_cleverbrush_auth.CookieOptions.html | 17 + ..._cleverbrush_auth.CookieSchemeOptions.html | 12 + .../_cleverbrush_auth.JwtPayload.html | 10 + .../_cleverbrush_auth.JwtSchemeOptions.html | 17 + .../_cleverbrush_auth.OAuthFlow.html | 6 + .../_cleverbrush_auth.OAuthFlows.html | 7 + .../_cleverbrush_auth.OidcSchemeOptions.html | 9 + .../_cleverbrush_client..ClientHooks.html | 23 + .../_cleverbrush_client..ClientOptions.html | 43 + .../_cleverbrush_client..EndpointMeta.html | 41 + .../_cleverbrush_client..FilePart.html | 10 + ..._cleverbrush_client..PerCallOverrides.html | 14 + .../_cleverbrush_client..Subscription.html | 11 + ...brush_client.batching.BatchingOptions.html | 30 + ...cleverbrush_client.cache.CacheOptions.html | 21 + ...lient.cache.CacheTagMiddlewareOptions.html | 11 + ...cleverbrush_client.cache.CacheTagRoot.html | 6 + ...client.cache.ExternalCacheTagsOptions.html | 13 + ...brush_client.cache.SerializedCacheTag.html | 4 + ...everbrush_client.dedupe.DedupeOptions.html | 11 + ...client.idempotency.IdempotencyOptions.html | 13 + ...ent.offline-queue.OfflineQueueOptions.html | 4 + ...lient.offline-queue.OfflineQueueStore.html | 4 + ...sh_client.offline-queue.QueuedRequest.html | 6 + ...t.optimistic-update.OptimisticFailure.html | 6 + ...mistic-update.OptimisticUpdateOptions.html | 3 + ...timistic-update.OptimisticUpdateStore.html | 2 + ...client.react.OptimisticMutationConfig.html | 6 + ...h_client.react.UseSubscriptionOptions.html | 6 + ...ush_client.react.UseSubscriptionState.html | 14 + ...cleverbrush_client.retry.RetryOptions.html | 30 + ...erbrush_client.timeout.TimeoutOptions.html | 5 + .../_cleverbrush_di.IServiceProvider.html | 11 + .../_cleverbrush_di.ServiceDescriptor.html | 18 + ...cleverbrush_di.ServiceProviderOptions.html | 9 + ...erbrush_di.ServiceRegistrationOptions.html | 12 + .../_cleverbrush_env.EnvBooleanOptions.html | 14 + .../_cleverbrush_env.InvalidEnvVar.html | 10 + .../_cleverbrush_env.MissingEnvVar.html | 8 + ...rush_knex-clickhouse.ClickHouseClient.html | 40 + ...leverbrush_knex-schema..AddColumnDiff.html | 9 + ...rbrush_knex-schema..AddForeignKeyDiff.html | 7 + ...cleverbrush_knex-schema..AddIndexDiff.html | 5 + ...verbrush_knex-schema..AlterColumnDiff.html | 4 + .../_cleverbrush_knex-schema..BoundQuery.html | 3 + ...h_knex-schema..CursorPaginationResult.html | 8 + ...rbrush_knex-schema..DatabaseCheckInfo.html | 4 + ...brush_knex-schema..DatabaseColumnInfo.html | 8 + ...h_knex-schema..DatabaseForeignKeyInfo.html | 8 + ...rbrush_knex-schema..DatabaseIndexInfo.html | 6 + ...brush_knex-schema..DatabaseTableState.html | 6 + ...-schema..EntitySchemaValidationResult.html | 8 + ...cleverbrush_knex-schema..JoinManySpec.html | 19 + ..._cleverbrush_knex-schema..JoinOneSpec.html | 15 + ...leverbrush_knex-schema..MigrationDiff.html | 9 + ...erbrush_knex-schema..PaginationResult.html | 16 + ...rbrush_knex-schema..PrimaryKeyColumns.html | 9 + ...cleverbrush_knex-schema..RelationInfo.html | 6 + ...cleverbrush_knex-schema..RelationSpec.html | 7 + ...sh_knex-schema..ResolvedVariantConfig.html | 8 + ...rush_knex-schema..ResolvedVariantSpec.html | 12 + ...erbrush_knex-schema..RowVersionColumn.html | 9 + ...everbrush_knex-schema..SchemaSnapshot.html | 11 + ...sh_knex-schema..ValidatedJoinManySpec.html | 9 + ...ush_knex-schema..ValidatedJoinOneSpec.html | 7 + ...ema.extension.VariantInputForResolver.html | 14 + ..._cleverbrush_log..BatchingSinkOptions.html | 34 + .../_cleverbrush_log..ConsoleSinkOptions.html | 17 + ...h_log..CorrelationIdMiddlewareOptions.html | 17 + .../_cleverbrush_log..CreateSinkOptions.html | 10 + .../_cleverbrush_log..FileSinkOptions.html | 8 + .../_cleverbrush_log..LogContextStore.html | 5 + .../_cleverbrush_log..LogEvent.html | 19 + .../interfaces/_cleverbrush_log..LogSink.html | 10 + .../_cleverbrush_log..LoggerConfig.html | 30 + ...leverbrush_log..RequestLoggingOptions.html | 23 + .../_cleverbrush_log..RotationOptions.html | 13 + .../_cleverbrush_log..SeqSinkOptions.html | 26 + ...cleverbrush_log..SerializationOptions.html | 12 + .../_cleverbrush_log..TypedTemplate.html | 12 + ...og.clickhouse.ClickHouseColumnMapping.html | 12 + ..._log.clickhouse.ClickHouseSinkOptions.html | 18 + ...log.clickhouse.CreateLogsTableOptions.html | 19 + ..._cleverbrush_orm-cli.MigrationsConfig.html | 11 + .../_cleverbrush_orm-cli.OrmCliConfig.html | 12 + .../_cleverbrush_orm.AddColumnDiff.html | 9 + .../_cleverbrush_orm.AddForeignKeyDiff.html | 7 + .../_cleverbrush_orm.AddIndexDiff.html | 5 + .../_cleverbrush_orm.AlterColumnDiff.html | 4 + .../_cleverbrush_orm.BoundQuery.html | 3 + ...leverbrush_orm.CursorPaginationResult.html | 8 + .../_cleverbrush_orm.DatabaseCheckInfo.html | 4 + .../_cleverbrush_orm.DatabaseColumnInfo.html | 8 + ...leverbrush_orm.DatabaseForeignKeyInfo.html | 8 + .../_cleverbrush_orm.DatabaseIndexInfo.html | 6 + .../_cleverbrush_orm.DatabaseTableState.html | 6 + .../interfaces/_cleverbrush_orm.DbSet.html | 139 ++ .../_cleverbrush_orm.EntityEntry.html | 15 + .../_cleverbrush_orm.EntityQuery.html | 104 ++ ...rush_orm.EntitySchemaValidationResult.html | 8 + .../_cleverbrush_orm.JoinManySpec.html | 19 + .../_cleverbrush_orm.JoinOneSpec.html | 15 + .../_cleverbrush_orm.MigrationDiff.html | 9 + .../_cleverbrush_orm.PaginationResult.html | 16 + .../_cleverbrush_orm.PrimaryKeyColumns.html | 9 + .../_cleverbrush_orm.RelationInfo.html | 6 + .../_cleverbrush_orm.RelationSpec.html | 7 + ...cleverbrush_orm.ResolvedVariantConfig.html | 8 + .../_cleverbrush_orm.ResolvedVariantSpec.html | 12 + .../_cleverbrush_orm.RowVersionColumn.html | 9 + .../_cleverbrush_orm.SchemaSnapshot.html | 11 + ...cleverbrush_orm.ValidatedJoinManySpec.html | 9 + ..._cleverbrush_orm.ValidatedJoinOneSpec.html | 7 + .../_cleverbrush_orm.VariantDbSet.html | 90 + ...leverbrush_otel..ConfigureOtelOptions.html | 14 + ...everbrush_otel..InstrumentKnexOptions.html | 21 + .../_cleverbrush_otel..OtelConfig.html | 61 + .../_cleverbrush_otel..OtelHandle.html | 11 + ..._cleverbrush_otel..OtelLogSinkOptions.html | 14 + .../_cleverbrush_otel..SpanHandle.html | 20 + ...rbrush_otel..TracingMiddlewareOptions.html | 34 + .../_cleverbrush_otel..WithSpanOptions.html | 16 + ...otel.client.ClientTracingEndpointMeta.html | 11 + ...erbrush_otel.client.ClientTracingInfo.html | 6 + ...client.ClientTracingMiddlewareOptions.html | 21 + ...a-json.StandardJSONSchemaV1.Converter.html | 6 + ...ema-json.StandardJSONSchemaV1.Options.html | 6 + ...chema-json.StandardJSONSchemaV1.Props.html | 10 + ...chema-json.StandardJSONSchemaV1.Types.html | 6 + ...rush_schema-json.StandardJSONSchemaV1.html | 4 + ...ush_schema-json.StandardTypedV1.Props.html | 8 + ...ush_schema-json.StandardTypedV1.Types.html | 6 + ...everbrush_schema-json.StandardTypedV1.html | 4 + ...erbrush_schema.ArrayBuiltinExtensions.html | 26 + ...rbrush_schema.NumberBuiltinExtensions.html | 61 + ...schema.StandardSchemaV1.FailureResult.html | 4 + ...erbrush_schema.StandardSchemaV1.Issue.html | 6 + ...brush_schema.StandardSchemaV1.Options.html | 3 + ...h_schema.StandardSchemaV1.PathSegment.html | 4 + ...erbrush_schema.StandardSchemaV1.Props.html | 10 + ...schema.StandardSchemaV1.SuccessResult.html | 6 + ...erbrush_schema.StandardSchemaV1.Types.html | 6 + .../_cleverbrush_schema.StandardSchemaV1.html | 4 + ...verbrush_schema.StandardTypedV1.Props.html | 8 + ...verbrush_schema.StandardTypedV1.Types.html | 6 + .../_cleverbrush_schema.StandardTypedV1.html | 4 + ...rbrush_schema.StringBuiltinExtensions.html | 84 + ...everbrush_server-openapi.AsyncApiInfo.html | 15 + ...rbrush_server-openapi.AsyncApiOptions.html | 20 + ...sh_server-openapi.AsyncApiServerEntry.html | 15 + ...ver-openapi.AsyncApiServer_ServerLike.html | 4 + ...server-openapi.OpenApiEndpointOptions.html | 21 + ...leverbrush_server-openapi.OpenApiInfo.html | 9 + ...erbrush_server-openapi.OpenApiOptions.html | 28 + ..._server-openapi.OpenApiSecurityScheme.html | 11 + ...verbrush_server-openapi.OpenApiServer.html | 5 + ...rver-openapi.OpenApiServer_ServerLike.html | 7 + ...cleverbrush_server-openapi.OpenApiTag.html | 11 + ...rush_server-openapi.PathParameterInfo.html | 5 + ...everbrush_server-openapi.ResolvedPath.html | 7 + ...h_server-openapi.ServeAsyncApiOptions.html | 18 + ...sh_server-openapi.ServeOpenApiOptions.html | 21 + ...verbrush_server..AuthenticationConfig.html | 14 + ...everbrush_server..AuthorizationConfig.html | 6 + ...leverbrush_server..CacheTagDefinition.html | 7 + ...rush_server..CacheTagPropertyAccessor.html | 5 + ...leverbrush_server..CallbackDefinition.html | 22 + ...leverbrush_server..ContentTypeHandler.html | 10 + ..._cleverbrush_server..EndpointMetadata.html | 63 + ...verbrush_server..EndpointRegistration.html | 6 + .../_cleverbrush_server..FilePart.html | 10 + .../_cleverbrush_server..HandlerMapping.html | 5 + .../_cleverbrush_server..LinkDefinition.html | 18 + .../_cleverbrush_server..ProblemDetails.html | 10 + .../_cleverbrush_server..RejectedFile.html | 8 + ...erbrush_server..ServerBatchingOptions.html | 31 + ...leverbrush_server..ServerCacheOptions.html | 7 + ...rush_server..ServerIdempotencyOptions.html | 11 + .../_cleverbrush_server..ServerOptions.html | 13 + ...verbrush_server..SubscriptionMetadata.html | 20 + ...rush_server..SubscriptionRegistration.html | 6 + .../_cleverbrush_server..TrackedEvent.html | 11 + .../_cleverbrush_server..UploadOptions.html | 16 + ...everbrush_server..ValidationErrorItem.html | 8 + ...cleverbrush_server..WebhookDefinition.html | 22 + .../docs/public/api-docs/v4.4.3/media/LICENSE | 52 + .../docs/public/api-docs/v4.4.3/media/ci.yml | 45 + .../docs/public/api-docs/v4.4.3/modules.html | 1 + .../v4.4.3/modules/_cleverbrush_async.html | 202 ++ .../v4.4.3/modules/_cleverbrush_auth.html | 52 + .../v4.4.3/modules/_cleverbrush_client..html | 6 + .../modules/_cleverbrush_client.batching.html | 1 + .../modules/_cleverbrush_client.cache.html | 1 + .../modules/_cleverbrush_client.dedupe.html | 1 + .../v4.4.3/modules/_cleverbrush_client.html | 705 +++++++ .../_cleverbrush_client.idempotency.html | 1 + .../_cleverbrush_client.offline-queue.html | 1 + ..._cleverbrush_client.optimistic-update.html | 1 + .../modules/_cleverbrush_client.react.html | 2 + .../modules/_cleverbrush_client.retry.html | 1 + .../modules/_cleverbrush_client.timeout.html | 1 + .../v4.4.3/modules/_cleverbrush_deep.html | 128 ++ .../v4.4.3/modules/_cleverbrush_di.html | 209 +++ .../v4.4.3/modules/_cleverbrush_env.html | 195 ++ .../modules/_cleverbrush_knex-clickhouse.html | 94 + .../modules/_cleverbrush_knex-schema..html | 1 + .../_cleverbrush_knex-schema.extension.html | 1 + .../modules/_cleverbrush_knex-schema.html | 293 +++ .../v4.4.3/modules/_cleverbrush_log..html | 1 + .../modules/_cleverbrush_log.clickhouse.html | 1 + .../v4.4.3/modules/_cleverbrush_log.html | 42 + .../v4.4.3/modules/_cleverbrush_mapper.html | 206 +++ .../v4.4.3/modules/_cleverbrush_orm-cli.html | 159 ++ .../v4.4.3/modules/_cleverbrush_orm.html | 313 ++++ .../v4.4.3/modules/_cleverbrush_otel..html | 1 + .../modules/_cleverbrush_otel.client.html | 1 + .../v4.4.3/modules/_cleverbrush_otel.html | 100 + .../_cleverbrush_otel.instrumentations.html | 8 + .../modules/_cleverbrush_react-form.html | 457 +++++ .../modules/_cleverbrush_scheduler.html | 154 ++ ...rush_schema-json.StandardJSONSchemaV1.html | 1 + ...everbrush_schema-json.StandardTypedV1.html | 1 + .../modules/_cleverbrush_schema-json.html | 335 ++++ .../_cleverbrush_schema.StandardSchemaV1.html | 1 + .../_cleverbrush_schema.StandardTypedV1.html | 1 + .../v4.4.3/modules/_cleverbrush_schema.html | 1034 +++++++++++ .../modules/_cleverbrush_server-openapi.html | 209 +++ .../v4.4.3/modules/_cleverbrush_server..html | 1 + .../modules/_cleverbrush_server.contract.html | 9 + .../v4.4.3/modules/_cleverbrush_server.html | 346 ++++ ...cleverbrush_auth.AuthenticationResult.html | 3 + ...erbrush_auth.AuthorizationRequirement.html | 3 + ..._cleverbrush_auth.AuthorizationResult.html | 2 + .../_cleverbrush_client..ApiContract.html | 7 + .../_cleverbrush_client..EndpointCall.html | 8 + ..._cleverbrush_client..EndpointCallArgs.html | 15 + ..._cleverbrush_client..EndpointResponse.html | 12 + .../types/_cleverbrush_client..FetchLike.html | 4 + .../_cleverbrush_client..Middleware.html | 6 + ..._cleverbrush_client..SubscriptionCall.html | 6 + ...verbrush_client..SubscriptionCallArgs.html | 2 + ...verbrush_client..SubscriptionIncoming.html | 2 + ...verbrush_client..SubscriptionOutgoing.html | 2 + .../_cleverbrush_client..TypedClient.html | 8 + ...brush_client.react.EndpointQueryHooks.html | 7 + ...client.react.InfiniteQueryHookOptions.html | 6 + ...rush_client.react.MutationHookOptions.html | 4 + ...erbrush_client.react.QueryHookOptions.html | 4 + ...client.react.SuspenseQueryHookOptions.html | 4 + ...erbrush_client.react.TypedQueryClient.html | 8 + ...verbrush_client.react.TypedQueryGroup.html | 11 + ...leverbrush_client.react.UnifiedClient.html | 8 + ...rush_client.react.UnifiedEndpointCall.html | 9 + ...cleverbrush_client.react.UnifiedGroup.html | 5 + .../v4.4.3/types/_cleverbrush_deep.Merge.html | 2 + .../types/_cleverbrush_deep.MergeTwo.html | 2 + .../types/_cleverbrush_di.ServiceFactory.html | 8 + .../types/_cleverbrush_env.EnvConfig.html | 2 + .../types/_cleverbrush_env.EnvConfigNode.html | 8 + .../types/_cleverbrush_env.EnvField.html | 6 + .../_cleverbrush_env.InferEnvConfig.html | 6 + .../_cleverbrush_knex-schema..ColumnRef.html | 1 + ...brush_knex-schema..EntityPropSelector.html | 6 + ...brush_knex-schema..EntityRelationKeys.html | 4 + ...verbrush_knex-schema..EntityRelations.html | 2 + ...cleverbrush_knex-schema..EntitySchema.html | 2 + ...x-schema..EntitySchemaValidationIssue.html | 2 + ...brush_knex-schema..EntityVariantUnion.html | 3 + ...erbrush_knex-schema..InferDatabaseRow.html | 5 + ...brush_knex-schema..InferDatabaseValue.html | 5 + .../_cleverbrush_knex-schema..InsertType.html | 1 + ...cleverbrush_knex-schema..PrimaryKeyOf.html | 13 + ...rbrush_knex-schema..PrimaryKeyValueOf.html | 9 + ...brush_knex-schema..RowVersionStrategy.html | 7 + .../_cleverbrush_knex-schema..SchemaKeys.html | 1 + ..._cleverbrush_knex-schema..SchemaProps.html | 3 + ...erbrush_knex-schema..SelectProjection.html | 5 + ...everbrush_knex-schema..SelectSelector.html | 4 + ...verbrush_knex-schema..UnwrapNavSchema.html | 3 + ...leverbrush_knex-schema..ValidatedSpec.html | 1 + ...leverbrush_knex-schema..VariantBranch.html | 4 + ...brush_knex-schema..VariantStorageType.html | 2 + ...everbrush_knex-schema..WithJoinedMany.html | 2 + ...leverbrush_knex-schema..WithJoinedOne.html | 2 + ...cleverbrush_knex-schema..WithRelation.html | 3 + .../types/_cleverbrush_log..Enricher.html | 4 + .../types/_cleverbrush_log..LogFilter.html | 3 + .../types/_cleverbrush_log..LogLevel.html | 7 + .../types/_cleverbrush_log..LogLevelName.html | 2 + .../_cleverbrush_log..SamplingRates.html | 3 + ...ush_mapper.SchemaToSchemaMapperResult.html | 1 + .../types/_cleverbrush_orm.ColumnRef.html | 1 + .../types/_cleverbrush_orm.DbContext.html | 8 + .../types/_cleverbrush_orm.EntityMap.html | 2 + .../_cleverbrush_orm.EntityPropSelector.html | 6 + .../_cleverbrush_orm.EntityRelationKeys.html | 4 + .../_cleverbrush_orm.EntityRelations.html | 2 + .../types/_cleverbrush_orm.EntityResult.html | 8 + ...cleverbrush_orm.EntityResultByVariant.html | 3 + .../types/_cleverbrush_orm.EntitySchema.html | 2 + ...brush_orm.EntitySchemaValidationIssue.html | 2 + .../_cleverbrush_orm.EntityVariantUnion.html | 3 + .../types/_cleverbrush_orm.EntryState.html | 2 + .../types/_cleverbrush_orm.ExtractBranch.html | 8 + .../types/_cleverbrush_orm.HasVariants.html | 3 + .../_cleverbrush_orm.InferDatabaseRow.html | 5 + .../_cleverbrush_orm.InferDatabaseValue.html | 5 + .../types/_cleverbrush_orm.InsertType.html | 1 + .../types/_cleverbrush_orm.PrimaryKeyOf.html | 13 + .../_cleverbrush_orm.PrimaryKeyValueOf.html | 9 + .../types/_cleverbrush_orm.RelKeyTree.html | 4 + .../types/_cleverbrush_orm.ResolvedRel.html | 3 + .../_cleverbrush_orm.RowVersionStrategy.html | 7 + .../types/_cleverbrush_orm.SaveGraph.html | 10 + .../_cleverbrush_orm.SavingChangesHook.html | 3 + .../types/_cleverbrush_orm.SchemaKeys.html | 1 + .../types/_cleverbrush_orm.SchemaProps.html | 3 + .../_cleverbrush_orm.SelectProjection.html | 5 + .../_cleverbrush_orm.SelectSelector.html | 4 + .../_cleverbrush_orm.TrackedDbContext.html | 26 + .../_cleverbrush_orm.UnwrapNavSchema.html | 3 + .../types/_cleverbrush_orm.ValidatedSpec.html | 1 + .../types/_cleverbrush_orm.VariantBranch.html | 4 + ..._cleverbrush_orm.VariantInsertPayload.html | 8 + .../types/_cleverbrush_orm.VariantResult.html | 8 + .../_cleverbrush_orm.VariantStorageType.html | 2 + ..._cleverbrush_orm.VariantUpdatePayload.html | 4 + .../types/_cleverbrush_orm.WithIncluded.html | 2 + .../_cleverbrush_orm.WithJoinedMany.html | 2 + .../types/_cleverbrush_orm.WithJoinedOne.html | 2 + .../types/_cleverbrush_orm.WithRelation.html | 3 + .../_cleverbrush_orm.WithVariantIncluded.html | 4 + ...verbrush_otel.client.ClientMiddleware.html | 2 + .../_cleverbrush_otel.client.FetchLike.html | 4 + .../_cleverbrush_react-form.FieldProps.html | 16 + ...everbrush_react-form.FieldRenderProps.html | 41 + ..._cleverbrush_react-form.FieldRenderer.html | 2 + .../_cleverbrush_react-form.FieldState.html | 8 + ...verbrush_react-form.FormProviderProps.html | 3 + ...everbrush_react-form.FormSystemConfig.html | 3 + ...sh_react-form.FormSystemProviderProps.html | 8 + ...erbrush_react-form.SchemaFormInstance.html | 11 + ...cleverbrush_react-form.UseFieldResult.html | 13 + ...brush_react-form.UseSchemaFormOptions.html | 19 + .../_cleverbrush_scheduler.TaskSchedule.html | 13 + ...brush_schema-json.InferFromJsonSchema.html | 8 + ...leverbrush_schema-json.JsonSchemaNode.html | 4 + ...h_schema-json.JsonSchemaNodeToBuilder.html | 19 + ...-json.StandardJSONSchemaV1.InferInput.html | 2 + ...json.StandardJSONSchemaV1.InferOutput.html | 2 + ...hema-json.StandardJSONSchemaV1.Target.html | 4 + ...chema-json.StandardTypedV1.InferInput.html | 2 + ...hema-json.StandardTypedV1.InferOutput.html | 2 + ...brush_schema-json.ToJsonSchemaOptions.html | 26 + ...sh_schema.ArraySchemaValidationResult.html | 9 + .../types/_cleverbrush_schema.BRAND-1.html | 2 + .../types/_cleverbrush_schema.Brand.html | 6 + .../_cleverbrush_schema.CleanExtended.html | 5 + ...rbrush_schema.ElementValidationResult.html | 5 + .../_cleverbrush_schema.ExtendedAny.html | 2 + .../_cleverbrush_schema.ExtendedArray.html | 2 + .../_cleverbrush_schema.ExtendedBoolean.html | 2 + .../_cleverbrush_schema.ExtendedDate.html | 2 + .../_cleverbrush_schema.ExtendedFunc.html | 2 + .../_cleverbrush_schema.ExtendedNumber.html | 2 + .../_cleverbrush_schema.ExtendedObject.html | 2 + .../_cleverbrush_schema.ExtendedPromise.html | 2 + .../_cleverbrush_schema.ExtendedRecord.html | 2 + .../_cleverbrush_schema.ExtendedString.html | 2 + .../_cleverbrush_schema.ExtendedTuple.html | 2 + .../_cleverbrush_schema.ExtendedUnion.html | 2 + .../_cleverbrush_schema.ExtensionConfig.html | 19 + ...leverbrush_schema.ExtensionDescriptor.html | 19 + ...everbrush_schema.ExtraTypeBrandSymbol.html | 1 + .../_cleverbrush_schema.FixedMethods.html | 21 + ...erbrush_schema.HiddenExtensionMethods.html | 9 + .../types/_cleverbrush_schema.InferType.html | 6 + ...ma.IntersectionSchemaValidationResult.html | 1 + .../_cleverbrush_schema.MakeOptional.html | 3 + ...brush_schema.MethodLiteralBrandSymbol.html | 1 + ...erbrush_schema.NestedValidationResult.html | 11 + ...everbrush_schema.NumberOneOfExtension.html | 3 + ...h_schema.ObjectSchemaValidationResult.html | 15 + ...rbrush_schema.OptionValidationResults.html | 6 + ...erbrush_schema.ParseStringTemplateTag.html | 7 + ...cleverbrush_schema.PropertyDescriptor.html | 5 + ...rbrush_schema.PropertyDescriptorInner.html | 40 + ...erbrush_schema.PropertyDescriptorTree.html | 3 + ...verbrush_schema.PropertySetterOptions.html | 28 + ...h_schema.RecordSchemaValidationResult.html | 34 + ...erbrush_schema.SchemaPropertySelector.html | 5 + .../_cleverbrush_schema.SchemaTypeBrand.html | 1 + ...sh_schema.StandardSchemaV1.InferInput.html | 2 + ...h_schema.StandardSchemaV1.InferOutput.html | 2 + ...rbrush_schema.StandardSchemaV1.Result.html | 2 + ...ush_schema.StandardTypedV1.InferInput.html | 2 + ...sh_schema.StandardTypedV1.InferOutput.html | 2 + ...everbrush_schema.StringOneOfExtension.html | 3 + ..._schema.TupleElementValidationResults.html | 6 + ...sh_schema.TupleSchemaValidationResult.html | 9 + ...sh_schema.UnionSchemaValidationResult.html | 11 + .../_cleverbrush_schema.ValidationError.html | 14 + ...schema.ValidationErrorMessageProvider.html | 5 + .../_cleverbrush_schema.ValidationResult.html | 7 + ...brush_server-openapi.AsyncApiDocument.html | 3 + ...rbrush_server-openapi.OpenApiDocument.html | 3 + .../_cleverbrush_server..ActionContext.html | 4 + ...erbrush_server..AllowedResponseReturn.html | 10 + ...h_server..EndpointMetadataDescriptors.html | 7 + .../types/_cleverbrush_server..Handler.html | 1 + .../_cleverbrush_server..HandlerEntry.html | 4 + .../_cleverbrush_server..HandlerMap.html | 9 + .../_cleverbrush_server..Middleware.html | 7 + .../_cleverbrush_server..PropertyRefTree.html | 7 + ..._cleverbrush_server..RawResultHandler.html | 4 + .../_cleverbrush_server..ResponsesOf.html | 3 + ...erbrush_server..ScopedEndpointFactory.html | 8 + ...everbrush_server..SubscriptionContext.html | 5 + ...everbrush_server..SubscriptionHandler.html | 7 + ...rush_server..SubscriptionHandlerEntry.html | 4 + ...everbrush_server.contract.ApiContract.html | 7 + ..._cleverbrush_server.contract.ApiGroup.html | 3 + ...brush_server.contract.MergedContracts.html | 9 + ..._cleverbrush_client..PER_CALL_OPTIONS.html | 6 + ...imistic-update.OPTIMISTIC_MUTATION_ID.html | 1 + ...erbrush_client.react.QUERY_KEY_PREFIX.html | 4 + .../_cleverbrush_deep.deepExtend.html | 11 + ...erbrush_knex-schema..EXTRA_TYPE_BRAND.html | 18 + .../_cleverbrush_knex-schema..MAPPERS.html | 9 + ...ush_knex-schema..METHOD_LITERAL_BRAND.html | 9 + ...extension.COMPOSITE_PRIMARY_KEY_BRAND.html | 3 + ...hema.extension.POLYMORPHIC_TYPE_BRAND.html | 5 + ...ex-schema.extension.PRIMARY_KEY_BRAND.html | 4 + ...cleverbrush_knex-schema.extension.any.html | 1 + ...everbrush_knex-schema.extension.array.html | 1 + ...erbrush_knex-schema.extension.boolean.html | 1 + ...leverbrush_knex-schema.extension.date.html | 1 + ...ush_knex-schema.extension.dbExtension.html | 8 + ...sh_knex-schema.extension.ddlExtension.html | 10 + ...leverbrush_knex-schema.extension.func.html | 1 + ...verbrush_knex-schema.extension.number.html | 1 + ...verbrush_knex-schema.extension.object.html | 1 + ...verbrush_knex-schema.extension.string.html | 1 + ...everbrush_knex-schema.extension.union.html | 1 + .../variables/_cleverbrush_log..ILogger.html | 4 + .../_cleverbrush_log..LogContext.html | 22 + .../variables/_cleverbrush_log..LogLevel.html | 7 + .../variables/_cleverbrush_log..SelfLog.html | 14 + ...brush_orm.COMPOSITE_PRIMARY_KEY_BRAND.html | 3 + .../_cleverbrush_orm.EXTRA_TYPE_BRAND.html | 18 + .../variables/_cleverbrush_orm.MAPPERS.html | 9 + ..._cleverbrush_orm.METHOD_LITERAL_BRAND.html | 9 + ...leverbrush_orm.POLYMORPHIC_TYPE_BRAND.html | 5 + .../_cleverbrush_orm.PRIMARY_KEY_BRAND.html | 4 + .../variables/_cleverbrush_orm.any.html | 1 + .../variables/_cleverbrush_orm.array.html | 1 + .../variables/_cleverbrush_orm.boolean.html | 1 + .../variables/_cleverbrush_orm.date.html | 1 + .../_cleverbrush_orm.dbExtension.html | 8 + .../_cleverbrush_orm.ddlExtension.html | 10 + .../variables/_cleverbrush_orm.func.html | 1 + .../variables/_cleverbrush_orm.number.html | 1 + .../variables/_cleverbrush_orm.object.html | 1 + .../variables/_cleverbrush_orm.string.html | 1 + .../variables/_cleverbrush_orm.union.html | 1 + .../variables/_cleverbrush_otel..IMeter.html | 4 + .../variables/_cleverbrush_otel..ITracer.html | 5 + ..._cleverbrush_otel..OTEL_SPAN_ITEM_KEY.html | 6 + .../_cleverbrush_scheduler.Schemas.html | 3 + .../_cleverbrush_schema.EXTRA_TYPE_BRAND.html | 18 + ...everbrush_schema.METHOD_LITERAL_BRAND.html | 9 + ...verbrush_schema.SYMBOL_HAS_PROPERTIES.html | 9 + ...ema.SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR.html | 5 + .../variables/_cleverbrush_schema.any.html | 1 + .../variables/_cleverbrush_schema.array.html | 1 + .../_cleverbrush_schema.arrayExtensions.html | 7 + .../_cleverbrush_schema.boolean.html | 1 + .../variables/_cleverbrush_schema.date.html | 1 + .../variables/_cleverbrush_schema.func.html | 1 + .../variables/_cleverbrush_schema.number.html | 1 + .../_cleverbrush_schema.numberExtensions.html | 7 + .../variables/_cleverbrush_schema.object.html | 1 + .../_cleverbrush_schema.promise.html | 1 + .../variables/_cleverbrush_schema.record.html | 1 + .../variables/_cleverbrush_schema.string.html | 1 + .../_cleverbrush_schema.stringExtensions.html | 7 + .../variables/_cleverbrush_schema.tuple.html | 1 + .../variables/_cleverbrush_schema.union.html | 1 + ...erbrush_server..DEFAULT_MAX_BODY_SIZE.html | 2 + .../_cleverbrush_server..IRequestContext.html | 3 + .../_cleverbrush_server..endpoint.html | 7 + ...ver..formUrlEncodedContentTypeHandler.html | 4 + ...rbrush_server..jsonContentTypeHandler.html | 2 + websites/docs/public/api-docs/versions.json | 1 + 792 files changed, 22687 insertions(+) create mode 100644 websites/docs/public/api-docs/v4.4.3/.nojekyll create mode 100644 websites/docs/public/api-docs/v4.4.3/assets/hierarchy.js create mode 100644 websites/docs/public/api-docs/v4.4.3/assets/highlight.css create mode 100644 websites/docs/public/api-docs/v4.4.3/assets/icons.js create mode 100644 websites/docs/public/api-docs/v4.4.3/assets/icons.svg create mode 100644 websites/docs/public/api-docs/v4.4.3/assets/main.js create mode 100644 websites/docs/public/api-docs/v4.4.3/assets/navigation.js create mode 100644 websites/docs/public/api-docs/v4.4.3/assets/search.js create mode 100644 websites/docs/public/api-docs/v4.4.3/assets/style.css create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_async.Collector.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_async.TimeoutError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_auth.AuthorizationService.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_auth.PolicyBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_auth.Principal.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_client..ApiError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_client..NetworkError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_client..OfflineError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_client..TimeoutError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_client..WebError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_di.ScopedServiceProvider.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_di.ServiceCollection.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_di.ServiceProvider.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_di.ServiceScope.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_env.EnvValidationError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_knex-clickhouse.ClickhouseKnexClient.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_knex-schema..Entity.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_knex-schema..SchemaQueryBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_log..BatchingSink.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_log..Logger.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_mapper.Mapper.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_mapper.MapperConfigurationError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_mapper.MappingRegistry.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_mapper.PropertyMappingBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_orm.ConcurrencyError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_orm.Entity.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_orm.EntityNotFoundError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_orm.InvariantViolationError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_orm.PendingChangesError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_orm.SchemaQueryBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_scheduler.JobScheduler.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_scheduler.ScheduleCalculator.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.AnySchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.ArraySchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.BooleanSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.DateSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.ExternSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.FunctionSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.GenericSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.IntersectionSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.LazySchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.NullSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.NumberSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.ObjectSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.ParseStringSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.PromiseSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.RecordSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.SchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.SchemaValidationError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.StringSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.TupleSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_schema.UnionSchemaBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server-openapi.SchemaRegistry.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..ActionResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..BadRequestError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..ConflictError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..ContentNegotiator.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..ContentResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..EndpointBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..FileResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..ForbiddenError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..HttpError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..JsonResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..NoContentResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..NotFoundError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..RawResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..RedirectResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..RequestContext.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..Server.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..ServerBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..StatusCodeResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..StreamResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..SubscriptionBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/classes/_cleverbrush_server..UnauthorizedError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/enums/_cleverbrush_di.ServiceLifetime.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_async.debounce.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_async.dedupe.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_async.retry.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_async.throttle.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_async.withTimeout.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_auth.authorizationCodeScheme.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_auth.clientCredentialsScheme.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_auth.cookieScheme.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_auth.defineRoles.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_auth.jwtScheme.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_auth.oidcScheme.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_auth.parseCookies.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_auth.requireRole.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_auth.serializeCookie.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_auth.signJwt.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client..composeMiddleware.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client..createClient.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client..getPerCallOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client..isApiError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client..isNetworkError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client..isOfflineError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client..isTimeoutError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client..isWebError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.batching.batching.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.cache.cacheTags.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.cache.computeCacheTagKey.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.cache.createCacheTagRoot.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.cache.externalCacheTags.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.cache.isMutatingMethod.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.cache.throttlingCache.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.dedupe.dedupe.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.idempotency.idempotency.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.offline-queue.offlineQueue.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.optimistic-update.optimisticUpdate.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.react.buildGroupQueryKey.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.react.buildQueryKey.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.react.createClient.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.react.useOptimisticMutation.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.react.useSubscription.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.retry.retry.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_client.timeout.timeout.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_deep.deepEqual.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_deep.deepFlatten.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_di.isAsyncDisposable.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_di.isDisposable.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_env.env.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_env.envBoolean.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_env.parseEnv.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_env.parseEnvFlat.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_env.splitBy.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-clickhouse.getClickhouseConnection.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..applyDiff.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..buildColumnMap.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..clearRow.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..createQuery.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..defineEntity.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..diffSchema.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..entitiesToSnapshot.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..entitySchemaToTableState.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..generateCreatePolymorphicTables.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..generateCreateTable.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..generateCreateTableSource.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..generateMigration.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..generateMigrationsForContext.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..getPrimaryKeyColumns.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..getRowVersionColumn.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..introspectDatabase.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..isDiffEmpty.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..loadSnapshot.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..mapObject.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..mapValue.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..query.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..rawQuery.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..resolveColumnRef.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..resolvePropertyKey.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..tableExistsInDb.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..validateEntitiesAgainstDatabase.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema..writeSnapshot.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema.extension.applyVariantsToSchema.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema.extension.getColumnName.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema.extension.getPolymorphicVariantSchemas.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema.extension.getProjections.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema.extension.getTableName.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_knex-schema.extension.getVariants.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..applicationEnricher.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..callerEnricher.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..captureProperties.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..computeEventId.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..configureLogging.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..consoleSink.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..correlationIdEnricher.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..correlationIdMiddleware.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..createLogEvent.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..createLogger.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..createSink.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..environmentEnricher.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..extractCorrelationId.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..fileSink.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..formatClef.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..formatClefBatch.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..generateCorrelationId.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..hostnameEnricher.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..levelToShortString.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..levelToString.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..parseLogLevel.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..parseTemplate.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..processIdEnricher.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..renderTemplate.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..requestLoggingMiddleware.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..safeSerialize.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..samplingFilter.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..seqSink.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log..useLogging.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log.clickhouse.clickHouseSink.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_log.clickhouse.createLogsTable.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_mapper.mapper.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm-cli.defineConfig.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.applyDiff.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.buildColumnMap.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.clearRow.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.createDb.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.createQuery.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.defineEntity.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.diffSchema.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.entitiesToSnapshot.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.entitySchemaToTableState.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.generateCreatePolymorphicTables.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.generateCreateTable.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.generateCreateTableSource.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.generateMigration.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.generateMigrationsForContext.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.getColumnName.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.getPolymorphicVariantSchemas.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.getPrimaryKeyColumns.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.getProjections.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.getRowVersionColumn.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.getTableName.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.getVariants.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.introspectDatabase.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.isDiffEmpty.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.loadSnapshot.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.mapObject.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.mapValue.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.query.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.rawQuery.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.resolveColumnRef.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.resolvePropertyKey.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.tableExistsInDb.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.validateEntitiesAgainstDatabase.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_orm.writeSnapshot.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_otel..configureOtel.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_otel..instrumentKnex.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_otel..otelLogSink.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_otel..setupOtel.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_otel..traceEnricher.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_otel..tracingMiddleware.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_otel..withSpan.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_otel.client.clientTracingMiddleware.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_otel.instrumentations.outboundHttpInstrumentations.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_otel.instrumentations.runtimeMetrics.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_react-form.Field.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_react-form.FormProvider.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_react-form.FormSystemProvider.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_react-form.useField.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_react-form.useFormSystem.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_react-form.useSchemaForm.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_schema-json.fromJsonSchema.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_schema-json.toJsonSchema.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_schema-json.withStandardJsonSchema.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_schema.defineExtension.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_schema.enumOf.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_schema.extern.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_schema.generic.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_schema.intersection.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_schema.lazy.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_schema.nul.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_schema.parseString.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_schema.withExtensions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server-openapi.convertSchema.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server-openapi.createOpenApiEndpoint.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server-openapi.generateAsyncApiSpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server-openapi.generateOpenApiSpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server-openapi.mapOperationSecurity.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server-openapi.mapSecuritySchemes.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server-openapi.resolvePath.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server-openapi.serveAsyncApi.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server-openapi.serveOpenApi.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server-openapi.walkSchemas.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server-openapi.writeOpenApiSpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server..cacheResponse.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server..computeCacheKey.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server..createCacheTagTree.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server..createEndpoints.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server..createProblemDetails.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server..createServer.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server..createValidationProblemDetails.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server..defineWebhook.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server..idempotency.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server..mapHandlers.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server..route.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server..serializeTag.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server..tracked.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server.contract.defineApi.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server.contract.mergeContracts.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server.contract.omitGroups.html create mode 100644 websites/docs/public/api-docs/v4.4.3/functions/_cleverbrush_server.contract.pickGroups.html create mode 100644 websites/docs/public/api-docs/v4.4.3/hierarchy.html create mode 100644 websites/docs/public/api-docs/v4.4.3/index.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_auth.AuthenticationContext.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_auth.AuthenticationScheme.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_auth.AuthorizationCodeSchemeOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_auth.AuthorizationPolicy.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_auth.ClientCredentialsSchemeOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_auth.CookieOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_auth.CookieSchemeOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_auth.JwtPayload.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_auth.JwtSchemeOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_auth.OAuthFlow.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_auth.OAuthFlows.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_auth.OidcSchemeOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client..ClientHooks.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client..ClientOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client..EndpointMeta.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client..FilePart.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client..PerCallOverrides.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client..Subscription.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.batching.BatchingOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.cache.CacheOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.cache.CacheTagMiddlewareOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.cache.CacheTagRoot.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.cache.ExternalCacheTagsOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.cache.SerializedCacheTag.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.dedupe.DedupeOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.idempotency.IdempotencyOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.offline-queue.OfflineQueueOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.offline-queue.OfflineQueueStore.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.offline-queue.QueuedRequest.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.optimistic-update.OptimisticFailure.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.optimistic-update.OptimisticUpdateOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.optimistic-update.OptimisticUpdateStore.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.react.OptimisticMutationConfig.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.react.UseSubscriptionOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.react.UseSubscriptionState.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.retry.RetryOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_client.timeout.TimeoutOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_di.IServiceProvider.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_di.ServiceDescriptor.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_di.ServiceProviderOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_di.ServiceRegistrationOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_env.EnvBooleanOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_env.InvalidEnvVar.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_env.MissingEnvVar.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-clickhouse.ClickHouseClient.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..AddColumnDiff.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..AddForeignKeyDiff.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..AddIndexDiff.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..AlterColumnDiff.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..BoundQuery.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..CursorPaginationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..DatabaseCheckInfo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..DatabaseColumnInfo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..DatabaseForeignKeyInfo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..DatabaseIndexInfo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..DatabaseTableState.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..EntitySchemaValidationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..JoinManySpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..JoinOneSpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..MigrationDiff.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..PaginationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..PrimaryKeyColumns.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..RelationInfo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..RelationSpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..ResolvedVariantConfig.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..ResolvedVariantSpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..RowVersionColumn.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..SchemaSnapshot.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..ValidatedJoinManySpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema..ValidatedJoinOneSpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_knex-schema.extension.VariantInputForResolver.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log..BatchingSinkOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log..ConsoleSinkOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log..CorrelationIdMiddlewareOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log..CreateSinkOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log..FileSinkOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log..LogContextStore.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log..LogEvent.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log..LogSink.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log..LoggerConfig.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log..RequestLoggingOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log..RotationOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log..SeqSinkOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log..SerializationOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log..TypedTemplate.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log.clickhouse.ClickHouseColumnMapping.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log.clickhouse.ClickHouseSinkOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_log.clickhouse.CreateLogsTableOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm-cli.MigrationsConfig.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm-cli.OrmCliConfig.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.AddColumnDiff.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.AddForeignKeyDiff.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.AddIndexDiff.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.AlterColumnDiff.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.BoundQuery.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.CursorPaginationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.DatabaseCheckInfo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.DatabaseColumnInfo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.DatabaseForeignKeyInfo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.DatabaseIndexInfo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.DatabaseTableState.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.DbSet.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.EntityEntry.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.EntityQuery.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.EntitySchemaValidationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.JoinManySpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.JoinOneSpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.MigrationDiff.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.PaginationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.PrimaryKeyColumns.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.RelationInfo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.RelationSpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.ResolvedVariantConfig.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.ResolvedVariantSpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.RowVersionColumn.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.SchemaSnapshot.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.ValidatedJoinManySpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.ValidatedJoinOneSpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_orm.VariantDbSet.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_otel..ConfigureOtelOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_otel..InstrumentKnexOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_otel..OtelConfig.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_otel..OtelHandle.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_otel..OtelLogSinkOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_otel..SpanHandle.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_otel..TracingMiddlewareOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_otel..WithSpanOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_otel.client.ClientTracingEndpointMeta.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_otel.client.ClientTracingInfo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_otel.client.ClientTracingMiddlewareOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema-json.StandardJSONSchemaV1.Converter.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema-json.StandardJSONSchemaV1.Options.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema-json.StandardJSONSchemaV1.Props.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema-json.StandardJSONSchemaV1.Types.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema-json.StandardJSONSchemaV1.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema-json.StandardTypedV1.Props.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema-json.StandardTypedV1.Types.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema-json.StandardTypedV1.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema.ArrayBuiltinExtensions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema.NumberBuiltinExtensions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema.StandardSchemaV1.FailureResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema.StandardSchemaV1.Issue.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema.StandardSchemaV1.Options.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema.StandardSchemaV1.PathSegment.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema.StandardSchemaV1.Props.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema.StandardSchemaV1.SuccessResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema.StandardSchemaV1.Types.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema.StandardSchemaV1.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema.StandardTypedV1.Props.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema.StandardTypedV1.Types.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema.StandardTypedV1.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_schema.StringBuiltinExtensions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.AsyncApiInfo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.AsyncApiOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.AsyncApiServerEntry.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.AsyncApiServer_ServerLike.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.OpenApiEndpointOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.OpenApiInfo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.OpenApiOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.OpenApiSecurityScheme.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.OpenApiServer.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.OpenApiServer_ServerLike.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.OpenApiTag.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.PathParameterInfo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.ResolvedPath.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.ServeAsyncApiOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server-openapi.ServeOpenApiOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..AuthenticationConfig.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..AuthorizationConfig.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..CacheTagDefinition.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..CacheTagPropertyAccessor.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..CallbackDefinition.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..ContentTypeHandler.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..EndpointMetadata.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..EndpointRegistration.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..FilePart.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..HandlerMapping.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..LinkDefinition.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..ProblemDetails.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..RejectedFile.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..ServerBatchingOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..ServerCacheOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..ServerIdempotencyOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..ServerOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..SubscriptionMetadata.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..SubscriptionRegistration.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..TrackedEvent.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..UploadOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..ValidationErrorItem.html create mode 100644 websites/docs/public/api-docs/v4.4.3/interfaces/_cleverbrush_server..WebhookDefinition.html create mode 100644 websites/docs/public/api-docs/v4.4.3/media/LICENSE create mode 100644 websites/docs/public/api-docs/v4.4.3/media/ci.yml create mode 100644 websites/docs/public/api-docs/v4.4.3/modules.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_async.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_auth.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_client..html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_client.batching.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_client.cache.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_client.dedupe.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_client.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_client.idempotency.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_client.offline-queue.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_client.optimistic-update.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_client.react.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_client.retry.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_client.timeout.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_deep.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_di.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_env.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_knex-clickhouse.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_knex-schema..html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_knex-schema.extension.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_knex-schema.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_log..html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_log.clickhouse.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_log.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_mapper.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_orm-cli.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_orm.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_otel..html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_otel.client.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_otel.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_otel.instrumentations.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_react-form.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_scheduler.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_schema-json.StandardJSONSchemaV1.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_schema-json.StandardTypedV1.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_schema-json.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_schema.StandardSchemaV1.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_schema.StandardTypedV1.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_schema.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_server-openapi.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_server..html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_server.contract.html create mode 100644 websites/docs/public/api-docs/v4.4.3/modules/_cleverbrush_server.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_auth.AuthenticationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_auth.AuthorizationRequirement.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_auth.AuthorizationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client..ApiContract.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client..EndpointCall.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client..EndpointCallArgs.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client..EndpointResponse.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client..FetchLike.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client..Middleware.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client..SubscriptionCall.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client..SubscriptionCallArgs.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client..SubscriptionIncoming.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client..SubscriptionOutgoing.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client..TypedClient.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client.react.EndpointQueryHooks.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client.react.InfiniteQueryHookOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client.react.MutationHookOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client.react.QueryHookOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client.react.SuspenseQueryHookOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client.react.TypedQueryClient.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client.react.TypedQueryGroup.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client.react.UnifiedClient.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client.react.UnifiedEndpointCall.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_client.react.UnifiedGroup.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_deep.Merge.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_deep.MergeTwo.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_di.ServiceFactory.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_env.EnvConfig.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_env.EnvConfigNode.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_env.EnvField.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_env.InferEnvConfig.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..ColumnRef.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..EntityPropSelector.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..EntityRelationKeys.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..EntityRelations.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..EntitySchema.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..EntitySchemaValidationIssue.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..EntityVariantUnion.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..InferDatabaseRow.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..InferDatabaseValue.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..InsertType.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..PrimaryKeyOf.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..PrimaryKeyValueOf.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..RowVersionStrategy.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..SchemaKeys.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..SchemaProps.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..SelectProjection.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..SelectSelector.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..UnwrapNavSchema.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..ValidatedSpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..VariantBranch.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..VariantStorageType.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..WithJoinedMany.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..WithJoinedOne.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_knex-schema..WithRelation.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_log..Enricher.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_log..LogFilter.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_log..LogLevel.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_log..LogLevelName.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_log..SamplingRates.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_mapper.SchemaToSchemaMapperResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.ColumnRef.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.DbContext.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.EntityMap.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.EntityPropSelector.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.EntityRelationKeys.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.EntityRelations.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.EntityResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.EntityResultByVariant.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.EntitySchema.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.EntitySchemaValidationIssue.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.EntityVariantUnion.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.EntryState.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.ExtractBranch.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.HasVariants.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.InferDatabaseRow.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.InferDatabaseValue.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.InsertType.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.PrimaryKeyOf.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.PrimaryKeyValueOf.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.RelKeyTree.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.ResolvedRel.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.RowVersionStrategy.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.SaveGraph.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.SavingChangesHook.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.SchemaKeys.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.SchemaProps.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.SelectProjection.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.SelectSelector.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.TrackedDbContext.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.UnwrapNavSchema.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.ValidatedSpec.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.VariantBranch.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.VariantInsertPayload.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.VariantResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.VariantStorageType.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.VariantUpdatePayload.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.WithIncluded.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.WithJoinedMany.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.WithJoinedOne.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.WithRelation.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_orm.WithVariantIncluded.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_otel.client.ClientMiddleware.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_otel.client.FetchLike.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_react-form.FieldProps.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_react-form.FieldRenderProps.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_react-form.FieldRenderer.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_react-form.FieldState.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_react-form.FormProviderProps.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_react-form.FormSystemConfig.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_react-form.FormSystemProviderProps.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_react-form.SchemaFormInstance.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_react-form.UseFieldResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_react-form.UseSchemaFormOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_scheduler.TaskSchedule.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema-json.InferFromJsonSchema.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema-json.JsonSchemaNode.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema-json.JsonSchemaNodeToBuilder.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema-json.StandardJSONSchemaV1.InferInput.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema-json.StandardJSONSchemaV1.InferOutput.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema-json.StandardJSONSchemaV1.Target.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema-json.StandardTypedV1.InferInput.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema-json.StandardTypedV1.InferOutput.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema-json.ToJsonSchemaOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ArraySchemaValidationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.BRAND-1.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.Brand.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.CleanExtended.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ElementValidationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtendedAny.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtendedArray.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtendedBoolean.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtendedDate.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtendedFunc.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtendedNumber.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtendedObject.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtendedPromise.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtendedRecord.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtendedString.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtendedTuple.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtendedUnion.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtensionConfig.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtensionDescriptor.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ExtraTypeBrandSymbol.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.FixedMethods.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.HiddenExtensionMethods.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.InferType.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.IntersectionSchemaValidationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.MakeOptional.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.MethodLiteralBrandSymbol.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.NestedValidationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.NumberOneOfExtension.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ObjectSchemaValidationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.OptionValidationResults.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ParseStringTemplateTag.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.PropertyDescriptor.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.PropertyDescriptorInner.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.PropertyDescriptorTree.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.PropertySetterOptions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.RecordSchemaValidationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.SchemaPropertySelector.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.SchemaTypeBrand.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.StandardSchemaV1.InferInput.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.StandardSchemaV1.InferOutput.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.StandardSchemaV1.Result.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.StandardTypedV1.InferInput.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.StandardTypedV1.InferOutput.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.StringOneOfExtension.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.TupleElementValidationResults.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.TupleSchemaValidationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.UnionSchemaValidationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ValidationError.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ValidationErrorMessageProvider.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_schema.ValidationResult.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server-openapi.AsyncApiDocument.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server-openapi.OpenApiDocument.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server..ActionContext.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server..AllowedResponseReturn.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server..EndpointMetadataDescriptors.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server..Handler.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server..HandlerEntry.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server..HandlerMap.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server..Middleware.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server..PropertyRefTree.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server..RawResultHandler.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server..ResponsesOf.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server..ScopedEndpointFactory.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server..SubscriptionContext.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server..SubscriptionHandler.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server..SubscriptionHandlerEntry.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server.contract.ApiContract.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server.contract.ApiGroup.html create mode 100644 websites/docs/public/api-docs/v4.4.3/types/_cleverbrush_server.contract.MergedContracts.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_client..PER_CALL_OPTIONS.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_client.optimistic-update.OPTIMISTIC_MUTATION_ID.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_client.react.QUERY_KEY_PREFIX.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_deep.deepExtend.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema..EXTRA_TYPE_BRAND.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema..MAPPERS.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema..METHOD_LITERAL_BRAND.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema.extension.COMPOSITE_PRIMARY_KEY_BRAND.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema.extension.POLYMORPHIC_TYPE_BRAND.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema.extension.PRIMARY_KEY_BRAND.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema.extension.any.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema.extension.array.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema.extension.boolean.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema.extension.date.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema.extension.dbExtension.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema.extension.ddlExtension.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema.extension.func.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema.extension.number.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema.extension.object.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema.extension.string.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_knex-schema.extension.union.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_log..ILogger.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_log..LogContext.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_log..LogLevel.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_log..SelfLog.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.COMPOSITE_PRIMARY_KEY_BRAND.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.EXTRA_TYPE_BRAND.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.MAPPERS.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.METHOD_LITERAL_BRAND.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.POLYMORPHIC_TYPE_BRAND.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.PRIMARY_KEY_BRAND.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.any.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.array.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.boolean.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.date.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.dbExtension.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.ddlExtension.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.func.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.number.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.object.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.string.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_orm.union.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_otel..IMeter.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_otel..ITracer.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_otel..OTEL_SPAN_ITEM_KEY.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_scheduler.Schemas.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.EXTRA_TYPE_BRAND.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.METHOD_LITERAL_BRAND.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.SYMBOL_HAS_PROPERTIES.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.any.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.array.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.arrayExtensions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.boolean.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.date.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.func.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.number.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.numberExtensions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.object.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.promise.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.record.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.string.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.stringExtensions.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.tuple.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_schema.union.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_server..DEFAULT_MAX_BODY_SIZE.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_server..IRequestContext.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_server..endpoint.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_server..formUrlEncodedContentTypeHandler.html create mode 100644 websites/docs/public/api-docs/v4.4.3/variables/_cleverbrush_server..jsonContentTypeHandler.html diff --git a/websites/docs/public/api-docs/index.html b/websites/docs/public/api-docs/index.html index bf5b3f38..0700e1c6 100644 --- a/websites/docs/public/api-docs/index.html +++ b/websites/docs/public/api-docs/index.html @@ -40,6 +40,8 @@

    Previous Versions

    + + @@ -65,6 +67,7 @@

    Previous Versions