/** * Standard Schema v1 integration. * * This file is the entry point for **schema validation**. Any * validator that implements the [Standard Schema v1 * protocol](https://standardschema.dev) — Zod, Valibot, ArkType, Effect * Schema, etc. — can be attached to a `Collection` or `defineNoydbStore` * and will: * * 1. Validate the record BEFORE encryption on `put()` — bad data is * rejected at the store boundary with a rich issue list. * 2. Validate the record AFTER decryption on `get()`/`list()`/`query()` * — stored data that has drifted from the current schema throws * loudly instead of silently propagating garbage to the UI. * * ## Why vendor the types? * * Standard Schema is a protocol, not a library. The spec is <200 lines of * TypeScript and has no runtime. There's an official `@standard-schema/spec` * types package on npm, but pulling it in would add a dependency edge * purely for type definitions. Vendoring the minimal surface keeps * `@noy-db/core` at **zero runtime dependencies** and gives us freedom to * evolve the helpers without a version-lock on the spec package. * * If the spec changes in a breaking way (unlikely — it's frozen at v1), * we update this file and bump our minor. * * ## Why not just run `schema.parse(value)` directly? * * Because then we'd be locked to whichever validator happens to have * `.parse`. Standard Schema's `'~standard'.validate` contract is the same * across every implementation and includes a structured issues list, * which is much more useful than a thrown error for programmatic error * handling (e.g., rendering field-level messages in a Vue component). */ /** * The Standard Schema v1 protocol. A schema is any object that exposes a * `'~standard'` property with `version: 1` and a `validate` function. * * The type parameters are: * - `Input` — the type accepted by `validate` (what the user passes in) * - `Output` — the type produced by `validate` (what we store/return, * may differ from Input if the schema transforms or coerces) * * In most cases `Input === Output`, but validators that transform * (Zod's `.transform`, Valibot's `transform`, etc.) can narrow or widen. * * We intentionally keep the `types` field `readonly` and optional — the * spec marks it as optional because it's only used for inference, and * not every implementation bothers populating it at runtime. */ export interface StandardSchemaV1 { readonly '~standard': { readonly version: 1; readonly vendor: string; readonly validate: (value: unknown) => StandardSchemaV1SyncResult | Promise>; readonly types?: { readonly input: Input; readonly output: Output; } | undefined; }; } /** * The result of a single call to `schema['~standard'].validate`. Either * `{ value }` on success or `{ issues }` on failure — never both. * * The spec allows `issues` to be undefined on success (and some * validators leave it that way), so consumers should discriminate on * `issues?.length` rather than on truthiness of `value`. */ export type StandardSchemaV1SyncResult = { readonly value: Output; readonly issues?: undefined; } | { readonly value?: undefined; readonly issues: readonly StandardSchemaV1Issue[]; }; /** * A single validation issue. The `message` is always present; the `path` * is optional and points at the offending field when the schema tracks * it (virtually every validator does for object types). * * The path is deliberately permissive — both a plain `PropertyKey` and a * `{ key }` wrapper are allowed so validators that wrap path segments in * objects (Zod does this in some modes) don't need special handling. */ export interface StandardSchemaV1Issue { readonly message: string; readonly path?: ReadonlyArray | undefined; } /** * Infer the output type of a Standard Schema. Consumers use this to * pull the type out of a schema instance when they want to declare a * Collection or defineNoydbStore with `T` derived from the schema. * * Example: * ```ts * const InvoiceSchema = z.object({ id: z.string(), amount: z.number() }) * type Invoice = InferOutput * ``` */ export type InferOutput = T extends StandardSchemaV1 ? O : never; /** * Validate an input value against a schema. Throws * `SchemaValidationError` if the schema rejects, with the rich issue * list attached. Otherwise returns the (possibly transformed) output * value. * * The `context` string is included in the thrown error's message so the * caller knows where the failure happened (e.g. `"put(inv-001)"`) without * every caller having to wrap the throw in a try/catch. * * This function is ALWAYS async because some validators (notably Effect * Schema and Zod's `.refine` with async predicates) can return a * Promise. We `await` the result unconditionally to normalize the * contract — the extra microtask is free compared to the cost of an * encrypt/decrypt round-trip. */ export declare function validateSchemaInput(schema: StandardSchemaV1, value: unknown, context: string): Promise; /** * Validate an already-stored value coming OUT of the collection. This * is a distinct helper from `validateSchemaInput` because the error * semantics differ: an output-validation failure means the data in * storage has drifted from the current schema (an unexpected state), * whereas an input-validation failure means the user passed bad data * (an expected state for a UI that isn't guarding its inputs). * * We still throw — silently returning bad data would be worse — but * the error carries `direction: 'output'` so upstream code (and a * potential migrate hook) can distinguish the two cases. */ export declare function validateSchemaOutput(schema: StandardSchemaV1, value: unknown, context: string): Promise;