import { StoreCredentialSource, NoydbPodStore, StoreCredentials, StoreLocator, StoreDescriptor, StoreFactory, NoydbStore } from '@noy-db/hub/to'; import { S3Client } from '@aws-sdk/client-s3'; /** * **s3Bundle** — whole-vault bundle store for noy-db over Amazon S3. * * Implements the `NoydbPodStore` contract (read/write/delete/list of whole * `.noydb` blobs) with optimistic concurrency via S3 conditional writes. Pairs * with `@noy-db/hub` snapshots (`withSnapshots({ store: s3Bundle(...) })`) and * with bundle-mode sync. * * Key scheme: `{prefix}/{vaultId}.noydb`. The version token is the object ETag. * * **OCC:** `writeBundle(id, bytes, expectedVersion)` — * - `expectedVersion === null` → unconditional `PutObject` (first write / rolling overwrite). * - `expectedVersion = ` → `PutObject` with `IfMatch`; a 412 becomes `PodVersionConflictError`. * * Requires `@aws-sdk/client-s3` ≥ 3.696 (conditional-write `IfMatch` on PutObject, GA Nov 2024). * * @packageDocumentation */ interface S3BundleOptions { /** S3 bucket name. */ bucket: string; /** Key prefix within the bucket. Default ''. Keys are `{prefix}/{vaultId}.noydb`. */ prefix?: string; /** AWS region. Used only when `client` is not provided. Default 'us-east-1'. */ region?: string; /** Pre-built S3Client. If provided, `region` is ignored. */ client?: S3Client; /** * Refresh hook (the #479 credential broker) — called by the SDK's own * credential provider whenever it has no credentials or they're near * expiry. Ignored when `client` is supplied (a pre-built client always * wins). */ credentials?: StoreCredentialSource; } declare function s3Bundle(options: S3BundleOptions): NoydbPodStore; /** * Minimal AWS credential identity shape (mirrors `@aws-sdk/types`' * `AwsCredentialIdentity`; not imported from there to avoid a new * dependency — `@aws-sdk/types` is only a transitive dep here). */ interface AwsCredentialIdentityLike { accessKeyId: string; secretAccessKey: string; sessionToken?: string; expiration?: Date; } /** * Maps a broker-issued `StoreCredentials` to the shape the AWS SDK v3 * credential provider expects. Shared by both S3Client construction sites * in this package (`toAwsS3()` and `s3Bundle()`). Conditional-spread for both * optional fields: `exactOptionalPropertyTypes` forbids `expiration: * undefined`, and the SDK's credential memoizer treats an *absent* * `expiration` as "unknown, never re-invoke" vs. a present `Date` as * "re-invoke at the rolling window". */ declare function mapAws(creds: StoreCredentials): AwsCredentialIdentityLike; /** * **@noy-db/to-aws-s3** — S3 object store for NOYDB. * * Each record is stored as a JSON object at * `{prefix}/{vault}/{collection}/{id}.json`. The `loadAll()` method uses * `ListObjectsV2` to enumerate keys then fetches them in parallel. * * ## When to use * * - **Blob / attachment storage** — pair with `@noy-db/to-aws-dynamo` via * `routeStore({ default: toAwsDynamo(...), blobs: toAwsS3(...) })` to route * encrypted binary chunks to S3. * - **Archive tier** — configure `routeStore` age-based tiering so old * records migrate to S3 while hot records stay in DynamoDB. * - **Large vaults** — S3 has no item size limit, unlike DynamoDB's 400 KB cap. * * ## Limitations * * - **`loadAll()` is O(N) requests** — listing + fetching every object in a * vault. Suitable for vaults up to ~10K records; beyond that, prefer * DynamoDB for indexed stores and S3 only for append-heavy blob storage. * * ## IAM minimum permissions * * ```json * { "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", * "s3:ListBucket"] } * ``` * * @packageDocumentation */ /** * Options for `toAwsS3()`. * * Objects are stored at `{prefix}/{vault}/{collection}/{id}.json`. * `loadAll()` uses `ListObjectsV2` over the vault prefix followed by parallel * `GetObject` calls — suitable for vaults with up to ~10K records. For larger * vaults, use DynamoDB or pair with `routeStore` age-tiering so S3 only * holds archived records. * * S3 supports conditional writes (`IfMatch` / `IfNoneMatch` on `PutObject`), * enabling atomic CAS (`casAtomic: true`). Server clock is read via a sentinel * object's `LastModified` timestamp — store-authoritative, not client wall clock. * ε defaults to 5 000 ms (S3 is NTP-synced; observed skew bound). */ interface S3Options { /** S3 bucket name. */ bucket: string; /** Key prefix within the bucket. Default: ''. */ prefix?: string; /** AWS region. Used only when `client` is not provided. Default: 'us-east-1'. */ region?: string; /** * Pre-built S3Client from `@aws-sdk/client-s3`. If provided, the adapter * uses this client directly and ignores `region`. Useful for apps that want * to share a client across adapters or supply custom middleware. */ client?: S3Client; /** Clock uncertainty bound for serverWriteTime (ms). Default: 5000. */ clockUncertaintyMs?: number; /** * Refresh hook (the #479 credential broker) — called by the SDK's own * credential provider whenever it has no credentials or they're near * expiry. Ignored when `client` is supplied (a pre-built client always * wins). */ credentials?: StoreCredentialSource; } declare function toAwsS3(options: S3Options): NoydbStore; /** Serializable location of an S3 store: bucket + region + key prefix. */ interface S3Address { readonly bucket: string; readonly region?: string; readonly prefix?: string; } /** Serializable tuning carried on the descriptor (never credentials). */ interface S3DescriptorOptions { readonly clockUncertaintyMs?: number; } /** * Device-local supplement resolved at `resolve()` time — a pre-built * `S3Client` (shared client, custom middleware, or a test fake). Never * serialized into a pod alongside the descriptor. This is `to-aws-s3`'s * binding-slot citizen (#58) — when supplied, it always wins over * address-derived construction. */ interface S3Binding { readonly client?: S3Client; } /** * Builds the `StoreDescriptor` form of a `toAwsS3()` store: * `kind: 'aws-s3'`, `class: 'cloud'`. Credentialless by construction — * AWS credentials arrive via `StoreCredentialSource` at `resolve()` * time (the #479 broker seam), or implicitly via the SDK's default * provider chain on the device. */ declare function s3StoreDescriptor(address: S3Address, options?: S3DescriptorOptions): StoreDescriptor; /** * `StoreFactory` for `to-aws-s3`: reconstructs the same store * `toAwsS3()` builds, from a descriptor produced by * {@link s3StoreDescriptor}. `opts.credentials` becomes the SDK's * credential provider; `opts.binding` may carry a pre-built client * ({@link S3Binding}), which always wins over region/credentials. */ declare const s3StoreFactory: StoreFactory; /** Registers {@link s3StoreFactory} under the `'aws-s3'` kind on `locator`. */ declare function registerS3Store(locator: StoreLocator): void; export { type AwsCredentialIdentityLike, type S3Address, type S3Binding, type S3BundleOptions, type S3DescriptorOptions, type S3Options, mapAws, registerS3Store, s3Bundle, s3StoreDescriptor, s3StoreFactory, toAwsS3 };