first commit
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# schemas
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`schemas.ts` provides Zod runtime validators for every public boundary type
|
||||
declared in `types.ts`. The validators are the contract enforced at API
|
||||
entry and exit: a `VaultItem` reaching `mergeVaultItems`, a `SyncOperation`
|
||||
queued by `SyncQueue.enqueue`, an `EncryptedSyncPayload` decoded by
|
||||
`decryptSyncPayload` — all are first parsed by the appropriate schema so a
|
||||
malformed input fails fast with a structured error rather than producing a
|
||||
hard-to-debug shape mismatch later in the pipeline.
|
||||
|
||||
## Features
|
||||
|
||||
- `VectorClockSchema` — `Record<string, non-negative-int>`
|
||||
- `VaultHistoryEntrySchema` — `{ historyId; timestamp; note }`, strict
|
||||
- `VaultItemSchema` — full canonical vault-item shape, strict
|
||||
- `MergeStrategySchema` — `"lww" | "append" | "atomic"`
|
||||
- `SyncOperationKindSchema` — `"create" | "update" | "delete"`
|
||||
- `SyncOperationSchema` — durable-queue entry shape with partial `fields`
|
||||
- `VaultSnapshotSchema` — `{ items; clock; snapshotAt }`, strict
|
||||
- `EncryptedSyncPayloadSchema` — version + 24-byte nonce + ciphertext
|
||||
- All schemas are `.strict()` — extra keys raise an error
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { VaultItemSchema } from "@suaveplan/vault-sync/schemas/schemas";
|
||||
|
||||
const parsed = VaultItemSchema.safeParse({
|
||||
id: "i1",
|
||||
title: "Email",
|
||||
password: "hunter2",
|
||||
folderId: null,
|
||||
history: [],
|
||||
nodeId: "n1",
|
||||
updatedAt: 0,
|
||||
clock: { n1: 1 },
|
||||
});
|
||||
if (!parsed.success) throw new Error("invalid item");
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
EncryptedSyncPayloadSchema,
|
||||
SyncOperationSchema,
|
||||
VaultSnapshotSchema,
|
||||
} from "@suaveplan/vault-sync/schemas/schemas";
|
||||
import { VaultSyncError } from "@suaveplan/vault-sync";
|
||||
import { VAULT_SYNC_CODES } from "@suaveplan/vault-sync/errors";
|
||||
|
||||
function asPayload(input: unknown): import("@suaveplan/vault-sync/types").EncryptedSyncPayload {
|
||||
const parsed = EncryptedSyncPayloadSchema.safeParse(input);
|
||||
if (!parsed.success) {
|
||||
throw new VaultSyncError(
|
||||
VAULT_SYNC_CODES["SYNC.PAYLOAD_INVALID_VERSION"],
|
||||
"error.vault-sync.sync.payload-invalid-version",
|
||||
{ context: { issues: parsed.error.issues.length } }
|
||||
);
|
||||
}
|
||||
return parsed.data;
|
||||
}
|
||||
|
||||
declare const wireSnapshot: unknown;
|
||||
declare const wireOperation: unknown;
|
||||
const snapshot = VaultSnapshotSchema.parse(wireSnapshot);
|
||||
const operation = SyncOperationSchema.parse(wireOperation);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `VectorClockSchema: ZodSchema<Record<string, number>>` — keys must be
|
||||
non-empty strings; values must be non-negative integers.
|
||||
- `VaultHistoryEntrySchema: ZodSchema<VaultHistoryEntry>` — strict.
|
||||
- `VaultItemSchema: ZodSchema<VaultItem>` — strict; `folderId` is `string |
|
||||
null`; `history` is an array; `clock` validated by `VectorClockSchema`.
|
||||
- `MergeStrategySchema: ZodUnion<[lww, append, atomic]>`.
|
||||
- `SyncOperationKindSchema: ZodUnion<[create, update, delete]>`.
|
||||
- `SyncOperationSchema: ZodSchema<SyncOperation>` — strict; `fields` validated
|
||||
with `VaultItemSchema.partial()` so any subset of vault fields is acceptable.
|
||||
- `VaultSnapshotSchema: ZodSchema<VaultSnapshot>` — strict.
|
||||
- `EncryptedSyncPayloadSchema: ZodSchema<EncryptedSyncPayload>` — `version`
|
||||
must equal `"v1"`; `nonce` must be exactly 24 bytes (XChaCha20).
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Strict mode (`.strict()`) is intentional — `vault-sync` is a wire
|
||||
boundary, and silently accepting unknown fields opens a downgrade attack
|
||||
where a malicious peer adds a future-version field that gets ignored. The
|
||||
`nonce` byte-length check is duplicated (also enforced by
|
||||
`crypto-cipher`'s XChaCha primitive) so callers parsing a payload without
|
||||
attempting to decrypt still see a clear schema-level failure. All schemas
|
||||
are pure values — they take no constructor arguments, they have no shared
|
||||
state, and they are safe to use in any execution context.
|
||||
Reference in New Issue
Block a user