import { StoreDescriptor, NoydbPodStore, StoreLocator } from '@noy-db/hub/to'; /** * **@noy-db/to-drive** — Google Drive bundle store for noy-db. * * Stores each vault as a single `.noydb` bundle in Drive. Implements * the `NoydbPodStore` contract (read/write/delete/list whole-bundle); * wrap with `wrapPodStore()` from `@noy-db/hub` to get the standard * six-method `NoydbStore` surface. * * ## Privacy posture * * - **ULID filenames** — `01HXG4F5ZK7…noydb`, never `acme.noydb`. * Drive folder listings are visible to share recipients, Workspace * admins on managed accounts, and Google's internal search index. * Naming the file after the vault leaks client identity to all of * those. ULIDs give a sortable, collision-free alternative that * discloses nothing. * - **`appDataFolder` by default** — a hidden Drive folder that does * NOT show in the Drive UI and is scoped to the installed app only. * Opt in to a user-visible folder for "restore from Drive" UX. * - **`drive.file` scope** — the OAuth token can only see files this * app created. No read access to the user's other Drive contents. * - **No custom properties** — Drive allows arbitrary key/value * metadata on files. This adapter writes nothing there. Every piece * of vault metadata lives inside the encrypted body. * * ## Driver — bring your own * * noy-db does NOT pull `googleapis` (or any Google SDK) as a runtime * dependency. The consumer wires their preferred client (`googleapis`, * `gapi-client-ts`, a bespoke `fetch` wrapper) and passes a duck-typed * `DriveClient` in. Every OAuth refresh concern stays outside this * package. * * ## Handle registry * * Drive's API identifies files by `fileId`, not filename. To resolve * "give me the bundle for vault ACME," the store needs to remember * which fileId corresponds to which vault. Two options: * * 1. **Consumer-persisted** — pass a `HandleStore` that implements * `getHandle(vaultId)` / `setHandle(vaultId, handle)`. Typical * implementations: localStorage in browsers, the vault's * `_sync_credentials` collection, or a JSON file on disk. * 2. **Drive-lookup** (default) — when no handle is cached, issue a * `files.list` call filtering by the ULID-encoded vaultId hash. * Slower but requires no extra plumbing. * * @packageDocumentation */ /** * Minimal shape the store needs from a Google Drive client. Every * production Drive wrapper (`googleapis`, `gapi`, hand-rolled `fetch` * code) can expose this surface with a thin adapter. */ interface DriveClient { createFile(request: DriveCreateRequest): Promise; updateFile(fileId: string, request: DriveUpdateRequest): Promise; getFileMetadata(fileId: string): Promise; getFileBytes(fileId: string): Promise; deleteFile(fileId: string): Promise; listFiles(query: DriveListQuery): Promise; } interface DriveCreateRequest { readonly name: string; readonly bytes: Uint8Array; readonly parents: readonly string[]; readonly mimeType?: string; } interface DriveUpdateRequest { readonly bytes: Uint8Array; readonly expectedRevision?: string | null; } interface DriveListQuery { readonly parents?: readonly string[]; readonly namePrefix?: string; readonly nameExact?: string; readonly mimeType?: string; readonly limit?: number; } interface DriveFileMeta { readonly id: string; readonly name: string; readonly headRevisionId: string; readonly size: number; } interface HandleStore { getHandle(vaultId: string): Promise; setHandle(vaultId: string, handle: DriveHandle): Promise; deleteHandle(vaultId: string): Promise; /** Return every known { vaultId, handle } pair. Used by `listBundles()`. */ listHandles?(): Promise>; } interface DriveHandle { /** Stable Drive fileId. */ readonly fileId: string; /** ULID filename — what lives in Drive. */ readonly name: string; } /** * In-memory handle store — fine for short-lived processes and tests. * Production consumers should persist handles to localStorage, IDB, * or a vault's `_sync_credentials` collection. */ declare function memoryHandleStore(): HandleStore; /** * Generate a new ULID — 26 characters: 10 chars of timestamp (ms * granularity, sortable lexically when generated ≥ 1 ms apart) plus * 16 chars of entropy (80 bits). */ declare function newUlid(): string; interface DriveStoreOptions { readonly drive: DriveClient; /** * Parent folder id. Defaults to Drive's `appDataFolder` — hidden from * the user, scoped to the app. Pass an explicit id to use a * user-visible folder. */ readonly parentId?: string; /** Handle registry — memory by default. Persist yourself in prod. */ readonly handles?: HandleStore; /** Bundle filename suffix. Default `'.noydb'`. */ readonly suffix?: string; } declare function toDrive(options: DriveStoreOptions): NoydbPodStore; /** * Serializable location of a Drive store. `parentId` is the Drive parent * folder id. Omitted means Drive's `appDataFolder` — the store's existing * default, unchanged. */ interface DriveAddress { /** Maps to `DriveStoreOptions.parentId`. Omitted means `appDataFolder` (unchanged default). */ readonly parentId?: string; } /** Serializable tuning carried on the descriptor (never credentials). */ interface DriveDescriptorOptions { readonly suffix?: string; } /** * Device-local supplement resolved at `resolve()` time — the live * `DriveClient` this store has no way to construct itself, plus the * optional per-device `HandleStore`. Never serialized into a pod alongside * the descriptor. */ interface DriveBinding { readonly client: DriveClient; /** Per-device handle registry. Omitted leaves the store's in-memory default. */ readonly handles?: HandleStore; } /** * Builds the `StoreDescriptor` form of a `toDrive()` store: `kind: * 'drive'`, `class: 'cloud'`, with the identity address and the * serializable tuning as `options`. Credentialless by construction — the * live client arrives via `binding.client` at `resolve()` time. */ declare function driveStoreDescriptor(address: DriveAddress, options?: DriveDescriptorOptions): StoreDescriptor; /** * Reconstructs the same store `toDrive()` builds, from a descriptor * produced by {@link driveStoreDescriptor}. `opts.binding.client` is * required — this store has no client library of its own and cannot build * a connection from `address` alone. * * `toDrive()` returns a `NoydbPodStore`, not a `NoydbStore` — this * factory's return type is deliberately NOT narrowed to match the hub's * generic `StoreFactory` shape. Callers that need the six-method contract * wrap the result with `wrapPodStore()` themselves, exactly as this * package's own tests do for `toDrive()` directly. */ declare function driveStoreFactory(descriptor: StoreDescriptor, opts: { binding?: unknown; }): NoydbPodStore; /** * Registers {@link driveStoreFactory} under the `'drive'` kind on * `locator`. No cast since hub `0.6.0-pre.11`: `register()` is generic over * both store shapes (noy-db#988), so the pod store's `NoydbPodStore` return * is inferred rather than asserted away. Callers resolving a descriptor * whose kind is not statically known should use `resolveAny()` + * `isPodStore()`. */ declare function registerDriveStore(locator: StoreLocator): void; export { type DriveAddress, type DriveBinding, type DriveClient, type DriveCreateRequest, type DriveDescriptorOptions, type DriveFileMeta, type DriveHandle, type DriveListQuery, type DriveStoreOptions, type DriveUpdateRequest, type HandleStore, driveStoreDescriptor, driveStoreFactory, memoryHandleStore, newUlid, registerDriveStore, toDrive };