@noy-db/core 0.1.0
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/LICENSE +21 -0
- package/dist/index.cjs +1687 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +526 -0
- package/dist/index.d.ts +526 -0
- package/dist/index.js +1630 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/env-check.ts","../src/types.ts","../src/errors.ts","../src/crypto.ts","../src/keyring.ts","../src/history.ts","../src/diff.ts","../src/collection.ts","../src/compartment.ts","../src/events.ts","../src/sync.ts","../src/noydb.ts","../src/biometric.ts","../src/validation.ts"],"sourcesContent":["// Environment check — throws if Node <18 or crypto.subtle missing\nimport './env-check.js'\n\n// Types\nexport type {\n Role,\n Permission,\n Permissions,\n EncryptedEnvelope,\n CompartmentSnapshot,\n NoydbAdapter,\n KeyringFile,\n CompartmentBackup,\n DirtyEntry,\n SyncMetadata,\n Conflict,\n ConflictStrategy,\n PushResult,\n PullResult,\n SyncStatus,\n ChangeEvent,\n NoydbEventMap,\n GrantOptions,\n RevokeOptions,\n UserInfo,\n NoydbOptions,\n HistoryConfig,\n HistoryOptions,\n HistoryEntry,\n PruneOptions,\n} from './types.js'\n\nexport {\n NOYDB_FORMAT_VERSION,\n NOYDB_KEYRING_VERSION,\n NOYDB_BACKUP_VERSION,\n NOYDB_SYNC_VERSION,\n defineAdapter,\n} from './types.js'\n\n// Errors\nexport {\n NoydbError,\n DecryptionError,\n TamperedError,\n InvalidKeyError,\n NoAccessError,\n ReadOnlyError,\n PermissionDeniedError,\n ConflictError,\n NetworkError,\n NotFoundError,\n ValidationError,\n} from './errors.js'\n\n// Core classes\nexport { Noydb, createNoydb } from './noydb.js'\nexport { Compartment } from './compartment.js'\nexport { Collection } from './collection.js'\nexport { SyncEngine } from './sync.js'\n\n// Biometric (browser only)\nexport {\n isBiometricAvailable,\n enrollBiometric,\n unlockBiometric,\n removeBiometric,\n saveBiometric,\n loadBiometric,\n} from './biometric.js'\nexport type { BiometricCredential } from './biometric.js'\n\n// Diff\nexport { diff, formatDiff } from './diff.js'\nexport type { DiffEntry, ChangeType } from './diff.js'\n\n// Validation\nexport { validatePassphrase, estimateEntropy } from './validation.js'\n","function checkEnvironment(): void {\n // Node.js version check\n if (typeof process !== 'undefined' && process.versions?.node) {\n const major = parseInt(process.versions.node.split('.')[0]!, 10)\n if (major < 18) {\n throw new Error(\n `@noy-db/core requires Node.js 18 or later (found ${process.versions.node}). ` +\n 'Node.js 18+ is required for the Web Crypto API (crypto.subtle).',\n )\n }\n }\n\n // Web Crypto API availability (works in both Node and browser)\n if (typeof globalThis.crypto?.subtle === 'undefined') {\n throw new Error(\n '@noy-db/core requires the Web Crypto API (crypto.subtle). ' +\n 'Ensure you are running Node.js 18+ or a modern browser ' +\n '(Chrome 63+, Firefox 57+, Safari 13+).',\n )\n }\n}\n\ncheckEnvironment()\n","/** 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// ─── Adapter Interface ─────────────────────────────────────────────────\n\nexport interface NoydbAdapter {\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// ─── 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,\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),\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 },\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 },\n dek,\n ciphertext,\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, InvalidKeyError } 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 const userKek = null // We can't derive other users' KEKs!\n // Instead, we re-wrap using the keyring file's existing salt\n // But we need their KEK... which we don't have.\n // Solution: wrap the new DEK with the user's existing wrapped approach\n // Actually, we need to use the caller's knowledge: the caller has the raw DEKs\n // and we need to re-wrap them for each user.\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\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 let 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","import type { NoydbAdapter, EncryptedEnvelope, ChangeEvent, HistoryConfig, HistoryOptions, HistoryEntry, PruneOptions } from './types.js'\nimport { NOYDB_FORMAT_VERSION } from './types.js'\nimport { encrypt, decrypt } from './crypto.js'\nimport { ReadOnlyError, NoAccessError } 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'\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/** 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\n private readonly cache = new Map<string, { record: T; version: number }>()\n private hydrated = false\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 }) {\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\n /** Get a single record by ID. Returns null if not found. */\n async get(id: string): Promise<T | null> {\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 await this.ensureHydrated()\n\n const existing = this.cache.get(id)\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 this.cache.set(id, { record, version })\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 const existing = this.cache.get(id)\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 this.cache.delete(id)\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 /** List all records in the collection. */\n async list(): Promise<T[]> {\n await this.ensureHydrated()\n return [...this.cache.values()].map(e => e.record)\n }\n\n /** Filter records by a predicate. */\n query(predicate: (record: T) => boolean): T[] {\n return [...this.cache.values()].map(e => e.record).filter(predicate)\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 /** Count records in the collection. */\n async count(): Promise<number> {\n await this.ensureHydrated()\n return this.cache.size\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 }\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 }\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, CompartmentSnapshot, HistoryConfig } from './types.js'\nimport { NOYDB_BACKUP_VERSION } from './types.js'\nimport { Collection } from './collection.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 /** Open a typed collection within this compartment. */\n collection<T>(collectionName: string): Collection<T> {\n let coll = this.collectionCache.get(collectionName)\n if (!coll) {\n coll = new Collection<T>({\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 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, NetworkError } 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"]}
|