@noy-db/core 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1428 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1270 -6
- package/dist/index.d.ts +1270 -6
- package/dist/index.js +1407 -24
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/errors.ts","../src/crypto.ts","../src/keyring.ts","../src/history.ts","../src/diff.ts","../src/query/predicate.ts","../src/query/builder.ts","../src/query/indexes.ts","../src/cache/lru.ts","../src/cache/policy.ts","../src/collection.ts","../src/compartment.ts","../src/events.ts","../src/sync.ts","../src/noydb.ts","../src/biometric.ts","../src/validation.ts"],"sourcesContent":["/** Format version for encrypted record envelopes. */\nexport const NOYDB_FORMAT_VERSION = 1 as const\n\n/** Format version for keyring files. */\nexport const NOYDB_KEYRING_VERSION = 1 as const\n\n/** Format version for backup files. */\nexport const NOYDB_BACKUP_VERSION = 1 as const\n\n/** Format version for sync metadata. */\nexport const NOYDB_SYNC_VERSION = 1 as const\n\n// ─── Roles & Permissions ───────────────────────────────────────────────\n\nexport type Role = 'owner' | 'admin' | 'operator' | 'viewer' | 'client'\n\nexport type Permission = 'rw' | 'ro'\n\nexport type Permissions = Record<string, Permission>\n\n// ─── Encrypted Envelope ────────────────────────────────────────────────\n\n/** The encrypted wrapper stored by adapters. Adapters only ever see this. */\nexport interface EncryptedEnvelope {\n readonly _noydb: typeof NOYDB_FORMAT_VERSION\n readonly _v: number\n readonly _ts: string\n readonly _iv: string\n readonly _data: string\n /** User who created this version (unencrypted metadata). */\n readonly _by?: string\n}\n\n// ─── Compartment Snapshot ──────────────────────────────────────────────\n\n/** All records across all collections for a compartment. */\nexport type CompartmentSnapshot = Record<string, Record<string, EncryptedEnvelope>>\n\n/**\n * Result of a single page fetch via the optional `listPage` adapter extension.\n *\n * `items` carries the actual encrypted envelopes (not just ids) so the\n * caller can decrypt and emit a single record without an extra `get()`\n * round-trip per id. `nextCursor` is `null` on the final page.\n */\nexport interface ListPageResult {\n /** Encrypted envelopes for this page, in adapter-defined order. */\n items: Array<{ id: string; envelope: EncryptedEnvelope }>\n /** Opaque cursor for the next page, or `null` if this was the last page. */\n nextCursor: string | null\n}\n\n// ─── Adapter Interface ─────────────────────────────────────────────────\n\nexport interface NoydbAdapter {\n /**\n * Optional human-readable adapter name (e.g. 'memory', 'file', 'dynamo').\n * Used in diagnostic messages and the listPage fallback warning. Adapters\n * are encouraged to set this so logs are clearer about which backend is\n * involved when something goes wrong.\n */\n name?: string\n\n /** Get a single record. Returns null if not found. */\n get(compartment: string, collection: string, id: string): Promise<EncryptedEnvelope | null>\n\n /** Put a record. Throws ConflictError if expectedVersion doesn't match. */\n put(\n compartment: string,\n collection: string,\n id: string,\n envelope: EncryptedEnvelope,\n expectedVersion?: number,\n ): Promise<void>\n\n /** Delete a record. */\n delete(compartment: string, collection: string, id: string): Promise<void>\n\n /** List all record IDs in a collection. */\n list(compartment: string, collection: string): Promise<string[]>\n\n /** Load all records for a compartment (initial hydration). */\n loadAll(compartment: string): Promise<CompartmentSnapshot>\n\n /** Save all records for a compartment (bulk write / restore). */\n saveAll(compartment: string, data: CompartmentSnapshot): Promise<void>\n\n /** Optional connectivity check for sync engine. */\n ping?(): Promise<boolean>\n\n /**\n * Optional pagination extension. Adapters that implement `listPage` get\n * the streaming `Collection.scan()` fast path; adapters that don't are\n * silently fallen back to a full `loadAll()` + slice (with a one-time\n * console.warn).\n *\n * `cursor` is opaque to the core — each adapter encodes its own paging\n * state (DynamoDB: base64 LastEvaluatedKey JSON; S3: ContinuationToken;\n * memory/file/browser: numeric offset of a sorted id list). Pass\n * `undefined` to start from the beginning.\n *\n * `limit` is a soft upper bound on `items.length`. Adapters MAY return\n * fewer items even when more exist (e.g. if the underlying store has\n * its own page size cap), and MUST signal \"no more pages\" by returning\n * `nextCursor: null`.\n *\n * The 6-method core contract is unchanged — this is an additive\n * extension discovered via `'listPage' in adapter`.\n */\n listPage?(\n compartment: string,\n collection: string,\n cursor?: string,\n limit?: number,\n ): Promise<ListPageResult>\n}\n\n// ─── Adapter Factory Helper ────────────────────────────────────────────\n\n/** Type-safe helper for creating adapter factories. */\nexport function defineAdapter<TOptions>(\n factory: (options: TOptions) => NoydbAdapter,\n): (options: TOptions) => NoydbAdapter {\n return factory\n}\n\n// ─── Keyring ───────────────────────────────────────────────────────────\n\nexport interface KeyringFile {\n readonly _noydb_keyring: typeof NOYDB_KEYRING_VERSION\n readonly user_id: string\n readonly display_name: string\n readonly role: Role\n readonly permissions: Permissions\n readonly deks: Record<string, string>\n readonly salt: string\n readonly created_at: string\n readonly granted_by: string\n}\n\n// ─── Backup ────────────────────────────────────────────────────────────\n\nexport interface CompartmentBackup {\n readonly _noydb_backup: typeof NOYDB_BACKUP_VERSION\n readonly _compartment: string\n readonly _exported_at: string\n readonly _exported_by: string\n readonly keyrings: Record<string, KeyringFile>\n readonly collections: CompartmentSnapshot\n}\n\n// ─── Sync ──────────────────────────────────────────────────────────────\n\nexport interface DirtyEntry {\n readonly compartment: string\n readonly collection: string\n readonly id: string\n readonly action: 'put' | 'delete'\n readonly version: number\n readonly timestamp: string\n}\n\nexport interface SyncMetadata {\n readonly _noydb_sync: typeof NOYDB_SYNC_VERSION\n readonly last_push: string | null\n readonly last_pull: string | null\n readonly dirty: DirtyEntry[]\n}\n\nexport interface Conflict {\n readonly compartment: string\n readonly collection: string\n readonly id: string\n readonly local: EncryptedEnvelope\n readonly remote: EncryptedEnvelope\n readonly localVersion: number\n readonly remoteVersion: number\n}\n\nexport type ConflictStrategy =\n | 'local-wins'\n | 'remote-wins'\n | 'version'\n | ((conflict: Conflict) => 'local' | 'remote')\n\nexport interface PushResult {\n readonly pushed: number\n readonly conflicts: Conflict[]\n readonly errors: Error[]\n}\n\nexport interface PullResult {\n readonly pulled: number\n readonly conflicts: Conflict[]\n readonly errors: Error[]\n}\n\nexport interface SyncStatus {\n readonly dirty: number\n readonly lastPush: string | null\n readonly lastPull: string | null\n readonly online: boolean\n}\n\n// ─── Events ────────────────────────────────────────────────────────────\n\nexport interface ChangeEvent {\n readonly compartment: string\n readonly collection: string\n readonly id: string\n readonly action: 'put' | 'delete'\n}\n\nexport interface NoydbEventMap {\n 'change': ChangeEvent\n 'error': Error\n 'sync:push': PushResult\n 'sync:pull': PullResult\n 'sync:conflict': Conflict\n 'sync:online': void\n 'sync:offline': void\n 'history:save': { compartment: string; collection: string; id: string; version: number }\n 'history:prune': { compartment: string; collection: string; id: string; pruned: number }\n}\n\n// ─── Grant / Revoke ────────────────────────────────────────────────────\n\nexport interface GrantOptions {\n readonly userId: string\n readonly displayName: string\n readonly role: Role\n readonly passphrase: string\n readonly permissions?: Permissions\n}\n\nexport interface RevokeOptions {\n readonly userId: string\n readonly rotateKeys?: boolean\n}\n\n// ─── User Info ─────────────────────────────────────────────────────────\n\nexport interface UserInfo {\n readonly userId: string\n readonly displayName: string\n readonly role: Role\n readonly permissions: Permissions\n readonly createdAt: string\n readonly grantedBy: string\n}\n\n// ─── Factory Options ───────────────────────────────────────────────────\n\nexport interface NoydbOptions {\n /** Primary adapter (local storage). */\n readonly adapter: NoydbAdapter\n /** Optional remote adapter for sync. */\n readonly sync?: NoydbAdapter\n /** User identifier. */\n readonly user: string\n /** Passphrase for key derivation. Required unless encrypt is false. */\n readonly secret?: string\n /** Auth method. Default: 'passphrase'. */\n readonly auth?: 'passphrase' | 'biometric'\n /** Enable encryption. Default: true. */\n readonly encrypt?: boolean\n /** Conflict resolution strategy. Default: 'version'. */\n readonly conflict?: ConflictStrategy\n /** Auto-sync on online/offline events. Default: false. */\n readonly autoSync?: boolean\n /** Periodic sync interval in ms. Default: 30000. */\n readonly syncInterval?: number\n /** Session timeout in ms. Clears keys after inactivity. Default: none. */\n readonly sessionTimeout?: number\n /** Validate passphrase strength on creation. Default: true. */\n readonly validatePassphrase?: boolean\n /** Audit history configuration. */\n readonly history?: HistoryConfig\n}\n\n// ─── History / Audit Trail ─────────────────────────────────────────────\n\n/** History configuration. */\nexport interface HistoryConfig {\n /** Enable history tracking. Default: true. */\n readonly enabled?: boolean\n /** Maximum history entries per record. Oldest pruned on overflow. Default: unlimited. */\n readonly maxVersions?: number\n}\n\n/** Options for querying history. */\nexport interface HistoryOptions {\n /** Start date (inclusive), ISO 8601. */\n readonly from?: string\n /** End date (inclusive), ISO 8601. */\n readonly to?: string\n /** Maximum entries to return. */\n readonly limit?: number\n}\n\n/** Options for pruning history. */\nexport interface PruneOptions {\n /** Keep only the N most recent versions. */\n readonly keepVersions?: number\n /** Delete versions older than this date, ISO 8601. */\n readonly beforeDate?: string\n}\n\n/** A decrypted history entry. */\nexport interface HistoryEntry<T> {\n readonly version: number\n readonly timestamp: string\n readonly userId: string\n readonly record: T\n}\n","export class NoydbError extends Error {\n readonly code: string\n\n constructor(code: string, message: string) {\n super(message)\n this.name = 'NoydbError'\n this.code = code\n }\n}\n\n// ─── Crypto Errors ─────────────────────────────────────────────────────\n\nexport class DecryptionError extends NoydbError {\n constructor(message = 'Decryption failed') {\n super('DECRYPTION_FAILED', message)\n this.name = 'DecryptionError'\n }\n}\n\nexport class TamperedError extends NoydbError {\n constructor(message = 'Data integrity check failed — record may have been tampered with') {\n super('TAMPERED', message)\n this.name = 'TamperedError'\n }\n}\n\nexport class InvalidKeyError extends NoydbError {\n constructor(message = 'Invalid key — wrong passphrase or corrupted keyring') {\n super('INVALID_KEY', message)\n this.name = 'InvalidKeyError'\n }\n}\n\n// ─── Access Errors ─────────────────────────────────────────────────────\n\nexport class NoAccessError extends NoydbError {\n constructor(message = 'No access — user does not have a key for this collection') {\n super('NO_ACCESS', message)\n this.name = 'NoAccessError'\n }\n}\n\nexport class ReadOnlyError extends NoydbError {\n constructor(message = 'Read-only — user has ro permission on this collection') {\n super('READ_ONLY', message)\n this.name = 'ReadOnlyError'\n }\n}\n\nexport class PermissionDeniedError extends NoydbError {\n constructor(message = 'Permission denied — insufficient role for this operation') {\n super('PERMISSION_DENIED', message)\n this.name = 'PermissionDeniedError'\n }\n}\n\n// ─── Sync Errors ───────────────────────────────────────────────────────\n\nexport class ConflictError extends NoydbError {\n readonly version: number\n\n constructor(version: number, message = 'Version conflict') {\n super('CONFLICT', message)\n this.name = 'ConflictError'\n this.version = version\n }\n}\n\nexport class NetworkError extends NoydbError {\n constructor(message = 'Network error') {\n super('NETWORK_ERROR', message)\n this.name = 'NetworkError'\n }\n}\n\n// ─── Data Errors ───────────────────────────────────────────────────────\n\nexport class NotFoundError extends NoydbError {\n constructor(message = 'Record not found') {\n super('NOT_FOUND', message)\n this.name = 'NotFoundError'\n }\n}\n\nexport class ValidationError extends NoydbError {\n constructor(message = 'Validation error') {\n super('VALIDATION_ERROR', message)\n this.name = 'ValidationError'\n }\n}\n","import { DecryptionError, InvalidKeyError, TamperedError } from './errors.js'\n\nconst PBKDF2_ITERATIONS = 600_000\nconst SALT_BYTES = 32\nconst IV_BYTES = 12\nconst KEY_BITS = 256\n\nconst subtle = globalThis.crypto.subtle\n\n// ─── Key Derivation ────────────────────────────────────────────────────\n\n/** Derive a KEK from a passphrase and salt using PBKDF2-SHA256. */\nexport async function deriveKey(\n passphrase: string,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n const keyMaterial = await subtle.importKey(\n 'raw',\n new TextEncoder().encode(passphrase),\n 'PBKDF2',\n false,\n ['deriveKey'],\n )\n\n return subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: salt as BufferSource,\n iterations: PBKDF2_ITERATIONS,\n hash: 'SHA-256',\n },\n keyMaterial,\n { name: 'AES-KW', length: KEY_BITS },\n false,\n ['wrapKey', 'unwrapKey'],\n )\n}\n\n// ─── DEK Generation ────────────────────────────────────────────────────\n\n/** Generate a random AES-256-GCM data encryption key. */\nexport async function generateDEK(): Promise<CryptoKey> {\n return subtle.generateKey(\n { name: 'AES-GCM', length: KEY_BITS },\n true, // extractable — needed for AES-KW wrapping\n ['encrypt', 'decrypt'],\n )\n}\n\n// ─── Key Wrapping ──────────────────────────────────────────────────────\n\n/** Wrap (encrypt) a DEK with a KEK using AES-KW. Returns base64 string. */\nexport async function wrapKey(dek: CryptoKey, kek: CryptoKey): Promise<string> {\n const wrapped = await subtle.wrapKey('raw', dek, kek, 'AES-KW')\n return bufferToBase64(wrapped)\n}\n\n/** Unwrap (decrypt) a DEK from base64 string using a KEK. */\nexport async function unwrapKey(\n wrappedBase64: string,\n kek: CryptoKey,\n): Promise<CryptoKey> {\n try {\n return await subtle.unwrapKey(\n 'raw',\n base64ToBuffer(wrappedBase64) as BufferSource,\n kek,\n 'AES-KW',\n { name: 'AES-GCM', length: KEY_BITS },\n true,\n ['encrypt', 'decrypt'],\n )\n } catch {\n throw new InvalidKeyError()\n }\n}\n\n// ─── Encrypt / Decrypt ─────────────────────────────────────────────────\n\nexport interface EncryptResult {\n iv: string // base64\n data: string // base64\n}\n\n/** Encrypt plaintext JSON string with AES-256-GCM. Fresh IV per call. */\nexport async function encrypt(\n plaintext: string,\n dek: CryptoKey,\n): Promise<EncryptResult> {\n const iv = generateIV()\n const encoded = new TextEncoder().encode(plaintext)\n\n const ciphertext = await subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n encoded,\n )\n\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/** Decrypt AES-256-GCM ciphertext. Throws on wrong key or tampered data. */\nexport async function decrypt(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n): Promise<string> {\n const iv = base64ToBuffer(ivBase64)\n const ciphertext = base64ToBuffer(dataBase64)\n\n try {\n const plaintext = await subtle.decrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n ciphertext as BufferSource,\n )\n return new TextDecoder().decode(plaintext)\n } catch (err) {\n if (err instanceof Error && err.name === 'OperationError') {\n throw new TamperedError()\n }\n throw new DecryptionError(\n err instanceof Error ? err.message : 'Decryption failed',\n )\n }\n}\n\n// ─── Random Generation ─────────────────────────────────────────────────\n\n/** Generate a random 12-byte IV for AES-GCM. */\nexport function generateIV(): Uint8Array {\n return globalThis.crypto.getRandomValues(new Uint8Array(IV_BYTES))\n}\n\n/** Generate a random 32-byte salt for PBKDF2. */\nexport function generateSalt(): Uint8Array {\n return globalThis.crypto.getRandomValues(new Uint8Array(SALT_BYTES))\n}\n\n// ─── Base64 Helpers ────────────────────────────────────────────────────\n\nexport function bufferToBase64(buffer: ArrayBuffer | Uint8Array): string {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)\n let binary = ''\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!)\n }\n return btoa(binary)\n}\n\nexport function base64ToBuffer(base64: string): Uint8Array {\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n","import type { NoydbAdapter, KeyringFile, Role, Permissions, GrantOptions, RevokeOptions, UserInfo, EncryptedEnvelope } from './types.js'\nimport { NOYDB_KEYRING_VERSION, NOYDB_FORMAT_VERSION } from './types.js'\nimport {\n deriveKey,\n generateDEK,\n generateSalt,\n wrapKey,\n unwrapKey,\n encrypt,\n decrypt,\n bufferToBase64,\n base64ToBuffer,\n} from './crypto.js'\nimport { NoAccessError, PermissionDeniedError } from './errors.js'\n\n// ─── Roles that can grant/revoke ───────────────────────────────────────\n\nconst GRANTABLE_BY_ADMIN: readonly Role[] = ['operator', 'viewer', 'client']\n\nfunction canGrant(callerRole: Role, targetRole: Role): boolean {\n if (callerRole === 'owner') return true\n if (callerRole === 'admin') return GRANTABLE_BY_ADMIN.includes(targetRole)\n return false\n}\n\nfunction canRevoke(callerRole: Role, targetRole: Role): boolean {\n if (targetRole === 'owner') return false // owner cannot be revoked\n if (callerRole === 'owner') return true\n if (callerRole === 'admin') return GRANTABLE_BY_ADMIN.includes(targetRole)\n return false\n}\n\n// ─── Unlocked Keyring ──────────────────────────────────────────────────\n\n/** In-memory representation of an unlocked keyring. */\nexport interface UnlockedKeyring {\n readonly userId: string\n readonly displayName: string\n readonly role: Role\n readonly permissions: Permissions\n readonly deks: Map<string, CryptoKey>\n readonly kek: CryptoKey\n readonly salt: Uint8Array\n}\n\n// ─── Load / Create ─────────────────────────────────────────────────────\n\n/** Load and unlock a user's keyring for a compartment. */\nexport async function loadKeyring(\n adapter: NoydbAdapter,\n compartment: string,\n userId: string,\n passphrase: string,\n): Promise<UnlockedKeyring> {\n const envelope = await adapter.get(compartment, '_keyring', userId)\n\n if (!envelope) {\n throw new NoAccessError(`No keyring found for user \"${userId}\" in compartment \"${compartment}\"`)\n }\n\n const keyringFile = JSON.parse(envelope._data) as KeyringFile\n const salt = base64ToBuffer(keyringFile.salt)\n const kek = await deriveKey(passphrase, salt)\n\n const deks = new Map<string, CryptoKey>()\n for (const [collName, wrappedDek] of Object.entries(keyringFile.deks)) {\n const dek = await unwrapKey(wrappedDek, kek)\n deks.set(collName, dek)\n }\n\n return {\n userId: keyringFile.user_id,\n displayName: keyringFile.display_name,\n role: keyringFile.role,\n permissions: keyringFile.permissions,\n deks,\n kek,\n salt,\n }\n}\n\n/** Create the initial owner keyring for a new compartment. */\nexport async function createOwnerKeyring(\n adapter: NoydbAdapter,\n compartment: string,\n userId: string,\n passphrase: string,\n): Promise<UnlockedKeyring> {\n const salt = generateSalt()\n const kek = await deriveKey(passphrase, salt)\n\n const keyringFile: KeyringFile = {\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n user_id: userId,\n display_name: userId,\n role: 'owner',\n permissions: {},\n deks: {},\n salt: bufferToBase64(salt),\n created_at: new Date().toISOString(),\n granted_by: userId,\n }\n\n await writeKeyringFile(adapter, compartment, userId, keyringFile)\n\n return {\n userId,\n displayName: userId,\n role: 'owner',\n permissions: {},\n deks: new Map(),\n kek,\n salt,\n }\n}\n\n// ─── Grant ─────────────────────────────────────────────────────────────\n\n/** Grant access to a new user. Caller must have grant privilege. */\nexport async function grant(\n adapter: NoydbAdapter,\n compartment: string,\n callerKeyring: UnlockedKeyring,\n options: GrantOptions,\n): Promise<void> {\n if (!canGrant(callerKeyring.role, options.role)) {\n throw new PermissionDeniedError(\n `Role \"${callerKeyring.role}\" cannot grant role \"${options.role}\"`,\n )\n }\n\n // Determine which collections the new user gets access to\n const permissions = resolvePermissions(options.role, options.permissions)\n\n // Derive the new user's KEK from their passphrase\n const newSalt = generateSalt()\n const newKek = await deriveKey(options.passphrase, newSalt)\n\n // Wrap the appropriate DEKs with the new user's KEK\n const wrappedDeks: Record<string, string> = {}\n for (const collName of Object.keys(permissions)) {\n const dek = callerKeyring.deks.get(collName)\n if (dek) {\n wrappedDeks[collName] = await wrapKey(dek, newKek)\n }\n }\n\n // For owner/admin/viewer roles, wrap ALL known DEKs\n if (options.role === 'owner' || options.role === 'admin' || options.role === 'viewer') {\n for (const [collName, dek] of callerKeyring.deks) {\n if (!(collName in wrappedDeks)) {\n wrappedDeks[collName] = await wrapKey(dek, newKek)\n }\n }\n }\n\n const keyringFile: KeyringFile = {\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n user_id: options.userId,\n display_name: options.displayName,\n role: options.role,\n permissions,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n created_at: new Date().toISOString(),\n granted_by: callerKeyring.userId,\n }\n\n await writeKeyringFile(adapter, compartment, options.userId, keyringFile)\n}\n\n// ─── Revoke ────────────────────────────────────────────────────────────\n\n/** Revoke a user's access. Optionally rotate keys for affected collections. */\nexport async function revoke(\n adapter: NoydbAdapter,\n compartment: string,\n callerKeyring: UnlockedKeyring,\n options: RevokeOptions,\n): Promise<void> {\n // Load the target's keyring to check their role\n const targetEnvelope = await adapter.get(compartment, '_keyring', options.userId)\n if (!targetEnvelope) {\n throw new NoAccessError(`User \"${options.userId}\" has no keyring in compartment \"${compartment}\"`)\n }\n\n const targetKeyring = JSON.parse(targetEnvelope._data) as KeyringFile\n\n if (!canRevoke(callerKeyring.role, targetKeyring.role)) {\n throw new PermissionDeniedError(\n `Role \"${callerKeyring.role}\" cannot revoke role \"${targetKeyring.role}\"`,\n )\n }\n\n // Collect which collections the revoked user had access to\n const affectedCollections = Object.keys(targetKeyring.deks)\n\n // Delete the revoked user's keyring\n await adapter.delete(compartment, '_keyring', options.userId)\n\n // Rotate keys if requested\n if (options.rotateKeys !== false && affectedCollections.length > 0) {\n await rotateKeys(adapter, compartment, callerKeyring, affectedCollections)\n }\n}\n\n// ─── Key Rotation ──────────────────────────────────────────────────────\n\n/**\n * Rotate DEKs for specified collections:\n * 1. Generate new DEKs\n * 2. Re-encrypt all records in affected collections\n * 3. Re-wrap new DEKs for all remaining users\n */\nexport async function rotateKeys(\n adapter: NoydbAdapter,\n compartment: string,\n callerKeyring: UnlockedKeyring,\n collections: string[],\n): Promise<void> {\n // Generate new DEKs for each affected collection\n const newDeks = new Map<string, CryptoKey>()\n for (const collName of collections) {\n newDeks.set(collName, await generateDEK())\n }\n\n // Re-encrypt all records in affected collections\n for (const collName of collections) {\n const oldDek = callerKeyring.deks.get(collName)\n const newDek = newDeks.get(collName)!\n if (!oldDek) continue\n\n const ids = await adapter.list(compartment, collName)\n for (const id of ids) {\n const envelope = await adapter.get(compartment, collName, id)\n if (!envelope || !envelope._iv) continue\n\n // Decrypt with old DEK\n const plaintext = await decrypt(envelope._iv, envelope._data, oldDek)\n\n // Re-encrypt with new DEK\n const { iv, data } = await encrypt(plaintext, newDek)\n const newEnvelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: envelope._v,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n }\n await adapter.put(compartment, collName, id, newEnvelope)\n }\n }\n\n // Update caller's keyring with new DEKs\n for (const [collName, newDek] of newDeks) {\n callerKeyring.deks.set(collName, newDek)\n }\n await persistKeyring(adapter, compartment, callerKeyring)\n\n // Update all remaining users' keyrings with re-wrapped new DEKs\n const userIds = await adapter.list(compartment, '_keyring')\n for (const userId of userIds) {\n if (userId === callerKeyring.userId) continue\n\n const userEnvelope = await adapter.get(compartment, '_keyring', userId)\n if (!userEnvelope) continue\n\n const userKeyringFile = JSON.parse(userEnvelope._data) as KeyringFile\n // Note: we can't derive other users' KEKs to re-wrap DEKs for them.\n // Rotation requires users to re-unlock and be re-granted after the caller\n // re-wraps with the raw DEKs held in memory. See rotation flow below.\n // The trick: import the user's KEK from their salt? No — we need their passphrase.\n //\n // Per the spec: the caller (owner/admin) wraps the new DEKs with each remaining\n // user's KEK. But we can't derive their KEK without their passphrase.\n //\n // Real solution from the spec: the caller wraps the DEK using the approach of\n // reading each user's existing wrapping. Since we can't derive their KEK,\n // we use a RE-KEYING approach: the new DEK is wrapped with a key-wrapping-key\n // that we CAN derive — we use the existing wrapped DEK as proof that the user\n // had access, and we replace it with the new wrapped DEK.\n //\n // Practical approach: Since the owner/admin has all raw DEKs in memory,\n // and each user's keyring contains their salt, we need the users to\n // re-authenticate to get the new wrapped keys. This is the standard approach.\n //\n // For NOYDB Phase 2: we'll update the keyring file to include a \"pending_rekey\"\n // flag. Users will get new DEKs on next login when the owner provides them.\n //\n // SIMPLER approach used here: Since the owner performed the rotation,\n // the owner has both old and new DEKs. We store a \"rekey token\" that the\n // user can use to unwrap: we wrap the new DEK with the OLD DEK (which the\n // user can still unwrap from their keyring, since their keyring has the old\n // wrapped DEK and their KEK can unwrap it).\n\n // Actually even simpler: we just need the user's KEK. We don't have it.\n // The spec says the owner wraps new DEKs for each remaining user.\n // This requires knowing each user's KEK (or having a shared secret).\n //\n // The CORRECT implementation from the spec: the owner/admin has all DEKs.\n // Each user's keyring stores DEKs wrapped with THAT USER's KEK.\n // To re-wrap, we need each user's KEK — which we can't get.\n //\n // Real-world solution: use a KEY ESCROW approach where the owner stores\n // each user's wrapping key (not their passphrase, but a key derived from\n // the grant process). During grant, the owner stores a copy of the new user's\n // KEK (wrapped with the owner's KEK) so they can re-wrap later.\n //\n // For now: mark the user's keyring as needing rekey. The user will need to\n // re-authenticate (owner provides new passphrase or re-grants).\n\n // Update: simplest correct approach — during grant, we store the user's KEK\n // wrapped with the owner's KEK in a separate escrow field. Then during rotation,\n // the owner unwraps the user's KEK from escrow and wraps the new DEKs.\n //\n // BUT: that means we need to change the KeyringFile format.\n // For Phase 2 MVP: just delete the user's old DEK entries and require re-grant.\n // This is secure (revoked keys are gone) but inconvenient (remaining users\n // need re-grant for rotated collections).\n\n // PHASE 2 APPROACH: Remove the affected collection DEKs from remaining users'\n // keyrings. The owner must re-grant access to those collections.\n // This is correct and secure — just requires the owner to re-run grant().\n\n const updatedDeks = { ...userKeyringFile.deks }\n for (const collName of collections) {\n delete updatedDeks[collName]\n }\n\n const updatedPermissions = { ...userKeyringFile.permissions }\n for (const collName of collections) {\n delete updatedPermissions[collName]\n }\n\n const updatedKeyring: KeyringFile = {\n ...userKeyringFile,\n deks: updatedDeks,\n permissions: updatedPermissions,\n }\n\n await writeKeyringFile(adapter, compartment, userId, updatedKeyring)\n }\n}\n\n// ─── Change Secret ─────────────────────────────────────────────────────\n\n/** Change the user's passphrase. Re-wraps all DEKs with the new KEK. */\nexport async function changeSecret(\n adapter: NoydbAdapter,\n compartment: string,\n keyring: UnlockedKeyring,\n newPassphrase: string,\n): Promise<UnlockedKeyring> {\n const newSalt = generateSalt()\n const newKek = await deriveKey(newPassphrase, newSalt)\n\n // Re-wrap all DEKs with the new KEK\n const wrappedDeks: Record<string, string> = {}\n for (const [collName, dek] of keyring.deks) {\n wrappedDeks[collName] = await wrapKey(dek, newKek)\n }\n\n const keyringFile: KeyringFile = {\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n user_id: keyring.userId,\n display_name: keyring.displayName,\n role: keyring.role,\n permissions: keyring.permissions,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n created_at: new Date().toISOString(),\n granted_by: keyring.userId,\n }\n\n await writeKeyringFile(adapter, compartment, keyring.userId, keyringFile)\n\n return {\n userId: keyring.userId,\n displayName: keyring.displayName,\n role: keyring.role,\n permissions: keyring.permissions,\n deks: keyring.deks, // Same DEKs, different wrapping\n kek: newKek,\n salt: newSalt,\n }\n}\n\n// ─── List Users ────────────────────────────────────────────────────────\n\n/** List all users with access to a compartment. */\nexport async function listUsers(\n adapter: NoydbAdapter,\n compartment: string,\n): Promise<UserInfo[]> {\n const userIds = await adapter.list(compartment, '_keyring')\n const users: UserInfo[] = []\n\n for (const userId of userIds) {\n const envelope = await adapter.get(compartment, '_keyring', userId)\n if (!envelope) continue\n const kf = JSON.parse(envelope._data) as KeyringFile\n users.push({\n userId: kf.user_id,\n displayName: kf.display_name,\n role: kf.role,\n permissions: kf.permissions,\n createdAt: kf.created_at,\n grantedBy: kf.granted_by,\n })\n }\n\n return users\n}\n\n// ─── DEK Management ────────────────────────────────────────────────────\n\n/** Ensure a DEK exists for a collection. Generates one if new. */\nexport async function ensureCollectionDEK(\n adapter: NoydbAdapter,\n compartment: string,\n keyring: UnlockedKeyring,\n): Promise<(collectionName: string) => Promise<CryptoKey>> {\n return async (collectionName: string): Promise<CryptoKey> => {\n const existing = keyring.deks.get(collectionName)\n if (existing) return existing\n\n const dek = await generateDEK()\n keyring.deks.set(collectionName, dek)\n await persistKeyring(adapter, compartment, keyring)\n return dek\n }\n}\n\n// ─── Permission Checks ─────────────────────────────────────────────────\n\n/** Check if a user has write permission for a collection. */\nexport function hasWritePermission(keyring: UnlockedKeyring, collectionName: string): boolean {\n if (keyring.role === 'owner' || keyring.role === 'admin') return true\n if (keyring.role === 'viewer' || keyring.role === 'client') return false\n return keyring.permissions[collectionName] === 'rw'\n}\n\n/** Check if a user has any access to a collection. */\nexport function hasAccess(keyring: UnlockedKeyring, collectionName: string): boolean {\n if (keyring.role === 'owner' || keyring.role === 'admin' || keyring.role === 'viewer') return true\n return collectionName in keyring.permissions\n}\n\n// ─── Helpers ───────────────────────────────────────────────────────────\n\n/** Persist a keyring file to the adapter. */\nexport async function persistKeyring(\n adapter: NoydbAdapter,\n compartment: string,\n keyring: UnlockedKeyring,\n): Promise<void> {\n const wrappedDeks: Record<string, string> = {}\n for (const [collName, dek] of keyring.deks) {\n wrappedDeks[collName] = await wrapKey(dek, keyring.kek)\n }\n\n const keyringFile: KeyringFile = {\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n user_id: keyring.userId,\n display_name: keyring.displayName,\n role: keyring.role,\n permissions: keyring.permissions,\n deks: wrappedDeks,\n salt: bufferToBase64(keyring.salt),\n created_at: new Date().toISOString(),\n granted_by: keyring.userId,\n }\n\n await writeKeyringFile(adapter, compartment, keyring.userId, keyringFile)\n}\n\nfunction resolvePermissions(role: Role, explicit?: Permissions): Permissions {\n if (role === 'owner' || role === 'admin' || role === 'viewer') return {}\n return explicit ?? {}\n}\n\nasync function writeKeyringFile(\n adapter: NoydbAdapter,\n compartment: string,\n userId: string,\n keyringFile: KeyringFile,\n): Promise<void> {\n const envelope = {\n _noydb: 1 as const,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(keyringFile),\n }\n await adapter.put(compartment, '_keyring', userId, envelope)\n}\n","import type { NoydbAdapter, EncryptedEnvelope, HistoryOptions, PruneOptions } from './types.js'\n\n/**\n * History storage convention:\n * Collection: `_history`\n * ID format: `{collection}:{recordId}:{paddedVersion}`\n * Version is zero-padded to 10 digits for lexicographic sorting.\n */\n\nconst HISTORY_COLLECTION = '_history'\nconst VERSION_PAD = 10\n\nfunction historyId(collection: string, recordId: string, version: number): string {\n return `${collection}:${recordId}:${String(version).padStart(VERSION_PAD, '0')}`\n}\n\n// Unused today, kept for future history-id parsing utilities.\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction parseHistoryId(id: string): { collection: string; recordId: string; version: number } | null {\n const lastColon = id.lastIndexOf(':')\n if (lastColon < 0) return null\n const versionStr = id.slice(lastColon + 1)\n const rest = id.slice(0, lastColon)\n const firstColon = rest.indexOf(':')\n if (firstColon < 0) return null\n return {\n collection: rest.slice(0, firstColon),\n recordId: rest.slice(firstColon + 1),\n version: parseInt(versionStr, 10),\n }\n}\n\nfunction matchesPrefix(id: string, collection: string, recordId?: string): boolean {\n if (recordId) {\n return id.startsWith(`${collection}:${recordId}:`)\n }\n return id.startsWith(`${collection}:`)\n}\n\n/** Save a history entry (a complete encrypted envelope snapshot). */\nexport async function saveHistory(\n adapter: NoydbAdapter,\n compartment: string,\n collection: string,\n recordId: string,\n envelope: EncryptedEnvelope,\n): Promise<void> {\n const id = historyId(collection, recordId, envelope._v)\n await adapter.put(compartment, HISTORY_COLLECTION, id, envelope)\n}\n\n/** Get history entries for a record, sorted newest-first. */\nexport async function getHistory(\n adapter: NoydbAdapter,\n compartment: string,\n collection: string,\n recordId: string,\n options?: HistoryOptions,\n): Promise<EncryptedEnvelope[]> {\n const allIds = await adapter.list(compartment, HISTORY_COLLECTION)\n const matchingIds = allIds\n .filter(id => matchesPrefix(id, collection, recordId))\n .sort()\n .reverse() // newest first\n\n const entries: EncryptedEnvelope[] = []\n\n for (const id of matchingIds) {\n const envelope = await adapter.get(compartment, HISTORY_COLLECTION, id)\n if (!envelope) continue\n\n // Apply time filters\n if (options?.from && envelope._ts < options.from) continue\n if (options?.to && envelope._ts > options.to) continue\n\n entries.push(envelope)\n\n if (options?.limit && entries.length >= options.limit) break\n }\n\n return entries\n}\n\n/** Get a specific version's envelope from history. */\nexport async function getVersionEnvelope(\n adapter: NoydbAdapter,\n compartment: string,\n collection: string,\n recordId: string,\n version: number,\n): Promise<EncryptedEnvelope | null> {\n const id = historyId(collection, recordId, version)\n return adapter.get(compartment, HISTORY_COLLECTION, id)\n}\n\n/** Prune history entries. Returns the number of entries deleted. */\nexport async function pruneHistory(\n adapter: NoydbAdapter,\n compartment: string,\n collection: string,\n recordId: string | undefined,\n options: PruneOptions,\n): Promise<number> {\n const allIds = await adapter.list(compartment, HISTORY_COLLECTION)\n const matchingIds = allIds\n .filter(id => recordId ? matchesPrefix(id, collection, recordId) : matchesPrefix(id, collection))\n .sort()\n\n let toDelete: string[] = []\n\n if (options.keepVersions !== undefined) {\n // Keep only the N most recent, delete the rest\n const keep = options.keepVersions\n if (matchingIds.length > keep) {\n toDelete = matchingIds.slice(0, matchingIds.length - keep)\n }\n }\n\n if (options.beforeDate) {\n // Delete entries older than the specified date\n for (const id of matchingIds) {\n if (toDelete.includes(id)) continue\n const envelope = await adapter.get(compartment, HISTORY_COLLECTION, id)\n if (envelope && envelope._ts < options.beforeDate) {\n toDelete.push(id)\n }\n }\n }\n\n // Deduplicate\n const uniqueDeletes = [...new Set(toDelete)]\n\n for (const id of uniqueDeletes) {\n await adapter.delete(compartment, HISTORY_COLLECTION, id)\n }\n\n return uniqueDeletes.length\n}\n\n/** Clear all history for a compartment, optionally scoped to a collection or record. */\nexport async function clearHistory(\n adapter: NoydbAdapter,\n compartment: string,\n collection?: string,\n recordId?: string,\n): Promise<number> {\n const allIds = await adapter.list(compartment, HISTORY_COLLECTION)\n let toDelete: string[]\n\n if (collection && recordId) {\n toDelete = allIds.filter(id => matchesPrefix(id, collection, recordId))\n } else if (collection) {\n toDelete = allIds.filter(id => matchesPrefix(id, collection))\n } else {\n toDelete = allIds\n }\n\n for (const id of toDelete) {\n await adapter.delete(compartment, HISTORY_COLLECTION, id)\n }\n\n return toDelete.length\n}\n","/**\n * Zero-dependency JSON diff.\n * Produces a flat list of changes between two plain objects.\n */\n\nexport type ChangeType = 'added' | 'removed' | 'changed'\n\nexport interface DiffEntry {\n /** Dot-separated path to the changed field (e.g. \"address.city\"). */\n readonly path: string\n /** Type of change. */\n readonly type: ChangeType\n /** Previous value (undefined for 'added'). */\n readonly from?: unknown\n /** New value (undefined for 'removed'). */\n readonly to?: unknown\n}\n\n/**\n * Compute differences between two objects.\n * Returns an array of DiffEntry describing each changed field.\n * Returns empty array if objects are identical.\n */\nexport function diff(oldObj: unknown, newObj: unknown, basePath = ''): DiffEntry[] {\n const changes: DiffEntry[] = []\n\n // Both primitives or nulls\n if (oldObj === newObj) return changes\n\n // One is null/undefined\n if (oldObj == null && newObj != null) {\n return [{ path: basePath || '(root)', type: 'added', to: newObj }]\n }\n if (oldObj != null && newObj == null) {\n return [{ path: basePath || '(root)', type: 'removed', from: oldObj }]\n }\n\n // Different types\n if (typeof oldObj !== typeof newObj) {\n return [{ path: basePath || '(root)', type: 'changed', from: oldObj, to: newObj }]\n }\n\n // Both primitives (and not equal — checked above)\n if (typeof oldObj !== 'object') {\n return [{ path: basePath || '(root)', type: 'changed', from: oldObj, to: newObj }]\n }\n\n // Both arrays\n if (Array.isArray(oldObj) && Array.isArray(newObj)) {\n const maxLen = Math.max(oldObj.length, newObj.length)\n for (let i = 0; i < maxLen; i++) {\n const p = basePath ? `${basePath}[${i}]` : `[${i}]`\n if (i >= oldObj.length) {\n changes.push({ path: p, type: 'added', to: newObj[i] })\n } else if (i >= newObj.length) {\n changes.push({ path: p, type: 'removed', from: oldObj[i] })\n } else {\n changes.push(...diff(oldObj[i], newObj[i], p))\n }\n }\n return changes\n }\n\n // Both objects\n const oldRecord = oldObj as Record<string, unknown>\n const newRecord = newObj as Record<string, unknown>\n const allKeys = new Set([...Object.keys(oldRecord), ...Object.keys(newRecord)])\n\n for (const key of allKeys) {\n const p = basePath ? `${basePath}.${key}` : key\n if (!(key in oldRecord)) {\n changes.push({ path: p, type: 'added', to: newRecord[key] })\n } else if (!(key in newRecord)) {\n changes.push({ path: p, type: 'removed', from: oldRecord[key] })\n } else {\n changes.push(...diff(oldRecord[key], newRecord[key], p))\n }\n }\n\n return changes\n}\n\n/** Format a diff as a human-readable string. */\nexport function formatDiff(changes: DiffEntry[]): string {\n if (changes.length === 0) return '(no changes)'\n return changes.map(c => {\n switch (c.type) {\n case 'added':\n return `+ ${c.path}: ${JSON.stringify(c.to)}`\n case 'removed':\n return `- ${c.path}: ${JSON.stringify(c.from)}`\n case 'changed':\n return `~ ${c.path}: ${JSON.stringify(c.from)} → ${JSON.stringify(c.to)}`\n }\n }).join('\\n')\n}\n","/**\n * Operator implementations for the query DSL.\n *\n * All predicates run client-side, AFTER decryption — they never see ciphertext.\n * This file is dependency-free and tree-shakeable.\n */\n\n/** Comparison operators supported by the where() builder. */\nexport type Operator =\n | '=='\n | '!='\n | '<'\n | '<='\n | '>'\n | '>='\n | 'in'\n | 'contains'\n | 'startsWith'\n | 'between'\n\n/**\n * A single field comparison clause inside a query plan.\n * Plans are JSON-serializable, so this type uses primitives only.\n */\nexport interface FieldClause {\n readonly type: 'field'\n readonly field: string\n readonly op: Operator\n readonly value: unknown\n}\n\n/**\n * A user-supplied predicate function escape hatch. Not serializable.\n *\n * The predicate accepts `unknown` at the type level so the surrounding\n * Clause type can stay non-parametric — this keeps Collection<T> covariant\n * in T at the public API surface. Builder methods cast user predicates\n * (typed `(record: T) => boolean`) into this shape on the way in.\n */\nexport interface FilterClause {\n readonly type: 'filter'\n readonly fn: (record: unknown) => boolean\n}\n\n/** A logical group of clauses combined by AND or OR. */\nexport interface GroupClause {\n readonly type: 'group'\n readonly op: 'and' | 'or'\n readonly clauses: readonly Clause[]\n}\n\nexport type Clause = FieldClause | FilterClause | GroupClause\n\n/**\n * Read a possibly nested field path like \"address.city\" from a record.\n * Returns undefined if any segment is missing.\n */\nexport function readPath(record: unknown, path: string): unknown {\n if (record === null || record === undefined) return undefined\n if (!path.includes('.')) {\n return (record as Record<string, unknown>)[path]\n }\n const segments = path.split('.')\n let cursor: unknown = record\n for (const segment of segments) {\n if (cursor === null || cursor === undefined) return undefined\n cursor = (cursor as Record<string, unknown>)[segment]\n }\n return cursor\n}\n\n/**\n * Evaluate a single field clause against a record.\n * Returns false on type mismatches rather than throwing — query results\n * exclude non-matching records by definition.\n */\nexport function evaluateFieldClause(record: unknown, clause: FieldClause): boolean {\n const actual = readPath(record, clause.field)\n const { op, value } = clause\n\n switch (op) {\n case '==':\n return actual === value\n case '!=':\n return actual !== value\n case '<':\n return isComparable(actual, value) && (actual as number) < (value as number)\n case '<=':\n return isComparable(actual, value) && (actual as number) <= (value as number)\n case '>':\n return isComparable(actual, value) && (actual as number) > (value as number)\n case '>=':\n return isComparable(actual, value) && (actual as number) >= (value as number)\n case 'in':\n return Array.isArray(value) && value.includes(actual)\n case 'contains':\n if (typeof actual === 'string') return typeof value === 'string' && actual.includes(value)\n if (Array.isArray(actual)) return actual.includes(value)\n return false\n case 'startsWith':\n return typeof actual === 'string' && typeof value === 'string' && actual.startsWith(value)\n case 'between': {\n if (!Array.isArray(value) || value.length !== 2) return false\n const [lo, hi] = value\n if (!isComparable(actual, lo) || !isComparable(actual, hi)) return false\n return (actual as number) >= (lo as number) && (actual as number) <= (hi as number)\n }\n default: {\n // Exhaustiveness — TS will error if a new operator is added without a case.\n const _exhaustive: never = op\n void _exhaustive\n return false\n }\n }\n}\n\n/**\n * Two values are \"comparable\" if they share an order-defined runtime type.\n * Strings compare lexicographically; numbers and Dates numerically; otherwise false.\n */\nfunction isComparable(a: unknown, b: unknown): boolean {\n if (typeof a === 'number' && typeof b === 'number') return true\n if (typeof a === 'string' && typeof b === 'string') return true\n if (a instanceof Date && b instanceof Date) return true\n return false\n}\n\n/**\n * Evaluate any clause (field / filter / group) against a record.\n * The recursion depth is bounded by the user's query expression — no risk of\n * blowing the stack on a 50K-record collection.\n */\nexport function evaluateClause(record: unknown, clause: Clause): boolean {\n switch (clause.type) {\n case 'field':\n return evaluateFieldClause(record, clause)\n case 'filter':\n return clause.fn(record)\n case 'group':\n if (clause.op === 'and') {\n for (const child of clause.clauses) {\n if (!evaluateClause(record, child)) return false\n }\n return true\n } else {\n for (const child of clause.clauses) {\n if (evaluateClause(record, child)) return true\n }\n return false\n }\n }\n}\n","/**\n * Chainable, immutable query builder.\n *\n * Each builder operation returns a NEW Query — the underlying plan is never\n * mutated. This makes plans safe to share, cache, and serialize.\n */\n\nimport type { Clause, FieldClause, FilterClause, GroupClause, Operator } from './predicate.js'\nimport { evaluateClause } from './predicate.js'\nimport type { CollectionIndexes } from './indexes.js'\n\nexport interface OrderBy {\n readonly field: string\n readonly direction: 'asc' | 'desc'\n}\n\n/**\n * A complete query plan: zero-or-more clauses, optional ordering, pagination.\n * Plans are JSON-serializable as long as no FilterClause is present.\n *\n * Plans are intentionally NOT parametric on T — see `predicate.ts` FilterClause\n * for the variance reasoning. The public `Query<T>` API attaches the type tag.\n */\nexport interface QueryPlan {\n readonly clauses: readonly Clause[]\n readonly orderBy: readonly OrderBy[]\n readonly limit: number | undefined\n readonly offset: number\n}\n\nconst EMPTY_PLAN: QueryPlan = {\n clauses: [],\n orderBy: [],\n limit: undefined,\n offset: 0,\n}\n\n/**\n * Source of records that a query executes against.\n *\n * The interface is non-parametric to keep variance friendly: callers cast\n * their typed source (e.g. `QuerySource<Invoice>`) into this opaque shape.\n *\n * `getIndexes` and `lookupById` are optional fast-path hooks. When both are\n * present and a where clause matches an indexed field, the executor uses\n * the index to skip a linear scan. Sources without these methods (or with\n * `getIndexes` returning `null`) always fall back to a linear scan.\n */\nexport interface QuerySource<T> {\n /** Snapshot of all current records. The query never mutates this array. */\n snapshot(): readonly T[]\n /** Subscribe to mutations; returns an unsubscribe function. */\n subscribe?(cb: () => void): () => void\n /** Index store for the indexed-fast-path. Optional. */\n getIndexes?(): CollectionIndexes | null\n /** O(1) record lookup by id, used to materialize index hits. */\n lookupById?(id: string): T | undefined\n}\n\ninterface InternalSource {\n snapshot(): readonly unknown[]\n subscribe?(cb: () => void): () => void\n getIndexes?(): CollectionIndexes | null\n lookupById?(id: string): unknown\n}\n\n/**\n * The chainable builder. All methods return a new Query — the original\n * remains unchanged. Terminal methods (`toArray`, `first`, `count`,\n * `subscribe`) execute the plan against the source.\n *\n * Type parameter T flows through the public API for ergonomics, but the\n * internal storage uses `unknown` so Collection<T> stays covariant.\n */\nexport class Query<T> {\n private readonly source: InternalSource\n private readonly plan: QueryPlan\n\n constructor(source: QuerySource<T>, plan: QueryPlan = EMPTY_PLAN) {\n this.source = source as InternalSource\n this.plan = plan\n }\n\n /** Add a field comparison. Multiple where() calls are AND-combined. */\n where(field: string, op: Operator, value: unknown): Query<T> {\n const clause: FieldClause = { type: 'field', field, op, value }\n return new Query<T>(this.source as QuerySource<T>, {\n ...this.plan,\n clauses: [...this.plan.clauses, clause],\n })\n }\n\n /**\n * Logical OR group. Pass a callback that builds a sub-query.\n * Each clause inside the callback is OR-combined; the group itself\n * joins the parent plan with AND.\n */\n or(builder: (q: Query<T>) => Query<T>): Query<T> {\n const sub = builder(new Query<T>(this.source as QuerySource<T>))\n const group: GroupClause = {\n type: 'group',\n op: 'or',\n clauses: sub.plan.clauses,\n }\n return new Query<T>(this.source as QuerySource<T>, {\n ...this.plan,\n clauses: [...this.plan.clauses, group],\n })\n }\n\n /**\n * Logical AND group. Same shape as `or()` but every clause inside the group\n * must match. Useful for explicit grouping inside a larger OR.\n */\n and(builder: (q: Query<T>) => Query<T>): Query<T> {\n const sub = builder(new Query<T>(this.source as QuerySource<T>))\n const group: GroupClause = {\n type: 'group',\n op: 'and',\n clauses: sub.plan.clauses,\n }\n return new Query<T>(this.source as QuerySource<T>, {\n ...this.plan,\n clauses: [...this.plan.clauses, group],\n })\n }\n\n /** Escape hatch: add an arbitrary predicate function. Not serializable. */\n filter(fn: (record: T) => boolean): Query<T> {\n const clause: FilterClause = {\n type: 'filter',\n fn: fn as (record: unknown) => boolean,\n }\n return new Query<T>(this.source as QuerySource<T>, {\n ...this.plan,\n clauses: [...this.plan.clauses, clause],\n })\n }\n\n /** Sort by a field. Subsequent calls are tie-breakers. */\n orderBy(field: string, direction: 'asc' | 'desc' = 'asc'): Query<T> {\n return new Query<T>(this.source as QuerySource<T>, {\n ...this.plan,\n orderBy: [...this.plan.orderBy, { field, direction }],\n })\n }\n\n /** Cap the result size. */\n limit(n: number): Query<T> {\n return new Query<T>(this.source as QuerySource<T>, { ...this.plan, limit: n })\n }\n\n /** Skip the first N matching records (after ordering). */\n offset(n: number): Query<T> {\n return new Query<T>(this.source as QuerySource<T>, { ...this.plan, offset: n })\n }\n\n /** Execute the plan and return the matching records. */\n toArray(): T[] {\n return executePlanWithSource(this.source, this.plan) as T[]\n }\n\n /** Return the first matching record, or null. */\n first(): T | null {\n const result = executePlanWithSource(this.source, { ...this.plan, limit: 1 })\n return (result[0] as T | undefined) ?? null\n }\n\n /** Return the number of matching records (after where/filter, before limit). */\n count(): number {\n // Use the same index-aware candidate machinery as toArray(); skip the\n // index-driving clause from re-evaluation. The length BEFORE limit/offset\n // is what `count()` documents.\n const { candidates, remainingClauses } = candidateRecords(this.source, this.plan.clauses)\n if (remainingClauses.length === 0) return candidates.length\n return filterRecords(candidates, remainingClauses).length\n }\n\n /**\n * Re-run the query whenever the source notifies of changes.\n * Returns an unsubscribe function. The callback receives the latest result.\n * Throws if the source does not support subscriptions.\n */\n subscribe(cb: (result: T[]) => void): () => void {\n if (!this.source.subscribe) {\n throw new Error('Query source does not support subscriptions. Pass a source with a subscribe() method.')\n }\n cb(this.toArray())\n return this.source.subscribe(() => cb(this.toArray()))\n }\n\n /**\n * Return the plan as a JSON-friendly object. FilterClause entries are\n * stripped (their `fn` cannot be serialized) and replaced with\n * { type: 'filter', fn: '[function]' } so devtools can still see them.\n */\n toPlan(): unknown {\n return serializePlan(this.plan)\n }\n}\n\n/**\n * Index-aware execution: try the indexed fast path first, fall back to a\n * full scan otherwise. Mirrors `executePlan` for the public surface but\n * takes a `QuerySource` so it can consult `getIndexes()` and `lookupById()`.\n */\nfunction executePlanWithSource(source: InternalSource, plan: QueryPlan): unknown[] {\n const { candidates, remainingClauses } = candidateRecords(source, plan.clauses)\n // Only the clauses NOT consumed by the index need re-evaluation. This is\n // the key optimization that makes indexed queries dominate linear scans:\n // for a single-clause query against an indexed field, `remainingClauses`\n // is empty and we skip the per-record predicate evaluation entirely.\n let result = remainingClauses.length === 0\n ? [...candidates]\n : filterRecords(candidates, remainingClauses)\n if (plan.orderBy.length > 0) {\n result = sortRecords(result, plan.orderBy)\n }\n if (plan.offset > 0) {\n result = result.slice(plan.offset)\n }\n if (plan.limit !== undefined) {\n result = result.slice(0, plan.limit)\n }\n return result\n}\n\ninterface CandidateResult {\n /** The reduced candidate set, materialized to record objects. */\n readonly candidates: readonly unknown[]\n /** The clauses that the index could not satisfy and must still be evaluated. */\n readonly remainingClauses: readonly Clause[]\n}\n\n/**\n * Pick a candidate record set using the index store when possible.\n *\n * Strategy: scan the top-level clauses for the FIRST `==` or `in` clause\n * against an indexed field. If found, use the index to materialize a\n * candidate set and return the OTHER clauses as `remainingClauses`. The\n * caller skips re-evaluating the index-driving clause because the index\n * is authoritative for that field.\n *\n * This is a deliberately simple planner. A future optimizer could pick\n * the most selective index, intersect multiple indexes, or push composite\n * keys through. For v0.3 the single-index fast path is good enough.\n */\nfunction candidateRecords(source: InternalSource, clauses: readonly Clause[]): CandidateResult {\n const indexes = source.getIndexes?.()\n if (!indexes || !source.lookupById || clauses.length === 0) {\n return { candidates: source.snapshot(), remainingClauses: clauses }\n }\n // Bind the lookup method through an arrow so it doesn't drift from\n // its `this` context — keeps the unbound-method lint rule happy.\n const lookupById = (id: string): unknown => source.lookupById?.(id)\n\n for (let i = 0; i < clauses.length; i++) {\n const clause = clauses[i]!\n if (clause.type !== 'field') continue\n if (!indexes.has(clause.field)) continue\n\n let ids: ReadonlySet<string> | null = null\n if (clause.op === '==') {\n ids = indexes.lookupEqual(clause.field, clause.value)\n } else if (clause.op === 'in' && Array.isArray(clause.value)) {\n ids = indexes.lookupIn(clause.field, clause.value)\n }\n\n if (ids !== null) {\n // Found an index-eligible clause: materialize the candidate set and\n // remove this clause from the remaining list.\n const remaining: Clause[] = []\n for (let j = 0; j < clauses.length; j++) {\n if (j !== i) remaining.push(clauses[j]!)\n }\n return {\n candidates: materializeIds(ids, lookupById),\n remainingClauses: remaining,\n }\n }\n // Not index-eligible — keep scanning in case a later clause is a\n // better candidate.\n }\n\n // No clause was index-eligible — fall back to a full scan.\n return { candidates: source.snapshot(), remainingClauses: clauses }\n}\n\nfunction materializeIds(\n ids: ReadonlySet<string>,\n lookupById: (id: string) => unknown,\n): unknown[] {\n const out: unknown[] = []\n for (const id of ids) {\n const record = lookupById(id)\n if (record !== undefined) out.push(record)\n }\n return out\n}\n\n/**\n * Execute a plan against a snapshot of records.\n * Pure function — same input, same output, no side effects.\n *\n * Records are typed as `unknown` because plans are non-parametric; callers\n * cast the return type at the API surface (see `Query.toArray()`).\n */\nexport function executePlan(records: readonly unknown[], plan: QueryPlan): unknown[] {\n let result = filterRecords(records, plan.clauses)\n if (plan.orderBy.length > 0) {\n result = sortRecords(result, plan.orderBy)\n }\n if (plan.offset > 0) {\n result = result.slice(plan.offset)\n }\n if (plan.limit !== undefined) {\n result = result.slice(0, plan.limit)\n }\n return result\n}\n\nfunction filterRecords(records: readonly unknown[], clauses: readonly Clause[]): unknown[] {\n if (clauses.length === 0) return [...records]\n const out: unknown[] = []\n for (const r of records) {\n let matches = true\n for (const clause of clauses) {\n if (!evaluateClause(r, clause)) {\n matches = false\n break\n }\n }\n if (matches) out.push(r)\n }\n return out\n}\n\nfunction sortRecords(records: unknown[], orderBy: readonly OrderBy[]): unknown[] {\n // Stable sort: Array.prototype.sort is required to be stable since ES2019.\n return [...records].sort((a, b) => {\n for (const { field, direction } of orderBy) {\n const av = readField(a, field)\n const bv = readField(b, field)\n const cmp = compareValues(av, bv)\n if (cmp !== 0) return direction === 'asc' ? cmp : -cmp\n }\n return 0\n })\n}\n\nfunction readField(record: unknown, field: string): unknown {\n if (record === null || record === undefined) return undefined\n if (!field.includes('.')) {\n return (record as Record<string, unknown>)[field]\n }\n const segments = field.split('.')\n let cursor: unknown = record\n for (const segment of segments) {\n if (cursor === null || cursor === undefined) return undefined\n cursor = (cursor as Record<string, unknown>)[segment]\n }\n return cursor\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n // Nullish goes last in asc order.\n if (a === undefined || a === null) return b === undefined || b === null ? 0 : 1\n if (b === undefined || b === null) return -1\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime()\n // Mixed/unsupported types: treat as equal so the sort stays stable.\n // (Deliberate choice — we don't try to coerce arbitrary objects to strings.)\n return 0\n}\n\nfunction serializePlan(plan: QueryPlan): unknown {\n return {\n clauses: plan.clauses.map(serializeClause),\n orderBy: plan.orderBy,\n limit: plan.limit,\n offset: plan.offset,\n }\n}\n\nfunction serializeClause(clause: Clause): unknown {\n if (clause.type === 'filter') {\n return { type: 'filter', fn: '[function]' }\n }\n if (clause.type === 'group') {\n return {\n type: 'group',\n op: clause.op,\n clauses: clause.clauses.map(serializeClause),\n }\n }\n return clause\n}\n","/**\n * Secondary indexes for the query DSL.\n *\n * v0.3 ships **in-memory hash indexes**:\n * - Built during `Collection.ensureHydrated()` from the decrypted cache\n * - Maintained incrementally on `put` and `delete`\n * - Consulted by the query executor for `==` and `in` operators on\n * indexed fields, falling back to a linear scan otherwise\n * - Live entirely in memory — no adapter writes for the index itself\n *\n * Persistent encrypted index blobs (the spec's \"store as a separate\n * AES-256-GCM blob\" note) are deferred to a follow-up issue. The reasons\n * are documented in the v0.3 PR body — short version: at the v0.3 target\n * scale of 1K–50K records, building the index during hydrate is free,\n * so persistence buys nothing measurable.\n */\n\nimport { readPath } from './predicate.js'\n\n/**\n * Index declaration accepted by `Collection`'s constructor.\n *\n * Today only single-field hash indexes are supported. Future shapes\n * (composite, sorted, unique constraints) will land as additive variants\n * of this discriminated union without breaking existing declarations.\n */\nexport type IndexDef = string\n\n/**\n * Internal representation of a built hash index.\n *\n * Maps stringified field values to the set of record ids whose value\n * for that field matches. Stringification keeps the index simple and\n * works uniformly for primitives (`'open'`, `'42'`, `'true'`).\n *\n * Records whose indexed field is `undefined` or `null` are NOT inserted\n * — `query().where('field', '==', undefined)` falls back to a linear\n * scan, which is the conservative behavior.\n */\nexport interface HashIndex {\n readonly field: string\n readonly buckets: Map<string, Set<string>>\n}\n\n/**\n * Container for all indexes on a single collection.\n *\n * Methods are pure with respect to the in-memory `buckets` Map — they\n * never touch the adapter or the keyring. The Collection class owns\n * lifecycle (build on hydrate, maintain on put/delete).\n */\nexport class CollectionIndexes {\n private readonly indexes = new Map<string, HashIndex>()\n\n /**\n * Declare an index. Subsequent record additions are tracked under it.\n * Calling this twice for the same field is a no-op (idempotent).\n */\n declare(field: string): void {\n if (this.indexes.has(field)) return\n this.indexes.set(field, { field, buckets: new Map() })\n }\n\n /** True if the given field has a declared index. */\n has(field: string): boolean {\n return this.indexes.has(field)\n }\n\n /** All declared field names, in declaration order. */\n fields(): string[] {\n return [...this.indexes.keys()]\n }\n\n /**\n * Build all declared indexes from a snapshot of records.\n * Called once per hydration. O(N × indexes.size).\n */\n build<T>(records: ReadonlyArray<{ id: string; record: T }>): void {\n for (const idx of this.indexes.values()) {\n idx.buckets.clear()\n for (const { id, record } of records) {\n addToIndex(idx, id, record)\n }\n }\n }\n\n /**\n * Insert or update a single record across all indexes.\n * Called by `Collection.put()` after the encrypted write succeeds.\n *\n * If `previousRecord` is provided, the record is removed from any old\n * buckets first — this is the update path. Pass `null` for fresh adds.\n */\n upsert<T>(id: string, newRecord: T, previousRecord: T | null): void {\n if (this.indexes.size === 0) return\n if (previousRecord !== null) {\n this.remove(id, previousRecord)\n }\n for (const idx of this.indexes.values()) {\n addToIndex(idx, id, newRecord)\n }\n }\n\n /**\n * Remove a record from all indexes. Called by `Collection.delete()`\n * (and as the first half of `upsert` for the update path).\n */\n remove<T>(id: string, record: T): void {\n if (this.indexes.size === 0) return\n for (const idx of this.indexes.values()) {\n removeFromIndex(idx, id, record)\n }\n }\n\n /** Drop all index data. Called when the collection is invalidated. */\n clear(): void {\n for (const idx of this.indexes.values()) {\n idx.buckets.clear()\n }\n }\n\n /**\n * Equality lookup: return the set of record ids whose `field` matches\n * the given value. Returns `null` if no index covers the field — the\n * caller should fall back to a linear scan.\n *\n * The returned Set is a reference to the index's internal storage —\n * callers must NOT mutate it.\n */\n lookupEqual(field: string, value: unknown): ReadonlySet<string> | null {\n const idx = this.indexes.get(field)\n if (!idx) return null\n const key = stringifyKey(value)\n return idx.buckets.get(key) ?? EMPTY_SET\n }\n\n /**\n * Set lookup: return the union of record ids whose `field` matches any\n * of the given values. Returns `null` if no index covers the field.\n */\n lookupIn(field: string, values: readonly unknown[]): ReadonlySet<string> | null {\n const idx = this.indexes.get(field)\n if (!idx) return null\n const out = new Set<string>()\n for (const value of values) {\n const key = stringifyKey(value)\n const bucket = idx.buckets.get(key)\n if (bucket) {\n for (const id of bucket) out.add(id)\n }\n }\n return out\n }\n}\n\nconst EMPTY_SET: ReadonlySet<string> = new Set()\n\n/**\n * Stringify a value into a stable bucket key.\n *\n * `null`/`undefined` produce a sentinel that records will never match\n * (so we never index nullish values — `where('x', '==', null)` falls back\n * to a linear scan). Numbers, booleans, strings, and Date objects are\n * coerced via `String()`. Objects produce a sentinel that no real record\n * will match — querying with object values is a code smell.\n */\nfunction stringifyKey(value: unknown): string {\n if (value === null || value === undefined) return '\\0NULL\\0'\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n if (value instanceof Date) return value.toISOString()\n return '\\0OBJECT\\0'\n}\n\nfunction addToIndex<T>(idx: HashIndex, id: string, record: T): void {\n const value = readPath(record, idx.field)\n if (value === null || value === undefined) return\n const key = stringifyKey(value)\n let bucket = idx.buckets.get(key)\n if (!bucket) {\n bucket = new Set()\n idx.buckets.set(key, bucket)\n }\n bucket.add(id)\n}\n\nfunction removeFromIndex<T>(idx: HashIndex, id: string, record: T): void {\n const value = readPath(record, idx.field)\n if (value === null || value === undefined) return\n const key = stringifyKey(value)\n const bucket = idx.buckets.get(key)\n if (!bucket) return\n bucket.delete(id)\n // Clean up empty buckets so the Map doesn't accumulate dead keys.\n if (bucket.size === 0) idx.buckets.delete(key)\n}\n","/**\n * Generic LRU cache for `Collection`'s lazy hydration mode.\n *\n * Backed by a JavaScript `Map`, which preserves insertion order. Promotion\n * is implemented as `delete()` + `set()` — O(1) on `Map` since both\n * operations are constant-time. Eviction walks the iterator from the front\n * (least recently used) until both budgets are satisfied.\n *\n * v0.3 ships in-memory only. The cache is never persisted; on collection\n * close every entry is dropped. Persisting cache state is a follow-up\n * once the access patterns from real consumers tell us whether it would\n * pay back the complexity.\n */\n\nexport interface LruEntry<V> {\n /** The cached value. */\n readonly value: V\n /**\n * Approximate decrypted byte size of the entry. Used by the byte-budget\n * eviction path. Callers compute this once at insert time and pass it\n * in — recomputing on every access would dominate the per-record cost.\n */\n readonly size: number\n}\n\nexport interface LruOptions {\n /** Maximum number of entries before eviction. Required if `maxBytes` is unset. */\n maxRecords?: number\n /** Maximum total bytes before eviction. Computed from per-entry `size`. */\n maxBytes?: number\n}\n\nexport interface LruStats {\n /** Total cache hits since construction (or `resetStats()`). */\n hits: number\n /** Total cache misses since construction (or `resetStats()`). */\n misses: number\n /** Total entries evicted since construction (or `resetStats()`). */\n evictions: number\n /** Current number of cached entries. */\n size: number\n /** Current sum of cached entry sizes (in bytes, approximate). */\n bytes: number\n}\n\n/**\n * O(1) LRU cache. Both `get()` and `set()` promote the touched entry to\n * the most-recently-used end. Eviction happens after every insert and\n * walks the front of the Map iterator dropping entries until both\n * budgets are satisfied.\n */\nexport class Lru<K, V> {\n private readonly entries = new Map<K, LruEntry<V>>()\n private readonly maxRecords: number | undefined\n private readonly maxBytes: number | undefined\n private currentBytes = 0\n private hits = 0\n private misses = 0\n private evictions = 0\n\n constructor(options: LruOptions) {\n if (options.maxRecords === undefined && options.maxBytes === undefined) {\n throw new Error('Lru: must specify maxRecords, maxBytes, or both')\n }\n this.maxRecords = options.maxRecords\n this.maxBytes = options.maxBytes\n }\n\n /**\n * Look up a key. Hits promote the entry to most-recently-used; misses\n * return undefined. Both update the running stats counters.\n */\n get(key: K): V | undefined {\n const entry = this.entries.get(key)\n if (!entry) {\n this.misses++\n return undefined\n }\n // Promote: re-insert moves the entry to the end of the iteration order.\n this.entries.delete(key)\n this.entries.set(key, entry)\n this.hits++\n return entry.value\n }\n\n /**\n * Insert or update a key. If the key already exists, its size is\n * accounted for and the entry is promoted to MRU. After insertion,\n * eviction runs to maintain both budgets.\n */\n set(key: K, value: V, size: number): void {\n const existing = this.entries.get(key)\n if (existing) {\n // Update path: subtract the old size before adding the new one.\n this.currentBytes -= existing.size\n this.entries.delete(key)\n }\n this.entries.set(key, { value, size })\n this.currentBytes += size\n this.evictUntilUnderBudget()\n }\n\n /**\n * Remove a key without affecting hit/miss stats. Used by `Collection.delete()`.\n * Returns true if the key was present.\n */\n remove(key: K): boolean {\n const existing = this.entries.get(key)\n if (!existing) return false\n this.currentBytes -= existing.size\n this.entries.delete(key)\n return true\n }\n\n /** True if the cache currently holds an entry for the given key. */\n has(key: K): boolean {\n return this.entries.has(key)\n }\n\n /**\n * Drop every entry. Stats counters survive — call `resetStats()` if you\n * want a clean slate. Used by `Collection.invalidate()` on key rotation.\n */\n clear(): void {\n this.entries.clear()\n this.currentBytes = 0\n }\n\n /** Reset hit/miss/eviction counters to zero. Does NOT touch entries. */\n resetStats(): void {\n this.hits = 0\n this.misses = 0\n this.evictions = 0\n }\n\n /** Snapshot of current cache statistics. Cheap — no copying. */\n stats(): LruStats {\n return {\n hits: this.hits,\n misses: this.misses,\n evictions: this.evictions,\n size: this.entries.size,\n bytes: this.currentBytes,\n }\n }\n\n /**\n * Iterate over all currently-cached values. Order is least-recently-used\n * first. Used by tests and devtools — production callers should use\n * `Collection.scan()` instead.\n */\n *values(): IterableIterator<V> {\n for (const entry of this.entries.values()) yield entry.value\n }\n\n /**\n * Walk the cache from the LRU end and drop entries until both budgets\n * are satisfied. Called after every `set()`. Single pass — entries are\n * never re-promoted during eviction.\n */\n private evictUntilUnderBudget(): void {\n while (this.overBudget()) {\n const oldest = this.entries.keys().next()\n if (oldest.done) return // empty cache; nothing more to evict\n const key = oldest.value\n const entry = this.entries.get(key)\n if (entry) this.currentBytes -= entry.size\n this.entries.delete(key)\n this.evictions++\n }\n }\n\n private overBudget(): boolean {\n if (this.maxRecords !== undefined && this.entries.size > this.maxRecords) return true\n if (this.maxBytes !== undefined && this.currentBytes > this.maxBytes) return true\n return false\n }\n}\n","/**\n * Cache policy helpers — parse human-friendly byte budgets into raw numbers.\n *\n * Accepted shapes (case-insensitive on suffix):\n * number — interpreted as raw bytes\n * '1024' — string of digits, raw bytes\n * '50KB' — kilobytes (×1024)\n * '50MB' — megabytes (×1024²)\n * '1GB' — gigabytes (×1024³)\n *\n * Decimals are accepted (`'1.5GB'` → 1610612736 bytes).\n *\n * Anything else throws — better to fail loud at construction time than\n * to silently treat a typo as 0 bytes (which would evict everything).\n */\n\nconst UNITS: Record<string, number> = {\n '': 1,\n 'B': 1,\n 'KB': 1024,\n 'MB': 1024 * 1024,\n 'GB': 1024 * 1024 * 1024,\n // 'TB' deliberately not supported — if you need it, you're not using NOYDB.\n}\n\n/** Parse a byte budget into a positive integer number of bytes. */\nexport function parseBytes(input: number | string): number {\n if (typeof input === 'number') {\n if (!Number.isFinite(input) || input <= 0) {\n throw new Error(`parseBytes: numeric input must be a positive finite number, got ${String(input)}`)\n }\n return Math.floor(input)\n }\n\n const trimmed = input.trim()\n if (trimmed === '') {\n throw new Error('parseBytes: empty string is not a valid byte budget')\n }\n\n // Accept either a bare number or a number followed by a unit suffix.\n // Regex: optional sign, digits with optional decimal, optional unit.\n const match = /^([0-9]+(?:\\.[0-9]+)?)\\s*([A-Za-z]*)$/.exec(trimmed)\n if (!match) {\n throw new Error(`parseBytes: invalid byte budget \"${input}\". Expected format: \"1024\", \"50KB\", \"50MB\", \"1GB\"`)\n }\n\n const value = parseFloat(match[1]!)\n const unit = (match[2] ?? '').toUpperCase()\n\n if (!(unit in UNITS)) {\n throw new Error(`parseBytes: unknown unit \"${match[2]}\" in \"${input}\". Supported: B, KB, MB, GB`)\n }\n\n const bytes = Math.floor(value * UNITS[unit]!)\n if (bytes <= 0) {\n throw new Error(`parseBytes: byte budget must be > 0, got ${bytes} from \"${input}\"`)\n }\n return bytes\n}\n\n/**\n * Estimate the in-memory byte size of a decrypted record.\n *\n * Uses `JSON.stringify().length` as a stand-in for actual heap usage.\n * It's a deliberate approximation: real V8 heap size includes pointer\n * overhead, hidden classes, and string interning that we can't measure\n * from JavaScript. The JSON length is a stable, monotonic proxy that\n * costs O(record size) per insert — fine when records are typically\n * < 1 KB and the cache eviction is the slow path anyway.\n *\n * Returns `0` (and the caller must treat it as 1 for accounting) if\n * stringification throws on circular references; this is documented\n * but in practice records always come from JSON-decoded envelopes.\n */\nexport function estimateRecordBytes(record: unknown): number {\n try {\n return JSON.stringify(record).length\n } catch {\n return 0\n }\n}\n","import type { NoydbAdapter, EncryptedEnvelope, ChangeEvent, HistoryConfig, HistoryOptions, HistoryEntry, PruneOptions, ListPageResult } from './types.js'\nimport { NOYDB_FORMAT_VERSION } from './types.js'\nimport { encrypt, decrypt } from './crypto.js'\nimport { ReadOnlyError } from './errors.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { hasWritePermission } from './keyring.js'\nimport type { NoydbEventEmitter } from './events.js'\nimport {\n saveHistory,\n getHistory as getHistoryEntries,\n getVersionEnvelope,\n pruneHistory as pruneHistoryEntries,\n clearHistory,\n} from './history.js'\nimport { diff as computeDiff } from './diff.js'\nimport type { DiffEntry } from './diff.js'\nimport { Query } from './query/index.js'\nimport type { QuerySource } from './query/index.js'\nimport { CollectionIndexes, type IndexDef } from './query/indexes.js'\nimport { Lru, parseBytes, estimateRecordBytes, type LruStats } from './cache/index.js'\n\n/** Callback for dirty tracking (sync engine integration). */\nexport type OnDirtyCallback = (collection: string, id: string, action: 'put' | 'delete', version: number) => Promise<void>\n\n/**\n * Per-collection cache configuration. Only meaningful when paired with\n * `prefetch: false` (lazy mode); eager mode keeps the entire decrypted\n * cache in memory and ignores these bounds.\n */\nexport interface CacheOptions {\n /** Maximum number of records to keep in memory before LRU eviction. */\n maxRecords?: number\n /**\n * Maximum total decrypted byte size before LRU eviction. Accepts a raw\n * number or a human-friendly string: `'50KB'`, `'50MB'`, `'1GB'`.\n * Eviction picks the least-recently-used entry until both budgets\n * (maxRecords AND maxBytes, if both are set) are satisfied.\n */\n maxBytes?: number | string\n}\n\n/** Statistics exposed via `Collection.cacheStats()`. */\nexport interface CacheStats extends LruStats {\n /** True if this collection is in lazy mode. */\n lazy: boolean\n}\n\n/**\n * Track which adapter names have already triggered the listPage fallback\n * warning. We only emit once per adapter per process so consumers see the\n * heads-up without log spam.\n */\nconst fallbackWarned = new Set<string>()\nfunction warnOnceFallback(adapterName: string): void {\n if (fallbackWarned.has(adapterName)) return\n fallbackWarned.add(adapterName)\n // Only warn in non-test environments — vitest runs are noisy enough.\n if (typeof process !== 'undefined' && process.env['NODE_ENV'] === 'test') return\n // eslint-disable-next-line no-console\n console.warn(\n `[noy-db] Adapter \"${adapterName}\" does not implement listPage(); ` +\n `Collection.scan()/listPage() are using a synthetic fallback (slower). ` +\n `Add a listPage method to opt into the streaming fast path.`,\n )\n}\n\n/** A typed collection of records within a compartment. */\nexport class Collection<T> {\n private readonly adapter: NoydbAdapter\n private readonly compartment: string\n private readonly name: string\n private readonly keyring: UnlockedKeyring\n private readonly encrypted: boolean\n private readonly emitter: NoydbEventEmitter\n private readonly getDEK: (collectionName: string) => Promise<CryptoKey>\n private readonly onDirty: OnDirtyCallback | undefined\n private readonly historyConfig: HistoryConfig\n\n // In-memory cache of decrypted records (eager mode only). Lazy mode\n // uses `lru` instead. Both fields exist so a single Collection instance\n // doesn't need a runtime branch on every cache access.\n private readonly cache = new Map<string, { record: T; version: number }>()\n private hydrated = false\n\n /**\n * Lazy mode flag. `true` when constructed with `prefetch: false`.\n * In lazy mode the cache is bounded by an LRU and `list()`/`query()`\n * throw — callers must use `scan()` or per-id `get()` instead.\n */\n private readonly lazy: boolean\n\n /**\n * LRU cache for lazy mode. Only allocated when `prefetch: false` is set.\n * Stores `{ record, version }` entries the same shape as `this.cache`.\n * Tree-shaking note: importing Collection without setting `prefetch:false`\n * still pulls in the Lru class today; future bundle-size work could\n * lazy-import the cache module.\n */\n private readonly lru: Lru<string, { record: T; version: number }> | null\n\n /**\n * In-memory secondary indexes for the query DSL.\n *\n * Built during `ensureHydrated()` and maintained on every put/delete.\n * The query executor consults these for `==` and `in` operators on\n * indexed fields, falling back to a linear scan for unindexed fields\n * or unsupported operators.\n *\n * v0.3 ships in-memory only — persistence as encrypted blobs is a\n * follow-up. See `query/indexes.ts` for the design rationale.\n *\n * Indexes are INCOMPATIBLE with lazy mode in v0.3 — the constructor\n * rejects the combination because evicted records would silently\n * disappear from the index without notification.\n */\n private readonly indexes = new CollectionIndexes()\n\n constructor(opts: {\n adapter: NoydbAdapter\n compartment: string\n name: string\n keyring: UnlockedKeyring\n encrypted: boolean\n emitter: NoydbEventEmitter\n getDEK: (collectionName: string) => Promise<CryptoKey>\n historyConfig?: HistoryConfig | undefined\n onDirty?: OnDirtyCallback | undefined\n indexes?: IndexDef[] | undefined\n /**\n * Hydration mode. `'eager'` (default) loads everything into memory on\n * first access — matches v0.2 behavior exactly. `'lazy'` defers loads\n * to per-id `get()` calls and bounds memory via the `cache` option.\n */\n prefetch?: boolean\n /**\n * LRU cache options. Only meaningful when `prefetch: false`. At least\n * one of `maxRecords` or `maxBytes` must be set in lazy mode — an\n * unbounded lazy cache defeats the purpose.\n */\n cache?: CacheOptions | undefined\n }) {\n this.adapter = opts.adapter\n this.compartment = opts.compartment\n this.name = opts.name\n this.keyring = opts.keyring\n this.encrypted = opts.encrypted\n this.emitter = opts.emitter\n this.getDEK = opts.getDEK\n this.onDirty = opts.onDirty\n this.historyConfig = opts.historyConfig ?? { enabled: true }\n\n // Default `prefetch: true` keeps v0.2 semantics. Only opt-in to lazy\n // mode when the consumer explicitly sets `prefetch: false`.\n this.lazy = opts.prefetch === false\n\n if (this.lazy) {\n // Lazy mode is incompatible with eager-cache features. Reject the\n // combinations early so users see the error at construction time\n // rather than at first query.\n if (opts.indexes && opts.indexes.length > 0) {\n throw new Error(\n `Collection \"${this.name}\": secondary indexes are not supported in lazy mode (prefetch: false). ` +\n `Either remove the indexes option or use prefetch: true. ` +\n `Index + lazy support is tracked as a v0.4 follow-up.`,\n )\n }\n if (!opts.cache || (opts.cache.maxRecords === undefined && opts.cache.maxBytes === undefined)) {\n throw new Error(\n `Collection \"${this.name}\": lazy mode (prefetch: false) requires a cache option ` +\n `with maxRecords and/or maxBytes. An unbounded lazy cache defeats the purpose.`,\n )\n }\n const lruOptions: { maxRecords?: number; maxBytes?: number } = {}\n if (opts.cache.maxRecords !== undefined) lruOptions.maxRecords = opts.cache.maxRecords\n if (opts.cache.maxBytes !== undefined) lruOptions.maxBytes = parseBytes(opts.cache.maxBytes)\n this.lru = new Lru<string, { record: T; version: number }>(lruOptions)\n this.hydrated = true // lazy mode is always \"hydrated\" — no bulk load\n } else {\n this.lru = null\n if (opts.indexes) {\n for (const def of opts.indexes) {\n this.indexes.declare(def)\n }\n }\n }\n }\n\n /** Get a single record by ID. Returns null if not found. */\n async get(id: string): Promise<T | null> {\n if (this.lazy && this.lru) {\n // Cache hit: promote and return.\n const cached = this.lru.get(id)\n if (cached) return cached.record\n // Cache miss: hit the adapter, decrypt, populate the LRU.\n const envelope = await this.adapter.get(this.compartment, this.name, id)\n if (!envelope) return null\n const record = await this.decryptRecord(envelope)\n this.lru.set(id, { record, version: envelope._v }, estimateRecordBytes(record))\n return record\n }\n\n // Eager mode: load everything once, then serve from the in-memory map.\n await this.ensureHydrated()\n const entry = this.cache.get(id)\n return entry ? entry.record : null\n }\n\n /** Create or update a record. */\n async put(id: string, record: T): Promise<void> {\n if (!hasWritePermission(this.keyring, this.name)) {\n throw new ReadOnlyError()\n }\n\n // Resolve the previous record. In eager mode this comes from the\n // in-memory map (no I/O); in lazy mode we have to ask the adapter\n // because the record may have been evicted (or never loaded).\n let existing: { record: T; version: number } | undefined\n if (this.lazy && this.lru) {\n existing = this.lru.get(id)\n if (!existing) {\n const previousEnvelope = await this.adapter.get(this.compartment, this.name, id)\n if (previousEnvelope) {\n const previousRecord = await this.decryptRecord(previousEnvelope)\n existing = { record: previousRecord, version: previousEnvelope._v }\n }\n }\n } else {\n await this.ensureHydrated()\n existing = this.cache.get(id)\n }\n\n const version = existing ? existing.version + 1 : 1\n\n // Save history snapshot of the PREVIOUS version before overwriting\n if (existing && this.historyConfig.enabled !== false) {\n const historyEnvelope = await this.encryptRecord(existing.record, existing.version)\n await saveHistory(this.adapter, this.compartment, this.name, id, historyEnvelope)\n\n this.emitter.emit('history:save', {\n compartment: this.compartment,\n collection: this.name,\n id,\n version: existing.version,\n })\n\n // Auto-prune if maxVersions configured\n if (this.historyConfig.maxVersions) {\n await pruneHistoryEntries(this.adapter, this.compartment, this.name, id, {\n keepVersions: this.historyConfig.maxVersions,\n })\n }\n }\n\n const envelope = await this.encryptRecord(record, version)\n await this.adapter.put(this.compartment, this.name, id, envelope)\n\n if (this.lazy && this.lru) {\n this.lru.set(id, { record, version }, estimateRecordBytes(record))\n } else {\n this.cache.set(id, { record, version })\n // Update secondary indexes incrementally — no-op if no indexes are\n // declared. Pass the previous record (if any) so old buckets are\n // cleaned up before the new value is added. Indexes are NEVER\n // touched in lazy mode (rejected at construction).\n this.indexes.upsert(id, record, existing ? existing.record : null)\n }\n\n await this.onDirty?.(this.name, id, 'put', version)\n\n this.emitter.emit('change', {\n compartment: this.compartment,\n collection: this.name,\n id,\n action: 'put',\n } satisfies ChangeEvent)\n }\n\n /** Delete a record by ID. */\n async delete(id: string): Promise<void> {\n if (!hasWritePermission(this.keyring, this.name)) {\n throw new ReadOnlyError()\n }\n\n // In lazy mode the record may not be cached; ask the adapter so we\n // can still write a history snapshot if history is enabled.\n let existing: { record: T; version: number } | undefined\n if (this.lazy && this.lru) {\n existing = this.lru.get(id)\n if (!existing && this.historyConfig.enabled !== false) {\n const previousEnvelope = await this.adapter.get(this.compartment, this.name, id)\n if (previousEnvelope) {\n const previousRecord = await this.decryptRecord(previousEnvelope)\n existing = { record: previousRecord, version: previousEnvelope._v }\n }\n }\n } else {\n existing = this.cache.get(id)\n }\n\n // Save history snapshot before deleting\n if (existing && this.historyConfig.enabled !== false) {\n const historyEnvelope = await this.encryptRecord(existing.record, existing.version)\n await saveHistory(this.adapter, this.compartment, this.name, id, historyEnvelope)\n }\n\n await this.adapter.delete(this.compartment, this.name, id)\n\n if (this.lazy && this.lru) {\n this.lru.remove(id)\n } else {\n this.cache.delete(id)\n // Remove from secondary indexes — no-op if no indexes are declared\n // or the record wasn't previously indexed. Indexes are never\n // declared in lazy mode (rejected at construction).\n if (existing) {\n this.indexes.remove(id, existing.record)\n }\n }\n\n await this.onDirty?.(this.name, id, 'delete', existing?.version ?? 0)\n\n this.emitter.emit('change', {\n compartment: this.compartment,\n collection: this.name,\n id,\n action: 'delete',\n } satisfies ChangeEvent)\n }\n\n /**\n * List all records in the collection.\n *\n * Throws in lazy mode — bulk listing defeats the purpose of lazy\n * hydration. Use `scan()` to iterate over the full collection\n * page-by-page without holding more than `pageSize` records in memory.\n */\n async list(): Promise<T[]> {\n if (this.lazy) {\n throw new Error(\n `Collection \"${this.name}\": list() is not available in lazy mode (prefetch: false). ` +\n `Use collection.scan({ pageSize }) to iterate over the full collection.`,\n )\n }\n await this.ensureHydrated()\n return [...this.cache.values()].map(e => e.record)\n }\n\n /**\n * Build a chainable query against the collection. Returns a `Query<T>`\n * builder when called with no arguments.\n *\n * Backward-compatible overload: passing a predicate function returns\n * the filtered records directly (the v0.2 API). Prefer the chainable\n * form for new code.\n *\n * @example\n * ```ts\n * // New chainable API:\n * const overdue = invoices.query()\n * .where('status', '==', 'open')\n * .where('dueDate', '<', new Date())\n * .orderBy('dueDate')\n * .toArray();\n *\n * // Legacy predicate form (still supported):\n * const drafts = invoices.query(i => i.status === 'draft');\n * ```\n */\n query(): Query<T>\n query(predicate: (record: T) => boolean): T[]\n query(predicate?: (record: T) => boolean): Query<T> | T[] {\n if (this.lazy) {\n throw new Error(\n `Collection \"${this.name}\": query() is not available in lazy mode (prefetch: false). ` +\n `Use collection.scan({ pageSize }) and filter the streamed records with a regular ` +\n `for-await loop. Streaming queries land in v0.4.`,\n )\n }\n if (predicate !== undefined) {\n // Legacy form: synchronous predicate filter against the cache.\n return [...this.cache.values()].map(e => e.record).filter(predicate)\n }\n // New form: return a chainable builder bound to this collection's cache.\n const source: QuerySource<T> = {\n snapshot: () => [...this.cache.values()].map(e => e.record),\n subscribe: (cb: () => void) => {\n const handler = (event: ChangeEvent): void => {\n if (event.compartment === this.compartment && event.collection === this.name) {\n cb()\n }\n }\n this.emitter.on('change', handler)\n return () => this.emitter.off('change', handler)\n },\n // Index-aware fast path for `==` and `in` operators on indexed\n // fields. The Query builder consults these when present and falls\n // back to a linear scan otherwise.\n getIndexes: () => this.getIndexes(),\n lookupById: (id: string) => this.cache.get(id)?.record,\n }\n return new Query<T>(source)\n }\n\n /**\n * Cache statistics — useful for devtools, monitoring, and verifying\n * that LRU eviction is happening as expected in lazy mode.\n *\n * In eager mode, returns size only (no hits/misses are tracked because\n * every read is a cache hit by construction). In lazy mode, returns\n * the full LRU stats: `{ hits, misses, evictions, size, bytes }`.\n */\n cacheStats(): CacheStats {\n if (this.lazy && this.lru) {\n return { ...this.lru.stats(), lazy: true }\n }\n return {\n hits: 0,\n misses: 0,\n evictions: 0,\n size: this.cache.size,\n bytes: 0,\n lazy: false,\n }\n }\n\n // ─── History Methods ────────────────────────────────────────────\n\n /** Get version history for a record, newest first. */\n async history(id: string, options?: HistoryOptions): Promise<HistoryEntry<T>[]> {\n const envelopes = await getHistoryEntries(\n this.adapter, this.compartment, this.name, id, options,\n )\n\n const entries: HistoryEntry<T>[] = []\n for (const env of envelopes) {\n const record = await this.decryptRecord(env)\n entries.push({\n version: env._v,\n timestamp: env._ts,\n userId: env._by ?? '',\n record,\n })\n }\n return entries\n }\n\n /** Get a specific past version of a record. */\n async getVersion(id: string, version: number): Promise<T | null> {\n const envelope = await getVersionEnvelope(\n this.adapter, this.compartment, this.name, id, version,\n )\n if (!envelope) return null\n return this.decryptRecord(envelope)\n }\n\n /** Revert a record to a past version. Creates a new version with the old content. */\n async revert(id: string, version: number): Promise<void> {\n const oldRecord = await this.getVersion(id, version)\n if (!oldRecord) {\n throw new Error(`Version ${version} not found for record \"${id}\"`)\n }\n await this.put(id, oldRecord)\n }\n\n /**\n * Compare two versions of a record and return the differences.\n * Use version 0 to represent \"before creation\" (empty).\n * Omit versionB to compare against the current version.\n */\n async diff(id: string, versionA: number, versionB?: number): Promise<DiffEntry[]> {\n const recordA = versionA === 0 ? null : await this.resolveVersion(id, versionA)\n const recordB = versionB === undefined || versionB === 0\n ? (versionB === 0 ? null : await this.resolveCurrentOrVersion(id))\n : await this.resolveVersion(id, versionB)\n return computeDiff(recordA, recordB)\n }\n\n /** Resolve a version: try history first, then check if it's the current version. */\n private async resolveVersion(id: string, version: number): Promise<T | null> {\n // Check history\n const fromHistory = await this.getVersion(id, version)\n if (fromHistory) return fromHistory\n // Check if it's the current live version\n await this.ensureHydrated()\n const current = this.cache.get(id)\n if (current && current.version === version) return current.record\n return null\n }\n\n private async resolveCurrentOrVersion(id: string): Promise<T | null> {\n await this.ensureHydrated()\n return this.cache.get(id)?.record ?? null\n }\n\n /** Prune history entries for a record (or all records if id is undefined). */\n async pruneRecordHistory(id: string | undefined, options: PruneOptions): Promise<number> {\n const pruned = await pruneHistoryEntries(\n this.adapter, this.compartment, this.name, id, options,\n )\n if (pruned > 0) {\n this.emitter.emit('history:prune', {\n compartment: this.compartment,\n collection: this.name,\n id: id ?? '*',\n pruned,\n })\n }\n return pruned\n }\n\n /** Clear all history for this collection (or a specific record). */\n async clearHistory(id?: string): Promise<number> {\n return clearHistory(this.adapter, this.compartment, this.name, id)\n }\n\n // ─── Core Methods ─────────────────────────────────────────────\n\n /**\n * Count records in the collection.\n *\n * In eager mode this returns the in-memory cache size (instant). In\n * lazy mode it asks the adapter via `list()` to enumerate ids — slower\n * but still correct, and avoids loading any record bodies into memory.\n */\n async count(): Promise<number> {\n if (this.lazy) {\n const ids = await this.adapter.list(this.compartment, this.name)\n return ids.length\n }\n await this.ensureHydrated()\n return this.cache.size\n }\n\n // ─── Pagination & Streaming ───────────────────────────────────\n\n /**\n * Fetch a single page of records via the adapter's optional `listPage`\n * extension. Returns the decrypted records for this page plus an opaque\n * cursor for the next page.\n *\n * Pass `cursor: undefined` (or omit it) to start from the beginning.\n * The final page returns `nextCursor: null`.\n *\n * If the adapter does NOT implement `listPage`, this falls back to a\n * synthetic implementation: it loads all ids via `list()`, sorts them,\n * and slices a window. The first call emits a one-time console.warn so\n * developers can spot adapters that should opt into the fast path.\n */\n async listPage(opts: { cursor?: string; limit?: number } = {}): Promise<{\n items: T[]\n nextCursor: string | null\n }> {\n const limit = opts.limit ?? 100\n\n if (this.adapter.listPage) {\n const result = await this.adapter.listPage(this.compartment, this.name, opts.cursor, limit)\n const decrypted: T[] = []\n for (const { record, version, id } of await this.decryptPage(result.items)) {\n // Update cache opportunistically — if the page-fetched record isn't\n // in cache yet, populate it. This makes a subsequent .get(id) free.\n // In LAZY mode we deliberately do NOT populate the LRU here:\n // streaming a 100K-record collection should not turn the LRU into\n // a giant write-once buffer that immediately evicts everything.\n // Random-access workloads via .get() are what the LRU is for.\n if (!this.lazy && !this.cache.has(id)) {\n this.cache.set(id, { record, version })\n }\n decrypted.push(record)\n }\n return { items: decrypted, nextCursor: result.nextCursor }\n }\n\n // Fallback: synthetic pagination over list() + get(). Slower than the\n // native path because every id requires its own round-trip, but\n // correct for adapters that haven't opted in.\n warnOnceFallback(this.adapter.name ?? 'unknown')\n const ids = (await this.adapter.list(this.compartment, this.name)).slice().sort()\n const start = opts.cursor ? parseInt(opts.cursor, 10) : 0\n const end = Math.min(start + limit, ids.length)\n const items: T[] = []\n for (let i = start; i < end; i++) {\n const id = ids[i]!\n const envelope = await this.adapter.get(this.compartment, this.name, id)\n if (envelope) {\n const record = await this.decryptRecord(envelope)\n items.push(record)\n // Same lazy-mode skip as the native path: don't pollute the LRU\n // with sequential scan results.\n if (!this.lazy && !this.cache.has(id)) {\n this.cache.set(id, { record, version: envelope._v })\n }\n }\n }\n return {\n items,\n nextCursor: end < ids.length ? String(end) : null,\n }\n }\n\n /**\n * Stream every record in the collection page-by-page, yielding decrypted\n * records as an `AsyncIterable<T>`. The whole point: process collections\n * larger than RAM without ever holding more than `pageSize` records\n * decrypted at once.\n *\n * @example\n * ```ts\n * for await (const record of invoices.scan({ pageSize: 500 })) {\n * await processOne(record)\n * }\n * ```\n *\n * Uses `adapter.listPage` when available; otherwise falls back to the\n * synthetic pagination path with the same one-time warning.\n */\n async *scan(opts: { pageSize?: number } = {}): AsyncIterableIterator<T> {\n const pageSize = opts.pageSize ?? 100\n // Start with no cursor (first page) and walk forward until the\n // adapter signals exhaustion via nextCursor === null.\n let page: { items: T[]; nextCursor: string | null } = await this.listPage({ limit: pageSize })\n while (true) {\n for (const item of page.items) {\n yield item\n }\n if (page.nextCursor === null) return\n page = await this.listPage({ cursor: page.nextCursor, limit: pageSize })\n }\n }\n\n /** Decrypt a page of envelopes returned by `adapter.listPage`. */\n private async decryptPage(\n items: ListPageResult['items'],\n ): Promise<Array<{ id: string; record: T; version: number }>> {\n const out: Array<{ id: string; record: T; version: number }> = []\n for (const { id, envelope } of items) {\n const record = await this.decryptRecord(envelope)\n out.push({ id, record, version: envelope._v })\n }\n return out\n }\n\n // ─── Internal ──────────────────────────────────────────────────\n\n /** Load all records from adapter into memory cache. */\n private async ensureHydrated(): Promise<void> {\n if (this.hydrated) return\n\n const ids = await this.adapter.list(this.compartment, this.name)\n for (const id of ids) {\n const envelope = await this.adapter.get(this.compartment, this.name, id)\n if (envelope) {\n const record = await this.decryptRecord(envelope)\n this.cache.set(id, { record, version: envelope._v })\n }\n }\n this.hydrated = true\n this.rebuildIndexes()\n }\n\n /** Hydrate from a pre-loaded snapshot (used by Compartment). */\n async hydrateFromSnapshot(records: Record<string, EncryptedEnvelope>): Promise<void> {\n for (const [id, envelope] of Object.entries(records)) {\n const record = await this.decryptRecord(envelope)\n this.cache.set(id, { record, version: envelope._v })\n }\n this.hydrated = true\n this.rebuildIndexes()\n }\n\n /**\n * Rebuild secondary indexes from the current in-memory cache.\n *\n * Called after any bulk hydration. Incremental put/delete updates\n * are handled by `indexes.upsert()` / `indexes.remove()` directly,\n * so this only fires for full reloads.\n *\n * Synchronous and O(N × indexes.size); for the v0.3 target scale of\n * 1K–50K records this completes in single-digit milliseconds.\n */\n private rebuildIndexes(): void {\n if (this.indexes.fields().length === 0) return\n const snapshot: Array<{ id: string; record: T }> = []\n for (const [id, entry] of this.cache) {\n snapshot.push({ id, record: entry.record })\n }\n this.indexes.build(snapshot)\n }\n\n /**\n * Get the in-memory index store. Used by `Query` to short-circuit\n * `==` and `in` lookups when an index covers the where clause.\n *\n * Returns `null` if no indexes are declared on this collection.\n */\n getIndexes(): CollectionIndexes | null {\n return this.indexes.fields().length > 0 ? this.indexes : null\n }\n\n /** Get all records as encrypted envelopes (for dump). */\n async dumpEnvelopes(): Promise<Record<string, EncryptedEnvelope>> {\n await this.ensureHydrated()\n const result: Record<string, EncryptedEnvelope> = {}\n for (const [id, entry] of this.cache) {\n result[id] = await this.encryptRecord(entry.record, entry.version)\n }\n return result\n }\n\n private async encryptRecord(record: T, version: number): Promise<EncryptedEnvelope> {\n const json = JSON.stringify(record)\n const by = this.keyring.userId\n\n if (!this.encrypted) {\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: json,\n _by: by,\n }\n }\n\n const dek = await this.getDEK(this.name)\n const { iv, data } = await encrypt(json, dek)\n\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _by: by,\n }\n }\n\n private async decryptRecord(envelope: EncryptedEnvelope): Promise<T> {\n if (!this.encrypted) {\n return JSON.parse(envelope._data) as T\n }\n\n const dek = await this.getDEK(this.name)\n const json = await decrypt(envelope._iv, envelope._data, dek)\n return JSON.parse(json) as T\n }\n}\n","import type { NoydbAdapter, CompartmentBackup, HistoryConfig } from './types.js'\nimport { NOYDB_BACKUP_VERSION } from './types.js'\nimport { Collection } from './collection.js'\nimport type { CacheOptions } from './collection.js'\nimport type { IndexDef } from './query/indexes.js'\nimport type { OnDirtyCallback } from './collection.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { ensureCollectionDEK } from './keyring.js'\nimport type { NoydbEventEmitter } from './events.js'\nimport { PermissionDeniedError } from './errors.js'\n\n/** A compartment (tenant namespace) containing collections. */\nexport class Compartment {\n private readonly adapter: NoydbAdapter\n private readonly name: string\n private readonly keyring: UnlockedKeyring\n private readonly encrypted: boolean\n private readonly emitter: NoydbEventEmitter\n private readonly onDirty: OnDirtyCallback | undefined\n private readonly historyConfig: HistoryConfig\n private readonly getDEK: (collectionName: string) => Promise<CryptoKey>\n private readonly collectionCache = new Map<string, Collection<unknown>>()\n\n constructor(opts: {\n adapter: NoydbAdapter\n name: string\n keyring: UnlockedKeyring\n encrypted: boolean\n emitter: NoydbEventEmitter\n onDirty?: OnDirtyCallback | undefined\n historyConfig?: HistoryConfig | undefined\n }) {\n this.adapter = opts.adapter\n this.name = opts.name\n this.keyring = opts.keyring\n this.encrypted = opts.encrypted\n this.emitter = opts.emitter\n this.onDirty = opts.onDirty\n this.historyConfig = opts.historyConfig ?? { enabled: true }\n\n // Create the DEK resolver (lazy — generates DEKs on first use)\n // We need to store the promise to avoid recreating it\n let getDEKFn: ((collectionName: string) => Promise<CryptoKey>) | null = null\n this.getDEK = async (collectionName: string): Promise<CryptoKey> => {\n if (!getDEKFn) {\n getDEKFn = await ensureCollectionDEK(this.adapter, this.name, this.keyring)\n }\n return getDEKFn(collectionName)\n }\n }\n\n /**\n * Open a typed collection within this compartment.\n *\n * - `options.indexes` declares secondary indexes for the query DSL.\n * Indexes are computed in memory after decryption; adapters never\n * see plaintext index data.\n * - `options.prefetch` (default `true`) controls hydration. Eager mode\n * loads everything on first access; lazy mode (`prefetch: false`)\n * loads records on demand and bounds memory via the LRU cache.\n * - `options.cache` configures the LRU bounds. Required in lazy mode.\n * Accepts `{ maxRecords, maxBytes: '50MB' | 1024 }`.\n *\n * Lazy mode + indexes is rejected at construction time — see the\n * Collection constructor for the rationale.\n */\n collection<T>(collectionName: string, options?: {\n indexes?: IndexDef[]\n prefetch?: boolean\n cache?: CacheOptions\n }): Collection<T> {\n let coll = this.collectionCache.get(collectionName)\n if (!coll) {\n const collOpts: ConstructorParameters<typeof Collection>[0] = {\n adapter: this.adapter,\n compartment: this.name,\n name: collectionName,\n keyring: this.keyring,\n encrypted: this.encrypted,\n emitter: this.emitter,\n getDEK: this.getDEK,\n onDirty: this.onDirty,\n historyConfig: this.historyConfig,\n }\n if (options?.indexes !== undefined) collOpts.indexes = options.indexes\n if (options?.prefetch !== undefined) collOpts.prefetch = options.prefetch\n if (options?.cache !== undefined) collOpts.cache = options.cache\n coll = new Collection<T>(collOpts)\n this.collectionCache.set(collectionName, coll)\n }\n return coll as Collection<T>\n }\n\n /** List all collection names in this compartment. */\n async collections(): Promise<string[]> {\n const snapshot = await this.adapter.loadAll(this.name)\n return Object.keys(snapshot)\n }\n\n /** Dump compartment as encrypted JSON backup string. */\n async dump(): Promise<string> {\n const snapshot = await this.adapter.loadAll(this.name)\n\n // Load keyrings\n const keyringIds = await this.adapter.list(this.name, '_keyring')\n const keyrings: Record<string, unknown> = {}\n for (const keyringId of keyringIds) {\n const envelope = await this.adapter.get(this.name, '_keyring', keyringId)\n if (envelope) {\n keyrings[keyringId] = JSON.parse(envelope._data)\n }\n }\n\n const backup: CompartmentBackup = {\n _noydb_backup: NOYDB_BACKUP_VERSION,\n _compartment: this.name,\n _exported_at: new Date().toISOString(),\n _exported_by: this.keyring.userId,\n keyrings: keyrings as CompartmentBackup['keyrings'],\n collections: snapshot,\n }\n\n return JSON.stringify(backup)\n }\n\n /** Restore compartment from an encrypted JSON backup string. */\n async load(backupJson: string): Promise<void> {\n const backup = JSON.parse(backupJson) as CompartmentBackup\n await this.adapter.saveAll(this.name, backup.collections)\n\n // Restore keyrings\n for (const [userId, keyringFile] of Object.entries(backup.keyrings)) {\n const envelope = {\n _noydb: 1 as const,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(keyringFile),\n }\n await this.adapter.put(this.name, '_keyring', userId, envelope)\n }\n\n // Clear collection cache so they re-hydrate\n this.collectionCache.clear()\n }\n\n /** Export compartment as decrypted JSON (owner only). */\n async export(): Promise<string> {\n if (this.keyring.role !== 'owner') {\n throw new PermissionDeniedError('Only the owner can export decrypted data')\n }\n\n const result: Record<string, Record<string, unknown>> = {}\n const snapshot = await this.adapter.loadAll(this.name)\n\n for (const [collName, records] of Object.entries(snapshot)) {\n const coll = this.collection(collName)\n const decrypted: Record<string, unknown> = {}\n for (const id of Object.keys(records)) {\n decrypted[id] = await coll.get(id)\n }\n result[collName] = decrypted\n }\n\n return JSON.stringify(result)\n }\n}\n","import type { NoydbEventMap } from './types.js'\n\ntype EventHandler<T> = (data: T) => void\n\n/** Typed event emitter for NOYDB events. */\nexport class NoydbEventEmitter {\n private readonly listeners = new Map<string, Set<EventHandler<unknown>>>()\n\n on<K extends keyof NoydbEventMap>(\n event: K,\n handler: EventHandler<NoydbEventMap[K]>,\n ): void {\n let set = this.listeners.get(event as string)\n if (!set) {\n set = new Set()\n this.listeners.set(event as string, set)\n }\n set.add(handler as EventHandler<unknown>)\n }\n\n off<K extends keyof NoydbEventMap>(\n event: K,\n handler: EventHandler<NoydbEventMap[K]>,\n ): void {\n this.listeners.get(event as string)?.delete(handler as EventHandler<unknown>)\n }\n\n emit<K extends keyof NoydbEventMap>(event: K, data: NoydbEventMap[K]): void {\n const set = this.listeners.get(event as string)\n if (set) {\n for (const handler of set) {\n handler(data)\n }\n }\n }\n\n removeAllListeners(): void {\n this.listeners.clear()\n }\n}\n","import type {\n NoydbAdapter,\n DirtyEntry,\n Conflict,\n ConflictStrategy,\n PushResult,\n PullResult,\n SyncStatus,\n EncryptedEnvelope,\n SyncMetadata,\n} from './types.js'\nimport { NOYDB_SYNC_VERSION } from './types.js'\nimport { ConflictError } from './errors.js'\nimport type { NoydbEventEmitter } from './events.js'\n\n/** Sync engine: dirty tracking, push, pull, conflict resolution. */\nexport class SyncEngine {\n private readonly local: NoydbAdapter\n private readonly remote: NoydbAdapter\n private readonly strategy: ConflictStrategy\n private readonly emitter: NoydbEventEmitter\n private readonly compartment: string\n\n private dirty: DirtyEntry[] = []\n private lastPush: string | null = null\n private lastPull: string | null = null\n private loaded = false\n private autoSyncInterval: ReturnType<typeof setInterval> | null = null\n private isOnline = true\n\n constructor(opts: {\n local: NoydbAdapter\n remote: NoydbAdapter\n compartment: string\n strategy: ConflictStrategy\n emitter: NoydbEventEmitter\n }) {\n this.local = opts.local\n this.remote = opts.remote\n this.compartment = opts.compartment\n this.strategy = opts.strategy\n this.emitter = opts.emitter\n }\n\n /** Record a local change for later push. */\n async trackChange(collection: string, id: string, action: 'put' | 'delete', version: number): Promise<void> {\n await this.ensureLoaded()\n\n // Deduplicate: if same collection+id already in dirty, update it\n const idx = this.dirty.findIndex(d => d.collection === collection && d.id === id)\n const entry: DirtyEntry = {\n compartment: this.compartment,\n collection,\n id,\n action,\n version,\n timestamp: new Date().toISOString(),\n }\n\n if (idx >= 0) {\n this.dirty[idx] = entry\n } else {\n this.dirty.push(entry)\n }\n\n await this.persistMeta()\n }\n\n /** Push dirty records to remote adapter. */\n async push(): Promise<PushResult> {\n await this.ensureLoaded()\n\n let pushed = 0\n const conflicts: Conflict[] = []\n const errors: Error[] = []\n const completed: number[] = []\n\n for (let i = 0; i < this.dirty.length; i++) {\n const entry = this.dirty[i]!\n try {\n if (entry.action === 'delete') {\n await this.remote.delete(this.compartment, entry.collection, entry.id)\n completed.push(i)\n pushed++\n } else {\n const envelope = await this.local.get(this.compartment, entry.collection, entry.id)\n if (!envelope) {\n // Record was deleted locally after being marked dirty\n completed.push(i)\n continue\n }\n\n try {\n await this.remote.put(\n this.compartment,\n entry.collection,\n entry.id,\n envelope,\n entry.version > 1 ? entry.version - 1 : undefined,\n )\n completed.push(i)\n pushed++\n } catch (err) {\n if (err instanceof ConflictError) {\n const remoteEnvelope = await this.remote.get(this.compartment, entry.collection, entry.id)\n if (remoteEnvelope) {\n const conflict: Conflict = {\n compartment: this.compartment,\n collection: entry.collection,\n id: entry.id,\n local: envelope,\n remote: remoteEnvelope,\n localVersion: envelope._v,\n remoteVersion: remoteEnvelope._v,\n }\n conflicts.push(conflict)\n this.emitter.emit('sync:conflict', conflict)\n\n // Auto-resolve based on strategy\n const resolution = this.resolveConflict(conflict)\n if (resolution === 'local') {\n await this.remote.put(this.compartment, entry.collection, entry.id, envelope)\n completed.push(i)\n pushed++\n } else if (resolution === 'remote') {\n await this.local.put(this.compartment, entry.collection, entry.id, remoteEnvelope)\n completed.push(i)\n }\n }\n } else {\n throw err\n }\n }\n }\n } catch (err) {\n errors.push(err instanceof Error ? err : new Error(String(err)))\n }\n }\n\n // Remove completed entries from dirty log (reverse order to preserve indices)\n for (const i of completed.sort((a, b) => b - a)) {\n this.dirty.splice(i, 1)\n }\n\n this.lastPush = new Date().toISOString()\n await this.persistMeta()\n\n const result: PushResult = { pushed, conflicts, errors }\n this.emitter.emit('sync:push', result)\n return result\n }\n\n /** Pull remote records to local adapter. */\n async pull(): Promise<PullResult> {\n await this.ensureLoaded()\n\n let pulled = 0\n const conflicts: Conflict[] = []\n const errors: Error[] = []\n\n try {\n const remoteSnapshot = await this.remote.loadAll(this.compartment)\n\n for (const [collName, records] of Object.entries(remoteSnapshot)) {\n for (const [id, remoteEnvelope] of Object.entries(records)) {\n try {\n const localEnvelope = await this.local.get(this.compartment, collName, id)\n\n if (!localEnvelope) {\n // New record from remote\n await this.local.put(this.compartment, collName, id, remoteEnvelope)\n pulled++\n } else if (remoteEnvelope._v > localEnvelope._v) {\n // Remote is newer — check if we have a dirty entry for this\n const isDirty = this.dirty.some(d => d.collection === collName && d.id === id)\n if (isDirty) {\n // Both changed — conflict\n const conflict: Conflict = {\n compartment: this.compartment,\n collection: collName,\n id,\n local: localEnvelope,\n remote: remoteEnvelope,\n localVersion: localEnvelope._v,\n remoteVersion: remoteEnvelope._v,\n }\n conflicts.push(conflict)\n this.emitter.emit('sync:conflict', conflict)\n\n const resolution = this.resolveConflict(conflict)\n if (resolution === 'remote') {\n await this.local.put(this.compartment, collName, id, remoteEnvelope)\n // Remove from dirty log\n this.dirty = this.dirty.filter(d => !(d.collection === collName && d.id === id))\n pulled++\n }\n // 'local' keeps local version, push will handle it\n } else {\n // Remote is newer, no local changes — update\n await this.local.put(this.compartment, collName, id, remoteEnvelope)\n pulled++\n }\n }\n // Same version or local is newer — skip (push will handle)\n } catch (err) {\n errors.push(err instanceof Error ? err : new Error(String(err)))\n }\n }\n }\n } catch (err) {\n errors.push(err instanceof Error ? err : new Error(String(err)))\n }\n\n this.lastPull = new Date().toISOString()\n await this.persistMeta()\n\n const result: PullResult = { pulled, conflicts, errors }\n this.emitter.emit('sync:pull', result)\n return result\n }\n\n /** Bidirectional sync: pull then push. */\n async sync(): Promise<{ pull: PullResult; push: PushResult }> {\n const pullResult = await this.pull()\n const pushResult = await this.push()\n return { pull: pullResult, push: pushResult }\n }\n\n /** Get current sync status. */\n status(): SyncStatus {\n return {\n dirty: this.dirty.length,\n lastPush: this.lastPush,\n lastPull: this.lastPull,\n online: this.isOnline,\n }\n }\n\n // ─── Auto-Sync ───────────────────────────────────────────────────\n\n /** Start auto-sync: listen for online/offline events, optional periodic sync. */\n startAutoSync(intervalMs?: number): void {\n // Online/offline detection\n if (typeof globalThis.addEventListener === 'function') {\n globalThis.addEventListener('online', this.handleOnline)\n globalThis.addEventListener('offline', this.handleOffline)\n }\n\n // Periodic sync\n if (intervalMs && intervalMs > 0) {\n this.autoSyncInterval = setInterval(() => {\n if (this.isOnline) {\n void this.sync()\n }\n }, intervalMs)\n }\n }\n\n /** Stop auto-sync. */\n stopAutoSync(): void {\n if (typeof globalThis.removeEventListener === 'function') {\n globalThis.removeEventListener('online', this.handleOnline)\n globalThis.removeEventListener('offline', this.handleOffline)\n }\n if (this.autoSyncInterval) {\n clearInterval(this.autoSyncInterval)\n this.autoSyncInterval = null\n }\n }\n\n private handleOnline = (): void => {\n this.isOnline = true\n this.emitter.emit('sync:online', undefined as never)\n void this.sync()\n }\n\n private handleOffline = (): void => {\n this.isOnline = false\n this.emitter.emit('sync:offline', undefined as never)\n }\n\n /** Resolve a conflict using the configured strategy. */\n private resolveConflict(conflict: Conflict): 'local' | 'remote' {\n if (typeof this.strategy === 'function') {\n return this.strategy(conflict)\n }\n\n switch (this.strategy) {\n case 'local-wins':\n return 'local'\n case 'remote-wins':\n return 'remote'\n case 'version':\n default:\n return conflict.localVersion >= conflict.remoteVersion ? 'local' : 'remote'\n }\n }\n\n // ─── Persistence ─────────────────────────────────────────────────\n\n private async ensureLoaded(): Promise<void> {\n if (this.loaded) return\n\n const envelope = await this.local.get(this.compartment, '_sync', 'meta')\n if (envelope) {\n const meta = JSON.parse(envelope._data) as SyncMetadata\n this.dirty = [...meta.dirty]\n this.lastPush = meta.last_push\n this.lastPull = meta.last_pull\n }\n\n this.loaded = true\n }\n\n private async persistMeta(): Promise<void> {\n const meta: SyncMetadata = {\n _noydb_sync: NOYDB_SYNC_VERSION,\n last_push: this.lastPush,\n last_pull: this.lastPull,\n dirty: this.dirty,\n }\n\n const envelope: EncryptedEnvelope = {\n _noydb: 1,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(meta),\n }\n\n await this.local.put(this.compartment, '_sync', 'meta', envelope)\n }\n}\n","import type { NoydbOptions, NoydbEventMap, GrantOptions, RevokeOptions, UserInfo, PushResult, PullResult, SyncStatus } from './types.js'\nimport { ValidationError, NoAccessError } from './errors.js'\nimport { Compartment } from './compartment.js'\nimport { NoydbEventEmitter } from './events.js'\nimport {\n loadKeyring,\n createOwnerKeyring,\n grant as keyringGrant,\n revoke as keyringRevoke,\n changeSecret as keyringChangeSecret,\n listUsers as keyringListUsers,\n} from './keyring.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { SyncEngine } from './sync.js'\n\n/** Dummy keyring for unencrypted mode. */\nfunction createPlaintextKeyring(userId: string): UnlockedKeyring {\n return {\n userId,\n displayName: userId,\n role: 'owner',\n permissions: {},\n deks: new Map(),\n kek: null as unknown as CryptoKey,\n salt: new Uint8Array(0),\n }\n}\n\n/** The top-level NOYDB instance. */\nexport class Noydb {\n private readonly options: NoydbOptions\n private readonly emitter = new NoydbEventEmitter()\n private readonly compartmentCache = new Map<string, Compartment>()\n private readonly keyringCache = new Map<string, UnlockedKeyring>()\n private readonly syncEngines = new Map<string, SyncEngine>()\n private closed = false\n private sessionTimer: ReturnType<typeof setTimeout> | null = null\n\n constructor(options: NoydbOptions) {\n this.options = options\n this.resetSessionTimer()\n }\n\n private resetSessionTimer(): void {\n if (this.sessionTimer) clearTimeout(this.sessionTimer)\n if (this.options.sessionTimeout && this.options.sessionTimeout > 0) {\n this.sessionTimer = setTimeout(() => {\n this.close()\n }, this.options.sessionTimeout)\n }\n }\n\n /** Open a compartment by name. */\n async openCompartment(name: string): Promise<Compartment> {\n if (this.closed) throw new ValidationError('Instance is closed')\n this.resetSessionTimer()\n\n let comp = this.compartmentCache.get(name)\n if (comp) return comp\n\n const keyring = await this.getKeyring(name)\n\n // Set up sync engine if remote adapter is configured\n let syncEngine: SyncEngine | undefined\n if (this.options.sync) {\n syncEngine = new SyncEngine({\n local: this.options.adapter,\n remote: this.options.sync,\n compartment: name,\n strategy: this.options.conflict ?? 'version',\n emitter: this.emitter,\n })\n this.syncEngines.set(name, syncEngine)\n }\n\n comp = new Compartment({\n adapter: this.options.adapter,\n name,\n keyring,\n encrypted: this.options.encrypt !== false,\n emitter: this.emitter,\n onDirty: syncEngine\n ? (coll, id, action, version) => syncEngine.trackChange(coll, id, action, version)\n : undefined,\n historyConfig: this.options.history,\n })\n this.compartmentCache.set(name, comp)\n return comp\n }\n\n /** Synchronous compartment access (must call openCompartment first, or auto-opens). */\n compartment(name: string): Compartment {\n const cached = this.compartmentCache.get(name)\n if (cached) return cached\n\n // For backwards compat: if not opened yet, create with cached keyring or plaintext\n if (this.options.encrypt === false) {\n const keyring = createPlaintextKeyring(this.options.user)\n const comp = new Compartment({\n adapter: this.options.adapter,\n name,\n keyring,\n encrypted: false,\n emitter: this.emitter,\n historyConfig: this.options.history,\n })\n this.compartmentCache.set(name, comp)\n return comp\n }\n\n const keyring = this.keyringCache.get(name)\n if (!keyring) {\n throw new ValidationError(\n `Compartment \"${name}\" not opened. Use await db.openCompartment(\"${name}\") first.`,\n )\n }\n\n const comp = new Compartment({\n adapter: this.options.adapter,\n name,\n keyring,\n encrypted: true,\n historyConfig: this.options.history,\n emitter: this.emitter,\n })\n this.compartmentCache.set(name, comp)\n return comp\n }\n\n /** Grant access to a user for a compartment. */\n async grant(compartment: string, options: GrantOptions): Promise<void> {\n const keyring = await this.getKeyring(compartment)\n await keyringGrant(this.options.adapter, compartment, keyring, options)\n }\n\n /** Revoke a user's access to a compartment. */\n async revoke(compartment: string, options: RevokeOptions): Promise<void> {\n const keyring = await this.getKeyring(compartment)\n await keyringRevoke(this.options.adapter, compartment, keyring, options)\n }\n\n /** List all users with access to a compartment. */\n async listUsers(compartment: string): Promise<UserInfo[]> {\n return keyringListUsers(this.options.adapter, compartment)\n }\n\n /** Change the current user's passphrase for a compartment. */\n async changeSecret(compartment: string, newPassphrase: string): Promise<void> {\n const keyring = await this.getKeyring(compartment)\n const updated = await keyringChangeSecret(\n this.options.adapter,\n compartment,\n keyring,\n newPassphrase,\n )\n this.keyringCache.set(compartment, updated)\n }\n\n // ─── Sync ──────────────────────────────────────────────────────\n\n /** Push local changes to remote for a compartment. */\n async push(compartment: string): Promise<PushResult> {\n const engine = this.getSyncEngine(compartment)\n return engine.push()\n }\n\n /** Pull remote changes to local for a compartment. */\n async pull(compartment: string): Promise<PullResult> {\n const engine = this.getSyncEngine(compartment)\n return engine.pull()\n }\n\n /** Bidirectional sync: pull then push. */\n async sync(compartment: string): Promise<{ pull: PullResult; push: PushResult }> {\n const engine = this.getSyncEngine(compartment)\n return engine.sync()\n }\n\n /** Get sync status for a compartment. */\n syncStatus(compartment: string): SyncStatus {\n const engine = this.syncEngines.get(compartment)\n if (!engine) {\n return { dirty: 0, lastPush: null, lastPull: null, online: true }\n }\n return engine.status()\n }\n\n private getSyncEngine(compartment: string): SyncEngine {\n const engine = this.syncEngines.get(compartment)\n if (!engine) {\n throw new ValidationError('No sync adapter configured. Pass a `sync` adapter to createNoydb().')\n }\n return engine\n }\n\n // ─── Events ────────────────────────────────────────────────────\n\n on<K extends keyof NoydbEventMap>(event: K, handler: (data: NoydbEventMap[K]) => void): void {\n this.emitter.on(event, handler)\n }\n\n off<K extends keyof NoydbEventMap>(event: K, handler: (data: NoydbEventMap[K]) => void): void {\n this.emitter.off(event, handler)\n }\n\n close(): void {\n this.closed = true\n if (this.sessionTimer) {\n clearTimeout(this.sessionTimer)\n this.sessionTimer = null\n }\n // Stop all sync engines\n for (const engine of this.syncEngines.values()) {\n engine.stopAutoSync()\n }\n this.syncEngines.clear()\n this.keyringCache.clear()\n this.compartmentCache.clear()\n this.emitter.removeAllListeners()\n }\n\n /** Get or load the keyring for a compartment. */\n private async getKeyring(compartment: string): Promise<UnlockedKeyring> {\n if (this.options.encrypt === false) {\n return createPlaintextKeyring(this.options.user)\n }\n\n const cached = this.keyringCache.get(compartment)\n if (cached) return cached\n\n if (!this.options.secret) {\n throw new ValidationError('A secret (passphrase) is required when encryption is enabled')\n }\n\n let keyring: UnlockedKeyring\n try {\n keyring = await loadKeyring(this.options.adapter, compartment, this.options.user, this.options.secret)\n } catch (err) {\n // Only create a new keyring if no keyring exists (NoAccessError).\n // If the keyring exists but the passphrase is wrong (InvalidKeyError), propagate the error.\n if (err instanceof NoAccessError) {\n keyring = await createOwnerKeyring(this.options.adapter, compartment, this.options.user, this.options.secret)\n } else {\n throw err\n }\n }\n\n this.keyringCache.set(compartment, keyring)\n return keyring\n }\n}\n\n/** Create a new NOYDB instance. */\nexport async function createNoydb(options: NoydbOptions): Promise<Noydb> {\n const encrypted = options.encrypt !== false\n\n if (encrypted && !options.secret) {\n throw new ValidationError('A secret (passphrase) is required when encryption is enabled')\n }\n\n return new Noydb(options)\n}\n","import { bufferToBase64, base64ToBuffer } from './crypto.js'\nimport { ValidationError } from './errors.js'\n\n/**\n * WebAuthn biometric enrollment and unlock.\n *\n * Enrollment: User enters passphrase → derive KEK → create WebAuthn credential\n * → wrap KEK with credential-derived key → store { credentialId, wrappedKek }\n *\n * Unlock: Retrieve { credentialId, wrappedKek } → WebAuthn assertion\n * → unwrap KEK → proceed as passphrase auth\n *\n * This module requires a browser environment with WebAuthn support.\n */\n\nexport interface BiometricCredential {\n credentialId: string\n wrappedKek: string\n salt: string\n}\n\n/** Check if WebAuthn is available in the current environment. */\nexport function isBiometricAvailable(): boolean {\n return (\n typeof window !== 'undefined' &&\n typeof window.PublicKeyCredential !== 'undefined' &&\n typeof navigator.credentials !== 'undefined'\n )\n}\n\n/**\n * Enroll a biometric credential for the current user.\n * Must be called after passphrase authentication (KEK is in memory).\n *\n * @param userId - User identifier for WebAuthn\n * @param kek - The KEK derived from the user's passphrase (in memory)\n * @returns BiometricCredential to persist in browser storage\n */\nexport async function enrollBiometric(\n userId: string,\n kek: CryptoKey,\n): Promise<BiometricCredential> {\n if (!isBiometricAvailable()) {\n throw new ValidationError('WebAuthn is not available in this environment')\n }\n\n // Create a WebAuthn credential\n const challenge = globalThis.crypto.getRandomValues(new Uint8Array(32))\n const userIdBytes = new TextEncoder().encode(userId)\n\n const credential = await navigator.credentials.create({\n publicKey: {\n challenge,\n rp: { name: 'NOYDB' },\n user: {\n id: userIdBytes,\n name: userId,\n displayName: userId,\n },\n pubKeyCredParams: [\n { type: 'public-key', alg: -7 }, // ES256\n { type: 'public-key', alg: -257 }, // RS256\n ],\n authenticatorSelection: {\n authenticatorAttachment: 'platform',\n userVerification: 'required',\n residentKey: 'preferred',\n },\n timeout: 60_000,\n },\n }) as PublicKeyCredential | null\n\n if (!credential) {\n throw new ValidationError('Biometric enrollment was cancelled')\n }\n\n // Export KEK and wrap it with a key derived from the credential\n const rawKek = await globalThis.crypto.subtle.exportKey('raw', kek)\n const wrappingKey = await deriveWrappingKey(credential.rawId)\n const iv = new Uint8Array(12) as unknown as ArrayBuffer\n const wrappedKek = await globalThis.crypto.subtle.encrypt(\n { name: 'AES-GCM', iv },\n wrappingKey,\n rawKek,\n )\n\n return {\n credentialId: bufferToBase64(credential.rawId),\n wrappedKek: bufferToBase64(wrappedKek),\n salt: bufferToBase64(globalThis.crypto.getRandomValues(new Uint8Array(32))),\n }\n}\n\n/**\n * Unlock using a previously enrolled biometric credential.\n *\n * @param storedCredential - The stored BiometricCredential from enrollment\n * @returns The unwrapped KEK as a CryptoKey\n */\nexport async function unlockBiometric(\n storedCredential: BiometricCredential,\n): Promise<CryptoKey> {\n if (!isBiometricAvailable()) {\n throw new ValidationError('WebAuthn is not available in this environment')\n }\n\n const credentialId = base64ToBuffer(storedCredential.credentialId)\n\n const assertion = await navigator.credentials.get({\n publicKey: {\n challenge: globalThis.crypto.getRandomValues(new Uint8Array(32)),\n allowCredentials: [{\n type: 'public-key',\n id: credentialId as BufferSource,\n }],\n userVerification: 'required',\n timeout: 60_000,\n },\n }) as PublicKeyCredential | null\n\n if (!assertion) {\n throw new ValidationError('Biometric authentication was cancelled')\n }\n\n // Unwrap KEK using credential-derived key\n const wrappingKey = await deriveWrappingKey(assertion.rawId)\n const unlockIv = new Uint8Array(12) as unknown as ArrayBuffer\n const rawKek = await globalThis.crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: unlockIv },\n wrappingKey,\n base64ToBuffer(storedCredential.wrappedKek) as BufferSource,\n )\n\n return globalThis.crypto.subtle.importKey(\n 'raw',\n rawKek,\n { name: 'AES-KW', length: 256 },\n false,\n ['wrapKey', 'unwrapKey'],\n )\n}\n\n/** Remove biometric enrollment from browser storage. */\nexport function removeBiometric(storage: Storage, userId: string): void {\n storage.removeItem(`noydb:biometric:${userId}`)\n}\n\n/** Save biometric credential to browser storage. */\nexport function saveBiometric(storage: Storage, userId: string, credential: BiometricCredential): void {\n storage.setItem(`noydb:biometric:${userId}`, JSON.stringify(credential))\n}\n\n/** Load biometric credential from browser storage. */\nexport function loadBiometric(storage: Storage, userId: string): BiometricCredential | null {\n const data = storage.getItem(`noydb:biometric:${userId}`)\n return data ? JSON.parse(data) as BiometricCredential : null\n}\n\n// ─── Internal ──────────────────────────────────────────────────────────\n\nasync function deriveWrappingKey(rawId: ArrayBuffer): Promise<CryptoKey> {\n const keyMaterial = await globalThis.crypto.subtle.importKey(\n 'raw',\n rawId,\n 'HKDF',\n false,\n ['deriveKey'],\n )\n\n return globalThis.crypto.subtle.deriveKey(\n {\n name: 'HKDF',\n hash: 'SHA-256',\n salt: new TextEncoder().encode('noydb-biometric-wrapping'),\n info: new TextEncoder().encode('kek-wrap'),\n },\n keyMaterial,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n","import { ValidationError } from './errors.js'\n\n/**\n * Validate passphrase strength.\n * Checks length and basic entropy heuristics.\n * Throws ValidationError if too weak.\n */\nexport function validatePassphrase(passphrase: string): void {\n if (passphrase.length < 8) {\n throw new ValidationError(\n 'Passphrase too short — minimum 8 characters. ' +\n 'Recommended: 12+ characters or a 4+ word passphrase.',\n )\n }\n\n const entropy = estimateEntropy(passphrase)\n if (entropy < 28) {\n throw new ValidationError(\n 'Passphrase too weak — too little entropy. ' +\n 'Use a mix of uppercase, lowercase, numbers, and symbols, ' +\n 'or use a 4+ word passphrase.',\n )\n }\n}\n\n/**\n * Estimate passphrase entropy in bits.\n * Uses character class analysis (not dictionary-based).\n */\nexport function estimateEntropy(passphrase: string): number {\n let charsetSize = 0\n\n if (/[a-z]/.test(passphrase)) charsetSize += 26\n if (/[A-Z]/.test(passphrase)) charsetSize += 26\n if (/[0-9]/.test(passphrase)) charsetSize += 10\n if (/[^a-zA-Z0-9]/.test(passphrase)) charsetSize += 32\n\n if (charsetSize === 0) charsetSize = 26 // fallback\n\n return Math.floor(passphrase.length * Math.log2(charsetSize))\n}\n"],"mappings":"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","names":["keyring","comp"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/errors.ts","../src/schema.ts","../src/crypto.ts","../src/ledger/entry.ts","../src/ledger/patch.ts","../src/ledger/store.ts","../src/refs.ts","../src/keyring.ts","../src/history.ts","../src/diff.ts","../src/query/predicate.ts","../src/query/builder.ts","../src/query/indexes.ts","../src/cache/lru.ts","../src/cache/policy.ts","../src/collection.ts","../src/compartment.ts","../src/events.ts","../src/sync.ts","../src/noydb.ts","../src/biometric.ts","../src/validation.ts"],"sourcesContent":["/** Format version for encrypted record envelopes. */\nexport const NOYDB_FORMAT_VERSION = 1 as const\n\n/** Format version for keyring files. */\nexport const NOYDB_KEYRING_VERSION = 1 as const\n\n/** Format version for backup files. */\nexport const NOYDB_BACKUP_VERSION = 1 as const\n\n/** Format version for sync metadata. */\nexport const NOYDB_SYNC_VERSION = 1 as const\n\n// ─── Roles & Permissions ───────────────────────────────────────────────\n\nexport type Role = 'owner' | 'admin' | 'operator' | 'viewer' | 'client'\n\nexport type Permission = 'rw' | 'ro'\n\nexport type Permissions = Record<string, Permission>\n\n// ─── Encrypted Envelope ────────────────────────────────────────────────\n\n/** The encrypted wrapper stored by adapters. Adapters only ever see this. */\nexport interface EncryptedEnvelope {\n readonly _noydb: typeof NOYDB_FORMAT_VERSION\n readonly _v: number\n readonly _ts: string\n readonly _iv: string\n readonly _data: string\n /** User who created this version (unencrypted metadata). */\n readonly _by?: string\n}\n\n// ─── Compartment Snapshot ──────────────────────────────────────────────\n\n/** All records across all collections for a compartment. */\nexport type CompartmentSnapshot = Record<string, Record<string, EncryptedEnvelope>>\n\n/**\n * Result of a single page fetch via the optional `listPage` adapter extension.\n *\n * `items` carries the actual encrypted envelopes (not just ids) so the\n * caller can decrypt and emit a single record without an extra `get()`\n * round-trip per id. `nextCursor` is `null` on the final page.\n */\nexport interface ListPageResult {\n /** Encrypted envelopes for this page, in adapter-defined order. */\n items: Array<{ id: string; envelope: EncryptedEnvelope }>\n /** Opaque cursor for the next page, or `null` if this was the last page. */\n nextCursor: string | null\n}\n\n// ─── Adapter Interface ─────────────────────────────────────────────────\n\nexport interface NoydbAdapter {\n /**\n * Optional human-readable adapter name (e.g. 'memory', 'file', 'dynamo').\n * Used in diagnostic messages and the listPage fallback warning. Adapters\n * are encouraged to set this so logs are clearer about which backend is\n * involved when something goes wrong.\n */\n name?: string\n\n /** Get a single record. Returns null if not found. */\n get(compartment: string, collection: string, id: string): Promise<EncryptedEnvelope | null>\n\n /** Put a record. Throws ConflictError if expectedVersion doesn't match. */\n put(\n compartment: string,\n collection: string,\n id: string,\n envelope: EncryptedEnvelope,\n expectedVersion?: number,\n ): Promise<void>\n\n /** Delete a record. */\n delete(compartment: string, collection: string, id: string): Promise<void>\n\n /** List all record IDs in a collection. */\n list(compartment: string, collection: string): Promise<string[]>\n\n /** Load all records for a compartment (initial hydration). */\n loadAll(compartment: string): Promise<CompartmentSnapshot>\n\n /** Save all records for a compartment (bulk write / restore). */\n saveAll(compartment: string, data: CompartmentSnapshot): Promise<void>\n\n /** Optional connectivity check for sync engine. */\n ping?(): Promise<boolean>\n\n /**\n * Optional pagination extension. Adapters that implement `listPage` get\n * the streaming `Collection.scan()` fast path; adapters that don't are\n * silently fallen back to a full `loadAll()` + slice (with a one-time\n * console.warn).\n *\n * `cursor` is opaque to the core — each adapter encodes its own paging\n * state (DynamoDB: base64 LastEvaluatedKey JSON; S3: ContinuationToken;\n * memory/file/browser: numeric offset of a sorted id list). Pass\n * `undefined` to start from the beginning.\n *\n * `limit` is a soft upper bound on `items.length`. Adapters MAY return\n * fewer items even when more exist (e.g. if the underlying store has\n * its own page size cap), and MUST signal \"no more pages\" by returning\n * `nextCursor: null`.\n *\n * The 6-method core contract is unchanged — this is an additive\n * extension discovered via `'listPage' in adapter`.\n */\n listPage?(\n compartment: string,\n collection: string,\n cursor?: string,\n limit?: number,\n ): Promise<ListPageResult>\n}\n\n// ─── Adapter Factory Helper ────────────────────────────────────────────\n\n/** Type-safe helper for creating adapter factories. */\nexport function defineAdapter<TOptions>(\n factory: (options: TOptions) => NoydbAdapter,\n): (options: TOptions) => NoydbAdapter {\n return factory\n}\n\n// ─── Keyring ───────────────────────────────────────────────────────────\n\nexport interface KeyringFile {\n readonly _noydb_keyring: typeof NOYDB_KEYRING_VERSION\n readonly user_id: string\n readonly display_name: string\n readonly role: Role\n readonly permissions: Permissions\n readonly deks: Record<string, string>\n readonly salt: string\n readonly created_at: string\n readonly granted_by: string\n}\n\n// ─── Backup ────────────────────────────────────────────────────────────\n\nexport interface CompartmentBackup {\n readonly _noydb_backup: typeof NOYDB_BACKUP_VERSION\n readonly _compartment: string\n readonly _exported_at: string\n readonly _exported_by: string\n readonly keyrings: Record<string, KeyringFile>\n readonly collections: CompartmentSnapshot\n /**\n * Internal collections (`_ledger`, `_ledger_deltas`, `_history`, `_sync`, …)\n * captured alongside the data collections. Optional for backwards\n * compat with v0.3 backups, which only stored data collections —\n * loading a v0.3 backup leaves the ledger empty (and `verifyBackupIntegrity`\n * skips the chain check, surfacing only a console warning).\n */\n readonly _internal?: CompartmentSnapshot\n /**\n * Verifiable-backup metadata (v0.4 #46). Embeds the ledger head at\n * dump time so `load()` can cross-check that the loaded chain matches\n * exactly what was exported. A backup whose chain has been tampered\n * with — either by modifying ledger entries or by modifying data\n * envelopes that the chain references — fails this check.\n *\n * Optional for backwards compat with v0.3 backups; missing means\n * \"legacy backup, load with a warning, no integrity check\".\n */\n readonly ledgerHead?: {\n /** Hex sha256 of the canonical JSON of the last ledger entry. */\n readonly hash: string\n /** Sequential index of the last ledger entry. */\n readonly index: number\n /** ISO timestamp captured at dump time. */\n readonly ts: string\n }\n}\n\n// ─── Sync ──────────────────────────────────────────────────────────────\n\nexport interface DirtyEntry {\n readonly compartment: string\n readonly collection: string\n readonly id: string\n readonly action: 'put' | 'delete'\n readonly version: number\n readonly timestamp: string\n}\n\nexport interface SyncMetadata {\n readonly _noydb_sync: typeof NOYDB_SYNC_VERSION\n readonly last_push: string | null\n readonly last_pull: string | null\n readonly dirty: DirtyEntry[]\n}\n\nexport interface Conflict {\n readonly compartment: string\n readonly collection: string\n readonly id: string\n readonly local: EncryptedEnvelope\n readonly remote: EncryptedEnvelope\n readonly localVersion: number\n readonly remoteVersion: number\n}\n\nexport type ConflictStrategy =\n | 'local-wins'\n | 'remote-wins'\n | 'version'\n | ((conflict: Conflict) => 'local' | 'remote')\n\nexport interface PushResult {\n readonly pushed: number\n readonly conflicts: Conflict[]\n readonly errors: Error[]\n}\n\nexport interface PullResult {\n readonly pulled: number\n readonly conflicts: Conflict[]\n readonly errors: Error[]\n}\n\nexport interface SyncStatus {\n readonly dirty: number\n readonly lastPush: string | null\n readonly lastPull: string | null\n readonly online: boolean\n}\n\n// ─── Events ────────────────────────────────────────────────────────────\n\nexport interface ChangeEvent {\n readonly compartment: string\n readonly collection: string\n readonly id: string\n readonly action: 'put' | 'delete'\n}\n\nexport interface NoydbEventMap {\n 'change': ChangeEvent\n 'error': Error\n 'sync:push': PushResult\n 'sync:pull': PullResult\n 'sync:conflict': Conflict\n 'sync:online': void\n 'sync:offline': void\n 'history:save': { compartment: string; collection: string; id: string; version: number }\n 'history:prune': { compartment: string; collection: string; id: string; pruned: number }\n}\n\n// ─── Grant / Revoke ────────────────────────────────────────────────────\n\nexport interface GrantOptions {\n readonly userId: string\n readonly displayName: string\n readonly role: Role\n readonly passphrase: string\n readonly permissions?: Permissions\n}\n\nexport interface RevokeOptions {\n readonly userId: string\n readonly rotateKeys?: boolean\n}\n\n// ─── User Info ─────────────────────────────────────────────────────────\n\nexport interface UserInfo {\n readonly userId: string\n readonly displayName: string\n readonly role: Role\n readonly permissions: Permissions\n readonly createdAt: string\n readonly grantedBy: string\n}\n\n// ─── Factory Options ───────────────────────────────────────────────────\n\nexport interface NoydbOptions {\n /** Primary adapter (local storage). */\n readonly adapter: NoydbAdapter\n /** Optional remote adapter for sync. */\n readonly sync?: NoydbAdapter\n /** User identifier. */\n readonly user: string\n /** Passphrase for key derivation. Required unless encrypt is false. */\n readonly secret?: string\n /** Auth method. Default: 'passphrase'. */\n readonly auth?: 'passphrase' | 'biometric'\n /** Enable encryption. Default: true. */\n readonly encrypt?: boolean\n /** Conflict resolution strategy. Default: 'version'. */\n readonly conflict?: ConflictStrategy\n /** Auto-sync on online/offline events. Default: false. */\n readonly autoSync?: boolean\n /** Periodic sync interval in ms. Default: 30000. */\n readonly syncInterval?: number\n /** Session timeout in ms. Clears keys after inactivity. Default: none. */\n readonly sessionTimeout?: number\n /** Validate passphrase strength on creation. Default: true. */\n readonly validatePassphrase?: boolean\n /** Audit history configuration. */\n readonly history?: HistoryConfig\n}\n\n// ─── History / Audit Trail ─────────────────────────────────────────────\n\n/** History configuration. */\nexport interface HistoryConfig {\n /** Enable history tracking. Default: true. */\n readonly enabled?: boolean\n /** Maximum history entries per record. Oldest pruned on overflow. Default: unlimited. */\n readonly maxVersions?: number\n}\n\n/** Options for querying history. */\nexport interface HistoryOptions {\n /** Start date (inclusive), ISO 8601. */\n readonly from?: string\n /** End date (inclusive), ISO 8601. */\n readonly to?: string\n /** Maximum entries to return. */\n readonly limit?: number\n}\n\n/** Options for pruning history. */\nexport interface PruneOptions {\n /** Keep only the N most recent versions. */\n readonly keepVersions?: number\n /** Delete versions older than this date, ISO 8601. */\n readonly beforeDate?: string\n}\n\n/** A decrypted history entry. */\nexport interface HistoryEntry<T> {\n readonly version: number\n readonly timestamp: string\n readonly userId: string\n readonly record: T\n}\n","export class NoydbError extends Error {\n readonly code: string\n\n constructor(code: string, message: string) {\n super(message)\n this.name = 'NoydbError'\n this.code = code\n }\n}\n\n// ─── Crypto Errors ─────────────────────────────────────────────────────\n\nexport class DecryptionError extends NoydbError {\n constructor(message = 'Decryption failed') {\n super('DECRYPTION_FAILED', message)\n this.name = 'DecryptionError'\n }\n}\n\nexport class TamperedError extends NoydbError {\n constructor(message = 'Data integrity check failed — record may have been tampered with') {\n super('TAMPERED', message)\n this.name = 'TamperedError'\n }\n}\n\nexport class InvalidKeyError extends NoydbError {\n constructor(message = 'Invalid key — wrong passphrase or corrupted keyring') {\n super('INVALID_KEY', message)\n this.name = 'InvalidKeyError'\n }\n}\n\n// ─── Access Errors ─────────────────────────────────────────────────────\n\nexport class NoAccessError extends NoydbError {\n constructor(message = 'No access — user does not have a key for this collection') {\n super('NO_ACCESS', message)\n this.name = 'NoAccessError'\n }\n}\n\nexport class ReadOnlyError extends NoydbError {\n constructor(message = 'Read-only — user has ro permission on this collection') {\n super('READ_ONLY', message)\n this.name = 'ReadOnlyError'\n }\n}\n\nexport class PermissionDeniedError extends NoydbError {\n constructor(message = 'Permission denied — insufficient role for this operation') {\n super('PERMISSION_DENIED', message)\n this.name = 'PermissionDeniedError'\n }\n}\n\n// ─── Sync Errors ───────────────────────────────────────────────────────\n\nexport class ConflictError extends NoydbError {\n readonly version: number\n\n constructor(version: number, message = 'Version conflict') {\n super('CONFLICT', message)\n this.name = 'ConflictError'\n this.version = version\n }\n}\n\nexport class NetworkError extends NoydbError {\n constructor(message = 'Network error') {\n super('NETWORK_ERROR', message)\n this.name = 'NetworkError'\n }\n}\n\n// ─── Data Errors ───────────────────────────────────────────────────────\n\nexport class NotFoundError extends NoydbError {\n constructor(message = 'Record not found') {\n super('NOT_FOUND', message)\n this.name = 'NotFoundError'\n }\n}\n\nexport class ValidationError extends NoydbError {\n constructor(message = 'Validation error') {\n super('VALIDATION_ERROR', message)\n this.name = 'ValidationError'\n }\n}\n\n/**\n * Thrown when a Standard Schema v1 validator rejects a record on\n * `put()` (input validation) or on read (output validation). Carries\n * the raw issue list so callers can render field-level errors.\n *\n * `direction` distinguishes the two cases:\n * - `'input'`: the user passed bad data into `put()`. This is a\n * normal error case that application code should handle — typically\n * by showing validation messages in the UI.\n * - `'output'`: stored data does not match the current schema. This\n * indicates a schema drift (the schema was changed without\n * migrating the existing records) and should be treated as a bug\n * — the application should not swallow it silently.\n *\n * The `issues` type is deliberately `readonly unknown[]` on this class\n * so that `errors.ts` doesn't need to import from `schema.ts` (and\n * create a dependency cycle). Callers who know they're holding a\n * `SchemaValidationError` can cast to the more precise\n * `readonly StandardSchemaV1Issue[]` from `schema.ts`.\n */\nexport class SchemaValidationError extends NoydbError {\n readonly issues: readonly unknown[]\n readonly direction: 'input' | 'output'\n\n constructor(\n message: string,\n issues: readonly unknown[],\n direction: 'input' | 'output',\n ) {\n super('SCHEMA_VALIDATION_FAILED', message)\n this.name = 'SchemaValidationError'\n this.issues = issues\n this.direction = direction\n }\n}\n\n// ─── Backup Errors (v0.4 #46) ─────────────────────────────────────────\n\n/**\n * Thrown when `Compartment.load()` finds that a backup's hash chain\n * doesn't verify, or that its embedded `ledgerHead.hash` doesn't\n * match the chain head reconstructed from the loaded entries.\n *\n * Distinct from `BackupCorruptedError` so callers can choose to\n * recover from one but not the other (e.g., a corrupted JSON file is\n * unrecoverable; a chain mismatch might mean the backup is from an\n * incompatible noy-db version).\n */\nexport class BackupLedgerError extends NoydbError {\n /** First-broken-entry index, if known. */\n readonly divergedAt?: number\n\n constructor(message: string, divergedAt?: number) {\n super('BACKUP_LEDGER', message)\n this.name = 'BackupLedgerError'\n if (divergedAt !== undefined) this.divergedAt = divergedAt\n }\n}\n\n/**\n * Thrown when `Compartment.load()` finds that the backup's data\n * collection content doesn't match the ledger's recorded\n * `payloadHash`es. This is the \"envelope was tampered with after\n * dump\" detection — the chain itself can be intact, but if any\n * encrypted record bytes were swapped, this check catches it.\n */\nexport class BackupCorruptedError extends NoydbError {\n /** The (collection, id) pair whose envelope failed the hash check. */\n readonly collection: string\n readonly id: string\n\n constructor(collection: string, id: string, message: string) {\n super('BACKUP_CORRUPTED', message)\n this.name = 'BackupCorruptedError'\n this.collection = collection\n this.id = id\n }\n}\n","/**\n * Standard Schema v1 integration.\n *\n * This file is the v0.4 entry point for **schema validation**. Any\n * validator that implements the [Standard Schema v1\n * protocol](https://standardschema.dev) — Zod, Valibot, ArkType, Effect\n * Schema, etc. — can be attached to a `Collection` or `defineNoydbStore`\n * and will:\n *\n * 1. Validate the record BEFORE encryption on `put()` — bad data is\n * rejected at the store boundary with a rich issue list.\n * 2. Validate the record AFTER decryption on `get()`/`list()`/`query()`\n * — stored data that has drifted from the current schema throws\n * loudly instead of silently propagating garbage to the UI.\n *\n * ## Why vendor the types?\n *\n * Standard Schema is a protocol, not a library. The spec is <200 lines of\n * TypeScript and has no runtime. There's an official `@standard-schema/spec`\n * types package on npm, but pulling it in would add a dependency edge\n * purely for type definitions. Vendoring the minimal surface keeps\n * `@noy-db/core` at **zero runtime dependencies** and gives us freedom to\n * evolve the helpers without a version-lock on the spec package.\n *\n * If the spec changes in a breaking way (unlikely — it's frozen at v1),\n * we update this file and bump our minor.\n *\n * ## Why not just run `schema.parse(value)` directly?\n *\n * Because then we'd be locked to whichever validator happens to have\n * `.parse`. Standard Schema's `'~standard'.validate` contract is the same\n * across every implementation and includes a structured issues list,\n * which is much more useful than a thrown error for programmatic error\n * handling (e.g., rendering field-level messages in a Vue component).\n */\n\nimport { SchemaValidationError } from './errors.js'\n\n/**\n * The Standard Schema v1 protocol. A schema is any object that exposes a\n * `'~standard'` property with `version: 1` and a `validate` function.\n *\n * The type parameters are:\n * - `Input` — the type accepted by `validate` (what the user passes in)\n * - `Output` — the type produced by `validate` (what we store/return,\n * may differ from Input if the schema transforms or coerces)\n *\n * In most cases `Input === Output`, but validators that transform\n * (Zod's `.transform`, Valibot's `transform`, etc.) can narrow or widen.\n *\n * We intentionally keep the `types` field `readonly` and optional — the\n * spec marks it as optional because it's only used for inference, and\n * not every implementation bothers populating it at runtime.\n */\nexport interface StandardSchemaV1<Input = unknown, Output = Input> {\n readonly '~standard': {\n readonly version: 1\n readonly vendor: string\n readonly validate: (\n value: unknown,\n ) =>\n | StandardSchemaV1SyncResult<Output>\n | Promise<StandardSchemaV1SyncResult<Output>>\n readonly types?:\n | {\n readonly input: Input\n readonly output: Output\n }\n | undefined\n }\n}\n\n/**\n * The result of a single call to `schema['~standard'].validate`. Either\n * `{ value }` on success or `{ issues }` on failure — never both.\n *\n * The spec allows `issues` to be undefined on success (and some\n * validators leave it that way), so consumers should discriminate on\n * `issues?.length` rather than on truthiness of `value`.\n */\nexport type StandardSchemaV1SyncResult<Output> =\n | { readonly value: Output; readonly issues?: undefined }\n | {\n readonly value?: undefined\n readonly issues: readonly StandardSchemaV1Issue[]\n }\n\n/**\n * A single validation issue. The `message` is always present; the `path`\n * is optional and points at the offending field when the schema tracks\n * it (virtually every validator does for object types).\n *\n * The path is deliberately permissive — both a plain `PropertyKey` and a\n * `{ key }` wrapper are allowed so validators that wrap path segments in\n * objects (Zod does this in some modes) don't need special handling.\n */\nexport interface StandardSchemaV1Issue {\n readonly message: string\n readonly path?:\n | ReadonlyArray<PropertyKey | { readonly key: PropertyKey }>\n | undefined\n}\n\n/**\n * Infer the output type of a Standard Schema. Consumers use this to\n * pull the type out of a schema instance when they want to declare a\n * Collection<T> or defineNoydbStore<T> with `T` derived from the schema.\n *\n * Example:\n * ```ts\n * const InvoiceSchema = z.object({ id: z.string(), amount: z.number() })\n * type Invoice = InferOutput<typeof InvoiceSchema>\n * ```\n */\nexport type InferOutput<T extends StandardSchemaV1> =\n T extends StandardSchemaV1<unknown, infer O> ? O : never\n\n/**\n * Validate an input value against a schema. Throws\n * `SchemaValidationError` if the schema rejects, with the rich issue\n * list attached. Otherwise returns the (possibly transformed) output\n * value.\n *\n * The `context` string is included in the thrown error's message so the\n * caller knows where the failure happened (e.g. `\"put(inv-001)\"`) without\n * every caller having to wrap the throw in a try/catch.\n *\n * This function is ALWAYS async because some validators (notably Effect\n * Schema and Zod's `.refine` with async predicates) can return a\n * Promise. We `await` the result unconditionally to normalize the\n * contract — the extra microtask is free compared to the cost of an\n * encrypt/decrypt round-trip.\n */\nexport async function validateSchemaInput<Output>(\n schema: StandardSchemaV1<unknown, Output>,\n value: unknown,\n context: string,\n): Promise<Output> {\n const result = await schema['~standard'].validate(value)\n if (result.issues !== undefined && result.issues.length > 0) {\n throw new SchemaValidationError(\n `Schema validation failed on ${context}: ${summarizeIssues(result.issues)}`,\n result.issues,\n 'input',\n )\n }\n // Safe: the spec guarantees `value` is present when `issues` is absent.\n return result.value as Output\n}\n\n/**\n * Validate an already-stored value coming OUT of the collection. This\n * is a distinct helper from `validateSchemaInput` because the error\n * semantics differ: an output-validation failure means the data in\n * storage has drifted from the current schema (an unexpected state),\n * whereas an input-validation failure means the user passed bad data\n * (an expected state for a UI that isn't guarding its inputs).\n *\n * We still throw — silently returning bad data would be worse — but\n * the error carries `direction: 'output'` so upstream code (and a\n * potential migrate hook) can distinguish the two cases.\n */\nexport async function validateSchemaOutput<Output>(\n schema: StandardSchemaV1<unknown, Output>,\n value: unknown,\n context: string,\n): Promise<Output> {\n const result = await schema['~standard'].validate(value)\n if (result.issues !== undefined && result.issues.length > 0) {\n throw new SchemaValidationError(\n `Stored data for ${context} does not match the current schema — ` +\n `schema drift? ${summarizeIssues(result.issues)}`,\n result.issues,\n 'output',\n )\n }\n return result.value as Output\n}\n\n/**\n * Produce a short human-readable summary of an issue list for the\n * thrown error's message. The full issue array is still attached to the\n * error as a property — this is only for the `.message` string that\n * shows up in console.error / stack traces.\n *\n * Format: `field: message; field2: message2` (up to 3 issues, then `…`).\n * Issues without a path are shown as `root: message`.\n */\nfunction summarizeIssues(\n issues: readonly StandardSchemaV1Issue[],\n): string {\n const shown = issues.slice(0, 3).map((issue) => {\n const pathStr = formatPath(issue.path)\n return `${pathStr}: ${issue.message}`\n })\n const suffix = issues.length > 3 ? ` (+${issues.length - 3} more)` : ''\n return shown.join('; ') + suffix\n}\n\nfunction formatPath(\n path: StandardSchemaV1Issue['path'],\n): string {\n if (!path || path.length === 0) return 'root'\n return path\n .map((segment) =>\n typeof segment === 'object' && segment !== null\n ? String(segment.key)\n : String(segment),\n )\n .join('.')\n}\n","import { DecryptionError, InvalidKeyError, TamperedError } from './errors.js'\n\nconst PBKDF2_ITERATIONS = 600_000\nconst SALT_BYTES = 32\nconst IV_BYTES = 12\nconst KEY_BITS = 256\n\nconst subtle = globalThis.crypto.subtle\n\n// ─── Key Derivation ────────────────────────────────────────────────────\n\n/** Derive a KEK from a passphrase and salt using PBKDF2-SHA256. */\nexport async function deriveKey(\n passphrase: string,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n const keyMaterial = await subtle.importKey(\n 'raw',\n new TextEncoder().encode(passphrase),\n 'PBKDF2',\n false,\n ['deriveKey'],\n )\n\n return subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: salt as BufferSource,\n iterations: PBKDF2_ITERATIONS,\n hash: 'SHA-256',\n },\n keyMaterial,\n { name: 'AES-KW', length: KEY_BITS },\n false,\n ['wrapKey', 'unwrapKey'],\n )\n}\n\n// ─── DEK Generation ────────────────────────────────────────────────────\n\n/** Generate a random AES-256-GCM data encryption key. */\nexport async function generateDEK(): Promise<CryptoKey> {\n return subtle.generateKey(\n { name: 'AES-GCM', length: KEY_BITS },\n true, // extractable — needed for AES-KW wrapping\n ['encrypt', 'decrypt'],\n )\n}\n\n// ─── Key Wrapping ──────────────────────────────────────────────────────\n\n/** Wrap (encrypt) a DEK with a KEK using AES-KW. Returns base64 string. */\nexport async function wrapKey(dek: CryptoKey, kek: CryptoKey): Promise<string> {\n const wrapped = await subtle.wrapKey('raw', dek, kek, 'AES-KW')\n return bufferToBase64(wrapped)\n}\n\n/** Unwrap (decrypt) a DEK from base64 string using a KEK. */\nexport async function unwrapKey(\n wrappedBase64: string,\n kek: CryptoKey,\n): Promise<CryptoKey> {\n try {\n return await subtle.unwrapKey(\n 'raw',\n base64ToBuffer(wrappedBase64) as BufferSource,\n kek,\n 'AES-KW',\n { name: 'AES-GCM', length: KEY_BITS },\n true,\n ['encrypt', 'decrypt'],\n )\n } catch {\n throw new InvalidKeyError()\n }\n}\n\n// ─── Encrypt / Decrypt ─────────────────────────────────────────────────\n\nexport interface EncryptResult {\n iv: string // base64\n data: string // base64\n}\n\n/** Encrypt plaintext JSON string with AES-256-GCM. Fresh IV per call. */\nexport async function encrypt(\n plaintext: string,\n dek: CryptoKey,\n): Promise<EncryptResult> {\n const iv = generateIV()\n const encoded = new TextEncoder().encode(plaintext)\n\n const ciphertext = await subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n encoded,\n )\n\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/** Decrypt AES-256-GCM ciphertext. Throws on wrong key or tampered data. */\nexport async function decrypt(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n): Promise<string> {\n const iv = base64ToBuffer(ivBase64)\n const ciphertext = base64ToBuffer(dataBase64)\n\n try {\n const plaintext = await subtle.decrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n ciphertext as BufferSource,\n )\n return new TextDecoder().decode(plaintext)\n } catch (err) {\n if (err instanceof Error && err.name === 'OperationError') {\n throw new TamperedError()\n }\n throw new DecryptionError(\n err instanceof Error ? err.message : 'Decryption failed',\n )\n }\n}\n\n// ─── Random Generation ─────────────────────────────────────────────────\n\n/** Generate a random 12-byte IV for AES-GCM. */\nexport function generateIV(): Uint8Array {\n return globalThis.crypto.getRandomValues(new Uint8Array(IV_BYTES))\n}\n\n/** Generate a random 32-byte salt for PBKDF2. */\nexport function generateSalt(): Uint8Array {\n return globalThis.crypto.getRandomValues(new Uint8Array(SALT_BYTES))\n}\n\n// ─── Base64 Helpers ────────────────────────────────────────────────────\n\nexport function bufferToBase64(buffer: ArrayBuffer | Uint8Array): string {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)\n let binary = ''\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!)\n }\n return btoa(binary)\n}\n\nexport function base64ToBuffer(base64: string): Uint8Array {\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n","/**\n * Ledger entry shape + canonical JSON + sha256 helpers.\n *\n * This file holds the PURE primitives used by the hash-chained ledger:\n * the entry type, the deterministic (sort-stable) JSON encoder, and\n * the sha256 hasher that produces `prevHash` and `ledger.head()`.\n *\n * Everything here is validator-free and side-effect free — the only\n * runtime dep is Web Crypto's `subtle.digest` for the sha256 call,\n * which we already use for every other hashing operation in the core.\n *\n * The hash chain property works like this:\n *\n * hash(entry[i]) = sha256(canonicalJSON(entry[i]))\n * entry[i+1].prevHash = hash(entry[i])\n *\n * Any modification to `entry[i]` (field values, field order, whitespace)\n * produces a different `hash(entry[i])`, which means `entry[i+1]`'s\n * stored `prevHash` no longer matches the recomputed hash, which means\n * `verify()` returns `{ ok: false, divergedAt: i + 1 }`. The chain is\n * append-only and tamper-evident without external anchoring.\n */\n\n/**\n * A single ledger entry in its plaintext form — what gets serialized,\n * hashed, and then encrypted with the ledger DEK before being written\n * to the `_ledger/` adapter collection.\n *\n * ## Why hash the ciphertext, not the plaintext?\n *\n * `payloadHash` is the sha256 of the record's ENCRYPTED envelope bytes,\n * not its plaintext. This matters:\n *\n * 1. **Zero-knowledge preserved.** A user (or a third party) can\n * verify the ledger against the stored envelopes without any\n * decryption keys. The adapter layer already holds only\n * ciphertext, so hashing the ciphertext keeps the ledger at the\n * same privacy level as the adapter.\n *\n * 2. **Determinism.** Plaintext → ciphertext is randomized by the\n * fresh per-write IV, so `hash(plaintext)` would need extra\n * normalization. `hash(ciphertext)` is already deterministic and\n * unique per write.\n *\n * 3. **Detection property.** If an attacker modifies even one byte of\n * the stored ciphertext (trying to flip a record), the hash\n * changes, the ledger's recorded `payloadHash` no longer matches,\n * and a data-integrity check fails. We don't do that check in\n * `verify()` today (v0.4 only checks chain consistency), but the\n * hook is there for a future `verifyIntegrity()` follow-up.\n *\n * Fields marked `op`, `collection`, `id`, `version`, `ts`, `actor` are\n * plaintext METADATA about the operation — NOT the record itself. The\n * entry is still encrypted at rest via the ledger DEK, but adapters\n * could theoretically infer operation patterns from the sizes and\n * timestamps. This is an accepted trade-off for the tamper-evidence\n * property; full ORAM-level privacy is out of scope for noy-db.\n */\nexport interface LedgerEntry {\n /**\n * Zero-based sequential position of this entry in the chain. The\n * canonical adapter key is this number zero-padded to 10 digits\n * (`\"0000000001\"`) so lexicographic ordering matches numeric order.\n */\n readonly index: number\n\n /**\n * Hex-encoded sha256 of the canonical JSON of the PREVIOUS entry.\n * The genesis entry (index 0) has `prevHash === ''` — the first\n * entry in a fresh compartment has nothing to point back to.\n */\n readonly prevHash: string\n\n /**\n * Which kind of mutation this entry records. v0.4 only supports\n * data operations (`put`, `delete`). Access-control operations\n * (`grant`, `revoke`, `rotate`) will be added in a follow-up once\n * the keyring write path is instrumented — that's tracked in the\n * v0.4 epic issue.\n */\n readonly op: 'put' | 'delete'\n\n /** The collection the mutation targeted. */\n readonly collection: string\n\n /** The record id the mutation targeted. */\n readonly id: string\n\n /**\n * The record version AFTER this mutation. For `put` this is the\n * newly assigned version; for `delete` this is the version that\n * was deleted (the last version visible to reads).\n */\n readonly version: number\n\n /** ISO timestamp of the mutation. */\n readonly ts: string\n\n /** User id of the actor who performed the mutation. */\n readonly actor: string\n\n /**\n * Hex-encoded sha256 of the encrypted envelope's `_data` field.\n * For `put`, this is the hash of the new ciphertext. For `delete`,\n * it's the hash of the last visible ciphertext at deletion time,\n * or the empty string if nothing was there to delete. Hashing the\n * ciphertext (not the plaintext) preserves zero-knowledge — see\n * the file docstring.\n */\n readonly payloadHash: string\n\n /**\n * Optional hex-encoded sha256 of the encrypted JSON Patch delta\n * blob stored alongside this entry in `_ledger_deltas/`. Present\n * only for `put` operations that had a previous version — the\n * genesis put of a new record, and every `delete`, leave this\n * field undefined.\n *\n * The delta payload itself lives in a sibling internal collection\n * (`_ledger_deltas/<paddedIndex>`) and is encrypted with the\n * ledger DEK. Callers use `ledger.loadDelta(index)` to decrypt and\n * deserialize it when reconstructing a historical version.\n *\n * Why optional instead of always-present: the first put of a\n * record has no previous version to diff against, so storing an\n * empty patch would be noise. For deletes there's no \"next\" state\n * to describe with a delta. Both cases set this field to undefined.\n *\n * Note: the canonical-JSON hasher treats `undefined` as invalid\n * (it's one of the guard rails), so on the wire this field is\n * either `{ deltaHash: '<hex>' }` or absent from the JSON\n * entirely — never `{ deltaHash: undefined }`.\n */\n readonly deltaHash?: string\n}\n\n/**\n * Canonical (sort-stable) JSON encoder.\n *\n * This function is the load-bearing primitive of the hash chain:\n * `sha256(canonicalJSON(entry))` must produce the same hex string\n * every time, on every machine, for the same logical entry — otherwise\n * `verify()` would return `{ ok: false }` on cross-platform reads.\n *\n * JavaScript's `JSON.stringify` is almost canonical, but NOT quite:\n * it preserves the insertion order of object keys, which means\n * `{a:1,b:2}` and `{b:2,a:1}` serialize differently. We fix this by\n * recursively walking objects and sorting their keys before\n * concatenation.\n *\n * Arrays keep their original order (reordering them would change\n * semantics). Numbers, strings, booleans, and `null` use the default\n * JSON encoding. `undefined` and functions are rejected — ledger\n * entries are plain data, and silently dropping `undefined` would\n * break the \"same input → same hash\" property if a caller forgot to\n * omit a field.\n *\n * Performance: one pass per nesting level; O(n log n) for key sorting\n * at each object. Entries are small (< 1 KB) so this is negligible\n * compared to the sha256 call.\n */\nexport function canonicalJson(value: unknown): string {\n if (value === null) return 'null'\n if (typeof value === 'boolean') return value ? 'true' : 'false'\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error(\n `canonicalJson: refusing to encode non-finite number ${String(value)}`,\n )\n }\n return JSON.stringify(value)\n }\n if (typeof value === 'string') return JSON.stringify(value)\n if (typeof value === 'bigint') {\n throw new Error('canonicalJson: BigInt is not JSON-serializable')\n }\n if (typeof value === 'undefined' || typeof value === 'function') {\n throw new Error(\n `canonicalJson: refusing to encode ${typeof value} — include all fields explicitly`,\n )\n }\n if (Array.isArray(value)) {\n return '[' + value.map((v) => canonicalJson(v)).join(',') + ']'\n }\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>\n const keys = Object.keys(obj).sort()\n const parts: string[] = []\n for (const key of keys) {\n parts.push(JSON.stringify(key) + ':' + canonicalJson(obj[key]))\n }\n return '{' + parts.join(',') + '}'\n }\n throw new Error(`canonicalJson: unexpected value type: ${typeof value}`)\n}\n\n/**\n * Compute a hex-encoded sha256 of a string via Web Crypto's subtle API.\n *\n * We use hex (not base64) for hashes because hex is case-insensitive,\n * fixed-length (64 chars), and easier to compare visually in debug\n * output. Base64 would save a few bytes in storage but every encrypted\n * ledger entry is already much larger than the hash itself.\n */\nexport async function sha256Hex(input: string): Promise<string> {\n const bytes = new TextEncoder().encode(input)\n const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes)\n return bytesToHex(new Uint8Array(digest))\n}\n\n/**\n * Compute the canonical hash of a ledger entry. Short wrapper around\n * `canonicalJson` + `sha256Hex`; callers use this instead of composing\n * the two functions every time, so any future change to the hashing\n * pipeline (e.g., adding a domain-separation prefix) lives in one place.\n */\nexport async function hashEntry(entry: LedgerEntry): Promise<string> {\n return sha256Hex(canonicalJson(entry))\n}\n\n/** Convert a Uint8Array to a lowercase hex string. */\nfunction bytesToHex(bytes: Uint8Array): string {\n const hex = new Array<string>(bytes.length)\n for (let i = 0; i < bytes.length; i++) {\n // Non-null assertion: indexing a Uint8Array within bounds always\n // returns a number, but the compiler's noUncheckedIndexedAccess\n // flag widens it to `number | undefined`. Safe here by construction.\n hex[i] = (bytes[i] ?? 0).toString(16).padStart(2, '0')\n }\n return hex.join('')\n}\n\n/**\n * Pad an index to the canonical 10-digit form used as the adapter key.\n * Ten digits is enough for ~10 billion ledger entries per compartment\n * — far beyond any realistic use case, but cheap enough that the extra\n * digits don't hurt storage.\n */\nexport function paddedIndex(index: number): string {\n return String(index).padStart(10, '0')\n}\n\n/** Parse a padded adapter key back into a number. Returns NaN on malformed input. */\nexport function parseIndex(key: string): number {\n return Number.parseInt(key, 10)\n}\n","/**\n * RFC 6902 JSON Patch — compute + apply.\n *\n * This module is the v0.4 \"delta history\" primitive: instead of\n * snapshotting the full record on every put (the v0.3 behavior),\n * `Collection.put` computes a JSON Patch from the previous version to\n * the new version and stores only the patch in the ledger. To\n * reconstruct version N, we walk from the genesis snapshot forward\n * applying patches. Storage scales with **edit size**, not record\n * size — a 10 KB record edited 1000 times costs ~10 KB of deltas\n * instead of ~10 MB of snapshots.\n *\n * ## Why hand-roll instead of using a library?\n *\n * RFC 6902 has good libraries (`fast-json-patch`, `rfc6902`) but every\n * single one of them adds a runtime dependency to `@noy-db/core`. The\n * \"zero runtime dependencies\" promise is one of the core's load-bearing\n * features, and the patch surface we actually need is small enough\n * (~150 LoC) that vendoring is the right call.\n *\n * What we implement:\n * - `add` — insert a value at a path\n * - `remove` — delete the value at a path\n * - `replace` — overwrite the value at a path\n *\n * What we deliberately skip (out of scope for the v0.4 ledger use):\n * - `move` and `copy` — optimizations; the diff algorithm doesn't\n * emit them, so the apply path doesn't need them\n * - `test` — used for transactional patches; we already have\n * optimistic concurrency via `_v` at the envelope layer\n * - Sophisticated array diffing (LCS, edit distance) — we treat\n * arrays as atomic values and emit a single `replace` op when\n * they differ. The accounting domain has small arrays where this\n * is fine; if we ever need patch-level array diffing we can add\n * it without changing the storage format.\n *\n * ## Path encoding (RFC 6902 §3)\n *\n * Paths look like `/foo/bar/0`. Each path segment is either an object\n * key or a numeric array index. Two characters need escaping inside\n * keys: `~` becomes `~0` and `/` becomes `~1`. We implement both.\n *\n * Empty path (`\"\"`) refers to the root document. Only `replace` makes\n * sense at the root, and our diff function emits it as a top-level\n * `replace` when `prev` and `next` differ in shape (object vs array,\n * primitive vs object, etc.).\n */\n\n/** A single JSON Patch operation. Subset of RFC 6902 — see file docstring. */\nexport type JsonPatchOp =\n | { readonly op: 'add'; readonly path: string; readonly value: unknown }\n | { readonly op: 'remove'; readonly path: string }\n | { readonly op: 'replace'; readonly path: string; readonly value: unknown }\n\n/** A complete JSON Patch document — an array of operations. */\nexport type JsonPatch = readonly JsonPatchOp[]\n\n// ─── Compute (diff) ──────────────────────────────────────────────────\n\n/**\n * Compute a JSON Patch that, when applied to `prev`, produces `next`.\n *\n * The algorithm is a straightforward recursive object walk:\n *\n * 1. If both inputs are plain objects (and not arrays/null):\n * - For each key in `prev`, recurse if `next` has it, else emit `remove`\n * - For each key in `next` not in `prev`, emit `add`\n * 2. If both inputs are arrays AND structurally equal, no-op.\n * Otherwise emit a single `replace` for the whole array.\n * 3. If both inputs are deeply equal primitives, no-op.\n * 4. Otherwise emit a `replace` at the current path.\n *\n * We do not minimize patches across move-like rearrangements — every\n * generated patch is straightforward enough to apply by hand if you\n * had to debug it.\n */\nexport function computePatch(prev: unknown, next: unknown): JsonPatch {\n const ops: JsonPatchOp[] = []\n diff(prev, next, '', ops)\n return ops\n}\n\nfunction diff(\n prev: unknown,\n next: unknown,\n path: string,\n out: JsonPatchOp[],\n): void {\n // Both null / both undefined → no-op (we don't differentiate them\n // in JSON terms; canonicalJson would reject undefined anyway).\n if (prev === next) return\n\n // One side null, the other not → straight replace.\n if (prev === null || next === null) {\n out.push({ op: 'replace', path, value: next })\n return\n }\n\n const prevIsArray = Array.isArray(prev)\n const nextIsArray = Array.isArray(next)\n const prevIsObject = typeof prev === 'object' && !prevIsArray\n const nextIsObject = typeof next === 'object' && !nextIsArray\n\n // Type changed (e.g., object → primitive, array → object). Replace.\n if (prevIsArray !== nextIsArray || prevIsObject !== nextIsObject) {\n out.push({ op: 'replace', path, value: next })\n return\n }\n\n // Both arrays. We don't do clever LCS-based diffing — emit a single\n // replace for the whole array if they differ. See file docstring for\n // the rationale.\n if (prevIsArray && nextIsArray) {\n if (!arrayDeepEqual(prev as unknown[], next as unknown[])) {\n out.push({ op: 'replace', path, value: next })\n }\n return\n }\n\n // Both plain objects. Recurse key by key.\n if (prevIsObject && nextIsObject) {\n const prevObj = prev as Record<string, unknown>\n const nextObj = next as Record<string, unknown>\n const prevKeys = Object.keys(prevObj)\n const nextKeys = Object.keys(nextObj)\n\n // Handle removes and overlapping recursions in one pass over prev.\n for (const key of prevKeys) {\n const childPath = path + '/' + escapePathSegment(key)\n if (!(key in nextObj)) {\n out.push({ op: 'remove', path: childPath })\n } else {\n diff(prevObj[key], nextObj[key], childPath, out)\n }\n }\n // Handle adds.\n for (const key of nextKeys) {\n if (!(key in prevObj)) {\n out.push({\n op: 'add',\n path: path + '/' + escapePathSegment(key),\n value: nextObj[key],\n })\n }\n }\n return\n }\n\n // Two primitives that aren't strictly equal — replace.\n out.push({ op: 'replace', path, value: next })\n}\n\nfunction arrayDeepEqual(a: unknown[], b: unknown[]): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false\n }\n return true\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (a === null || b === null) return false\n if (typeof a !== typeof b) return false\n if (typeof a !== 'object') return false\n const aArray = Array.isArray(a)\n const bArray = Array.isArray(b)\n if (aArray !== bArray) return false\n if (aArray && bArray) return arrayDeepEqual(a, b as unknown[])\n const aObj = a as Record<string, unknown>\n const bObj = b as Record<string, unknown>\n const aKeys = Object.keys(aObj)\n const bKeys = Object.keys(bObj)\n if (aKeys.length !== bKeys.length) return false\n for (const key of aKeys) {\n if (!(key in bObj)) return false\n if (!deepEqual(aObj[key], bObj[key])) return false\n }\n return true\n}\n\n// ─── Apply ──────────────────────────────────────────────────────────\n\n/**\n * Apply a JSON Patch to a base document and return the result.\n *\n * The base document is **not mutated** — every op clones the parent\n * container before writing to it, so the caller's reference to `base`\n * stays untouched. This costs an extra allocation per op but makes\n * the apply pipeline reorderable and safe to interrupt.\n *\n * Throws on:\n * - Removing a path that doesn't exist\n * - Adding to a path whose parent doesn't exist\n * - A path component that doesn't match the document shape (e.g.,\n * trying to step into a primitive)\n *\n * Throwing is the right behavior for the ledger use case: a failed\n * apply means the chain is corrupted, which should be loud rather\n * than silently producing a wrong reconstruction.\n */\nexport function applyPatch<T = unknown>(base: T, patch: JsonPatch): T {\n let result: unknown = clone(base)\n for (const op of patch) {\n result = applyOp(result, op)\n }\n return result as T\n}\n\nfunction applyOp(doc: unknown, op: JsonPatchOp): unknown {\n // Empty path → operation targets the root. Only `replace` and `add`\n // make sense at the root, but we handle `remove` for completeness\n // (root removal returns null).\n if (op.path === '') {\n if (op.op === 'remove') return null\n return clone(op.value)\n }\n\n const segments = parsePath(op.path)\n return walkAndApply(doc, segments, op)\n}\n\nfunction walkAndApply(\n doc: unknown,\n segments: string[],\n op: JsonPatchOp,\n): unknown {\n if (segments.length === 0) {\n // Should never happen — empty path is handled in applyOp().\n throw new Error('walkAndApply: empty segments (internal error)')\n }\n\n const [head, ...rest] = segments\n if (head === undefined) throw new Error('walkAndApply: undefined segment')\n\n if (rest.length === 0) {\n return applyAtTerminal(doc, head, op)\n }\n\n // Recurse into the child container, then rebuild the parent with\n // the modified child.\n if (Array.isArray(doc)) {\n const idx = parseArrayIndex(head, doc.length)\n const child = doc[idx]\n const newChild = walkAndApply(child, rest, op)\n const next = doc.slice()\n next[idx] = newChild\n return next\n }\n if (doc !== null && typeof doc === 'object') {\n const obj = doc as Record<string, unknown>\n if (!(head in obj)) {\n throw new Error(`applyPatch: path segment \"${head}\" not found in object`)\n }\n const newChild = walkAndApply(obj[head], rest, op)\n return { ...obj, [head]: newChild }\n }\n throw new Error(\n `applyPatch: cannot step into ${typeof doc} at segment \"${head}\"`,\n )\n}\n\nfunction applyAtTerminal(\n doc: unknown,\n segment: string,\n op: JsonPatchOp,\n): unknown {\n if (Array.isArray(doc)) {\n const idx =\n segment === '-' ? doc.length : parseArrayIndex(segment, doc.length + 1)\n const next = doc.slice()\n if (op.op === 'remove') {\n next.splice(idx, 1)\n return next\n }\n if (op.op === 'add') {\n next.splice(idx, 0, clone(op.value))\n return next\n }\n if (op.op === 'replace') {\n if (idx >= doc.length) {\n throw new Error(\n `applyPatch: replace at out-of-bounds array index ${idx}`,\n )\n }\n next[idx] = clone(op.value)\n return next\n }\n }\n if (doc !== null && typeof doc === 'object') {\n const obj = doc as Record<string, unknown>\n if (op.op === 'remove') {\n if (!(segment in obj)) {\n throw new Error(\n `applyPatch: remove on missing key \"${segment}\"`,\n )\n }\n const next = { ...obj }\n delete next[segment]\n return next\n }\n if (op.op === 'add') {\n // RFC 6902: `add` on an existing key replaces it.\n return { ...obj, [segment]: clone(op.value) }\n }\n if (op.op === 'replace') {\n if (!(segment in obj)) {\n throw new Error(\n `applyPatch: replace on missing key \"${segment}\"`,\n )\n }\n return { ...obj, [segment]: clone(op.value) }\n }\n }\n throw new Error(\n `applyPatch: cannot apply ${op.op} at terminal segment \"${segment}\"`,\n )\n}\n\n// ─── Path encoding (RFC 6902 §3) ─────────────────────────────────────\n\n/**\n * Escape a single path segment per RFC 6902 §3:\n * `~` → `~0`\n * `/` → `~1`\n *\n * Order matters: `~` must be escaped first, otherwise the `~1` we\n * just emitted would be re-escaped to `~01`.\n */\nfunction escapePathSegment(segment: string): string {\n return segment.replace(/~/g, '~0').replace(/\\//g, '~1')\n}\n\nfunction unescapePathSegment(segment: string): string {\n return segment.replace(/~1/g, '/').replace(/~0/g, '~')\n}\n\nfunction parsePath(path: string): string[] {\n if (!path.startsWith('/')) {\n throw new Error(`applyPatch: path must start with '/', got \"${path}\"`)\n }\n return path\n .slice(1)\n .split('/')\n .map(unescapePathSegment)\n}\n\nfunction parseArrayIndex(segment: string, max: number): number {\n if (!/^\\d+$/.test(segment)) {\n throw new Error(\n `applyPatch: array index must be a non-negative integer, got \"${segment}\"`,\n )\n }\n const idx = Number.parseInt(segment, 10)\n if (idx < 0 || idx > max) {\n throw new Error(\n `applyPatch: array index ${idx} out of range [0, ${max}]`,\n )\n }\n return idx\n}\n\n// ─── Cheap structural clone ─────────────────────────────────────────\n\n/**\n * Plain-JSON clone via JSON.parse(JSON.stringify(value)).\n *\n * Faster than `structuredClone` for our use because (a) we know our\n * inputs are JSON-compatible (no Dates, Maps, or BigInts — anything\n * else gets rejected by canonicalJson upstream), and (b) `structuredClone`\n * has overhead for handling arbitrary structured data we don't need.\n *\n * For tiny ledger entries (< 1 KB), the JSON round-trip is in the\n * single-digit microsecond range.\n */\nfunction clone<T>(value: T): T {\n if (value === null || value === undefined) return value\n if (typeof value !== 'object') return value\n return JSON.parse(JSON.stringify(value)) as T\n}\n","/**\n * `LedgerStore` — read/write access to a compartment's hash-chained\n * audit log.\n *\n * The store is a thin wrapper around the adapter's `_ledger/` internal\n * collection. Every append:\n *\n * 1. Loads the current head (or treats an empty ledger as head = -1)\n * 2. Computes `prevHash` = sha256(canonicalJson(head))\n * 3. Builds the new entry with `index = head.index + 1`\n * 4. Encrypts the entry with the compartment's ledger DEK\n * 5. Writes the encrypted envelope to `_ledger/<paddedIndex>`\n *\n * `verify()` walks the chain from genesis forward and returns\n * `{ ok: true, head }` on success or `{ ok: false, divergedAt }` on the\n * first broken link.\n *\n * ## Thread / concurrency model\n *\n * For v0.4 we assume a **single writer per compartment**. Two\n * concurrent `append()` calls would race on the \"read head, write\n * head+1\" cycle and could produce a broken chain. The v0.3 sync engine\n * is the primary concurrent-writer scenario, and it uses\n * optimistic-concurrency via `expectedVersion` on the adapter — but\n * the ledger path has no such guard today. Multi-writer hardening is a\n * v0.5 follow-up.\n *\n * Single-writer usage IS safe, including across process restarts:\n * `head()` reads the adapter fresh each call, so a crash between the\n * adapter.put of a data record and the ledger append just means the\n * ledger is missing an entry for that record. `verify()` still\n * succeeds; a future `verifyIntegrity()` helper can cross-check the\n * ledger against the data collections to catch the gap.\n *\n * ## Why hide the ledger from `compartment.collection()`?\n *\n * The `_ledger` name starts with `_`, matching the existing prefix\n * convention for internal collections (`_keyring`, `_sync`,\n * `_history`). The Compartment's public `collection()` method already\n * returns entries for any name, but `loadAll()` filters out\n * underscore-prefixed collections so backups and exports don't leak\n * ledger metadata. We keep the ledger accessible ONLY via\n * `compartment.ledger()` to enforce the hash-chain invariants — direct\n * puts via `collection('_ledger')` would bypass the `append()` logic.\n */\n\nimport type { NoydbAdapter, EncryptedEnvelope } from '../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport { encrypt, decrypt } from '../crypto.js'\nimport {\n canonicalJson,\n hashEntry,\n paddedIndex,\n sha256Hex,\n type LedgerEntry,\n} from './entry.js'\nimport type { JsonPatch } from './patch.js'\nimport { applyPatch } from './patch.js'\n\n/** The internal collection name used for ledger entry storage. */\nexport const LEDGER_COLLECTION = '_ledger'\n\n/**\n * The internal collection name used for delta payload storage.\n *\n * Deltas live in a sibling collection (not inside `_ledger`) for two\n * reasons:\n *\n * 1. **Listing efficiency.** `ledger.loadAllEntries()` calls\n * `adapter.list(_ledger)` which would otherwise return every\n * delta key alongside every entry key. Splitting them keeps the\n * list small (one key per ledger entry) and the delta reads\n * keyed by the entry's index.\n *\n * 2. **Prune-friendliness.** A future `pruneHistory()` will delete\n * old deltas while keeping the ledger chain intact (folding old\n * deltas into a base snapshot). Separating the storage makes\n * that deletion a targeted operation on one collection instead\n * of a filter across a mixed list.\n *\n * Both collections share the same ledger DEK — one DEK, two\n * internal collections, same zero-knowledge guarantees.\n */\nexport const LEDGER_DELTAS_COLLECTION = '_ledger_deltas'\n\n/**\n * Input shape for `LedgerStore.append()`. The caller supplies the\n * operation metadata; the store fills in `index` and `prevHash`.\n */\nexport interface AppendInput {\n op: LedgerEntry['op']\n collection: string\n id: string\n version: number\n actor: string\n payloadHash: string\n /**\n * Optional JSON Patch representing the delta from the previous\n * version to the new version. Present only for `put` operations\n * that had a previous version; omitted for genesis puts and for\n * deletes. When present, `LedgerStore.append` persists the patch\n * in `_ledger_deltas/<paddedIndex>` and records its sha256 hash\n * as the entry's `deltaHash` field.\n */\n delta?: JsonPatch\n}\n\n/**\n * Result of `LedgerStore.verify()`. On success, `head` is the hash of\n * the last entry — the same value that should be published to any\n * external anchoring service (blockchain, OpenTimestamps, etc.). On\n * failure, `divergedAt` is the 0-based index of the first entry whose\n * recorded `prevHash` does not match the recomputed hash of its\n * predecessor. Entries at `divergedAt` and later are untrustworthy;\n * entries before that index are still valid.\n */\nexport type VerifyResult =\n | { readonly ok: true; readonly head: string; readonly length: number }\n | {\n readonly ok: false\n readonly divergedAt: number\n readonly expected: string\n readonly actual: string\n }\n\n/**\n * A LedgerStore is bound to a single compartment. Callers obtain one\n * via `compartment.ledger()` — there is no public constructor to keep\n * the hash-chain invariants in one place.\n *\n * The class holds no mutable state beyond its dependencies (adapter,\n * compartment name, DEK resolver, actor id). Every method reads the\n * adapter fresh so multiple instances against the same compartment\n * see each other's writes immediately (at the cost of re-parsing the\n * ledger on every head() / verify() call; acceptable at v0.4 scale).\n */\nexport class LedgerStore {\n private readonly adapter: NoydbAdapter\n private readonly compartment: string\n private readonly encrypted: boolean\n private readonly getDEK: (collectionName: string) => Promise<CryptoKey>\n private readonly actor: string\n\n /**\n * In-memory cache of the chain head — the most recently appended\n * entry along with its precomputed hash. Without this, every\n * `append()` would re-load every prior entry to recompute the\n * prevHash, making N puts O(N²) — a 1K-record stress test goes from\n * < 100ms to a multi-second timeout.\n *\n * The cache is populated on first read (`append`, `head`, `verify`)\n * and updated in-place on every successful `append`. Single-writer\n * usage (the v0.4 assumption) keeps it consistent. A second\n * LedgerStore instance writing to the same compartment would not\n * see the first instance's appends in its cached state — that's the\n * concurrency caveat documented at the class level.\n *\n * Sentinel `undefined` means \"not yet loaded\"; an explicit `null`\n * value means \"loaded and confirmed empty\" — distinguishing these\n * matters because an empty ledger is a valid state (genesis prevHash\n * is the empty string), and we don't want to re-scan the adapter\n * just because the chain is freshly initialized.\n */\n private headCache: { entry: LedgerEntry; hash: string } | null | undefined = undefined\n\n constructor(opts: {\n adapter: NoydbAdapter\n compartment: string\n encrypted: boolean\n getDEK: (collectionName: string) => Promise<CryptoKey>\n actor: string\n }) {\n this.adapter = opts.adapter\n this.compartment = opts.compartment\n this.encrypted = opts.encrypted\n this.getDEK = opts.getDEK\n this.actor = opts.actor\n }\n\n /**\n * Lazily load (or return cached) the current chain head. The cache\n * sentinel is `undefined` until first access; after the first call,\n * the cache holds either a `{ entry, hash }` for non-empty ledgers\n * or `null` for empty ones.\n */\n private async getCachedHead(): Promise<{ entry: LedgerEntry; hash: string } | null> {\n if (this.headCache !== undefined) return this.headCache\n const entries = await this.loadAllEntries()\n const last = entries[entries.length - 1]\n if (!last) {\n this.headCache = null\n return null\n }\n this.headCache = { entry: last, hash: await hashEntry(last) }\n return this.headCache\n }\n\n /**\n * Append a new entry to the ledger. Returns the full entry that was\n * written (with its assigned index and computed prevHash) so the\n * caller can use the hash for downstream purposes (e.g., embedding\n * in a verifiable backup).\n *\n * This is the **only** way to add entries. Direct adapter writes to\n * `_ledger/` would bypass the chain math and would be caught by the\n * next `verify()` call as a divergence.\n */\n async append(input: AppendInput): Promise<LedgerEntry> {\n const cached = await this.getCachedHead()\n const lastEntry = cached?.entry\n const prevHash = cached?.hash ?? ''\n const nextIndex = lastEntry ? lastEntry.index + 1 : 0\n\n // If the caller supplied a delta, persist it in\n // `_ledger_deltas/<paddedIndex>` and compute its hash for the\n // entry's `deltaHash` field. The hash is of the CIPHERTEXT of the\n // delta payload (same as `payloadHash` is of the record\n // ciphertext), preserving zero-knowledge.\n let deltaHash: string | undefined\n if (input.delta !== undefined) {\n const deltaEnvelope = await this.encryptDelta(input.delta)\n await this.adapter.put(\n this.compartment,\n LEDGER_DELTAS_COLLECTION,\n paddedIndex(nextIndex),\n deltaEnvelope,\n )\n deltaHash = await sha256Hex(deltaEnvelope._data)\n }\n\n // Build the entry. Conditionally include `deltaHash` so\n // canonicalJson (which rejects undefined) never sees it when\n // there's no delta. The on-the-wire shape is either\n // `{ ...fields, deltaHash: '...' }` or `{ ...fields }` — never\n // `{ ..., deltaHash: undefined }`.\n const entryBase = {\n index: nextIndex,\n prevHash,\n op: input.op,\n collection: input.collection,\n id: input.id,\n version: input.version,\n ts: new Date().toISOString(),\n actor: input.actor === '' ? this.actor : input.actor,\n payloadHash: input.payloadHash,\n } as const\n const entry: LedgerEntry =\n deltaHash !== undefined\n ? { ...entryBase, deltaHash }\n : entryBase\n\n const envelope = await this.encryptEntry(entry)\n await this.adapter.put(\n this.compartment,\n LEDGER_COLLECTION,\n paddedIndex(entry.index),\n envelope,\n )\n\n // Update the head cache so the next append() doesn't re-scan the\n // adapter. Computing the hash here is cheap (sha256 over a small\n // canonical JSON string) and avoids any possibility of cache drift\n // — the value we store is exactly what `prevHash` will be on the\n // next append.\n this.headCache = { entry, hash: await hashEntry(entry) }\n return entry\n }\n\n /**\n * Load a delta payload by its entry index. Returns `null` if the\n * entry at that index doesn't reference a delta (genesis puts and\n * deletes leave the slot empty) or if the delta row is missing\n * (possible after a `pruneHistory` fold).\n *\n * The caller is responsible for deciding what to do with a missing\n * delta — `ledger.reconstruct()` uses it as a \"stop walking\n * backward\" signal and falls back to the on-disk current value.\n */\n async loadDelta(index: number): Promise<JsonPatch | null> {\n const envelope = await this.adapter.get(\n this.compartment,\n LEDGER_DELTAS_COLLECTION,\n paddedIndex(index),\n )\n if (!envelope) return null\n if (!this.encrypted) {\n return JSON.parse(envelope._data) as JsonPatch\n }\n const dek = await this.getDEK(LEDGER_COLLECTION)\n const json = await decrypt(envelope._iv, envelope._data, dek)\n return JSON.parse(json) as JsonPatch\n }\n\n /** Encrypt a JSON Patch into an envelope for storage. Mirrors encryptEntry. */\n private async encryptDelta(patch: JsonPatch): Promise<EncryptedEnvelope> {\n const json = JSON.stringify(patch)\n if (!this.encrypted) {\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: json,\n _by: this.actor,\n }\n }\n const dek = await this.getDEK(LEDGER_COLLECTION)\n const { iv, data } = await encrypt(json, dek)\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _by: this.actor,\n }\n }\n\n /**\n * Read all entries in ascending-index order. Used internally by\n * `append()`, `head()`, `verify()`, and `entries()`. Decryption is\n * serial because the entries are tiny and the overhead of a Promise\n * pool would dominate at realistic chain lengths (< 100K entries).\n */\n async loadAllEntries(): Promise<LedgerEntry[]> {\n const keys = await this.adapter.list(this.compartment, LEDGER_COLLECTION)\n // Sort lexicographically, which matches numeric order because\n // keys are zero-padded to 10 digits.\n keys.sort()\n const entries: LedgerEntry[] = []\n for (const key of keys) {\n const envelope = await this.adapter.get(\n this.compartment,\n LEDGER_COLLECTION,\n key,\n )\n if (!envelope) continue\n entries.push(await this.decryptEntry(envelope))\n }\n return entries\n }\n\n /**\n * Return the current head of the ledger: the last entry, its hash,\n * and the total chain length. `null` on an empty ledger so callers\n * can distinguish \"no history yet\" from \"empty history\".\n */\n async head(): Promise<\n | { readonly entry: LedgerEntry; readonly hash: string; readonly length: number }\n | null\n > {\n const cached = await this.getCachedHead()\n if (!cached) return null\n // `length` is `entry.index + 1` because indices are zero-based and\n // contiguous. We don't need to re-scan the adapter to compute it.\n return {\n entry: cached.entry,\n hash: cached.hash,\n length: cached.entry.index + 1,\n }\n }\n\n /**\n * Return entries in the requested half-open range `[from, to)`.\n * Defaults: `from = 0`, `to = length`. The indices are clipped to\n * the valid range; no error is thrown for out-of-range queries.\n */\n async entries(opts: { from?: number; to?: number } = {}): Promise<LedgerEntry[]> {\n const all = await this.loadAllEntries()\n const from = Math.max(0, opts.from ?? 0)\n const to = Math.min(all.length, opts.to ?? all.length)\n return all.slice(from, to)\n }\n\n /**\n * Reconstruct a record's state at a given historical version by\n * walking the ledger's delta chain backward from the current state.\n *\n * ## Algorithm\n *\n * Ledger deltas are stored in **reverse** form — each entry's\n * patch describes how to undo that put, transforming the new\n * record back into the previous one. `reconstruct` exploits this\n * by:\n *\n * 1. Finding every ledger entry for `(collection, id)` in the\n * chain, sorted by index ascending.\n * 2. Starting from `current` (the present value of the record,\n * as held by the caller — typically fetched via\n * `Collection.get()`).\n * 3. Walking entries in **descending** index order and applying\n * each entry's reverse patch, stopping when we reach the\n * entry whose version equals `atVersion`.\n *\n * The result is the record as it existed immediately AFTER the\n * put at `atVersion`. To get the state at the genesis put\n * (version 1), the walk runs all the way back through every put\n * after the first.\n *\n * ## Caveats\n *\n * - **Delete entries** break the walk: once we see a delete, the\n * record didn't exist before that point, so there's nothing to\n * reconstruct. We return `null` in that case.\n * - **Missing deltas** (e.g., after `pruneHistory` folds old\n * entries into a base snapshot) also stop the walk. v0.4 does\n * not ship pruneHistory, so today this only happens if an entry\n * was deleted out-of-band.\n * - The caller MUST pass the correct current value. Passing a\n * mutated object would corrupt the reconstruction — the patch\n * chain is only valid against the exact state that was in\n * effect when the most recent put happened.\n *\n * For v0.4, `reconstruct` is the only way to read a historical\n * version via deltas. The legacy `_history` collection still\n * holds full snapshots and `Collection.getVersion()` still reads\n * from there — the two paths coexist until pruneHistory lands in\n * a follow-up and delta becomes the default.\n */\n async reconstruct<T>(\n collection: string,\n id: string,\n current: T,\n atVersion: number,\n ): Promise<T | null> {\n const all = await this.loadAllEntries()\n // Filter to entries for this (collection, id), in ascending index.\n const matching = all.filter(\n (e) => e.collection === collection && e.id === id,\n )\n if (matching.length === 0) {\n // No ledger history at all; the current state IS version 1\n // (or there's nothing), so the only valid atVersion is the\n // current record's version. We can't verify that here, so\n // return current if atVersion is plausible, null otherwise.\n return null\n }\n\n // Walk entries in descending index order, applying each reverse\n // delta until we reach the target version.\n let state: T | null = current\n for (let i = matching.length - 1; i >= 0; i--) {\n const entry = matching[i]\n if (!entry) continue\n\n // Match check FIRST — before applying this entry's reverse\n // patch. `state` at this point is the record state immediately\n // after this entry's put (or before this entry's delete), so\n // if the caller asked for this exact version, we're done.\n if (entry.version === atVersion && entry.op !== 'delete') {\n return state\n }\n\n if (entry.op === 'delete') {\n // A delete erases the live state. If the caller asks for a\n // version older than the delete we should continue walking\n // (state becomes null and the next put resets it). But we\n // can't reconstruct that pre-delete state from the current\n // in-memory `state` — the delete has no reverse patch. So\n // anything past this point is unreachable; return null.\n return null\n }\n\n if (entry.deltaHash === undefined) {\n // Genesis put — the earliest state for this lifecycle. We\n // can't walk further back. If the caller asked for exactly\n // this version, return the current state (we already failed\n // the match check above because a fresh genesis after a\n // delete can have version === atVersion). Otherwise the\n // target is unreachable from here.\n if (entry.version === atVersion) return state\n return null\n }\n\n const patch = await this.loadDelta(entry.index)\n if (!patch) {\n // Delta row is missing (probably pruned). Stop walking.\n return null\n }\n\n if (state === null) {\n // We're trying to walk back across a delete range and there's\n // nothing to apply a reverse patch to. Bail.\n return null\n }\n\n state = applyPatch(state, patch)\n }\n\n // Ran off the end of the walk without matching. The target\n // version doesn't exist in this record's chain.\n return null\n }\n\n /**\n * Walk the chain from genesis forward and verify every link.\n *\n * Returns `{ ok: true, head, length }` if every entry's `prevHash`\n * matches the recomputed hash of its predecessor (and the genesis\n * entry's `prevHash` is the empty string).\n *\n * Returns `{ ok: false, divergedAt, expected, actual }` on the first\n * mismatch. `divergedAt` is the 0-based index of the BROKEN entry\n * — entries before that index still verify cleanly; entries at and\n * after `divergedAt` are untrustworthy.\n *\n * This method detects:\n * - Mutated entry content (fields changed)\n * - Reordered entries (if any adjacent pair swaps, the prevHash\n * of the second no longer matches)\n * - Inserted entries (the inserted entry's prevHash likely fails,\n * and the following entry's prevHash definitely fails)\n * - Deleted entries (the entry after the deletion sees a wrong\n * prevHash)\n *\n * It does NOT detect:\n * - Tampering with the DATA collections that bypassed the ledger\n * entirely (e.g., an attacker who modifies records without\n * appending matching ledger entries — this is why we also\n * plan a `verifyIntegrity()` helper in a follow-up)\n * - Truncation of the chain at the tail (dropping the last N\n * entries leaves a shorter but still consistent chain). External\n * anchoring of `head.hash` to a trusted service is the defense\n * against this.\n */\n async verify(): Promise<VerifyResult> {\n const entries = await this.loadAllEntries()\n let expectedPrevHash = ''\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i]\n if (!entry) continue\n if (entry.prevHash !== expectedPrevHash) {\n return {\n ok: false,\n divergedAt: i,\n expected: expectedPrevHash,\n actual: entry.prevHash,\n }\n }\n if (entry.index !== i) {\n // An entry whose stored index doesn't match its position in\n // the sorted list means someone rewrote the adapter keys.\n // Treat as divergence.\n return {\n ok: false,\n divergedAt: i,\n expected: `index=${i}`,\n actual: `index=${entry.index}`,\n }\n }\n expectedPrevHash = await hashEntry(entry)\n }\n return {\n ok: true,\n head: expectedPrevHash,\n length: entries.length,\n }\n }\n\n // ─── Encryption plumbing ─────────────────────────────────────────\n\n /**\n * Serialize + encrypt a ledger entry into an EncryptedEnvelope. The\n * envelope's `_v` field is set to `entry.index + 1` so the usual\n * optimistic-concurrency machinery has a reasonable version number\n * to compare against (the ledger is append-only, so concurrent\n * writes should always bump the index).\n */\n private async encryptEntry(entry: LedgerEntry): Promise<EncryptedEnvelope> {\n const json = canonicalJson(entry)\n if (!this.encrypted) {\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: entry.index + 1,\n _ts: entry.ts,\n _iv: '',\n _data: json,\n _by: entry.actor,\n }\n }\n const dek = await this.getDEK(LEDGER_COLLECTION)\n const { iv, data } = await encrypt(json, dek)\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: entry.index + 1,\n _ts: entry.ts,\n _iv: iv,\n _data: data,\n _by: entry.actor,\n }\n }\n\n /** Decrypt an envelope into a LedgerEntry. Throws on bad key / tamper. */\n private async decryptEntry(envelope: EncryptedEnvelope): Promise<LedgerEntry> {\n if (!this.encrypted) {\n return JSON.parse(envelope._data) as LedgerEntry\n }\n const dek = await this.getDEK(LEDGER_COLLECTION)\n const json = await decrypt(envelope._iv, envelope._data, dek)\n return JSON.parse(json) as LedgerEntry\n }\n}\n\n/**\n * Compute the `payloadHash` value for an encrypted envelope. Pulled\n * out as a standalone helper because both `put` (hash the new\n * envelope's `_data`) and `delete` (hash the previous envelope's\n * `_data`) need the same calculation, and the logic is small enough\n * that duplicating it would be noise.\n */\nexport async function envelopePayloadHash(\n envelope: EncryptedEnvelope | null,\n): Promise<string> {\n if (!envelope) return ''\n // `_data` is a base64 string for encrypted envelopes and the raw\n // JSON for plaintext ones. Both are strings, so a single sha256Hex\n // call works for both modes — the hash value is different between\n // encrypted/plaintext compartments, but that's correct: they're\n // different bytes on disk.\n return sha256Hex(envelope._data)\n}\n","/**\n * Foreign-key references — the v0.4 soft-FK mechanism.\n *\n * A collection declares its references as metadata at construction\n * time:\n *\n * ```ts\n * import { ref } from '@noy-db/core'\n *\n * const invoices = company.collection<Invoice>('invoices', {\n * refs: {\n * clientId: ref('clients'), // default: strict\n * categoryId: ref('categories', 'warn'),\n * parentId: ref('invoices', 'cascade'), // self-reference OK\n * },\n * })\n * ```\n *\n * Three modes:\n *\n * - **strict** — the default. `put()` rejects records whose\n * reference target doesn't exist, and `delete()` of the target\n * rejects if any strict-referencing records still exist.\n * Matches SQL's default FK semantics.\n *\n * - **warn** — both operations succeed unconditionally. Broken\n * references surface only through\n * `compartment.checkIntegrity()`, which walks every collection\n * and reports orphans. Use when you want soft validation for\n * imports from messy sources.\n *\n * - **cascade** — `put()` is same as warn. `delete()` of the\n * target deletes every referencing record. Cycles are detected\n * and broken via an in-progress set, so mutual cascades\n * terminate instead of recursing forever.\n *\n * Cross-compartment refs are explicitly rejected: if the target\n * name contains a `/`, `ref()` throws `RefScopeError`. Cross-\n * compartment refs need an auth story (multi-keyring reads) that\n * v0.4 doesn't ship — tracked for v0.5.\n */\n\nimport { NoydbError } from './errors.js'\n\n/** The three enforcement modes. Default for new refs is `'strict'`. */\nexport type RefMode = 'strict' | 'warn' | 'cascade'\n\n/**\n * Descriptor returned by `ref()`. Collections accept a\n * `Record<string, RefDescriptor>` in their options. The key is the\n * field name on the record (top-level only — dotted paths are out of\n * scope for v0.4), the value describes which target collection the\n * field references and under what mode.\n *\n * The descriptor carries only plain data so it can be serialized,\n * passed around, and introspected without any class machinery.\n */\nexport interface RefDescriptor {\n readonly target: string\n readonly mode: RefMode\n}\n\n/**\n * Thrown when a strict reference is violated — either `put()` with a\n * missing target id, or `delete()` of a target that still has\n * strict-referencing records.\n *\n * Carries structured detail so UI code (and a potential future\n * devtools panel) can render \"client X cannot be deleted because\n * invoices 1, 2, and 3 reference it\" instead of a bare error string.\n */\nexport class RefIntegrityError extends NoydbError {\n readonly collection: string\n readonly id: string\n readonly field: string\n readonly refTo: string\n readonly refId: string | null\n\n constructor(opts: {\n collection: string\n id: string\n field: string\n refTo: string\n refId: string | null\n message: string\n }) {\n super('REF_INTEGRITY', opts.message)\n this.name = 'RefIntegrityError'\n this.collection = opts.collection\n this.id = opts.id\n this.field = opts.field\n this.refTo = opts.refTo\n this.refId = opts.refId\n }\n}\n\n/**\n * Thrown when `ref()` is called with a target name that looks like\n * a cross-compartment reference (contains a `/`). Separate error\n * class because the fix is different: RefIntegrityError means \"data\n * is wrong\"; RefScopeError means \"the ref declaration is wrong\".\n */\nexport class RefScopeError extends NoydbError {\n constructor(target: string) {\n super(\n 'REF_SCOPE',\n `Cross-compartment references are not supported in v0.4 — got target \"${target}\". ` +\n `Use a simple collection name (e.g. \"clients\"), not a path. ` +\n `Cross-compartment refs are tracked for a future release.`,\n )\n this.name = 'RefScopeError'\n }\n}\n\n/**\n * Helper constructor. Thin wrapper around the object literal so user\n * code reads like `ref('clients')` instead of `{ target: 'clients',\n * mode: 'strict' }` — this is the only ergonomics reason it exists.\n *\n * Validates the target name eagerly so a misconfigured ref declaration\n * fails at collection construction time, not at the first put.\n */\nexport function ref(target: string, mode: RefMode = 'strict'): RefDescriptor {\n if (target.includes('/')) {\n throw new RefScopeError(target)\n }\n if (!target || target.startsWith('_')) {\n throw new Error(\n `ref(): target collection name must be non-empty and cannot start with '_' (reserved for internal collections). Got \"${target}\".`,\n )\n }\n return { target, mode }\n}\n\n/**\n * Per-compartment registry of reference declarations.\n *\n * The registry is populated by `Collection` constructors (which pass\n * their `refs` option through the Compartment) and consulted by the\n * Compartment on every `put` / `delete` and by `checkIntegrity`. A\n * single instance lives on the Compartment for its lifetime; there's\n * no global state.\n *\n * The data structure is two parallel maps:\n *\n * - `outbound`: `collection → { field → RefDescriptor }` — what\n * refs does `collection` declare? Used on put to check\n * strict-target-exists and on checkIntegrity to walk each\n * collection's outbound refs.\n *\n * - `inbound`: `target → Array<{ collection, field, mode }>` —\n * which collections reference `target`? Used on delete to find\n * the records that might be affected by cascade / strict.\n *\n * The two views are kept in sync by `register()` and never mutated\n * otherwise — refs can't be unregistered at runtime in v0.4.\n */\nexport class RefRegistry {\n private readonly outbound = new Map<string, Record<string, RefDescriptor>>()\n private readonly inbound = new Map<\n string,\n Array<{ collection: string; field: string; mode: RefMode }>\n >()\n\n /**\n * Register the refs declared by a single collection. Idempotent in\n * the happy path — calling twice with the same data is a no-op.\n * Calling twice with DIFFERENT data throws, because silent\n * overrides would be confusing (\"I changed the ref and it doesn't\n * update\" vs \"I declared the same collection twice with different\n * refs and the second call won\").\n */\n register(collection: string, refs: Record<string, RefDescriptor>): void {\n const existing = this.outbound.get(collection)\n if (existing) {\n // Compare shallowly — if any field disagrees, reject.\n const existingKeys = Object.keys(existing).sort()\n const newKeys = Object.keys(refs).sort()\n if (existingKeys.join(',') !== newKeys.join(',')) {\n throw new Error(\n `RefRegistry: conflicting ref declarations for collection \"${collection}\"`,\n )\n }\n for (const k of existingKeys) {\n const a = existing[k]\n const b = refs[k]\n if (!a || !b || a.target !== b.target || a.mode !== b.mode) {\n throw new Error(\n `RefRegistry: conflicting ref declarations for collection \"${collection}\" field \"${k}\"`,\n )\n }\n }\n return\n }\n this.outbound.set(collection, { ...refs })\n for (const [field, desc] of Object.entries(refs)) {\n const list = this.inbound.get(desc.target) ?? []\n list.push({ collection, field, mode: desc.mode })\n this.inbound.set(desc.target, list)\n }\n }\n\n /** Get the outbound refs declared by a collection (or `{}` if none). */\n getOutbound(collection: string): Record<string, RefDescriptor> {\n return this.outbound.get(collection) ?? {}\n }\n\n /** Get the inbound refs that target a given collection (or `[]`). */\n getInbound(\n target: string,\n ): ReadonlyArray<{ collection: string; field: string; mode: RefMode }> {\n return this.inbound.get(target) ?? []\n }\n\n /**\n * Iterate every (collection → refs) pair that has at least one\n * declared reference. Used by `checkIntegrity` to walk the full\n * universe of outbound refs without needing to track collection\n * names elsewhere.\n */\n entries(): Array<[string, Record<string, RefDescriptor>]> {\n return [...this.outbound.entries()]\n }\n\n /** Clear the registry. Test-only escape hatch; never called from production code. */\n clear(): void {\n this.outbound.clear()\n this.inbound.clear()\n }\n}\n\n/**\n * Shape of a single violation reported by `compartment.checkIntegrity()`.\n *\n * `refId` is the value we saw in the referencing field — it's the\n * ID we expected to find in `refTo`, but didn't. Left as `unknown`\n * because records are loosely typed at the integrity-check layer.\n */\nexport interface RefViolation {\n readonly collection: string\n readonly id: string\n readonly field: string\n readonly refTo: string\n readonly refId: unknown\n readonly mode: RefMode\n}\n","import type { NoydbAdapter, KeyringFile, Role, Permissions, GrantOptions, RevokeOptions, UserInfo, EncryptedEnvelope } from './types.js'\nimport { NOYDB_KEYRING_VERSION, NOYDB_FORMAT_VERSION } from './types.js'\nimport {\n deriveKey,\n generateDEK,\n generateSalt,\n wrapKey,\n unwrapKey,\n encrypt,\n decrypt,\n bufferToBase64,\n base64ToBuffer,\n} from './crypto.js'\nimport { NoAccessError, PermissionDeniedError } from './errors.js'\n\n// ─── Roles that can grant/revoke ───────────────────────────────────────\n\nconst GRANTABLE_BY_ADMIN: readonly Role[] = ['operator', 'viewer', 'client']\n\nfunction canGrant(callerRole: Role, targetRole: Role): boolean {\n if (callerRole === 'owner') return true\n if (callerRole === 'admin') return GRANTABLE_BY_ADMIN.includes(targetRole)\n return false\n}\n\nfunction canRevoke(callerRole: Role, targetRole: Role): boolean {\n if (targetRole === 'owner') return false // owner cannot be revoked\n if (callerRole === 'owner') return true\n if (callerRole === 'admin') return GRANTABLE_BY_ADMIN.includes(targetRole)\n return false\n}\n\n// ─── Unlocked Keyring ──────────────────────────────────────────────────\n\n/** In-memory representation of an unlocked keyring. */\nexport interface UnlockedKeyring {\n readonly userId: string\n readonly displayName: string\n readonly role: Role\n readonly permissions: Permissions\n readonly deks: Map<string, CryptoKey>\n readonly kek: CryptoKey\n readonly salt: Uint8Array\n}\n\n// ─── Load / Create ─────────────────────────────────────────────────────\n\n/** Load and unlock a user's keyring for a compartment. */\nexport async function loadKeyring(\n adapter: NoydbAdapter,\n compartment: string,\n userId: string,\n passphrase: string,\n): Promise<UnlockedKeyring> {\n const envelope = await adapter.get(compartment, '_keyring', userId)\n\n if (!envelope) {\n throw new NoAccessError(`No keyring found for user \"${userId}\" in compartment \"${compartment}\"`)\n }\n\n const keyringFile = JSON.parse(envelope._data) as KeyringFile\n const salt = base64ToBuffer(keyringFile.salt)\n const kek = await deriveKey(passphrase, salt)\n\n const deks = new Map<string, CryptoKey>()\n for (const [collName, wrappedDek] of Object.entries(keyringFile.deks)) {\n const dek = await unwrapKey(wrappedDek, kek)\n deks.set(collName, dek)\n }\n\n return {\n userId: keyringFile.user_id,\n displayName: keyringFile.display_name,\n role: keyringFile.role,\n permissions: keyringFile.permissions,\n deks,\n kek,\n salt,\n }\n}\n\n/** Create the initial owner keyring for a new compartment. */\nexport async function createOwnerKeyring(\n adapter: NoydbAdapter,\n compartment: string,\n userId: string,\n passphrase: string,\n): Promise<UnlockedKeyring> {\n const salt = generateSalt()\n const kek = await deriveKey(passphrase, salt)\n\n const keyringFile: KeyringFile = {\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n user_id: userId,\n display_name: userId,\n role: 'owner',\n permissions: {},\n deks: {},\n salt: bufferToBase64(salt),\n created_at: new Date().toISOString(),\n granted_by: userId,\n }\n\n await writeKeyringFile(adapter, compartment, userId, keyringFile)\n\n return {\n userId,\n displayName: userId,\n role: 'owner',\n permissions: {},\n deks: new Map(),\n kek,\n salt,\n }\n}\n\n// ─── Grant ─────────────────────────────────────────────────────────────\n\n/** Grant access to a new user. Caller must have grant privilege. */\nexport async function grant(\n adapter: NoydbAdapter,\n compartment: string,\n callerKeyring: UnlockedKeyring,\n options: GrantOptions,\n): Promise<void> {\n if (!canGrant(callerKeyring.role, options.role)) {\n throw new PermissionDeniedError(\n `Role \"${callerKeyring.role}\" cannot grant role \"${options.role}\"`,\n )\n }\n\n // Determine which collections the new user gets access to\n const permissions = resolvePermissions(options.role, options.permissions)\n\n // Derive the new user's KEK from their passphrase\n const newSalt = generateSalt()\n const newKek = await deriveKey(options.passphrase, newSalt)\n\n // Wrap the appropriate DEKs with the new user's KEK\n const wrappedDeks: Record<string, string> = {}\n for (const collName of Object.keys(permissions)) {\n const dek = callerKeyring.deks.get(collName)\n if (dek) {\n wrappedDeks[collName] = await wrapKey(dek, newKek)\n }\n }\n\n // For owner/admin/viewer roles, wrap ALL known DEKs\n if (options.role === 'owner' || options.role === 'admin' || options.role === 'viewer') {\n for (const [collName, dek] of callerKeyring.deks) {\n if (!(collName in wrappedDeks)) {\n wrappedDeks[collName] = await wrapKey(dek, newKek)\n }\n }\n }\n\n // For ALL roles, propagate system-prefixed collection DEKs\n // (`_ledger`, `_history`, `_sync`, …). These are internal collections\n // that any user with access to the compartment must be able to\n // read and write — for example, the v0.4 hash-chained ledger writes\n // an entry on every put/delete, so operators and clients with write\n // access to a single data collection still need the `_ledger` DEK.\n //\n // Trade-off: a granted user can decrypt every system-collection\n // entry, including ones they would not otherwise have access to\n // (e.g., an operator on `invoices` can read ledger entries for\n // mutations in `salaries`). This is a metadata leak, not a\n // plaintext leak — the ledger entries record collection names,\n // record ids, and ciphertext hashes, but never plaintext records.\n // Per-collection ledger DEKs are tracked as a v0.5 follow-up.\n for (const [collName, dek] of callerKeyring.deks) {\n if (collName.startsWith('_') && !(collName in wrappedDeks)) {\n wrappedDeks[collName] = await wrapKey(dek, newKek)\n }\n }\n\n const keyringFile: KeyringFile = {\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n user_id: options.userId,\n display_name: options.displayName,\n role: options.role,\n permissions,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n created_at: new Date().toISOString(),\n granted_by: callerKeyring.userId,\n }\n\n await writeKeyringFile(adapter, compartment, options.userId, keyringFile)\n}\n\n// ─── Revoke ────────────────────────────────────────────────────────────\n\n/** Revoke a user's access. Optionally rotate keys for affected collections. */\nexport async function revoke(\n adapter: NoydbAdapter,\n compartment: string,\n callerKeyring: UnlockedKeyring,\n options: RevokeOptions,\n): Promise<void> {\n // Load the target's keyring to check their role\n const targetEnvelope = await adapter.get(compartment, '_keyring', options.userId)\n if (!targetEnvelope) {\n throw new NoAccessError(`User \"${options.userId}\" has no keyring in compartment \"${compartment}\"`)\n }\n\n const targetKeyring = JSON.parse(targetEnvelope._data) as KeyringFile\n\n if (!canRevoke(callerKeyring.role, targetKeyring.role)) {\n throw new PermissionDeniedError(\n `Role \"${callerKeyring.role}\" cannot revoke role \"${targetKeyring.role}\"`,\n )\n }\n\n // Collect which collections the revoked user had access to\n const affectedCollections = Object.keys(targetKeyring.deks)\n\n // Delete the revoked user's keyring\n await adapter.delete(compartment, '_keyring', options.userId)\n\n // Rotate keys if requested\n if (options.rotateKeys !== false && affectedCollections.length > 0) {\n await rotateKeys(adapter, compartment, callerKeyring, affectedCollections)\n }\n}\n\n// ─── Key Rotation ──────────────────────────────────────────────────────\n\n/**\n * Rotate DEKs for specified collections:\n * 1. Generate new DEKs\n * 2. Re-encrypt all records in affected collections\n * 3. Re-wrap new DEKs for all remaining users\n */\nexport async function rotateKeys(\n adapter: NoydbAdapter,\n compartment: string,\n callerKeyring: UnlockedKeyring,\n collections: string[],\n): Promise<void> {\n // Generate new DEKs for each affected collection\n const newDeks = new Map<string, CryptoKey>()\n for (const collName of collections) {\n newDeks.set(collName, await generateDEK())\n }\n\n // Re-encrypt all records in affected collections\n for (const collName of collections) {\n const oldDek = callerKeyring.deks.get(collName)\n const newDek = newDeks.get(collName)!\n if (!oldDek) continue\n\n const ids = await adapter.list(compartment, collName)\n for (const id of ids) {\n const envelope = await adapter.get(compartment, collName, id)\n if (!envelope || !envelope._iv) continue\n\n // Decrypt with old DEK\n const plaintext = await decrypt(envelope._iv, envelope._data, oldDek)\n\n // Re-encrypt with new DEK\n const { iv, data } = await encrypt(plaintext, newDek)\n const newEnvelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: envelope._v,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n }\n await adapter.put(compartment, collName, id, newEnvelope)\n }\n }\n\n // Update caller's keyring with new DEKs\n for (const [collName, newDek] of newDeks) {\n callerKeyring.deks.set(collName, newDek)\n }\n await persistKeyring(adapter, compartment, callerKeyring)\n\n // Update all remaining users' keyrings with re-wrapped new DEKs\n const userIds = await adapter.list(compartment, '_keyring')\n for (const userId of userIds) {\n if (userId === callerKeyring.userId) continue\n\n const userEnvelope = await adapter.get(compartment, '_keyring', userId)\n if (!userEnvelope) continue\n\n const userKeyringFile = JSON.parse(userEnvelope._data) as KeyringFile\n // Note: we can't derive other users' KEKs to re-wrap DEKs for them.\n // Rotation requires users to re-unlock and be re-granted after the caller\n // re-wraps with the raw DEKs held in memory. See rotation flow below.\n // The trick: import the user's KEK from their salt? No — we need their passphrase.\n //\n // Per the spec: the caller (owner/admin) wraps the new DEKs with each remaining\n // user's KEK. But we can't derive their KEK without their passphrase.\n //\n // Real solution from the spec: the caller wraps the DEK using the approach of\n // reading each user's existing wrapping. Since we can't derive their KEK,\n // we use a RE-KEYING approach: the new DEK is wrapped with a key-wrapping-key\n // that we CAN derive — we use the existing wrapped DEK as proof that the user\n // had access, and we replace it with the new wrapped DEK.\n //\n // Practical approach: Since the owner/admin has all raw DEKs in memory,\n // and each user's keyring contains their salt, we need the users to\n // re-authenticate to get the new wrapped keys. This is the standard approach.\n //\n // For NOYDB Phase 2: we'll update the keyring file to include a \"pending_rekey\"\n // flag. Users will get new DEKs on next login when the owner provides them.\n //\n // SIMPLER approach used here: Since the owner performed the rotation,\n // the owner has both old and new DEKs. We store a \"rekey token\" that the\n // user can use to unwrap: we wrap the new DEK with the OLD DEK (which the\n // user can still unwrap from their keyring, since their keyring has the old\n // wrapped DEK and their KEK can unwrap it).\n\n // Actually even simpler: we just need the user's KEK. We don't have it.\n // The spec says the owner wraps new DEKs for each remaining user.\n // This requires knowing each user's KEK (or having a shared secret).\n //\n // The CORRECT implementation from the spec: the owner/admin has all DEKs.\n // Each user's keyring stores DEKs wrapped with THAT USER's KEK.\n // To re-wrap, we need each user's KEK — which we can't get.\n //\n // Real-world solution: use a KEY ESCROW approach where the owner stores\n // each user's wrapping key (not their passphrase, but a key derived from\n // the grant process). During grant, the owner stores a copy of the new user's\n // KEK (wrapped with the owner's KEK) so they can re-wrap later.\n //\n // For now: mark the user's keyring as needing rekey. The user will need to\n // re-authenticate (owner provides new passphrase or re-grants).\n\n // Update: simplest correct approach — during grant, we store the user's KEK\n // wrapped with the owner's KEK in a separate escrow field. Then during rotation,\n // the owner unwraps the user's KEK from escrow and wraps the new DEKs.\n //\n // BUT: that means we need to change the KeyringFile format.\n // For Phase 2 MVP: just delete the user's old DEK entries and require re-grant.\n // This is secure (revoked keys are gone) but inconvenient (remaining users\n // need re-grant for rotated collections).\n\n // PHASE 2 APPROACH: Remove the affected collection DEKs from remaining users'\n // keyrings. The owner must re-grant access to those collections.\n // This is correct and secure — just requires the owner to re-run grant().\n\n const updatedDeks = { ...userKeyringFile.deks }\n for (const collName of collections) {\n delete updatedDeks[collName]\n }\n\n const updatedPermissions = { ...userKeyringFile.permissions }\n for (const collName of collections) {\n delete updatedPermissions[collName]\n }\n\n const updatedKeyring: KeyringFile = {\n ...userKeyringFile,\n deks: updatedDeks,\n permissions: updatedPermissions,\n }\n\n await writeKeyringFile(adapter, compartment, userId, updatedKeyring)\n }\n}\n\n// ─── Change Secret ─────────────────────────────────────────────────────\n\n/** Change the user's passphrase. Re-wraps all DEKs with the new KEK. */\nexport async function changeSecret(\n adapter: NoydbAdapter,\n compartment: string,\n keyring: UnlockedKeyring,\n newPassphrase: string,\n): Promise<UnlockedKeyring> {\n const newSalt = generateSalt()\n const newKek = await deriveKey(newPassphrase, newSalt)\n\n // Re-wrap all DEKs with the new KEK\n const wrappedDeks: Record<string, string> = {}\n for (const [collName, dek] of keyring.deks) {\n wrappedDeks[collName] = await wrapKey(dek, newKek)\n }\n\n const keyringFile: KeyringFile = {\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n user_id: keyring.userId,\n display_name: keyring.displayName,\n role: keyring.role,\n permissions: keyring.permissions,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n created_at: new Date().toISOString(),\n granted_by: keyring.userId,\n }\n\n await writeKeyringFile(adapter, compartment, keyring.userId, keyringFile)\n\n return {\n userId: keyring.userId,\n displayName: keyring.displayName,\n role: keyring.role,\n permissions: keyring.permissions,\n deks: keyring.deks, // Same DEKs, different wrapping\n kek: newKek,\n salt: newSalt,\n }\n}\n\n// ─── List Users ────────────────────────────────────────────────────────\n\n/** List all users with access to a compartment. */\nexport async function listUsers(\n adapter: NoydbAdapter,\n compartment: string,\n): Promise<UserInfo[]> {\n const userIds = await adapter.list(compartment, '_keyring')\n const users: UserInfo[] = []\n\n for (const userId of userIds) {\n const envelope = await adapter.get(compartment, '_keyring', userId)\n if (!envelope) continue\n const kf = JSON.parse(envelope._data) as KeyringFile\n users.push({\n userId: kf.user_id,\n displayName: kf.display_name,\n role: kf.role,\n permissions: kf.permissions,\n createdAt: kf.created_at,\n grantedBy: kf.granted_by,\n })\n }\n\n return users\n}\n\n// ─── DEK Management ────────────────────────────────────────────────────\n\n/** Ensure a DEK exists for a collection. Generates one if new. */\nexport async function ensureCollectionDEK(\n adapter: NoydbAdapter,\n compartment: string,\n keyring: UnlockedKeyring,\n): Promise<(collectionName: string) => Promise<CryptoKey>> {\n return async (collectionName: string): Promise<CryptoKey> => {\n const existing = keyring.deks.get(collectionName)\n if (existing) return existing\n\n const dek = await generateDEK()\n keyring.deks.set(collectionName, dek)\n await persistKeyring(adapter, compartment, keyring)\n return dek\n }\n}\n\n// ─── Permission Checks ─────────────────────────────────────────────────\n\n/** Check if a user has write permission for a collection. */\nexport function hasWritePermission(keyring: UnlockedKeyring, collectionName: string): boolean {\n if (keyring.role === 'owner' || keyring.role === 'admin') return true\n if (keyring.role === 'viewer' || keyring.role === 'client') return false\n return keyring.permissions[collectionName] === 'rw'\n}\n\n/** Check if a user has any access to a collection. */\nexport function hasAccess(keyring: UnlockedKeyring, collectionName: string): boolean {\n if (keyring.role === 'owner' || keyring.role === 'admin' || keyring.role === 'viewer') return true\n return collectionName in keyring.permissions\n}\n\n// ─── Helpers ───────────────────────────────────────────────────────────\n\n/** Persist a keyring file to the adapter. */\nexport async function persistKeyring(\n adapter: NoydbAdapter,\n compartment: string,\n keyring: UnlockedKeyring,\n): Promise<void> {\n const wrappedDeks: Record<string, string> = {}\n for (const [collName, dek] of keyring.deks) {\n wrappedDeks[collName] = await wrapKey(dek, keyring.kek)\n }\n\n const keyringFile: KeyringFile = {\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n user_id: keyring.userId,\n display_name: keyring.displayName,\n role: keyring.role,\n permissions: keyring.permissions,\n deks: wrappedDeks,\n salt: bufferToBase64(keyring.salt),\n created_at: new Date().toISOString(),\n granted_by: keyring.userId,\n }\n\n await writeKeyringFile(adapter, compartment, keyring.userId, keyringFile)\n}\n\nfunction resolvePermissions(role: Role, explicit?: Permissions): Permissions {\n if (role === 'owner' || role === 'admin' || role === 'viewer') return {}\n return explicit ?? {}\n}\n\nasync function writeKeyringFile(\n adapter: NoydbAdapter,\n compartment: string,\n userId: string,\n keyringFile: KeyringFile,\n): Promise<void> {\n const envelope = {\n _noydb: 1 as const,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(keyringFile),\n }\n await adapter.put(compartment, '_keyring', userId, envelope)\n}\n","import type { NoydbAdapter, EncryptedEnvelope, HistoryOptions, PruneOptions } from './types.js'\n\n/**\n * History storage convention:\n * Collection: `_history`\n * ID format: `{collection}:{recordId}:{paddedVersion}`\n * Version is zero-padded to 10 digits for lexicographic sorting.\n */\n\nconst HISTORY_COLLECTION = '_history'\nconst VERSION_PAD = 10\n\nfunction historyId(collection: string, recordId: string, version: number): string {\n return `${collection}:${recordId}:${String(version).padStart(VERSION_PAD, '0')}`\n}\n\n// Unused today, kept for future history-id parsing utilities.\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction parseHistoryId(id: string): { collection: string; recordId: string; version: number } | null {\n const lastColon = id.lastIndexOf(':')\n if (lastColon < 0) return null\n const versionStr = id.slice(lastColon + 1)\n const rest = id.slice(0, lastColon)\n const firstColon = rest.indexOf(':')\n if (firstColon < 0) return null\n return {\n collection: rest.slice(0, firstColon),\n recordId: rest.slice(firstColon + 1),\n version: parseInt(versionStr, 10),\n }\n}\n\nfunction matchesPrefix(id: string, collection: string, recordId?: string): boolean {\n if (recordId) {\n return id.startsWith(`${collection}:${recordId}:`)\n }\n return id.startsWith(`${collection}:`)\n}\n\n/** Save a history entry (a complete encrypted envelope snapshot). */\nexport async function saveHistory(\n adapter: NoydbAdapter,\n compartment: string,\n collection: string,\n recordId: string,\n envelope: EncryptedEnvelope,\n): Promise<void> {\n const id = historyId(collection, recordId, envelope._v)\n await adapter.put(compartment, HISTORY_COLLECTION, id, envelope)\n}\n\n/** Get history entries for a record, sorted newest-first. */\nexport async function getHistory(\n adapter: NoydbAdapter,\n compartment: string,\n collection: string,\n recordId: string,\n options?: HistoryOptions,\n): Promise<EncryptedEnvelope[]> {\n const allIds = await adapter.list(compartment, HISTORY_COLLECTION)\n const matchingIds = allIds\n .filter(id => matchesPrefix(id, collection, recordId))\n .sort()\n .reverse() // newest first\n\n const entries: EncryptedEnvelope[] = []\n\n for (const id of matchingIds) {\n const envelope = await adapter.get(compartment, HISTORY_COLLECTION, id)\n if (!envelope) continue\n\n // Apply time filters\n if (options?.from && envelope._ts < options.from) continue\n if (options?.to && envelope._ts > options.to) continue\n\n entries.push(envelope)\n\n if (options?.limit && entries.length >= options.limit) break\n }\n\n return entries\n}\n\n/** Get a specific version's envelope from history. */\nexport async function getVersionEnvelope(\n adapter: NoydbAdapter,\n compartment: string,\n collection: string,\n recordId: string,\n version: number,\n): Promise<EncryptedEnvelope | null> {\n const id = historyId(collection, recordId, version)\n return adapter.get(compartment, HISTORY_COLLECTION, id)\n}\n\n/** Prune history entries. Returns the number of entries deleted. */\nexport async function pruneHistory(\n adapter: NoydbAdapter,\n compartment: string,\n collection: string,\n recordId: string | undefined,\n options: PruneOptions,\n): Promise<number> {\n const allIds = await adapter.list(compartment, HISTORY_COLLECTION)\n const matchingIds = allIds\n .filter(id => recordId ? matchesPrefix(id, collection, recordId) : matchesPrefix(id, collection))\n .sort()\n\n let toDelete: string[] = []\n\n if (options.keepVersions !== undefined) {\n // Keep only the N most recent, delete the rest\n const keep = options.keepVersions\n if (matchingIds.length > keep) {\n toDelete = matchingIds.slice(0, matchingIds.length - keep)\n }\n }\n\n if (options.beforeDate) {\n // Delete entries older than the specified date\n for (const id of matchingIds) {\n if (toDelete.includes(id)) continue\n const envelope = await adapter.get(compartment, HISTORY_COLLECTION, id)\n if (envelope && envelope._ts < options.beforeDate) {\n toDelete.push(id)\n }\n }\n }\n\n // Deduplicate\n const uniqueDeletes = [...new Set(toDelete)]\n\n for (const id of uniqueDeletes) {\n await adapter.delete(compartment, HISTORY_COLLECTION, id)\n }\n\n return uniqueDeletes.length\n}\n\n/** Clear all history for a compartment, optionally scoped to a collection or record. */\nexport async function clearHistory(\n adapter: NoydbAdapter,\n compartment: string,\n collection?: string,\n recordId?: string,\n): Promise<number> {\n const allIds = await adapter.list(compartment, HISTORY_COLLECTION)\n let toDelete: string[]\n\n if (collection && recordId) {\n toDelete = allIds.filter(id => matchesPrefix(id, collection, recordId))\n } else if (collection) {\n toDelete = allIds.filter(id => matchesPrefix(id, collection))\n } else {\n toDelete = allIds\n }\n\n for (const id of toDelete) {\n await adapter.delete(compartment, HISTORY_COLLECTION, id)\n }\n\n return toDelete.length\n}\n","/**\n * Zero-dependency JSON diff.\n * Produces a flat list of changes between two plain objects.\n */\n\nexport type ChangeType = 'added' | 'removed' | 'changed'\n\nexport interface DiffEntry {\n /** Dot-separated path to the changed field (e.g. \"address.city\"). */\n readonly path: string\n /** Type of change. */\n readonly type: ChangeType\n /** Previous value (undefined for 'added'). */\n readonly from?: unknown\n /** New value (undefined for 'removed'). */\n readonly to?: unknown\n}\n\n/**\n * Compute differences between two objects.\n * Returns an array of DiffEntry describing each changed field.\n * Returns empty array if objects are identical.\n */\nexport function diff(oldObj: unknown, newObj: unknown, basePath = ''): DiffEntry[] {\n const changes: DiffEntry[] = []\n\n // Both primitives or nulls\n if (oldObj === newObj) return changes\n\n // One is null/undefined\n if (oldObj == null && newObj != null) {\n return [{ path: basePath || '(root)', type: 'added', to: newObj }]\n }\n if (oldObj != null && newObj == null) {\n return [{ path: basePath || '(root)', type: 'removed', from: oldObj }]\n }\n\n // Different types\n if (typeof oldObj !== typeof newObj) {\n return [{ path: basePath || '(root)', type: 'changed', from: oldObj, to: newObj }]\n }\n\n // Both primitives (and not equal — checked above)\n if (typeof oldObj !== 'object') {\n return [{ path: basePath || '(root)', type: 'changed', from: oldObj, to: newObj }]\n }\n\n // Both arrays\n if (Array.isArray(oldObj) && Array.isArray(newObj)) {\n const maxLen = Math.max(oldObj.length, newObj.length)\n for (let i = 0; i < maxLen; i++) {\n const p = basePath ? `${basePath}[${i}]` : `[${i}]`\n if (i >= oldObj.length) {\n changes.push({ path: p, type: 'added', to: newObj[i] })\n } else if (i >= newObj.length) {\n changes.push({ path: p, type: 'removed', from: oldObj[i] })\n } else {\n changes.push(...diff(oldObj[i], newObj[i], p))\n }\n }\n return changes\n }\n\n // Both objects\n const oldRecord = oldObj as Record<string, unknown>\n const newRecord = newObj as Record<string, unknown>\n const allKeys = new Set([...Object.keys(oldRecord), ...Object.keys(newRecord)])\n\n for (const key of allKeys) {\n const p = basePath ? `${basePath}.${key}` : key\n if (!(key in oldRecord)) {\n changes.push({ path: p, type: 'added', to: newRecord[key] })\n } else if (!(key in newRecord)) {\n changes.push({ path: p, type: 'removed', from: oldRecord[key] })\n } else {\n changes.push(...diff(oldRecord[key], newRecord[key], p))\n }\n }\n\n return changes\n}\n\n/** Format a diff as a human-readable string. */\nexport function formatDiff(changes: DiffEntry[]): string {\n if (changes.length === 0) return '(no changes)'\n return changes.map(c => {\n switch (c.type) {\n case 'added':\n return `+ ${c.path}: ${JSON.stringify(c.to)}`\n case 'removed':\n return `- ${c.path}: ${JSON.stringify(c.from)}`\n case 'changed':\n return `~ ${c.path}: ${JSON.stringify(c.from)} → ${JSON.stringify(c.to)}`\n }\n }).join('\\n')\n}\n","/**\n * Operator implementations for the query DSL.\n *\n * All predicates run client-side, AFTER decryption — they never see ciphertext.\n * This file is dependency-free and tree-shakeable.\n */\n\n/** Comparison operators supported by the where() builder. */\nexport type Operator =\n | '=='\n | '!='\n | '<'\n | '<='\n | '>'\n | '>='\n | 'in'\n | 'contains'\n | 'startsWith'\n | 'between'\n\n/**\n * A single field comparison clause inside a query plan.\n * Plans are JSON-serializable, so this type uses primitives only.\n */\nexport interface FieldClause {\n readonly type: 'field'\n readonly field: string\n readonly op: Operator\n readonly value: unknown\n}\n\n/**\n * A user-supplied predicate function escape hatch. Not serializable.\n *\n * The predicate accepts `unknown` at the type level so the surrounding\n * Clause type can stay non-parametric — this keeps Collection<T> covariant\n * in T at the public API surface. Builder methods cast user predicates\n * (typed `(record: T) => boolean`) into this shape on the way in.\n */\nexport interface FilterClause {\n readonly type: 'filter'\n readonly fn: (record: unknown) => boolean\n}\n\n/** A logical group of clauses combined by AND or OR. */\nexport interface GroupClause {\n readonly type: 'group'\n readonly op: 'and' | 'or'\n readonly clauses: readonly Clause[]\n}\n\nexport type Clause = FieldClause | FilterClause | GroupClause\n\n/**\n * Read a possibly nested field path like \"address.city\" from a record.\n * Returns undefined if any segment is missing.\n */\nexport function readPath(record: unknown, path: string): unknown {\n if (record === null || record === undefined) return undefined\n if (!path.includes('.')) {\n return (record as Record<string, unknown>)[path]\n }\n const segments = path.split('.')\n let cursor: unknown = record\n for (const segment of segments) {\n if (cursor === null || cursor === undefined) return undefined\n cursor = (cursor as Record<string, unknown>)[segment]\n }\n return cursor\n}\n\n/**\n * Evaluate a single field clause against a record.\n * Returns false on type mismatches rather than throwing — query results\n * exclude non-matching records by definition.\n */\nexport function evaluateFieldClause(record: unknown, clause: FieldClause): boolean {\n const actual = readPath(record, clause.field)\n const { op, value } = clause\n\n switch (op) {\n case '==':\n return actual === value\n case '!=':\n return actual !== value\n case '<':\n return isComparable(actual, value) && (actual as number) < (value as number)\n case '<=':\n return isComparable(actual, value) && (actual as number) <= (value as number)\n case '>':\n return isComparable(actual, value) && (actual as number) > (value as number)\n case '>=':\n return isComparable(actual, value) && (actual as number) >= (value as number)\n case 'in':\n return Array.isArray(value) && value.includes(actual)\n case 'contains':\n if (typeof actual === 'string') return typeof value === 'string' && actual.includes(value)\n if (Array.isArray(actual)) return actual.includes(value)\n return false\n case 'startsWith':\n return typeof actual === 'string' && typeof value === 'string' && actual.startsWith(value)\n case 'between': {\n if (!Array.isArray(value) || value.length !== 2) return false\n const [lo, hi] = value\n if (!isComparable(actual, lo) || !isComparable(actual, hi)) return false\n return (actual as number) >= (lo as number) && (actual as number) <= (hi as number)\n }\n default: {\n // Exhaustiveness — TS will error if a new operator is added without a case.\n const _exhaustive: never = op\n void _exhaustive\n return false\n }\n }\n}\n\n/**\n * Two values are \"comparable\" if they share an order-defined runtime type.\n * Strings compare lexicographically; numbers and Dates numerically; otherwise false.\n */\nfunction isComparable(a: unknown, b: unknown): boolean {\n if (typeof a === 'number' && typeof b === 'number') return true\n if (typeof a === 'string' && typeof b === 'string') return true\n if (a instanceof Date && b instanceof Date) return true\n return false\n}\n\n/**\n * Evaluate any clause (field / filter / group) against a record.\n * The recursion depth is bounded by the user's query expression — no risk of\n * blowing the stack on a 50K-record collection.\n */\nexport function evaluateClause(record: unknown, clause: Clause): boolean {\n switch (clause.type) {\n case 'field':\n return evaluateFieldClause(record, clause)\n case 'filter':\n return clause.fn(record)\n case 'group':\n if (clause.op === 'and') {\n for (const child of clause.clauses) {\n if (!evaluateClause(record, child)) return false\n }\n return true\n } else {\n for (const child of clause.clauses) {\n if (evaluateClause(record, child)) return true\n }\n return false\n }\n }\n}\n","/**\n * Chainable, immutable query builder.\n *\n * Each builder operation returns a NEW Query — the underlying plan is never\n * mutated. This makes plans safe to share, cache, and serialize.\n */\n\nimport type { Clause, FieldClause, FilterClause, GroupClause, Operator } from './predicate.js'\nimport { evaluateClause } from './predicate.js'\nimport type { CollectionIndexes } from './indexes.js'\n\nexport interface OrderBy {\n readonly field: string\n readonly direction: 'asc' | 'desc'\n}\n\n/**\n * A complete query plan: zero-or-more clauses, optional ordering, pagination.\n * Plans are JSON-serializable as long as no FilterClause is present.\n *\n * Plans are intentionally NOT parametric on T — see `predicate.ts` FilterClause\n * for the variance reasoning. The public `Query<T>` API attaches the type tag.\n */\nexport interface QueryPlan {\n readonly clauses: readonly Clause[]\n readonly orderBy: readonly OrderBy[]\n readonly limit: number | undefined\n readonly offset: number\n}\n\nconst EMPTY_PLAN: QueryPlan = {\n clauses: [],\n orderBy: [],\n limit: undefined,\n offset: 0,\n}\n\n/**\n * Source of records that a query executes against.\n *\n * The interface is non-parametric to keep variance friendly: callers cast\n * their typed source (e.g. `QuerySource<Invoice>`) into this opaque shape.\n *\n * `getIndexes` and `lookupById` are optional fast-path hooks. When both are\n * present and a where clause matches an indexed field, the executor uses\n * the index to skip a linear scan. Sources without these methods (or with\n * `getIndexes` returning `null`) always fall back to a linear scan.\n */\nexport interface QuerySource<T> {\n /** Snapshot of all current records. The query never mutates this array. */\n snapshot(): readonly T[]\n /** Subscribe to mutations; returns an unsubscribe function. */\n subscribe?(cb: () => void): () => void\n /** Index store for the indexed-fast-path. Optional. */\n getIndexes?(): CollectionIndexes | null\n /** O(1) record lookup by id, used to materialize index hits. */\n lookupById?(id: string): T | undefined\n}\n\ninterface InternalSource {\n snapshot(): readonly unknown[]\n subscribe?(cb: () => void): () => void\n getIndexes?(): CollectionIndexes | null\n lookupById?(id: string): unknown\n}\n\n/**\n * The chainable builder. All methods return a new Query — the original\n * remains unchanged. Terminal methods (`toArray`, `first`, `count`,\n * `subscribe`) execute the plan against the source.\n *\n * Type parameter T flows through the public API for ergonomics, but the\n * internal storage uses `unknown` so Collection<T> stays covariant.\n */\nexport class Query<T> {\n private readonly source: InternalSource\n private readonly plan: QueryPlan\n\n constructor(source: QuerySource<T>, plan: QueryPlan = EMPTY_PLAN) {\n this.source = source as InternalSource\n this.plan = plan\n }\n\n /** Add a field comparison. Multiple where() calls are AND-combined. */\n where(field: string, op: Operator, value: unknown): Query<T> {\n const clause: FieldClause = { type: 'field', field, op, value }\n return new Query<T>(this.source as QuerySource<T>, {\n ...this.plan,\n clauses: [...this.plan.clauses, clause],\n })\n }\n\n /**\n * Logical OR group. Pass a callback that builds a sub-query.\n * Each clause inside the callback is OR-combined; the group itself\n * joins the parent plan with AND.\n */\n or(builder: (q: Query<T>) => Query<T>): Query<T> {\n const sub = builder(new Query<T>(this.source as QuerySource<T>))\n const group: GroupClause = {\n type: 'group',\n op: 'or',\n clauses: sub.plan.clauses,\n }\n return new Query<T>(this.source as QuerySource<T>, {\n ...this.plan,\n clauses: [...this.plan.clauses, group],\n })\n }\n\n /**\n * Logical AND group. Same shape as `or()` but every clause inside the group\n * must match. Useful for explicit grouping inside a larger OR.\n */\n and(builder: (q: Query<T>) => Query<T>): Query<T> {\n const sub = builder(new Query<T>(this.source as QuerySource<T>))\n const group: GroupClause = {\n type: 'group',\n op: 'and',\n clauses: sub.plan.clauses,\n }\n return new Query<T>(this.source as QuerySource<T>, {\n ...this.plan,\n clauses: [...this.plan.clauses, group],\n })\n }\n\n /** Escape hatch: add an arbitrary predicate function. Not serializable. */\n filter(fn: (record: T) => boolean): Query<T> {\n const clause: FilterClause = {\n type: 'filter',\n fn: fn as (record: unknown) => boolean,\n }\n return new Query<T>(this.source as QuerySource<T>, {\n ...this.plan,\n clauses: [...this.plan.clauses, clause],\n })\n }\n\n /** Sort by a field. Subsequent calls are tie-breakers. */\n orderBy(field: string, direction: 'asc' | 'desc' = 'asc'): Query<T> {\n return new Query<T>(this.source as QuerySource<T>, {\n ...this.plan,\n orderBy: [...this.plan.orderBy, { field, direction }],\n })\n }\n\n /** Cap the result size. */\n limit(n: number): Query<T> {\n return new Query<T>(this.source as QuerySource<T>, { ...this.plan, limit: n })\n }\n\n /** Skip the first N matching records (after ordering). */\n offset(n: number): Query<T> {\n return new Query<T>(this.source as QuerySource<T>, { ...this.plan, offset: n })\n }\n\n /** Execute the plan and return the matching records. */\n toArray(): T[] {\n return executePlanWithSource(this.source, this.plan) as T[]\n }\n\n /** Return the first matching record, or null. */\n first(): T | null {\n const result = executePlanWithSource(this.source, { ...this.plan, limit: 1 })\n return (result[0] as T | undefined) ?? null\n }\n\n /** Return the number of matching records (after where/filter, before limit). */\n count(): number {\n // Use the same index-aware candidate machinery as toArray(); skip the\n // index-driving clause from re-evaluation. The length BEFORE limit/offset\n // is what `count()` documents.\n const { candidates, remainingClauses } = candidateRecords(this.source, this.plan.clauses)\n if (remainingClauses.length === 0) return candidates.length\n return filterRecords(candidates, remainingClauses).length\n }\n\n /**\n * Re-run the query whenever the source notifies of changes.\n * Returns an unsubscribe function. The callback receives the latest result.\n * Throws if the source does not support subscriptions.\n */\n subscribe(cb: (result: T[]) => void): () => void {\n if (!this.source.subscribe) {\n throw new Error('Query source does not support subscriptions. Pass a source with a subscribe() method.')\n }\n cb(this.toArray())\n return this.source.subscribe(() => cb(this.toArray()))\n }\n\n /**\n * Return the plan as a JSON-friendly object. FilterClause entries are\n * stripped (their `fn` cannot be serialized) and replaced with\n * { type: 'filter', fn: '[function]' } so devtools can still see them.\n */\n toPlan(): unknown {\n return serializePlan(this.plan)\n }\n}\n\n/**\n * Index-aware execution: try the indexed fast path first, fall back to a\n * full scan otherwise. Mirrors `executePlan` for the public surface but\n * takes a `QuerySource` so it can consult `getIndexes()` and `lookupById()`.\n */\nfunction executePlanWithSource(source: InternalSource, plan: QueryPlan): unknown[] {\n const { candidates, remainingClauses } = candidateRecords(source, plan.clauses)\n // Only the clauses NOT consumed by the index need re-evaluation. This is\n // the key optimization that makes indexed queries dominate linear scans:\n // for a single-clause query against an indexed field, `remainingClauses`\n // is empty and we skip the per-record predicate evaluation entirely.\n let result = remainingClauses.length === 0\n ? [...candidates]\n : filterRecords(candidates, remainingClauses)\n if (plan.orderBy.length > 0) {\n result = sortRecords(result, plan.orderBy)\n }\n if (plan.offset > 0) {\n result = result.slice(plan.offset)\n }\n if (plan.limit !== undefined) {\n result = result.slice(0, plan.limit)\n }\n return result\n}\n\ninterface CandidateResult {\n /** The reduced candidate set, materialized to record objects. */\n readonly candidates: readonly unknown[]\n /** The clauses that the index could not satisfy and must still be evaluated. */\n readonly remainingClauses: readonly Clause[]\n}\n\n/**\n * Pick a candidate record set using the index store when possible.\n *\n * Strategy: scan the top-level clauses for the FIRST `==` or `in` clause\n * against an indexed field. If found, use the index to materialize a\n * candidate set and return the OTHER clauses as `remainingClauses`. The\n * caller skips re-evaluating the index-driving clause because the index\n * is authoritative for that field.\n *\n * This is a deliberately simple planner. A future optimizer could pick\n * the most selective index, intersect multiple indexes, or push composite\n * keys through. For v0.3 the single-index fast path is good enough.\n */\nfunction candidateRecords(source: InternalSource, clauses: readonly Clause[]): CandidateResult {\n const indexes = source.getIndexes?.()\n if (!indexes || !source.lookupById || clauses.length === 0) {\n return { candidates: source.snapshot(), remainingClauses: clauses }\n }\n // Bind the lookup method through an arrow so it doesn't drift from\n // its `this` context — keeps the unbound-method lint rule happy.\n const lookupById = (id: string): unknown => source.lookupById?.(id)\n\n for (let i = 0; i < clauses.length; i++) {\n const clause = clauses[i]!\n if (clause.type !== 'field') continue\n if (!indexes.has(clause.field)) continue\n\n let ids: ReadonlySet<string> | null = null\n if (clause.op === '==') {\n ids = indexes.lookupEqual(clause.field, clause.value)\n } else if (clause.op === 'in' && Array.isArray(clause.value)) {\n ids = indexes.lookupIn(clause.field, clause.value)\n }\n\n if (ids !== null) {\n // Found an index-eligible clause: materialize the candidate set and\n // remove this clause from the remaining list.\n const remaining: Clause[] = []\n for (let j = 0; j < clauses.length; j++) {\n if (j !== i) remaining.push(clauses[j]!)\n }\n return {\n candidates: materializeIds(ids, lookupById),\n remainingClauses: remaining,\n }\n }\n // Not index-eligible — keep scanning in case a later clause is a\n // better candidate.\n }\n\n // No clause was index-eligible — fall back to a full scan.\n return { candidates: source.snapshot(), remainingClauses: clauses }\n}\n\nfunction materializeIds(\n ids: ReadonlySet<string>,\n lookupById: (id: string) => unknown,\n): unknown[] {\n const out: unknown[] = []\n for (const id of ids) {\n const record = lookupById(id)\n if (record !== undefined) out.push(record)\n }\n return out\n}\n\n/**\n * Execute a plan against a snapshot of records.\n * Pure function — same input, same output, no side effects.\n *\n * Records are typed as `unknown` because plans are non-parametric; callers\n * cast the return type at the API surface (see `Query.toArray()`).\n */\nexport function executePlan(records: readonly unknown[], plan: QueryPlan): unknown[] {\n let result = filterRecords(records, plan.clauses)\n if (plan.orderBy.length > 0) {\n result = sortRecords(result, plan.orderBy)\n }\n if (plan.offset > 0) {\n result = result.slice(plan.offset)\n }\n if (plan.limit !== undefined) {\n result = result.slice(0, plan.limit)\n }\n return result\n}\n\nfunction filterRecords(records: readonly unknown[], clauses: readonly Clause[]): unknown[] {\n if (clauses.length === 0) return [...records]\n const out: unknown[] = []\n for (const r of records) {\n let matches = true\n for (const clause of clauses) {\n if (!evaluateClause(r, clause)) {\n matches = false\n break\n }\n }\n if (matches) out.push(r)\n }\n return out\n}\n\nfunction sortRecords(records: unknown[], orderBy: readonly OrderBy[]): unknown[] {\n // Stable sort: Array.prototype.sort is required to be stable since ES2019.\n return [...records].sort((a, b) => {\n for (const { field, direction } of orderBy) {\n const av = readField(a, field)\n const bv = readField(b, field)\n const cmp = compareValues(av, bv)\n if (cmp !== 0) return direction === 'asc' ? cmp : -cmp\n }\n return 0\n })\n}\n\nfunction readField(record: unknown, field: string): unknown {\n if (record === null || record === undefined) return undefined\n if (!field.includes('.')) {\n return (record as Record<string, unknown>)[field]\n }\n const segments = field.split('.')\n let cursor: unknown = record\n for (const segment of segments) {\n if (cursor === null || cursor === undefined) return undefined\n cursor = (cursor as Record<string, unknown>)[segment]\n }\n return cursor\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n // Nullish goes last in asc order.\n if (a === undefined || a === null) return b === undefined || b === null ? 0 : 1\n if (b === undefined || b === null) return -1\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime()\n // Mixed/unsupported types: treat as equal so the sort stays stable.\n // (Deliberate choice — we don't try to coerce arbitrary objects to strings.)\n return 0\n}\n\nfunction serializePlan(plan: QueryPlan): unknown {\n return {\n clauses: plan.clauses.map(serializeClause),\n orderBy: plan.orderBy,\n limit: plan.limit,\n offset: plan.offset,\n }\n}\n\nfunction serializeClause(clause: Clause): unknown {\n if (clause.type === 'filter') {\n return { type: 'filter', fn: '[function]' }\n }\n if (clause.type === 'group') {\n return {\n type: 'group',\n op: clause.op,\n clauses: clause.clauses.map(serializeClause),\n }\n }\n return clause\n}\n","/**\n * Secondary indexes for the query DSL.\n *\n * v0.3 ships **in-memory hash indexes**:\n * - Built during `Collection.ensureHydrated()` from the decrypted cache\n * - Maintained incrementally on `put` and `delete`\n * - Consulted by the query executor for `==` and `in` operators on\n * indexed fields, falling back to a linear scan otherwise\n * - Live entirely in memory — no adapter writes for the index itself\n *\n * Persistent encrypted index blobs (the spec's \"store as a separate\n * AES-256-GCM blob\" note) are deferred to a follow-up issue. The reasons\n * are documented in the v0.3 PR body — short version: at the v0.3 target\n * scale of 1K–50K records, building the index during hydrate is free,\n * so persistence buys nothing measurable.\n */\n\nimport { readPath } from './predicate.js'\n\n/**\n * Index declaration accepted by `Collection`'s constructor.\n *\n * Today only single-field hash indexes are supported. Future shapes\n * (composite, sorted, unique constraints) will land as additive variants\n * of this discriminated union without breaking existing declarations.\n */\nexport type IndexDef = string\n\n/**\n * Internal representation of a built hash index.\n *\n * Maps stringified field values to the set of record ids whose value\n * for that field matches. Stringification keeps the index simple and\n * works uniformly for primitives (`'open'`, `'42'`, `'true'`).\n *\n * Records whose indexed field is `undefined` or `null` are NOT inserted\n * — `query().where('field', '==', undefined)` falls back to a linear\n * scan, which is the conservative behavior.\n */\nexport interface HashIndex {\n readonly field: string\n readonly buckets: Map<string, Set<string>>\n}\n\n/**\n * Container for all indexes on a single collection.\n *\n * Methods are pure with respect to the in-memory `buckets` Map — they\n * never touch the adapter or the keyring. The Collection class owns\n * lifecycle (build on hydrate, maintain on put/delete).\n */\nexport class CollectionIndexes {\n private readonly indexes = new Map<string, HashIndex>()\n\n /**\n * Declare an index. Subsequent record additions are tracked under it.\n * Calling this twice for the same field is a no-op (idempotent).\n */\n declare(field: string): void {\n if (this.indexes.has(field)) return\n this.indexes.set(field, { field, buckets: new Map() })\n }\n\n /** True if the given field has a declared index. */\n has(field: string): boolean {\n return this.indexes.has(field)\n }\n\n /** All declared field names, in declaration order. */\n fields(): string[] {\n return [...this.indexes.keys()]\n }\n\n /**\n * Build all declared indexes from a snapshot of records.\n * Called once per hydration. O(N × indexes.size).\n */\n build<T>(records: ReadonlyArray<{ id: string; record: T }>): void {\n for (const idx of this.indexes.values()) {\n idx.buckets.clear()\n for (const { id, record } of records) {\n addToIndex(idx, id, record)\n }\n }\n }\n\n /**\n * Insert or update a single record across all indexes.\n * Called by `Collection.put()` after the encrypted write succeeds.\n *\n * If `previousRecord` is provided, the record is removed from any old\n * buckets first — this is the update path. Pass `null` for fresh adds.\n */\n upsert<T>(id: string, newRecord: T, previousRecord: T | null): void {\n if (this.indexes.size === 0) return\n if (previousRecord !== null) {\n this.remove(id, previousRecord)\n }\n for (const idx of this.indexes.values()) {\n addToIndex(idx, id, newRecord)\n }\n }\n\n /**\n * Remove a record from all indexes. Called by `Collection.delete()`\n * (and as the first half of `upsert` for the update path).\n */\n remove<T>(id: string, record: T): void {\n if (this.indexes.size === 0) return\n for (const idx of this.indexes.values()) {\n removeFromIndex(idx, id, record)\n }\n }\n\n /** Drop all index data. Called when the collection is invalidated. */\n clear(): void {\n for (const idx of this.indexes.values()) {\n idx.buckets.clear()\n }\n }\n\n /**\n * Equality lookup: return the set of record ids whose `field` matches\n * the given value. Returns `null` if no index covers the field — the\n * caller should fall back to a linear scan.\n *\n * The returned Set is a reference to the index's internal storage —\n * callers must NOT mutate it.\n */\n lookupEqual(field: string, value: unknown): ReadonlySet<string> | null {\n const idx = this.indexes.get(field)\n if (!idx) return null\n const key = stringifyKey(value)\n return idx.buckets.get(key) ?? EMPTY_SET\n }\n\n /**\n * Set lookup: return the union of record ids whose `field` matches any\n * of the given values. Returns `null` if no index covers the field.\n */\n lookupIn(field: string, values: readonly unknown[]): ReadonlySet<string> | null {\n const idx = this.indexes.get(field)\n if (!idx) return null\n const out = new Set<string>()\n for (const value of values) {\n const key = stringifyKey(value)\n const bucket = idx.buckets.get(key)\n if (bucket) {\n for (const id of bucket) out.add(id)\n }\n }\n return out\n }\n}\n\nconst EMPTY_SET: ReadonlySet<string> = new Set()\n\n/**\n * Stringify a value into a stable bucket key.\n *\n * `null`/`undefined` produce a sentinel that records will never match\n * (so we never index nullish values — `where('x', '==', null)` falls back\n * to a linear scan). Numbers, booleans, strings, and Date objects are\n * coerced via `String()`. Objects produce a sentinel that no real record\n * will match — querying with object values is a code smell.\n */\nfunction stringifyKey(value: unknown): string {\n if (value === null || value === undefined) return '\\0NULL\\0'\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n if (value instanceof Date) return value.toISOString()\n return '\\0OBJECT\\0'\n}\n\nfunction addToIndex<T>(idx: HashIndex, id: string, record: T): void {\n const value = readPath(record, idx.field)\n if (value === null || value === undefined) return\n const key = stringifyKey(value)\n let bucket = idx.buckets.get(key)\n if (!bucket) {\n bucket = new Set()\n idx.buckets.set(key, bucket)\n }\n bucket.add(id)\n}\n\nfunction removeFromIndex<T>(idx: HashIndex, id: string, record: T): void {\n const value = readPath(record, idx.field)\n if (value === null || value === undefined) return\n const key = stringifyKey(value)\n const bucket = idx.buckets.get(key)\n if (!bucket) return\n bucket.delete(id)\n // Clean up empty buckets so the Map doesn't accumulate dead keys.\n if (bucket.size === 0) idx.buckets.delete(key)\n}\n","/**\n * Generic LRU cache for `Collection`'s lazy hydration mode.\n *\n * Backed by a JavaScript `Map`, which preserves insertion order. Promotion\n * is implemented as `delete()` + `set()` — O(1) on `Map` since both\n * operations are constant-time. Eviction walks the iterator from the front\n * (least recently used) until both budgets are satisfied.\n *\n * v0.3 ships in-memory only. The cache is never persisted; on collection\n * close every entry is dropped. Persisting cache state is a follow-up\n * once the access patterns from real consumers tell us whether it would\n * pay back the complexity.\n */\n\nexport interface LruEntry<V> {\n /** The cached value. */\n readonly value: V\n /**\n * Approximate decrypted byte size of the entry. Used by the byte-budget\n * eviction path. Callers compute this once at insert time and pass it\n * in — recomputing on every access would dominate the per-record cost.\n */\n readonly size: number\n}\n\nexport interface LruOptions {\n /** Maximum number of entries before eviction. Required if `maxBytes` is unset. */\n maxRecords?: number\n /** Maximum total bytes before eviction. Computed from per-entry `size`. */\n maxBytes?: number\n}\n\nexport interface LruStats {\n /** Total cache hits since construction (or `resetStats()`). */\n hits: number\n /** Total cache misses since construction (or `resetStats()`). */\n misses: number\n /** Total entries evicted since construction (or `resetStats()`). */\n evictions: number\n /** Current number of cached entries. */\n size: number\n /** Current sum of cached entry sizes (in bytes, approximate). */\n bytes: number\n}\n\n/**\n * O(1) LRU cache. Both `get()` and `set()` promote the touched entry to\n * the most-recently-used end. Eviction happens after every insert and\n * walks the front of the Map iterator dropping entries until both\n * budgets are satisfied.\n */\nexport class Lru<K, V> {\n private readonly entries = new Map<K, LruEntry<V>>()\n private readonly maxRecords: number | undefined\n private readonly maxBytes: number | undefined\n private currentBytes = 0\n private hits = 0\n private misses = 0\n private evictions = 0\n\n constructor(options: LruOptions) {\n if (options.maxRecords === undefined && options.maxBytes === undefined) {\n throw new Error('Lru: must specify maxRecords, maxBytes, or both')\n }\n this.maxRecords = options.maxRecords\n this.maxBytes = options.maxBytes\n }\n\n /**\n * Look up a key. Hits promote the entry to most-recently-used; misses\n * return undefined. Both update the running stats counters.\n */\n get(key: K): V | undefined {\n const entry = this.entries.get(key)\n if (!entry) {\n this.misses++\n return undefined\n }\n // Promote: re-insert moves the entry to the end of the iteration order.\n this.entries.delete(key)\n this.entries.set(key, entry)\n this.hits++\n return entry.value\n }\n\n /**\n * Insert or update a key. If the key already exists, its size is\n * accounted for and the entry is promoted to MRU. After insertion,\n * eviction runs to maintain both budgets.\n */\n set(key: K, value: V, size: number): void {\n const existing = this.entries.get(key)\n if (existing) {\n // Update path: subtract the old size before adding the new one.\n this.currentBytes -= existing.size\n this.entries.delete(key)\n }\n this.entries.set(key, { value, size })\n this.currentBytes += size\n this.evictUntilUnderBudget()\n }\n\n /**\n * Remove a key without affecting hit/miss stats. Used by `Collection.delete()`.\n * Returns true if the key was present.\n */\n remove(key: K): boolean {\n const existing = this.entries.get(key)\n if (!existing) return false\n this.currentBytes -= existing.size\n this.entries.delete(key)\n return true\n }\n\n /** True if the cache currently holds an entry for the given key. */\n has(key: K): boolean {\n return this.entries.has(key)\n }\n\n /**\n * Drop every entry. Stats counters survive — call `resetStats()` if you\n * want a clean slate. Used by `Collection.invalidate()` on key rotation.\n */\n clear(): void {\n this.entries.clear()\n this.currentBytes = 0\n }\n\n /** Reset hit/miss/eviction counters to zero. Does NOT touch entries. */\n resetStats(): void {\n this.hits = 0\n this.misses = 0\n this.evictions = 0\n }\n\n /** Snapshot of current cache statistics. Cheap — no copying. */\n stats(): LruStats {\n return {\n hits: this.hits,\n misses: this.misses,\n evictions: this.evictions,\n size: this.entries.size,\n bytes: this.currentBytes,\n }\n }\n\n /**\n * Iterate over all currently-cached values. Order is least-recently-used\n * first. Used by tests and devtools — production callers should use\n * `Collection.scan()` instead.\n */\n *values(): IterableIterator<V> {\n for (const entry of this.entries.values()) yield entry.value\n }\n\n /**\n * Walk the cache from the LRU end and drop entries until both budgets\n * are satisfied. Called after every `set()`. Single pass — entries are\n * never re-promoted during eviction.\n */\n private evictUntilUnderBudget(): void {\n while (this.overBudget()) {\n const oldest = this.entries.keys().next()\n if (oldest.done) return // empty cache; nothing more to evict\n const key = oldest.value\n const entry = this.entries.get(key)\n if (entry) this.currentBytes -= entry.size\n this.entries.delete(key)\n this.evictions++\n }\n }\n\n private overBudget(): boolean {\n if (this.maxRecords !== undefined && this.entries.size > this.maxRecords) return true\n if (this.maxBytes !== undefined && this.currentBytes > this.maxBytes) return true\n return false\n }\n}\n","/**\n * Cache policy helpers — parse human-friendly byte budgets into raw numbers.\n *\n * Accepted shapes (case-insensitive on suffix):\n * number — interpreted as raw bytes\n * '1024' — string of digits, raw bytes\n * '50KB' — kilobytes (×1024)\n * '50MB' — megabytes (×1024²)\n * '1GB' — gigabytes (×1024³)\n *\n * Decimals are accepted (`'1.5GB'` → 1610612736 bytes).\n *\n * Anything else throws — better to fail loud at construction time than\n * to silently treat a typo as 0 bytes (which would evict everything).\n */\n\nconst UNITS: Record<string, number> = {\n '': 1,\n 'B': 1,\n 'KB': 1024,\n 'MB': 1024 * 1024,\n 'GB': 1024 * 1024 * 1024,\n // 'TB' deliberately not supported — if you need it, you're not using NOYDB.\n}\n\n/** Parse a byte budget into a positive integer number of bytes. */\nexport function parseBytes(input: number | string): number {\n if (typeof input === 'number') {\n if (!Number.isFinite(input) || input <= 0) {\n throw new Error(`parseBytes: numeric input must be a positive finite number, got ${String(input)}`)\n }\n return Math.floor(input)\n }\n\n const trimmed = input.trim()\n if (trimmed === '') {\n throw new Error('parseBytes: empty string is not a valid byte budget')\n }\n\n // Accept either a bare number or a number followed by a unit suffix.\n // Regex: optional sign, digits with optional decimal, optional unit.\n const match = /^([0-9]+(?:\\.[0-9]+)?)\\s*([A-Za-z]*)$/.exec(trimmed)\n if (!match) {\n throw new Error(`parseBytes: invalid byte budget \"${input}\". Expected format: \"1024\", \"50KB\", \"50MB\", \"1GB\"`)\n }\n\n const value = parseFloat(match[1]!)\n const unit = (match[2] ?? '').toUpperCase()\n\n if (!(unit in UNITS)) {\n throw new Error(`parseBytes: unknown unit \"${match[2]}\" in \"${input}\". Supported: B, KB, MB, GB`)\n }\n\n const bytes = Math.floor(value * UNITS[unit]!)\n if (bytes <= 0) {\n throw new Error(`parseBytes: byte budget must be > 0, got ${bytes} from \"${input}\"`)\n }\n return bytes\n}\n\n/**\n * Estimate the in-memory byte size of a decrypted record.\n *\n * Uses `JSON.stringify().length` as a stand-in for actual heap usage.\n * It's a deliberate approximation: real V8 heap size includes pointer\n * overhead, hidden classes, and string interning that we can't measure\n * from JavaScript. The JSON length is a stable, monotonic proxy that\n * costs O(record size) per insert — fine when records are typically\n * < 1 KB and the cache eviction is the slow path anyway.\n *\n * Returns `0` (and the caller must treat it as 1 for accounting) if\n * stringification throws on circular references; this is documented\n * but in practice records always come from JSON-decoded envelopes.\n */\nexport function estimateRecordBytes(record: unknown): number {\n try {\n return JSON.stringify(record).length\n } catch {\n return 0\n }\n}\n","import type { NoydbAdapter, EncryptedEnvelope, ChangeEvent, HistoryConfig, HistoryOptions, HistoryEntry, PruneOptions, ListPageResult } from './types.js'\nimport { NOYDB_FORMAT_VERSION } from './types.js'\nimport { encrypt, decrypt } from './crypto.js'\nimport { ReadOnlyError } from './errors.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { hasWritePermission } from './keyring.js'\nimport type { NoydbEventEmitter } from './events.js'\nimport type { StandardSchemaV1 } from './schema.js'\nimport { validateSchemaInput, validateSchemaOutput } from './schema.js'\nimport type { LedgerStore } from './ledger/index.js'\nimport { envelopePayloadHash } from './ledger/index.js'\nimport { computePatch } from './ledger/patch.js'\nimport {\n saveHistory,\n getHistory as getHistoryEntries,\n getVersionEnvelope,\n pruneHistory as pruneHistoryEntries,\n clearHistory,\n} from './history.js'\nimport { diff as computeDiff } from './diff.js'\nimport type { DiffEntry } from './diff.js'\nimport { Query } from './query/index.js'\nimport type { QuerySource } from './query/index.js'\nimport { CollectionIndexes, type IndexDef } from './query/indexes.js'\nimport { Lru, parseBytes, estimateRecordBytes, type LruStats } from './cache/index.js'\n\n/** Callback for dirty tracking (sync engine integration). */\nexport type OnDirtyCallback = (collection: string, id: string, action: 'put' | 'delete', version: number) => Promise<void>\n\n/**\n * Per-collection cache configuration. Only meaningful when paired with\n * `prefetch: false` (lazy mode); eager mode keeps the entire decrypted\n * cache in memory and ignores these bounds.\n */\nexport interface CacheOptions {\n /** Maximum number of records to keep in memory before LRU eviction. */\n maxRecords?: number\n /**\n * Maximum total decrypted byte size before LRU eviction. Accepts a raw\n * number or a human-friendly string: `'50KB'`, `'50MB'`, `'1GB'`.\n * Eviction picks the least-recently-used entry until both budgets\n * (maxRecords AND maxBytes, if both are set) are satisfied.\n */\n maxBytes?: number | string\n}\n\n/** Statistics exposed via `Collection.cacheStats()`. */\nexport interface CacheStats extends LruStats {\n /** True if this collection is in lazy mode. */\n lazy: boolean\n}\n\n/**\n * Track which adapter names have already triggered the listPage fallback\n * warning. We only emit once per adapter per process so consumers see the\n * heads-up without log spam.\n */\nconst fallbackWarned = new Set<string>()\nfunction warnOnceFallback(adapterName: string): void {\n if (fallbackWarned.has(adapterName)) return\n fallbackWarned.add(adapterName)\n // Only warn in non-test environments — vitest runs are noisy enough.\n if (typeof process !== 'undefined' && process.env['NODE_ENV'] === 'test') return\n console.warn(\n `[noy-db] Adapter \"${adapterName}\" does not implement listPage(); ` +\n `Collection.scan()/listPage() are using a synthetic fallback (slower). ` +\n `Add a listPage method to opt into the streaming fast path.`,\n )\n}\n\n/** A typed collection of records within a compartment. */\nexport class Collection<T> {\n private readonly adapter: NoydbAdapter\n private readonly compartment: string\n private readonly name: string\n private readonly keyring: UnlockedKeyring\n private readonly encrypted: boolean\n private readonly emitter: NoydbEventEmitter\n private readonly getDEK: (collectionName: string) => Promise<CryptoKey>\n private readonly onDirty: OnDirtyCallback | undefined\n private readonly historyConfig: HistoryConfig\n\n // In-memory cache of decrypted records (eager mode only). Lazy mode\n // uses `lru` instead. Both fields exist so a single Collection instance\n // doesn't need a runtime branch on every cache access.\n private readonly cache = new Map<string, { record: T; version: number }>()\n private hydrated = false\n\n /**\n * Lazy mode flag. `true` when constructed with `prefetch: false`.\n * In lazy mode the cache is bounded by an LRU and `list()`/`query()`\n * throw — callers must use `scan()` or per-id `get()` instead.\n */\n private readonly lazy: boolean\n\n /**\n * LRU cache for lazy mode. Only allocated when `prefetch: false` is set.\n * Stores `{ record, version }` entries the same shape as `this.cache`.\n * Tree-shaking note: importing Collection without setting `prefetch:false`\n * still pulls in the Lru class today; future bundle-size work could\n * lazy-import the cache module.\n */\n private readonly lru: Lru<string, { record: T; version: number }> | null\n\n /**\n * In-memory secondary indexes for the query DSL.\n *\n * Built during `ensureHydrated()` and maintained on every put/delete.\n * The query executor consults these for `==` and `in` operators on\n * indexed fields, falling back to a linear scan for unindexed fields\n * or unsupported operators.\n *\n * v0.3 ships in-memory only — persistence as encrypted blobs is a\n * follow-up. See `query/indexes.ts` for the design rationale.\n *\n * Indexes are INCOMPATIBLE with lazy mode in v0.3 — the constructor\n * rejects the combination because evicted records would silently\n * disappear from the index without notification.\n */\n private readonly indexes = new CollectionIndexes()\n\n /**\n * Optional Standard Schema v1 validator. When set, every `put()` runs\n * the input through `validateSchemaInput` before encryption, and every\n * record coming OUT of `decryptRecord` runs through\n * `validateSchemaOutput`. A rejected input throws\n * `SchemaValidationError` with `direction: 'input'`; drifted stored\n * data throws with `direction: 'output'`. Both carry the rich issue\n * list from the validator so UI code can render field-level messages.\n *\n * The schema is stored as `StandardSchemaV1<unknown, T>` because the\n * collection type parameter `T` is the OUTPUT type — whatever the\n * validator produces after transforms and coercion. Users who pass a\n * schema to `defineNoydbStore` (or `Collection.constructor`) get their\n * `T` inferred automatically via `InferOutput<Schema>`.\n */\n private readonly schema: StandardSchemaV1<unknown, T> | undefined\n\n /**\n * Optional reference to the compartment-level hash-chained audit\n * log. When present, every successful `put()` and `delete()` appends\n * an entry to the ledger AFTER the adapter write succeeds (so a\n * failed adapter write never produces an orphan ledger entry).\n *\n * The ledger is always a compartment-wide singleton — all\n * collections in the same compartment share the same LedgerStore.\n * Compartment.ledger() does the lazy init; this field just holds\n * the reference so Collection doesn't need to reach back up to the\n * compartment on every mutation.\n *\n * `undefined` means \"no ledger attached\" — supported for tests that\n * construct a Collection directly without a compartment, and for\n * future backwards-compat scenarios. Production usage always has a\n * ledger because Compartment.collection() passes one through.\n */\n private readonly ledger: LedgerStore | undefined\n\n /**\n * Optional back-reference to the owning compartment's ref\n * enforcer. When present, `Collection.put` calls\n * `refEnforcer.enforceRefsOnPut(name, record)` before the adapter\n * write, and `Collection.delete` calls\n * `refEnforcer.enforceRefsOnDelete(name, id)` before its own\n * adapter delete. The Compartment handles the actual registry\n * lookup and cross-collection enforcement — Collection just\n * notifies it at the right points in the lifecycle.\n *\n * Typed as a structural interface rather than `Compartment`\n * directly to avoid a circular import. Compartment implements\n * these two methods; any other object with the same shape would\n * work too (used only in unit tests).\n */\n private readonly refEnforcer:\n | {\n enforceRefsOnPut(collectionName: string, record: unknown): Promise<void>\n enforceRefsOnDelete(collectionName: string, id: string): Promise<void>\n }\n | undefined\n\n constructor(opts: {\n adapter: NoydbAdapter\n compartment: string\n name: string\n keyring: UnlockedKeyring\n encrypted: boolean\n emitter: NoydbEventEmitter\n getDEK: (collectionName: string) => Promise<CryptoKey>\n historyConfig?: HistoryConfig | undefined\n onDirty?: OnDirtyCallback | undefined\n indexes?: IndexDef[] | undefined\n /**\n * Hydration mode. `'eager'` (default) loads everything into memory on\n * first access — matches v0.2 behavior exactly. `'lazy'` defers loads\n * to per-id `get()` calls and bounds memory via the `cache` option.\n */\n prefetch?: boolean\n /**\n * LRU cache options. Only meaningful when `prefetch: false`. At least\n * one of `maxRecords` or `maxBytes` must be set in lazy mode — an\n * unbounded lazy cache defeats the purpose.\n */\n cache?: CacheOptions | undefined\n /**\n * Optional Standard Schema v1 validator (Zod, Valibot, ArkType,\n * Effect Schema, etc.). When set, every `put()` is validated before\n * encryption and every read is validated after decryption. See the\n * `schema` field docstring for the error semantics.\n */\n schema?: StandardSchemaV1<unknown, T> | undefined\n /**\n * Optional reference to the compartment's hash-chained ledger.\n * When present, successful mutations append a ledger entry via\n * `LedgerStore.append()`. Constructed at the Compartment level and\n * threaded through — see the Compartment.collection() source for\n * the wiring.\n */\n ledger?: LedgerStore | undefined\n /**\n * Optional back-reference to the owning compartment's ref\n * enforcer (v0.4 #45 — foreign-key references via `ref()`).\n * Collection.put calls `enforceRefsOnPut` before the adapter\n * write; Collection.delete calls `enforceRefsOnDelete` before\n * its own adapter delete. See the `refEnforcer` field docstring\n * for the full protocol.\n */\n refEnforcer?:\n | {\n enforceRefsOnPut(collectionName: string, record: unknown): Promise<void>\n enforceRefsOnDelete(collectionName: string, id: string): Promise<void>\n }\n | undefined\n }) {\n this.adapter = opts.adapter\n this.compartment = opts.compartment\n this.name = opts.name\n this.keyring = opts.keyring\n this.encrypted = opts.encrypted\n this.emitter = opts.emitter\n this.getDEK = opts.getDEK\n this.onDirty = opts.onDirty\n this.historyConfig = opts.historyConfig ?? { enabled: true }\n this.schema = opts.schema\n this.ledger = opts.ledger\n this.refEnforcer = opts.refEnforcer\n\n // Default `prefetch: true` keeps v0.2 semantics. Only opt-in to lazy\n // mode when the consumer explicitly sets `prefetch: false`.\n this.lazy = opts.prefetch === false\n\n if (this.lazy) {\n // Lazy mode is incompatible with eager-cache features. Reject the\n // combinations early so users see the error at construction time\n // rather than at first query.\n if (opts.indexes && opts.indexes.length > 0) {\n throw new Error(\n `Collection \"${this.name}\": secondary indexes are not supported in lazy mode (prefetch: false). ` +\n `Either remove the indexes option or use prefetch: true. ` +\n `Index + lazy support is tracked as a v0.4 follow-up.`,\n )\n }\n if (!opts.cache || (opts.cache.maxRecords === undefined && opts.cache.maxBytes === undefined)) {\n throw new Error(\n `Collection \"${this.name}\": lazy mode (prefetch: false) requires a cache option ` +\n `with maxRecords and/or maxBytes. An unbounded lazy cache defeats the purpose.`,\n )\n }\n const lruOptions: { maxRecords?: number; maxBytes?: number } = {}\n if (opts.cache.maxRecords !== undefined) lruOptions.maxRecords = opts.cache.maxRecords\n if (opts.cache.maxBytes !== undefined) lruOptions.maxBytes = parseBytes(opts.cache.maxBytes)\n this.lru = new Lru<string, { record: T; version: number }>(lruOptions)\n this.hydrated = true // lazy mode is always \"hydrated\" — no bulk load\n } else {\n this.lru = null\n if (opts.indexes) {\n for (const def of opts.indexes) {\n this.indexes.declare(def)\n }\n }\n }\n }\n\n /** Get a single record by ID. Returns null if not found. */\n async get(id: string): Promise<T | null> {\n if (this.lazy && this.lru) {\n // Cache hit: promote and return.\n const cached = this.lru.get(id)\n if (cached) return cached.record\n // Cache miss: hit the adapter, decrypt, populate the LRU.\n const envelope = await this.adapter.get(this.compartment, this.name, id)\n if (!envelope) return null\n const record = await this.decryptRecord(envelope)\n this.lru.set(id, { record, version: envelope._v }, estimateRecordBytes(record))\n return record\n }\n\n // Eager mode: load everything once, then serve from the in-memory map.\n await this.ensureHydrated()\n const entry = this.cache.get(id)\n return entry ? entry.record : null\n }\n\n /** Create or update a record. */\n async put(id: string, record: T): Promise<void> {\n if (!hasWritePermission(this.keyring, this.name)) {\n throw new ReadOnlyError()\n }\n\n // Schema validation — runs BEFORE encryption so invalid records are\n // rejected at the store boundary. The validator may transform the\n // input (e.g., coerce strings → numbers, strip unknown fields), in\n // which case we persist the validated value rather than the raw one.\n // Users who pass a bad shape get a SchemaValidationError with a\n // structured issue list, not a stack trace from deep inside the\n // encrypt path.\n if (this.schema !== undefined) {\n record = await validateSchemaInput(this.schema, record, `put(${id})`)\n }\n\n // Foreign-key ref enforcement (v0.4 #45). Runs AFTER schema\n // validation (so the record shape is trustworthy) but BEFORE\n // any write (so a failed strict ref leaves no trace on disk,\n // in history, or in the ledger). The Compartment handles the\n // actual target lookups — see `enforceRefsOnPut` over there.\n if (this.refEnforcer !== undefined) {\n await this.refEnforcer.enforceRefsOnPut(this.name, record)\n }\n\n // Resolve the previous record. In eager mode this comes from the\n // in-memory map (no I/O); in lazy mode we have to ask the adapter\n // because the record may have been evicted (or never loaded).\n let existing: { record: T; version: number } | undefined\n if (this.lazy && this.lru) {\n existing = this.lru.get(id)\n if (!existing) {\n const previousEnvelope = await this.adapter.get(this.compartment, this.name, id)\n if (previousEnvelope) {\n const previousRecord = await this.decryptRecord(previousEnvelope)\n existing = { record: previousRecord, version: previousEnvelope._v }\n }\n }\n } else {\n await this.ensureHydrated()\n existing = this.cache.get(id)\n }\n\n const version = existing ? existing.version + 1 : 1\n\n // Save history snapshot of the PREVIOUS version before overwriting\n if (existing && this.historyConfig.enabled !== false) {\n const historyEnvelope = await this.encryptRecord(existing.record, existing.version)\n await saveHistory(this.adapter, this.compartment, this.name, id, historyEnvelope)\n\n this.emitter.emit('history:save', {\n compartment: this.compartment,\n collection: this.name,\n id,\n version: existing.version,\n })\n\n // Auto-prune if maxVersions configured\n if (this.historyConfig.maxVersions) {\n await pruneHistoryEntries(this.adapter, this.compartment, this.name, id, {\n keepVersions: this.historyConfig.maxVersions,\n })\n }\n }\n\n const envelope = await this.encryptRecord(record, version)\n await this.adapter.put(this.compartment, this.name, id, envelope)\n\n // Ledger append — AFTER the adapter write succeeds so a failed\n // write never produces an orphan ledger entry. Computing the\n // payloadHash here uses the envelope we just wrote, which is the\n // exact bytes the adapter now holds. The ledger entry records\n // only metadata (collection, id, version, hash) — NOT the record\n // itself — and is then encrypted with the compartment's ledger\n // DEK, preserving zero-knowledge. See `LedgerStore.append`.\n //\n // **Delta history (#44)**: if there was a previous version, we\n // compute a JSON Patch from it to the new record and pass it\n // through `append.delta`. The LedgerStore stores the patch in\n // the sibling `_ledger_deltas/` collection and records its hash\n // in the entry's `deltaHash` field. Genesis puts (no existing\n // record) leave `delta` undefined — there's nothing to diff\n // against — and the ledger entry has no `deltaHash`.\n if (this.ledger) {\n const appendInput: Parameters<typeof this.ledger.append>[0] = {\n op: 'put',\n collection: this.name,\n id,\n version,\n actor: this.keyring.userId,\n payloadHash: await envelopePayloadHash(envelope),\n }\n if (existing) {\n // REVERSE patch: describes how to undo this put — i.e., how\n // to transform the NEW record back into the PREVIOUS one.\n // Storing reverse patches lets `ledger.reconstruct()` walk\n // backward from the current state (readily available in the\n // data collection) without needing a forward-walking base\n // snapshot, which would double the storage cost of the\n // delta scheme. See `LedgerStore.reconstruct` for the walk.\n appendInput.delta = computePatch(record, existing.record)\n }\n await this.ledger.append(appendInput)\n }\n\n if (this.lazy && this.lru) {\n this.lru.set(id, { record, version }, estimateRecordBytes(record))\n } else {\n this.cache.set(id, { record, version })\n // Update secondary indexes incrementally — no-op if no indexes are\n // declared. Pass the previous record (if any) so old buckets are\n // cleaned up before the new value is added. Indexes are NEVER\n // touched in lazy mode (rejected at construction).\n this.indexes.upsert(id, record, existing ? existing.record : null)\n }\n\n await this.onDirty?.(this.name, id, 'put', version)\n\n this.emitter.emit('change', {\n compartment: this.compartment,\n collection: this.name,\n id,\n action: 'put',\n } satisfies ChangeEvent)\n }\n\n /** Delete a record by ID. */\n async delete(id: string): Promise<void> {\n if (!hasWritePermission(this.keyring, this.name)) {\n throw new ReadOnlyError()\n }\n\n // Foreign-key ref enforcement on delete (v0.4 #45). Runs BEFORE\n // the adapter delete so a `strict` inbound ref with existing\n // references blocks the delete entirely (no partial state, no\n // history churn, no ledger entry for a rejected op). `cascade`\n // recursively deletes the referencing records first, then falls\n // through to the normal delete path below. `warn` is a no-op\n // here — violations surface through `checkIntegrity()`.\n if (this.refEnforcer !== undefined) {\n await this.refEnforcer.enforceRefsOnDelete(this.name, id)\n }\n\n // In lazy mode the record may not be cached; ask the adapter so we\n // can still write a history snapshot if history is enabled.\n let existing: { record: T; version: number } | undefined\n if (this.lazy && this.lru) {\n existing = this.lru.get(id)\n if (!existing && this.historyConfig.enabled !== false) {\n const previousEnvelope = await this.adapter.get(this.compartment, this.name, id)\n if (previousEnvelope) {\n const previousRecord = await this.decryptRecord(previousEnvelope)\n existing = { record: previousRecord, version: previousEnvelope._v }\n }\n }\n } else {\n existing = this.cache.get(id)\n }\n\n // Save history snapshot before deleting\n if (existing && this.historyConfig.enabled !== false) {\n const historyEnvelope = await this.encryptRecord(existing.record, existing.version)\n await saveHistory(this.adapter, this.compartment, this.name, id, historyEnvelope)\n }\n\n // Capture the previous envelope's payloadHash BEFORE delete so we\n // have a stable reference for the ledger entry. The hash is of\n // whatever was last visible to readers — for a `delete` of a\n // never-existed record, we use the empty string (which the\n // ledger entry's `payloadHash` field tolerates).\n const previousEnvelope = await this.adapter.get(this.compartment, this.name, id)\n const previousPayloadHash = await envelopePayloadHash(previousEnvelope)\n\n await this.adapter.delete(this.compartment, this.name, id)\n\n // Ledger append — same after-write timing as put(). The recorded\n // version is the version that WAS deleted (existing?.version), not\n // a successor. A delete of a missing record still appends an\n // entry with version 0 so the chain captures the intent.\n if (this.ledger) {\n await this.ledger.append({\n op: 'delete',\n collection: this.name,\n id,\n version: existing?.version ?? 0,\n actor: this.keyring.userId,\n payloadHash: previousPayloadHash,\n })\n }\n\n if (this.lazy && this.lru) {\n this.lru.remove(id)\n } else {\n this.cache.delete(id)\n // Remove from secondary indexes — no-op if no indexes are declared\n // or the record wasn't previously indexed. Indexes are never\n // declared in lazy mode (rejected at construction).\n if (existing) {\n this.indexes.remove(id, existing.record)\n }\n }\n\n await this.onDirty?.(this.name, id, 'delete', existing?.version ?? 0)\n\n this.emitter.emit('change', {\n compartment: this.compartment,\n collection: this.name,\n id,\n action: 'delete',\n } satisfies ChangeEvent)\n }\n\n /**\n * List all records in the collection.\n *\n * Throws in lazy mode — bulk listing defeats the purpose of lazy\n * hydration. Use `scan()` to iterate over the full collection\n * page-by-page without holding more than `pageSize` records in memory.\n */\n async list(): Promise<T[]> {\n if (this.lazy) {\n throw new Error(\n `Collection \"${this.name}\": list() is not available in lazy mode (prefetch: false). ` +\n `Use collection.scan({ pageSize }) to iterate over the full collection.`,\n )\n }\n await this.ensureHydrated()\n return [...this.cache.values()].map(e => e.record)\n }\n\n /**\n * Build a chainable query against the collection. Returns a `Query<T>`\n * builder when called with no arguments.\n *\n * Backward-compatible overload: passing a predicate function returns\n * the filtered records directly (the v0.2 API). Prefer the chainable\n * form for new code.\n *\n * @example\n * ```ts\n * // New chainable API:\n * const overdue = invoices.query()\n * .where('status', '==', 'open')\n * .where('dueDate', '<', new Date())\n * .orderBy('dueDate')\n * .toArray();\n *\n * // Legacy predicate form (still supported):\n * const drafts = invoices.query(i => i.status === 'draft');\n * ```\n */\n query(): Query<T>\n query(predicate: (record: T) => boolean): T[]\n query(predicate?: (record: T) => boolean): Query<T> | T[] {\n if (this.lazy) {\n throw new Error(\n `Collection \"${this.name}\": query() is not available in lazy mode (prefetch: false). ` +\n `Use collection.scan({ pageSize }) and filter the streamed records with a regular ` +\n `for-await loop. Streaming queries land in v0.4.`,\n )\n }\n if (predicate !== undefined) {\n // Legacy form: synchronous predicate filter against the cache.\n return [...this.cache.values()].map(e => e.record).filter(predicate)\n }\n // New form: return a chainable builder bound to this collection's cache.\n const source: QuerySource<T> = {\n snapshot: () => [...this.cache.values()].map(e => e.record),\n subscribe: (cb: () => void) => {\n const handler = (event: ChangeEvent): void => {\n if (event.compartment === this.compartment && event.collection === this.name) {\n cb()\n }\n }\n this.emitter.on('change', handler)\n return () => this.emitter.off('change', handler)\n },\n // Index-aware fast path for `==` and `in` operators on indexed\n // fields. The Query builder consults these when present and falls\n // back to a linear scan otherwise.\n getIndexes: () => this.getIndexes(),\n lookupById: (id: string) => this.cache.get(id)?.record,\n }\n return new Query<T>(source)\n }\n\n /**\n * Cache statistics — useful for devtools, monitoring, and verifying\n * that LRU eviction is happening as expected in lazy mode.\n *\n * In eager mode, returns size only (no hits/misses are tracked because\n * every read is a cache hit by construction). In lazy mode, returns\n * the full LRU stats: `{ hits, misses, evictions, size, bytes }`.\n */\n cacheStats(): CacheStats {\n if (this.lazy && this.lru) {\n return { ...this.lru.stats(), lazy: true }\n }\n return {\n hits: 0,\n misses: 0,\n evictions: 0,\n size: this.cache.size,\n bytes: 0,\n lazy: false,\n }\n }\n\n // ─── History Methods ────────────────────────────────────────────\n\n /** Get version history for a record, newest first. */\n async history(id: string, options?: HistoryOptions): Promise<HistoryEntry<T>[]> {\n const envelopes = await getHistoryEntries(\n this.adapter, this.compartment, this.name, id, options,\n )\n\n const entries: HistoryEntry<T>[] = []\n for (const env of envelopes) {\n // History reads skip schema validation — see getVersion() docs.\n const record = await this.decryptRecord(env, { skipValidation: true })\n entries.push({\n version: env._v,\n timestamp: env._ts,\n userId: env._by ?? '',\n record,\n })\n }\n return entries\n }\n\n /**\n * Get a specific past version of a record.\n *\n * History reads intentionally **skip schema validation** — historical\n * records predate the current schema by definition, so validating them\n * against today's shape would be a false positive on any schema\n * evolution. If a caller needs validated history, they should filter\n * and re-put the records through the normal `put()` path.\n */\n async getVersion(id: string, version: number): Promise<T | null> {\n const envelope = await getVersionEnvelope(\n this.adapter, this.compartment, this.name, id, version,\n )\n if (!envelope) return null\n return this.decryptRecord(envelope, { skipValidation: true })\n }\n\n /** Revert a record to a past version. Creates a new version with the old content. */\n async revert(id: string, version: number): Promise<void> {\n const oldRecord = await this.getVersion(id, version)\n if (!oldRecord) {\n throw new Error(`Version ${version} not found for record \"${id}\"`)\n }\n await this.put(id, oldRecord)\n }\n\n /**\n * Compare two versions of a record and return the differences.\n * Use version 0 to represent \"before creation\" (empty).\n * Omit versionB to compare against the current version.\n */\n async diff(id: string, versionA: number, versionB?: number): Promise<DiffEntry[]> {\n const recordA = versionA === 0 ? null : await this.resolveVersion(id, versionA)\n const recordB = versionB === undefined || versionB === 0\n ? (versionB === 0 ? null : await this.resolveCurrentOrVersion(id))\n : await this.resolveVersion(id, versionB)\n return computeDiff(recordA, recordB)\n }\n\n /** Resolve a version: try history first, then check if it's the current version. */\n private async resolveVersion(id: string, version: number): Promise<T | null> {\n // Check history\n const fromHistory = await this.getVersion(id, version)\n if (fromHistory) return fromHistory\n // Check if it's the current live version\n await this.ensureHydrated()\n const current = this.cache.get(id)\n if (current && current.version === version) return current.record\n return null\n }\n\n private async resolveCurrentOrVersion(id: string): Promise<T | null> {\n await this.ensureHydrated()\n return this.cache.get(id)?.record ?? null\n }\n\n /** Prune history entries for a record (or all records if id is undefined). */\n async pruneRecordHistory(id: string | undefined, options: PruneOptions): Promise<number> {\n const pruned = await pruneHistoryEntries(\n this.adapter, this.compartment, this.name, id, options,\n )\n if (pruned > 0) {\n this.emitter.emit('history:prune', {\n compartment: this.compartment,\n collection: this.name,\n id: id ?? '*',\n pruned,\n })\n }\n return pruned\n }\n\n /** Clear all history for this collection (or a specific record). */\n async clearHistory(id?: string): Promise<number> {\n return clearHistory(this.adapter, this.compartment, this.name, id)\n }\n\n // ─── Core Methods ─────────────────────────────────────────────\n\n /**\n * Count records in the collection.\n *\n * In eager mode this returns the in-memory cache size (instant). In\n * lazy mode it asks the adapter via `list()` to enumerate ids — slower\n * but still correct, and avoids loading any record bodies into memory.\n */\n async count(): Promise<number> {\n if (this.lazy) {\n const ids = await this.adapter.list(this.compartment, this.name)\n return ids.length\n }\n await this.ensureHydrated()\n return this.cache.size\n }\n\n // ─── Pagination & Streaming ───────────────────────────────────\n\n /**\n * Fetch a single page of records via the adapter's optional `listPage`\n * extension. Returns the decrypted records for this page plus an opaque\n * cursor for the next page.\n *\n * Pass `cursor: undefined` (or omit it) to start from the beginning.\n * The final page returns `nextCursor: null`.\n *\n * If the adapter does NOT implement `listPage`, this falls back to a\n * synthetic implementation: it loads all ids via `list()`, sorts them,\n * and slices a window. The first call emits a one-time console.warn so\n * developers can spot adapters that should opt into the fast path.\n */\n async listPage(opts: { cursor?: string; limit?: number } = {}): Promise<{\n items: T[]\n nextCursor: string | null\n }> {\n const limit = opts.limit ?? 100\n\n if (this.adapter.listPage) {\n const result = await this.adapter.listPage(this.compartment, this.name, opts.cursor, limit)\n const decrypted: T[] = []\n for (const { record, version, id } of await this.decryptPage(result.items)) {\n // Update cache opportunistically — if the page-fetched record isn't\n // in cache yet, populate it. This makes a subsequent .get(id) free.\n // In LAZY mode we deliberately do NOT populate the LRU here:\n // streaming a 100K-record collection should not turn the LRU into\n // a giant write-once buffer that immediately evicts everything.\n // Random-access workloads via .get() are what the LRU is for.\n if (!this.lazy && !this.cache.has(id)) {\n this.cache.set(id, { record, version })\n }\n decrypted.push(record)\n }\n return { items: decrypted, nextCursor: result.nextCursor }\n }\n\n // Fallback: synthetic pagination over list() + get(). Slower than the\n // native path because every id requires its own round-trip, but\n // correct for adapters that haven't opted in.\n warnOnceFallback(this.adapter.name ?? 'unknown')\n const ids = (await this.adapter.list(this.compartment, this.name)).slice().sort()\n const start = opts.cursor ? parseInt(opts.cursor, 10) : 0\n const end = Math.min(start + limit, ids.length)\n const items: T[] = []\n for (let i = start; i < end; i++) {\n const id = ids[i]!\n const envelope = await this.adapter.get(this.compartment, this.name, id)\n if (envelope) {\n const record = await this.decryptRecord(envelope)\n items.push(record)\n // Same lazy-mode skip as the native path: don't pollute the LRU\n // with sequential scan results.\n if (!this.lazy && !this.cache.has(id)) {\n this.cache.set(id, { record, version: envelope._v })\n }\n }\n }\n return {\n items,\n nextCursor: end < ids.length ? String(end) : null,\n }\n }\n\n /**\n * Stream every record in the collection page-by-page, yielding decrypted\n * records as an `AsyncIterable<T>`. The whole point: process collections\n * larger than RAM without ever holding more than `pageSize` records\n * decrypted at once.\n *\n * @example\n * ```ts\n * for await (const record of invoices.scan({ pageSize: 500 })) {\n * await processOne(record)\n * }\n * ```\n *\n * Uses `adapter.listPage` when available; otherwise falls back to the\n * synthetic pagination path with the same one-time warning.\n */\n async *scan(opts: { pageSize?: number } = {}): AsyncIterableIterator<T> {\n const pageSize = opts.pageSize ?? 100\n // Start with no cursor (first page) and walk forward until the\n // adapter signals exhaustion via nextCursor === null.\n let page: { items: T[]; nextCursor: string | null } = await this.listPage({ limit: pageSize })\n while (true) {\n for (const item of page.items) {\n yield item\n }\n if (page.nextCursor === null) return\n page = await this.listPage({ cursor: page.nextCursor, limit: pageSize })\n }\n }\n\n /** Decrypt a page of envelopes returned by `adapter.listPage`. */\n private async decryptPage(\n items: ListPageResult['items'],\n ): Promise<Array<{ id: string; record: T; version: number }>> {\n const out: Array<{ id: string; record: T; version: number }> = []\n for (const { id, envelope } of items) {\n const record = await this.decryptRecord(envelope)\n out.push({ id, record, version: envelope._v })\n }\n return out\n }\n\n // ─── Internal ──────────────────────────────────────────────────\n\n /** Load all records from adapter into memory cache. */\n private async ensureHydrated(): Promise<void> {\n if (this.hydrated) return\n\n const ids = await this.adapter.list(this.compartment, this.name)\n for (const id of ids) {\n const envelope = await this.adapter.get(this.compartment, this.name, id)\n if (envelope) {\n const record = await this.decryptRecord(envelope)\n this.cache.set(id, { record, version: envelope._v })\n }\n }\n this.hydrated = true\n this.rebuildIndexes()\n }\n\n /** Hydrate from a pre-loaded snapshot (used by Compartment). */\n async hydrateFromSnapshot(records: Record<string, EncryptedEnvelope>): Promise<void> {\n for (const [id, envelope] of Object.entries(records)) {\n const record = await this.decryptRecord(envelope)\n this.cache.set(id, { record, version: envelope._v })\n }\n this.hydrated = true\n this.rebuildIndexes()\n }\n\n /**\n * Rebuild secondary indexes from the current in-memory cache.\n *\n * Called after any bulk hydration. Incremental put/delete updates\n * are handled by `indexes.upsert()` / `indexes.remove()` directly,\n * so this only fires for full reloads.\n *\n * Synchronous and O(N × indexes.size); for the v0.3 target scale of\n * 1K–50K records this completes in single-digit milliseconds.\n */\n private rebuildIndexes(): void {\n if (this.indexes.fields().length === 0) return\n const snapshot: Array<{ id: string; record: T }> = []\n for (const [id, entry] of this.cache) {\n snapshot.push({ id, record: entry.record })\n }\n this.indexes.build(snapshot)\n }\n\n /**\n * Get the in-memory index store. Used by `Query` to short-circuit\n * `==` and `in` lookups when an index covers the where clause.\n *\n * Returns `null` if no indexes are declared on this collection.\n */\n getIndexes(): CollectionIndexes | null {\n return this.indexes.fields().length > 0 ? this.indexes : null\n }\n\n /** Get all records as encrypted envelopes (for dump). */\n async dumpEnvelopes(): Promise<Record<string, EncryptedEnvelope>> {\n await this.ensureHydrated()\n const result: Record<string, EncryptedEnvelope> = {}\n for (const [id, entry] of this.cache) {\n result[id] = await this.encryptRecord(entry.record, entry.version)\n }\n return result\n }\n\n private async encryptRecord(record: T, version: number): Promise<EncryptedEnvelope> {\n const json = JSON.stringify(record)\n const by = this.keyring.userId\n\n if (!this.encrypted) {\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: json,\n _by: by,\n }\n }\n\n const dek = await this.getDEK(this.name)\n const { iv, data } = await encrypt(json, dek)\n\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _by: by,\n }\n }\n\n /**\n * Decrypt an envelope into a record of type `T`.\n *\n * When a schema is attached, the decrypted value is validated before\n * being returned. A divergence between the stored bytes and the\n * current schema throws `SchemaValidationError` with\n * `direction: 'output'` — silently returning drifted data would\n * propagate garbage into the UI and break the whole point of having\n * a schema.\n *\n * `skipValidation` exists for history reads: when calling\n * `getVersion()` the caller is explicitly asking for an old snapshot\n * that may predate a schema change, so validating it would be a\n * false positive. Every non-history read leaves this flag `false`.\n */\n private async decryptRecord(\n envelope: EncryptedEnvelope,\n opts: { skipValidation?: boolean } = {},\n ): Promise<T> {\n let record: T\n if (!this.encrypted) {\n record = JSON.parse(envelope._data) as T\n } else {\n const dek = await this.getDEK(this.name)\n const json = await decrypt(envelope._iv, envelope._data, dek)\n record = JSON.parse(json) as T\n }\n\n if (this.schema !== undefined && !opts.skipValidation) {\n // Context string deliberately avoids leaking the record id — the\n // envelope only carries the version, not the id (the id lives in\n // the adapter-side key). `<collection>@v<n>` is enough for the\n // developer to find the offending record.\n record = await validateSchemaOutput(\n this.schema,\n record,\n `${this.name}@v${envelope._v}`,\n )\n }\n\n return record\n }\n}\n","import type { NoydbAdapter, EncryptedEnvelope, CompartmentBackup, CompartmentSnapshot, HistoryConfig } from './types.js'\nimport { NOYDB_BACKUP_VERSION } from './types.js'\nimport { Collection } from './collection.js'\nimport type { CacheOptions } from './collection.js'\nimport type { IndexDef } from './query/indexes.js'\nimport type { OnDirtyCallback } from './collection.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { ensureCollectionDEK } from './keyring.js'\nimport type { NoydbEventEmitter } from './events.js'\nimport { PermissionDeniedError, BackupLedgerError, BackupCorruptedError } from './errors.js'\nimport type { StandardSchemaV1 } from './schema.js'\nimport { LedgerStore, sha256Hex, LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION } from './ledger/index.js'\nimport {\n RefRegistry,\n RefIntegrityError,\n type RefDescriptor,\n type RefViolation,\n} from './refs.js'\n\n/** A compartment (tenant namespace) containing collections. */\nexport class Compartment {\n private readonly adapter: NoydbAdapter\n private readonly name: string\n /**\n * The active in-memory keyring. NOT readonly because `load()`\n * needs to refresh it after restoring a different keyring file —\n * otherwise the in-memory DEKs (from the pre-load session) and\n * the on-disk wrapped DEKs (from the loaded backup) drift apart\n * and every subsequent decrypt fails with TamperedError.\n */\n private keyring: UnlockedKeyring\n private readonly encrypted: boolean\n private readonly emitter: NoydbEventEmitter\n private readonly onDirty: OnDirtyCallback | undefined\n private readonly historyConfig: HistoryConfig\n private getDEK: (collectionName: string) => Promise<CryptoKey>\n\n /**\n * Optional callback that re-derives an UnlockedKeyring from the\n * adapter using the active user's passphrase. Called by `load()`\n * after the on-disk keyring file has been replaced — refreshes\n * `this.keyring` so the next DEK access uses the loaded wrapped\n * DEKs instead of the stale pre-load ones.\n *\n * Provided by Noydb at openCompartment() time. Tests that\n * construct Compartment directly can pass `undefined`; load()\n * skips the refresh in that case (which is fine for plaintext\n * compartments — there's nothing to re-unwrap).\n */\n private readonly reloadKeyring: (() => Promise<UnlockedKeyring>) | undefined\n private readonly collectionCache = new Map<string, Collection<unknown>>()\n\n /**\n * Per-compartment ledger store. Lazy-initialized on first\n * `collection()` call (which passes it through to the Collection)\n * or on first `ledger()` call from user code.\n *\n * One LedgerStore is shared across all collections in a compartment\n * because the hash chain is compartment-scoped: the chain head is a\n * single \"what did this compartment do last\" identifier, not a\n * per-collection one. Two collections appending concurrently is the\n * single-writer concurrency concern documented in the LedgerStore\n * docstring.\n */\n private ledgerStore: LedgerStore | null = null\n\n /**\n * Per-compartment foreign-key reference registry. Collections\n * register their `refs` option here on construction; the\n * compartment uses the registry on every put/delete/checkIntegrity\n * call. One instance lives for the compartment's lifetime.\n */\n private readonly refRegistry = new RefRegistry()\n\n /**\n * Set of collection record-ids currently being deleted as part of\n * a cascade. Populated on entry to `enforceRefsOnDelete` and\n * drained on exit. Used to break mutual-cascade cycles: deleting\n * A → cascade to B → cascade back to A would otherwise recurse\n * forever, so we short-circuit when we see an already-in-progress\n * delete on the same (collection, id) pair.\n */\n private readonly cascadeInProgress = new Set<string>()\n\n constructor(opts: {\n adapter: NoydbAdapter\n name: string\n keyring: UnlockedKeyring\n encrypted: boolean\n emitter: NoydbEventEmitter\n onDirty?: OnDirtyCallback | undefined\n historyConfig?: HistoryConfig | undefined\n reloadKeyring?: (() => Promise<UnlockedKeyring>) | undefined\n }) {\n this.adapter = opts.adapter\n this.name = opts.name\n this.keyring = opts.keyring\n this.encrypted = opts.encrypted\n this.emitter = opts.emitter\n this.onDirty = opts.onDirty\n this.historyConfig = opts.historyConfig ?? { enabled: true }\n this.reloadKeyring = opts.reloadKeyring\n\n // Build the lazy DEK resolver. Pulled out into a private method\n // so `load()` can rebuild it after a keyring refresh — the\n // closure captures `this.keyring` by reference, so changing the\n // field is enough, but resetting the cached `getDEKFn` ensures\n // ensureCollectionDEK runs again against the freshly-loaded\n // wrapped DEKs.\n this.getDEK = this.makeGetDEK()\n }\n\n /**\n * Construct (or reconstruct) the lazy DEK resolver. Captures the\n * CURRENT value of `this.keyring` and `this.adapter` in a closure,\n * memoizing the inner getDEKFn after first use so subsequent\n * lookups are O(1).\n *\n * `load()` calls this after refreshing `this.keyring` to discard\n * the prior session's cached DEKs.\n */\n private makeGetDEK(): (collectionName: string) => Promise<CryptoKey> {\n let getDEKFn: ((collectionName: string) => Promise<CryptoKey>) | null = null\n return async (collectionName: string): Promise<CryptoKey> => {\n if (!getDEKFn) {\n getDEKFn = await ensureCollectionDEK(this.adapter, this.name, this.keyring)\n }\n return getDEKFn(collectionName)\n }\n }\n\n /**\n * Open a typed collection within this compartment.\n *\n * - `options.indexes` declares secondary indexes for the query DSL.\n * Indexes are computed in memory after decryption; adapters never\n * see plaintext index data.\n * - `options.prefetch` (default `true`) controls hydration. Eager mode\n * loads everything on first access; lazy mode (`prefetch: false`)\n * loads records on demand and bounds memory via the LRU cache.\n * - `options.cache` configures the LRU bounds. Required in lazy mode.\n * Accepts `{ maxRecords, maxBytes: '50MB' | 1024 }`.\n * - `options.schema` attaches a Standard Schema v1 validator (Zod,\n * Valibot, ArkType, Effect Schema, etc.). Every `put()` is validated\n * before encryption; every read is validated after decryption.\n * Failing records throw `SchemaValidationError`.\n *\n * Lazy mode + indexes is rejected at construction time — see the\n * Collection constructor for the rationale.\n */\n collection<T>(collectionName: string, options?: {\n indexes?: IndexDef[]\n prefetch?: boolean\n cache?: CacheOptions\n schema?: StandardSchemaV1<unknown, T>\n refs?: Record<string, RefDescriptor>\n }): Collection<T> {\n let coll = this.collectionCache.get(collectionName)\n if (!coll) {\n // Register ref declarations (if any) with the compartment-level\n // registry BEFORE constructing the Collection. This way the\n // first put() on the new collection already sees its refs via\n // compartment.enforceRefsOnPut.\n if (options?.refs) {\n this.refRegistry.register(collectionName, options.refs)\n }\n const collOpts: ConstructorParameters<typeof Collection<T>>[0] = {\n adapter: this.adapter,\n compartment: this.name,\n name: collectionName,\n keyring: this.keyring,\n encrypted: this.encrypted,\n emitter: this.emitter,\n getDEK: this.getDEK,\n onDirty: this.onDirty,\n historyConfig: this.historyConfig,\n ledger: this.ledger(),\n refEnforcer: this,\n }\n if (options?.indexes !== undefined) collOpts.indexes = options.indexes\n if (options?.prefetch !== undefined) collOpts.prefetch = options.prefetch\n if (options?.cache !== undefined) collOpts.cache = options.cache\n if (options?.schema !== undefined) collOpts.schema = options.schema\n coll = new Collection<T>(collOpts)\n this.collectionCache.set(collectionName, coll)\n }\n return coll as Collection<T>\n }\n\n /**\n * Enforce strict outbound refs on a `put()`. Called by Collection\n * just before it writes to the adapter. For every strict ref\n * declared on the collection, check that the target id exists in\n * the target collection; throw `RefIntegrityError` if not.\n *\n * `warn` and `cascade` modes don't affect put semantics — they're\n * enforced at delete time or via `checkIntegrity()`.\n */\n async enforceRefsOnPut(collectionName: string, record: unknown): Promise<void> {\n const outbound = this.refRegistry.getOutbound(collectionName)\n if (Object.keys(outbound).length === 0) return\n if (!record || typeof record !== 'object') return\n const obj = record as Record<string, unknown>\n\n for (const [field, descriptor] of Object.entries(outbound)) {\n if (descriptor.mode !== 'strict') continue\n const rawId = obj[field]\n // Nullish ref values are allowed — treat them as \"no reference\".\n // Users who want \"always required\" should express it in their\n // Standard Schema validator via a non-optional field.\n if (rawId === null || rawId === undefined) continue\n // Refs must be strings or numbers — anything else (object,\n // array, boolean) is a programming error and should fail\n // loudly rather than serialize as \"[object Object]\".\n if (typeof rawId !== 'string' && typeof rawId !== 'number') {\n throw new RefIntegrityError({\n collection: collectionName,\n id: (obj['id'] as string | undefined) ?? '<unknown>',\n field,\n refTo: descriptor.target,\n refId: null,\n message:\n `Ref field \"${collectionName}.${field}\" must be a string or number, got ${typeof rawId}.`,\n })\n }\n const refId = String(rawId)\n const target = this.collection<Record<string, unknown>>(descriptor.target)\n const exists = await target.get(refId)\n if (!exists) {\n throw new RefIntegrityError({\n collection: collectionName,\n id: (obj['id'] as string | undefined) ?? '<unknown>',\n field,\n refTo: descriptor.target,\n refId,\n message:\n `Strict ref \"${collectionName}.${field}\" → \"${descriptor.target}\" ` +\n `cannot be satisfied: target id \"${refId}\" not found in \"${descriptor.target}\".`,\n })\n }\n }\n }\n\n /**\n * Enforce inbound ref modes on a `delete()`. Called by Collection\n * just before it deletes from the adapter. Walks every inbound\n * ref that targets this (collection, id) and:\n *\n * - `strict`: throws if any referencing records exist\n * - `cascade`: deletes every referencing record\n * - `warn`: no-op (checkIntegrity picks it up)\n *\n * Cascade cycles are broken via `cascadeInProgress` — re-entering\n * for the same (collection, id) returns immediately so two\n * mutually-cascading collections don't recurse forever.\n */\n async enforceRefsOnDelete(collectionName: string, id: string): Promise<void> {\n const key = `${collectionName}/${id}`\n if (this.cascadeInProgress.has(key)) return\n this.cascadeInProgress.add(key)\n\n try {\n const inbound = this.refRegistry.getInbound(collectionName)\n for (const rule of inbound) {\n const fromCollection = this.collection<Record<string, unknown>>(rule.collection)\n // Scan the referencing collection for records whose ref\n // field matches this id. For eager-mode collections this\n // is an in-memory filter; for lazy-mode it requires a scan.\n const allRecords = await fromCollection.list()\n const matches = allRecords.filter((rec) => {\n const raw = rec[rule.field]\n // Same string/number-only restriction as enforceRefsOnPut.\n // Anything else can't have been a valid ref to begin with,\n // so it can't match.\n if (typeof raw !== 'string' && typeof raw !== 'number') return false\n return String(raw) === id\n })\n if (matches.length === 0) continue\n\n if (rule.mode === 'strict') {\n const first = matches[0]\n throw new RefIntegrityError({\n collection: rule.collection,\n id: (first?.['id'] as string | undefined) ?? '<unknown>',\n field: rule.field,\n refTo: collectionName,\n refId: id,\n message:\n `Cannot delete \"${collectionName}\"/\"${id}\": ` +\n `${matches.length} record(s) in \"${rule.collection}\" still reference it via strict ref \"${rule.field}\".`,\n })\n }\n if (rule.mode === 'cascade') {\n for (const match of matches) {\n const matchId = (match['id'] as string | undefined) ?? null\n if (matchId === null) continue\n // Recursive delete — the cycle breaker above catches\n // infinite loops.\n await fromCollection.delete(matchId)\n }\n }\n // warn: no-op\n }\n } finally {\n this.cascadeInProgress.delete(key)\n }\n }\n\n /**\n * Walk every collection that has declared refs, load its records,\n * and report any reference whose target id is missing. Modes are\n * reported alongside each violation so the caller can distinguish\n * \"this is a warning the user asked for\" from \"this should never\n * have happened\" (strict violations produced by out-of-band\n * writes).\n *\n * Returns `{ violations: [...] }` instead of throwing — the whole\n * point of `checkIntegrity()` is to surface a list for display\n * or repair, not to fail noisily.\n */\n async checkIntegrity(): Promise<{ violations: RefViolation[] }> {\n const violations: RefViolation[] = []\n for (const [collectionName, refs] of this.refRegistry.entries()) {\n const coll = this.collection<Record<string, unknown>>(collectionName)\n const records = await coll.list()\n for (const record of records) {\n const recId = (record['id'] as string | undefined) ?? '<unknown>'\n for (const [field, descriptor] of Object.entries(refs)) {\n const rawId = record[field]\n if (rawId === null || rawId === undefined) continue\n // Non-scalar ref values are flagged as a violation rather\n // than thrown — `checkIntegrity` is a \"report what's wrong\"\n // tool, not a \"block on first failure\" tool. The thrown\n // version lives in `enforceRefsOnPut`.\n if (typeof rawId !== 'string' && typeof rawId !== 'number') {\n violations.push({\n collection: collectionName,\n id: recId,\n field,\n refTo: descriptor.target,\n refId: rawId,\n mode: descriptor.mode,\n })\n continue\n }\n const refId = String(rawId)\n const target = this.collection<Record<string, unknown>>(descriptor.target)\n const exists = await target.get(refId)\n if (!exists) {\n violations.push({\n collection: collectionName,\n id: recId,\n field,\n refTo: descriptor.target,\n refId: rawId,\n mode: descriptor.mode,\n })\n }\n }\n }\n }\n return { violations }\n }\n\n /**\n * Return this compartment's hash-chained audit log.\n *\n * The ledger is lazy-initialized on first access and cached for the\n * lifetime of the Compartment instance. Every LedgerStore instance\n * shares the same adapter and DEK resolver, so `compartment.ledger()`\n * can be called repeatedly without performance cost.\n *\n * The LedgerStore itself is the public API: consumers call\n * `.append()` (via Collection internals), `.head()`, `.verify()`,\n * and `.entries({ from, to })`. See the LedgerStore docstring for\n * the full surface and the concurrency caveats.\n */\n ledger(): LedgerStore {\n if (!this.ledgerStore) {\n this.ledgerStore = new LedgerStore({\n adapter: this.adapter,\n compartment: this.name,\n encrypted: this.encrypted,\n getDEK: this.getDEK,\n actor: this.keyring.userId,\n })\n }\n return this.ledgerStore\n }\n\n /** List all collection names in this compartment. */\n async collections(): Promise<string[]> {\n const snapshot = await this.adapter.loadAll(this.name)\n return Object.keys(snapshot)\n }\n\n /**\n * Dump compartment as a verifiable encrypted JSON backup string.\n *\n * v0.4 backups embed the current ledger head and the full\n * `_ledger` + `_ledger_deltas` internal collections so the\n * receiver can run `verifyBackupIntegrity()` after `load()` and\n * detect any tampering between dump and restore. Pre-v0.4 callers\n * who didn't have a ledger get a backup without these fields, and\n * the corresponding `load()` skips the integrity check with a\n * warning — both modes round-trip cleanly.\n */\n async dump(): Promise<string> {\n const snapshot = await this.adapter.loadAll(this.name)\n\n // Load keyrings (separate path because loadAll filters them out\n // along with all other underscore-prefixed internal collections).\n const keyringIds = await this.adapter.list(this.name, '_keyring')\n const keyrings: Record<string, unknown> = {}\n for (const keyringId of keyringIds) {\n const envelope = await this.adapter.get(this.name, '_keyring', keyringId)\n if (envelope) {\n keyrings[keyringId] = JSON.parse(envelope._data)\n }\n }\n\n // Load the ledger entries + deltas so the receiver can replay\n // the chain after restore. Without this, `load()` would have an\n // empty ledger and `verifyBackupIntegrity()` would have nothing\n // to compare against.\n const internalSnapshot: CompartmentSnapshot = {}\n for (const internalName of [LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION]) {\n const ids = await this.adapter.list(this.name, internalName)\n if (ids.length === 0) continue\n const records: Record<string, EncryptedEnvelope> = {}\n for (const id of ids) {\n const envelope = await this.adapter.get(this.name, internalName, id)\n if (envelope) records[id] = envelope\n }\n internalSnapshot[internalName] = records\n }\n\n // Embed the ledger head if there's a chain. An empty ledger\n // (fresh compartment) leaves `ledgerHead` undefined, which\n // load() treats the same as a legacy backup (no integrity\n // check, console warning).\n const head = await this.ledger().head()\n const backup: CompartmentBackup = {\n _noydb_backup: NOYDB_BACKUP_VERSION,\n _compartment: this.name,\n _exported_at: new Date().toISOString(),\n _exported_by: this.keyring.userId,\n keyrings: keyrings as CompartmentBackup['keyrings'],\n collections: snapshot,\n ...(Object.keys(internalSnapshot).length > 0\n ? { _internal: internalSnapshot }\n : {}),\n ...(head\n ? {\n ledgerHead: {\n hash: head.hash,\n index: head.entry.index,\n ts: head.entry.ts,\n },\n }\n : {}),\n }\n\n return JSON.stringify(backup)\n }\n\n /**\n * Restore a compartment from a verifiable backup.\n *\n * After loading, runs `verifyBackupIntegrity()` to confirm:\n * 1. The hash chain is intact (no `prevHash` mismatches)\n * 2. The chain head matches the embedded `ledgerHead.hash`\n * from the backup\n * 3. Every data envelope's `payloadHash` matches the\n * corresponding ledger entry — i.e. nobody swapped\n * ciphertext between dump and restore\n *\n * On any failure, throws `BackupLedgerError` (chain or head\n * mismatch) or `BackupCorruptedError` (data envelope mismatch).\n * The compartment state on the adapter has already been written\n * by the time we throw, so the caller is responsible for either\n * accepting the suspect state or wiping it and trying a different\n * backup.\n *\n * Pre-v0.4 backups (no `ledgerHead` field, no `_internal`) load\n * with a console warning and skip the integrity check entirely\n * — there's no chain to verify against.\n */\n async load(backupJson: string): Promise<void> {\n const backup = JSON.parse(backupJson) as CompartmentBackup\n\n // 1. Restore data collections.\n await this.adapter.saveAll(this.name, backup.collections)\n\n // 2. Restore keyrings (same as v0.3).\n for (const [userId, keyringFile] of Object.entries(backup.keyrings)) {\n const envelope = {\n _noydb: 1 as const,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(keyringFile),\n }\n await this.adapter.put(this.name, '_keyring', userId, envelope)\n }\n\n // 3. Restore internal collections (`_ledger`, `_ledger_deltas`).\n // Required so verifyBackupIntegrity has the chain to walk.\n if (backup._internal) {\n for (const [internalName, records] of Object.entries(backup._internal)) {\n for (const [id, envelope] of Object.entries(records)) {\n await this.adapter.put(this.name, internalName, id, envelope)\n }\n }\n }\n\n // 4. Refresh the in-memory keyring from the freshly-loaded\n // keyring file. Without this, the Compartment's getDEK\n // closure still holds the OLD session's DEKs, and every\n // decrypt of a loaded ledger entry / data envelope fails\n // with TamperedError because the DEK doesn't match the\n // ciphertext that was encrypted with the SOURCE user's DEK.\n // Skipped for plaintext compartments and for tests that\n // construct Compartment without a reloadKeyring callback.\n if (this.reloadKeyring) {\n this.keyring = await this.reloadKeyring()\n // Rebuild the DEK resolver against the refreshed keyring so\n // the next ensureCollectionDEK call sees the loaded wrapped\n // DEKs, not the cached pre-load ones.\n this.getDEK = this.makeGetDEK()\n }\n\n // 5. Clear collection cache + reset the ledger store so the\n // next ledger() call rebuilds its head cache from the\n // freshly-loaded entries.\n this.collectionCache.clear()\n this.ledgerStore = null\n\n // 5. Run the verification gate. Pre-v0.4 backups skip this with\n // a one-line warning so existing consumers can still read\n // them while migrating.\n if (!backup.ledgerHead) {\n console.warn(\n `[noy-db] Loaded a legacy backup with no ledgerHead — ` +\n `verifiable-backup integrity check skipped. ` +\n `Re-export with v0.4+ to get tamper detection.`,\n )\n return\n }\n\n const result = await this.verifyBackupIntegrity()\n if (!result.ok) {\n // Surface the most specific error class we can. The result\n // shape carries enough info for callers to inspect.\n if (result.kind === 'data') {\n throw new BackupCorruptedError(\n result.collection,\n result.id,\n result.message,\n )\n }\n throw new BackupLedgerError(result.message, result.divergedAt)\n }\n\n // 6. Cross-check: the freshly-verified head must match the\n // value embedded at dump time. A mismatch means someone\n // truncated or extended the chain after dump.\n if (result.head !== backup.ledgerHead.hash) {\n throw new BackupLedgerError(\n `Backup ledger head mismatch: embedded \"${backup.ledgerHead.hash}\" ` +\n `but reconstructed \"${result.head}\".`,\n )\n }\n }\n\n /**\n * End-to-end backup integrity check. Runs both:\n *\n * 1. `ledger.verify()` — walks the hash chain and confirms\n * every `prevHash` matches the recomputed hash of its\n * predecessor.\n *\n * 2. **Data envelope cross-check** — for every (collection, id)\n * that has a current value, find the most recent ledger\n * entry recording a `put` for that pair, recompute the\n * sha256 of the stored envelope's `_data`, and compare to\n * the entry's `payloadHash`. Any mismatch means an\n * out-of-band write modified the data without updating the\n * ledger.\n *\n * Returns a discriminated union so callers can handle the two\n * failure modes differently:\n * - `{ ok: true, head, length }` — chain verified and all\n * data matches; safe to use.\n * - `{ ok: false, kind: 'chain', divergedAt, message }` — the\n * chain itself is broken at the given index.\n * - `{ ok: false, kind: 'data', collection, id, message }` —\n * a specific data envelope doesn't match its ledger entry.\n *\n * This method is exposed so users can call it any time, not just\n * during `load()`. A scheduled background check is the simplest\n * way to detect tampering of an in-place compartment.\n */\n async verifyBackupIntegrity(): Promise<\n | { readonly ok: true; readonly head: string; readonly length: number }\n | {\n readonly ok: false\n readonly kind: 'chain'\n readonly divergedAt: number\n readonly message: string\n }\n | {\n readonly ok: false\n readonly kind: 'data'\n readonly collection: string\n readonly id: string\n readonly message: string\n }\n > {\n // Step 1: chain verification.\n const chainResult = await this.ledger().verify()\n if (!chainResult.ok) {\n return {\n ok: false,\n kind: 'chain',\n divergedAt: chainResult.divergedAt,\n message:\n `Ledger chain diverged at index ${chainResult.divergedAt}: ` +\n `expected prevHash \"${chainResult.expected}\" but found \"${chainResult.actual}\".`,\n }\n }\n\n // Step 2: data envelope cross-check. Walk every entry in the\n // ledger and, for the LATEST `put` per (collection, id), recompute\n // the data envelope's payloadHash and compare. Earlier puts of the\n // same id are skipped because the data collection only holds the\n // current version — historical envelopes live in the deltas\n // collection (which is itself protected by the chain).\n const ledger = this.ledger()\n const allEntries = await ledger.loadAllEntries()\n\n // Find the latest non-delete entry per (collection, id). Walk\n // the entries in reverse so we hit the latest first; mark each\n // (collection, id) as seen and skip subsequent entries.\n const seen = new Set<string>()\n const latest = new Map<\n string,\n { collection: string; id: string; expectedHash: string }\n >()\n for (let i = allEntries.length - 1; i >= 0; i--) {\n const entry = allEntries[i]\n if (!entry) continue\n const key = `${entry.collection}/${entry.id}`\n if (seen.has(key)) continue\n seen.add(key)\n // For deletes the data collection should NOT have the record,\n // so we skip — there's nothing to cross-check.\n if (entry.op === 'delete') continue\n latest.set(key, {\n collection: entry.collection,\n id: entry.id,\n expectedHash: entry.payloadHash,\n })\n }\n\n for (const { collection, id, expectedHash } of latest.values()) {\n const envelope = await this.adapter.get(this.name, collection, id)\n if (!envelope) {\n return {\n ok: false,\n kind: 'data',\n collection,\n id,\n message:\n `Ledger expects data record \"${collection}/${id}\" to exist, ` +\n `but the adapter has no envelope for it.`,\n }\n }\n const actualHash = await sha256Hex(envelope._data)\n if (actualHash !== expectedHash) {\n return {\n ok: false,\n kind: 'data',\n collection,\n id,\n message:\n `Data envelope \"${collection}/${id}\" has been tampered with: ` +\n `expected payloadHash \"${expectedHash}\", got \"${actualHash}\".`,\n }\n }\n }\n\n return {\n ok: true,\n head: chainResult.head,\n length: chainResult.length,\n }\n }\n\n /** Export compartment as decrypted JSON (owner only). */\n async export(): Promise<string> {\n if (this.keyring.role !== 'owner') {\n throw new PermissionDeniedError('Only the owner can export decrypted data')\n }\n\n const result: Record<string, Record<string, unknown>> = {}\n const snapshot = await this.adapter.loadAll(this.name)\n\n for (const [collName, records] of Object.entries(snapshot)) {\n const coll = this.collection(collName)\n const decrypted: Record<string, unknown> = {}\n for (const id of Object.keys(records)) {\n decrypted[id] = await coll.get(id)\n }\n result[collName] = decrypted\n }\n\n return JSON.stringify(result)\n }\n}\n","import type { NoydbEventMap } from './types.js'\n\ntype EventHandler<T> = (data: T) => void\n\n/** Typed event emitter for NOYDB events. */\nexport class NoydbEventEmitter {\n private readonly listeners = new Map<string, Set<EventHandler<unknown>>>()\n\n on<K extends keyof NoydbEventMap>(\n event: K,\n handler: EventHandler<NoydbEventMap[K]>,\n ): void {\n let set = this.listeners.get(event as string)\n if (!set) {\n set = new Set()\n this.listeners.set(event as string, set)\n }\n set.add(handler as EventHandler<unknown>)\n }\n\n off<K extends keyof NoydbEventMap>(\n event: K,\n handler: EventHandler<NoydbEventMap[K]>,\n ): void {\n this.listeners.get(event as string)?.delete(handler as EventHandler<unknown>)\n }\n\n emit<K extends keyof NoydbEventMap>(event: K, data: NoydbEventMap[K]): void {\n const set = this.listeners.get(event as string)\n if (set) {\n for (const handler of set) {\n handler(data)\n }\n }\n }\n\n removeAllListeners(): void {\n this.listeners.clear()\n }\n}\n","import type {\n NoydbAdapter,\n DirtyEntry,\n Conflict,\n ConflictStrategy,\n PushResult,\n PullResult,\n SyncStatus,\n EncryptedEnvelope,\n SyncMetadata,\n} from './types.js'\nimport { NOYDB_SYNC_VERSION } from './types.js'\nimport { ConflictError } from './errors.js'\nimport type { NoydbEventEmitter } from './events.js'\n\n/** Sync engine: dirty tracking, push, pull, conflict resolution. */\nexport class SyncEngine {\n private readonly local: NoydbAdapter\n private readonly remote: NoydbAdapter\n private readonly strategy: ConflictStrategy\n private readonly emitter: NoydbEventEmitter\n private readonly compartment: string\n\n private dirty: DirtyEntry[] = []\n private lastPush: string | null = null\n private lastPull: string | null = null\n private loaded = false\n private autoSyncInterval: ReturnType<typeof setInterval> | null = null\n private isOnline = true\n\n constructor(opts: {\n local: NoydbAdapter\n remote: NoydbAdapter\n compartment: string\n strategy: ConflictStrategy\n emitter: NoydbEventEmitter\n }) {\n this.local = opts.local\n this.remote = opts.remote\n this.compartment = opts.compartment\n this.strategy = opts.strategy\n this.emitter = opts.emitter\n }\n\n /** Record a local change for later push. */\n async trackChange(collection: string, id: string, action: 'put' | 'delete', version: number): Promise<void> {\n await this.ensureLoaded()\n\n // Deduplicate: if same collection+id already in dirty, update it\n const idx = this.dirty.findIndex(d => d.collection === collection && d.id === id)\n const entry: DirtyEntry = {\n compartment: this.compartment,\n collection,\n id,\n action,\n version,\n timestamp: new Date().toISOString(),\n }\n\n if (idx >= 0) {\n this.dirty[idx] = entry\n } else {\n this.dirty.push(entry)\n }\n\n await this.persistMeta()\n }\n\n /** Push dirty records to remote adapter. */\n async push(): Promise<PushResult> {\n await this.ensureLoaded()\n\n let pushed = 0\n const conflicts: Conflict[] = []\n const errors: Error[] = []\n const completed: number[] = []\n\n for (let i = 0; i < this.dirty.length; i++) {\n const entry = this.dirty[i]!\n try {\n if (entry.action === 'delete') {\n await this.remote.delete(this.compartment, entry.collection, entry.id)\n completed.push(i)\n pushed++\n } else {\n const envelope = await this.local.get(this.compartment, entry.collection, entry.id)\n if (!envelope) {\n // Record was deleted locally after being marked dirty\n completed.push(i)\n continue\n }\n\n try {\n await this.remote.put(\n this.compartment,\n entry.collection,\n entry.id,\n envelope,\n entry.version > 1 ? entry.version - 1 : undefined,\n )\n completed.push(i)\n pushed++\n } catch (err) {\n if (err instanceof ConflictError) {\n const remoteEnvelope = await this.remote.get(this.compartment, entry.collection, entry.id)\n if (remoteEnvelope) {\n const conflict: Conflict = {\n compartment: this.compartment,\n collection: entry.collection,\n id: entry.id,\n local: envelope,\n remote: remoteEnvelope,\n localVersion: envelope._v,\n remoteVersion: remoteEnvelope._v,\n }\n conflicts.push(conflict)\n this.emitter.emit('sync:conflict', conflict)\n\n // Auto-resolve based on strategy\n const resolution = this.resolveConflict(conflict)\n if (resolution === 'local') {\n await this.remote.put(this.compartment, entry.collection, entry.id, envelope)\n completed.push(i)\n pushed++\n } else if (resolution === 'remote') {\n await this.local.put(this.compartment, entry.collection, entry.id, remoteEnvelope)\n completed.push(i)\n }\n }\n } else {\n throw err\n }\n }\n }\n } catch (err) {\n errors.push(err instanceof Error ? err : new Error(String(err)))\n }\n }\n\n // Remove completed entries from dirty log (reverse order to preserve indices)\n for (const i of completed.sort((a, b) => b - a)) {\n this.dirty.splice(i, 1)\n }\n\n this.lastPush = new Date().toISOString()\n await this.persistMeta()\n\n const result: PushResult = { pushed, conflicts, errors }\n this.emitter.emit('sync:push', result)\n return result\n }\n\n /** Pull remote records to local adapter. */\n async pull(): Promise<PullResult> {\n await this.ensureLoaded()\n\n let pulled = 0\n const conflicts: Conflict[] = []\n const errors: Error[] = []\n\n try {\n const remoteSnapshot = await this.remote.loadAll(this.compartment)\n\n for (const [collName, records] of Object.entries(remoteSnapshot)) {\n for (const [id, remoteEnvelope] of Object.entries(records)) {\n try {\n const localEnvelope = await this.local.get(this.compartment, collName, id)\n\n if (!localEnvelope) {\n // New record from remote\n await this.local.put(this.compartment, collName, id, remoteEnvelope)\n pulled++\n } else if (remoteEnvelope._v > localEnvelope._v) {\n // Remote is newer — check if we have a dirty entry for this\n const isDirty = this.dirty.some(d => d.collection === collName && d.id === id)\n if (isDirty) {\n // Both changed — conflict\n const conflict: Conflict = {\n compartment: this.compartment,\n collection: collName,\n id,\n local: localEnvelope,\n remote: remoteEnvelope,\n localVersion: localEnvelope._v,\n remoteVersion: remoteEnvelope._v,\n }\n conflicts.push(conflict)\n this.emitter.emit('sync:conflict', conflict)\n\n const resolution = this.resolveConflict(conflict)\n if (resolution === 'remote') {\n await this.local.put(this.compartment, collName, id, remoteEnvelope)\n // Remove from dirty log\n this.dirty = this.dirty.filter(d => !(d.collection === collName && d.id === id))\n pulled++\n }\n // 'local' keeps local version, push will handle it\n } else {\n // Remote is newer, no local changes — update\n await this.local.put(this.compartment, collName, id, remoteEnvelope)\n pulled++\n }\n }\n // Same version or local is newer — skip (push will handle)\n } catch (err) {\n errors.push(err instanceof Error ? err : new Error(String(err)))\n }\n }\n }\n } catch (err) {\n errors.push(err instanceof Error ? err : new Error(String(err)))\n }\n\n this.lastPull = new Date().toISOString()\n await this.persistMeta()\n\n const result: PullResult = { pulled, conflicts, errors }\n this.emitter.emit('sync:pull', result)\n return result\n }\n\n /** Bidirectional sync: pull then push. */\n async sync(): Promise<{ pull: PullResult; push: PushResult }> {\n const pullResult = await this.pull()\n const pushResult = await this.push()\n return { pull: pullResult, push: pushResult }\n }\n\n /** Get current sync status. */\n status(): SyncStatus {\n return {\n dirty: this.dirty.length,\n lastPush: this.lastPush,\n lastPull: this.lastPull,\n online: this.isOnline,\n }\n }\n\n // ─── Auto-Sync ───────────────────────────────────────────────────\n\n /** Start auto-sync: listen for online/offline events, optional periodic sync. */\n startAutoSync(intervalMs?: number): void {\n // Online/offline detection\n if (typeof globalThis.addEventListener === 'function') {\n globalThis.addEventListener('online', this.handleOnline)\n globalThis.addEventListener('offline', this.handleOffline)\n }\n\n // Periodic sync\n if (intervalMs && intervalMs > 0) {\n this.autoSyncInterval = setInterval(() => {\n if (this.isOnline) {\n void this.sync()\n }\n }, intervalMs)\n }\n }\n\n /** Stop auto-sync. */\n stopAutoSync(): void {\n if (typeof globalThis.removeEventListener === 'function') {\n globalThis.removeEventListener('online', this.handleOnline)\n globalThis.removeEventListener('offline', this.handleOffline)\n }\n if (this.autoSyncInterval) {\n clearInterval(this.autoSyncInterval)\n this.autoSyncInterval = null\n }\n }\n\n private handleOnline = (): void => {\n this.isOnline = true\n this.emitter.emit('sync:online', undefined as never)\n void this.sync()\n }\n\n private handleOffline = (): void => {\n this.isOnline = false\n this.emitter.emit('sync:offline', undefined as never)\n }\n\n /** Resolve a conflict using the configured strategy. */\n private resolveConflict(conflict: Conflict): 'local' | 'remote' {\n if (typeof this.strategy === 'function') {\n return this.strategy(conflict)\n }\n\n switch (this.strategy) {\n case 'local-wins':\n return 'local'\n case 'remote-wins':\n return 'remote'\n case 'version':\n default:\n return conflict.localVersion >= conflict.remoteVersion ? 'local' : 'remote'\n }\n }\n\n // ─── Persistence ─────────────────────────────────────────────────\n\n private async ensureLoaded(): Promise<void> {\n if (this.loaded) return\n\n const envelope = await this.local.get(this.compartment, '_sync', 'meta')\n if (envelope) {\n const meta = JSON.parse(envelope._data) as SyncMetadata\n this.dirty = [...meta.dirty]\n this.lastPush = meta.last_push\n this.lastPull = meta.last_pull\n }\n\n this.loaded = true\n }\n\n private async persistMeta(): Promise<void> {\n const meta: SyncMetadata = {\n _noydb_sync: NOYDB_SYNC_VERSION,\n last_push: this.lastPush,\n last_pull: this.lastPull,\n dirty: this.dirty,\n }\n\n const envelope: EncryptedEnvelope = {\n _noydb: 1,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(meta),\n }\n\n await this.local.put(this.compartment, '_sync', 'meta', envelope)\n }\n}\n","import type { NoydbOptions, NoydbEventMap, GrantOptions, RevokeOptions, UserInfo, PushResult, PullResult, SyncStatus } from './types.js'\nimport { ValidationError, NoAccessError } from './errors.js'\nimport { Compartment } from './compartment.js'\nimport { NoydbEventEmitter } from './events.js'\nimport {\n loadKeyring,\n createOwnerKeyring,\n grant as keyringGrant,\n revoke as keyringRevoke,\n changeSecret as keyringChangeSecret,\n listUsers as keyringListUsers,\n} from './keyring.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { SyncEngine } from './sync.js'\n\n/** Dummy keyring for unencrypted mode. */\nfunction createPlaintextKeyring(userId: string): UnlockedKeyring {\n return {\n userId,\n displayName: userId,\n role: 'owner',\n permissions: {},\n deks: new Map(),\n kek: null as unknown as CryptoKey,\n salt: new Uint8Array(0),\n }\n}\n\n/** The top-level NOYDB instance. */\nexport class Noydb {\n private readonly options: NoydbOptions\n private readonly emitter = new NoydbEventEmitter()\n private readonly compartmentCache = new Map<string, Compartment>()\n private readonly keyringCache = new Map<string, UnlockedKeyring>()\n private readonly syncEngines = new Map<string, SyncEngine>()\n private closed = false\n private sessionTimer: ReturnType<typeof setTimeout> | null = null\n\n constructor(options: NoydbOptions) {\n this.options = options\n this.resetSessionTimer()\n }\n\n private resetSessionTimer(): void {\n if (this.sessionTimer) clearTimeout(this.sessionTimer)\n if (this.options.sessionTimeout && this.options.sessionTimeout > 0) {\n this.sessionTimer = setTimeout(() => {\n this.close()\n }, this.options.sessionTimeout)\n }\n }\n\n /** Open a compartment by name. */\n async openCompartment(name: string): Promise<Compartment> {\n if (this.closed) throw new ValidationError('Instance is closed')\n this.resetSessionTimer()\n\n let comp = this.compartmentCache.get(name)\n if (comp) return comp\n\n const keyring = await this.getKeyring(name)\n\n // Set up sync engine if remote adapter is configured\n let syncEngine: SyncEngine | undefined\n if (this.options.sync) {\n syncEngine = new SyncEngine({\n local: this.options.adapter,\n remote: this.options.sync,\n compartment: name,\n strategy: this.options.conflict ?? 'version',\n emitter: this.emitter,\n })\n this.syncEngines.set(name, syncEngine)\n }\n\n comp = new Compartment({\n adapter: this.options.adapter,\n name,\n keyring,\n encrypted: this.options.encrypt !== false,\n emitter: this.emitter,\n onDirty: syncEngine\n ? (coll, id, action, version) => syncEngine.trackChange(coll, id, action, version)\n : undefined,\n historyConfig: this.options.history,\n // Refresh callback used by Compartment.load() to re-derive\n // the in-memory keyring from a freshly-loaded keyring file.\n // Encrypted compartments need this so post-load decrypts work\n // against the loaded session's wrapped DEKs; plaintext\n // compartments leave it null and load() skips the refresh.\n reloadKeyring:\n this.options.encrypt !== false && this.options.secret\n ? async () => {\n // Drop the cached keyring so the next loadKeyring\n // call reads fresh from the adapter, then update the\n // cache so subsequent openCompartment calls see the\n // refreshed keyring too.\n this.keyringCache.delete(name)\n const refreshed = await loadKeyring(\n this.options.adapter,\n name,\n this.options.user,\n this.options.secret as string,\n )\n this.keyringCache.set(name, refreshed)\n return refreshed\n }\n : undefined,\n })\n this.compartmentCache.set(name, comp)\n return comp\n }\n\n /** Synchronous compartment access (must call openCompartment first, or auto-opens). */\n compartment(name: string): Compartment {\n const cached = this.compartmentCache.get(name)\n if (cached) return cached\n\n // For backwards compat: if not opened yet, create with cached keyring or plaintext\n if (this.options.encrypt === false) {\n const keyring = createPlaintextKeyring(this.options.user)\n const comp = new Compartment({\n adapter: this.options.adapter,\n name,\n keyring,\n encrypted: false,\n emitter: this.emitter,\n historyConfig: this.options.history,\n })\n this.compartmentCache.set(name, comp)\n return comp\n }\n\n const keyring = this.keyringCache.get(name)\n if (!keyring) {\n throw new ValidationError(\n `Compartment \"${name}\" not opened. Use await db.openCompartment(\"${name}\") first.`,\n )\n }\n\n const comp = new Compartment({\n adapter: this.options.adapter,\n name,\n keyring,\n encrypted: true,\n historyConfig: this.options.history,\n emitter: this.emitter,\n })\n this.compartmentCache.set(name, comp)\n return comp\n }\n\n /** Grant access to a user for a compartment. */\n async grant(compartment: string, options: GrantOptions): Promise<void> {\n const keyring = await this.getKeyring(compartment)\n await keyringGrant(this.options.adapter, compartment, keyring, options)\n }\n\n /** Revoke a user's access to a compartment. */\n async revoke(compartment: string, options: RevokeOptions): Promise<void> {\n const keyring = await this.getKeyring(compartment)\n await keyringRevoke(this.options.adapter, compartment, keyring, options)\n }\n\n /** List all users with access to a compartment. */\n async listUsers(compartment: string): Promise<UserInfo[]> {\n return keyringListUsers(this.options.adapter, compartment)\n }\n\n /** Change the current user's passphrase for a compartment. */\n async changeSecret(compartment: string, newPassphrase: string): Promise<void> {\n const keyring = await this.getKeyring(compartment)\n const updated = await keyringChangeSecret(\n this.options.adapter,\n compartment,\n keyring,\n newPassphrase,\n )\n this.keyringCache.set(compartment, updated)\n }\n\n // ─── Sync ──────────────────────────────────────────────────────\n\n /** Push local changes to remote for a compartment. */\n async push(compartment: string): Promise<PushResult> {\n const engine = this.getSyncEngine(compartment)\n return engine.push()\n }\n\n /** Pull remote changes to local for a compartment. */\n async pull(compartment: string): Promise<PullResult> {\n const engine = this.getSyncEngine(compartment)\n return engine.pull()\n }\n\n /** Bidirectional sync: pull then push. */\n async sync(compartment: string): Promise<{ pull: PullResult; push: PushResult }> {\n const engine = this.getSyncEngine(compartment)\n return engine.sync()\n }\n\n /** Get sync status for a compartment. */\n syncStatus(compartment: string): SyncStatus {\n const engine = this.syncEngines.get(compartment)\n if (!engine) {\n return { dirty: 0, lastPush: null, lastPull: null, online: true }\n }\n return engine.status()\n }\n\n private getSyncEngine(compartment: string): SyncEngine {\n const engine = this.syncEngines.get(compartment)\n if (!engine) {\n throw new ValidationError('No sync adapter configured. Pass a `sync` adapter to createNoydb().')\n }\n return engine\n }\n\n // ─── Events ────────────────────────────────────────────────────\n\n on<K extends keyof NoydbEventMap>(event: K, handler: (data: NoydbEventMap[K]) => void): void {\n this.emitter.on(event, handler)\n }\n\n off<K extends keyof NoydbEventMap>(event: K, handler: (data: NoydbEventMap[K]) => void): void {\n this.emitter.off(event, handler)\n }\n\n close(): void {\n this.closed = true\n if (this.sessionTimer) {\n clearTimeout(this.sessionTimer)\n this.sessionTimer = null\n }\n // Stop all sync engines\n for (const engine of this.syncEngines.values()) {\n engine.stopAutoSync()\n }\n this.syncEngines.clear()\n this.keyringCache.clear()\n this.compartmentCache.clear()\n this.emitter.removeAllListeners()\n }\n\n /** Get or load the keyring for a compartment. */\n private async getKeyring(compartment: string): Promise<UnlockedKeyring> {\n if (this.options.encrypt === false) {\n return createPlaintextKeyring(this.options.user)\n }\n\n const cached = this.keyringCache.get(compartment)\n if (cached) return cached\n\n if (!this.options.secret) {\n throw new ValidationError('A secret (passphrase) is required when encryption is enabled')\n }\n\n let keyring: UnlockedKeyring\n try {\n keyring = await loadKeyring(this.options.adapter, compartment, this.options.user, this.options.secret)\n } catch (err) {\n // Only create a new keyring if no keyring exists (NoAccessError).\n // If the keyring exists but the passphrase is wrong (InvalidKeyError), propagate the error.\n if (err instanceof NoAccessError) {\n keyring = await createOwnerKeyring(this.options.adapter, compartment, this.options.user, this.options.secret)\n } else {\n throw err\n }\n }\n\n this.keyringCache.set(compartment, keyring)\n return keyring\n }\n}\n\n/** Create a new NOYDB instance. */\nexport async function createNoydb(options: NoydbOptions): Promise<Noydb> {\n const encrypted = options.encrypt !== false\n\n if (encrypted && !options.secret) {\n throw new ValidationError('A secret (passphrase) is required when encryption is enabled')\n }\n\n return new Noydb(options)\n}\n","import { bufferToBase64, base64ToBuffer } from './crypto.js'\nimport { ValidationError } from './errors.js'\n\n/**\n * WebAuthn biometric enrollment and unlock.\n *\n * Enrollment: User enters passphrase → derive KEK → create WebAuthn credential\n * → wrap KEK with credential-derived key → store { credentialId, wrappedKek }\n *\n * Unlock: Retrieve { credentialId, wrappedKek } → WebAuthn assertion\n * → unwrap KEK → proceed as passphrase auth\n *\n * This module requires a browser environment with WebAuthn support.\n */\n\nexport interface BiometricCredential {\n credentialId: string\n wrappedKek: string\n salt: string\n}\n\n/** Check if WebAuthn is available in the current environment. */\nexport function isBiometricAvailable(): boolean {\n return (\n typeof window !== 'undefined' &&\n typeof window.PublicKeyCredential !== 'undefined' &&\n typeof navigator.credentials !== 'undefined'\n )\n}\n\n/**\n * Enroll a biometric credential for the current user.\n * Must be called after passphrase authentication (KEK is in memory).\n *\n * @param userId - User identifier for WebAuthn\n * @param kek - The KEK derived from the user's passphrase (in memory)\n * @returns BiometricCredential to persist in browser storage\n */\nexport async function enrollBiometric(\n userId: string,\n kek: CryptoKey,\n): Promise<BiometricCredential> {\n if (!isBiometricAvailable()) {\n throw new ValidationError('WebAuthn is not available in this environment')\n }\n\n // Create a WebAuthn credential\n const challenge = globalThis.crypto.getRandomValues(new Uint8Array(32))\n const userIdBytes = new TextEncoder().encode(userId)\n\n const credential = await navigator.credentials.create({\n publicKey: {\n challenge,\n rp: { name: 'NOYDB' },\n user: {\n id: userIdBytes,\n name: userId,\n displayName: userId,\n },\n pubKeyCredParams: [\n { type: 'public-key', alg: -7 }, // ES256\n { type: 'public-key', alg: -257 }, // RS256\n ],\n authenticatorSelection: {\n authenticatorAttachment: 'platform',\n userVerification: 'required',\n residentKey: 'preferred',\n },\n timeout: 60_000,\n },\n }) as PublicKeyCredential | null\n\n if (!credential) {\n throw new ValidationError('Biometric enrollment was cancelled')\n }\n\n // Export KEK and wrap it with a key derived from the credential\n const rawKek = await globalThis.crypto.subtle.exportKey('raw', kek)\n const wrappingKey = await deriveWrappingKey(credential.rawId)\n const iv = new Uint8Array(12) as unknown as ArrayBuffer\n const wrappedKek = await globalThis.crypto.subtle.encrypt(\n { name: 'AES-GCM', iv },\n wrappingKey,\n rawKek,\n )\n\n return {\n credentialId: bufferToBase64(credential.rawId),\n wrappedKek: bufferToBase64(wrappedKek),\n salt: bufferToBase64(globalThis.crypto.getRandomValues(new Uint8Array(32))),\n }\n}\n\n/**\n * Unlock using a previously enrolled biometric credential.\n *\n * @param storedCredential - The stored BiometricCredential from enrollment\n * @returns The unwrapped KEK as a CryptoKey\n */\nexport async function unlockBiometric(\n storedCredential: BiometricCredential,\n): Promise<CryptoKey> {\n if (!isBiometricAvailable()) {\n throw new ValidationError('WebAuthn is not available in this environment')\n }\n\n const credentialId = base64ToBuffer(storedCredential.credentialId)\n\n const assertion = await navigator.credentials.get({\n publicKey: {\n challenge: globalThis.crypto.getRandomValues(new Uint8Array(32)),\n allowCredentials: [{\n type: 'public-key',\n id: credentialId as BufferSource,\n }],\n userVerification: 'required',\n timeout: 60_000,\n },\n }) as PublicKeyCredential | null\n\n if (!assertion) {\n throw new ValidationError('Biometric authentication was cancelled')\n }\n\n // Unwrap KEK using credential-derived key\n const wrappingKey = await deriveWrappingKey(assertion.rawId)\n const unlockIv = new Uint8Array(12) as unknown as ArrayBuffer\n const rawKek = await globalThis.crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: unlockIv },\n wrappingKey,\n base64ToBuffer(storedCredential.wrappedKek) as BufferSource,\n )\n\n return globalThis.crypto.subtle.importKey(\n 'raw',\n rawKek,\n { name: 'AES-KW', length: 256 },\n false,\n ['wrapKey', 'unwrapKey'],\n )\n}\n\n/** Remove biometric enrollment from browser storage. */\nexport function removeBiometric(storage: Storage, userId: string): void {\n storage.removeItem(`noydb:biometric:${userId}`)\n}\n\n/** Save biometric credential to browser storage. */\nexport function saveBiometric(storage: Storage, userId: string, credential: BiometricCredential): void {\n storage.setItem(`noydb:biometric:${userId}`, JSON.stringify(credential))\n}\n\n/** Load biometric credential from browser storage. */\nexport function loadBiometric(storage: Storage, userId: string): BiometricCredential | null {\n const data = storage.getItem(`noydb:biometric:${userId}`)\n return data ? JSON.parse(data) as BiometricCredential : null\n}\n\n// ─── Internal ──────────────────────────────────────────────────────────\n\nasync function deriveWrappingKey(rawId: ArrayBuffer): Promise<CryptoKey> {\n const keyMaterial = await globalThis.crypto.subtle.importKey(\n 'raw',\n rawId,\n 'HKDF',\n false,\n ['deriveKey'],\n )\n\n return globalThis.crypto.subtle.deriveKey(\n {\n name: 'HKDF',\n hash: 'SHA-256',\n salt: new TextEncoder().encode('noydb-biometric-wrapping'),\n info: new TextEncoder().encode('kek-wrap'),\n },\n keyMaterial,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n","import { ValidationError } from './errors.js'\n\n/**\n * Validate passphrase strength.\n * Checks length and basic entropy heuristics.\n * Throws ValidationError if too weak.\n */\nexport function validatePassphrase(passphrase: string): void {\n if (passphrase.length < 8) {\n throw new ValidationError(\n 'Passphrase too short — minimum 8 characters. ' +\n 'Recommended: 12+ characters or a 4+ word passphrase.',\n )\n }\n\n const entropy = estimateEntropy(passphrase)\n if (entropy < 28) {\n throw new ValidationError(\n 'Passphrase too weak — too little entropy. ' +\n 'Use a mix of uppercase, lowercase, numbers, and symbols, ' +\n 'or use a 4+ word passphrase.',\n )\n }\n}\n\n/**\n * Estimate passphrase entropy in bits.\n * Uses character class analysis (not dictionary-based).\n */\nexport function estimateEntropy(passphrase: string): number {\n let charsetSize = 0\n\n if (/[a-z]/.test(passphrase)) charsetSize += 26\n if (/[A-Z]/.test(passphrase)) charsetSize += 26\n if (/[0-9]/.test(passphrase)) charsetSize += 10\n if (/[^a-zA-Z0-9]/.test(passphrase)) charsetSize += 32\n\n if (charsetSize === 0) charsetSize = 26 // fallback\n\n return Math.floor(passphrase.length * Math.log2(charsetSize))\n}\n"],"mappings":"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","names":["diff","previousEnvelope","diff","keyring","comp"]}
|