import type { ClientContext } from './internal/request.js'; import type { IndexDeclaration, IndexSchema } from './codec.js'; import { Resource } from './Resource.js'; import type { ChangesCheckpoint, ChangesPage } from '@interop/storage-core'; import type { AddResult, BackendDescriptor, BackendUsage, CollectionDescription, CollectionMetadata, CollectionWritableFields, EncryptionOverride, FindPage, GrantOptions, HandleOptions, IDelegatedZcap, IZcap, Json, ResourceData, LinkSet, PolicyDocument, CollectionResourcesList, ResourceMetadataCustomInput, ResourceSummary } from './types.js'; export declare class Collection { #private; readonly spaceId: string; readonly id: string; /** * @param options {object} * @param options.context {ClientContext} - Shared context (serverUrl, ezcap * client, controllerDid) * @param options.spaceId {string} * @param options.collectionId {string} * @param [options.capability] {IZcap} - capability attached to every request * @param [options.encryption] {EncryptionOverride} - per-handle encryption * override; wins over the Collection's declared descriptor and skips the * descriptor-discovery round-trip */ constructor({ context, spaceId, collectionId, capability, encryption }: { context: ClientContext; spaceId: string; collectionId: string; capability?: IZcap; encryption?: EncryptionOverride; }); /** * Reads the Collection Description. Returns `null` if the collection is * missing or not visible to you (WAS returns 404 for both not-found and * unauthorized). * * @returns {Promise} */ describe(): Promise; /** * Creates or updates the collection by id (upsert). Merges the given fields * over the current description. * * The merge needs a readable current description to be lost-update-safe, and * `describe()` cannot distinguish "absent" from "unreadable" (WAS masks * unauthorized reads as 404). When it returns `null` and neither `backend` * nor `encryption` is supplied, this fails closed rather than sending a PUT * body that would silently drop an existing collection's `backend` (a * data-placement change) or trip `encryption-immutable` by clearing its * descriptor on a replace-semantics server. Pass `force: true` to proceed * anyway * -- e.g. when creating a new collection through a handle (or use * `space.createCollection()`, which does not merge). * * @param desc {CollectionWritableFields} the fields to merge; `encryption` * declares the client-side encryption descriptor, which is set-once on the * server (it may be added to a Collection that lacks one, but * changing/clearing an existing descriptor is rejected -- `ConflictError`, * `encryption-immutable`) * @param [desc.force] {boolean} proceed even when the current description * is unreadable and `backend`/`encryption` are omitted (see above) * @returns {Promise} */ configure(desc: CollectionWritableFields & { force?: boolean; }): Promise; /** * Reads the Collection Description together with its `ETag` validator (the * server's `conditional-writes` / description-version support). The `ETag` is * the opaque validator to pass to {@link replaceDescription}'s `ifMatch` for a * lost-update-safe (compare-and-swap) description write. Returns `null` if the * collection is missing or not visible to you (404 conflation caveat); `etag` * is absent against a server that does not version the description. * * @returns {Promise<{ description: CollectionDescription; etag?: string } | null>} */ describeWithEtag(): Promise<{ description: CollectionDescription; etag?: string; } | null>; /** * Writes (replaces) the Collection Description, optionally as a * compare-and-swap against a prior `ETag` (`ifMatch`, from {@link * describeWithEtag}) so a concurrent writer cannot be silently clobbered -- a * stale validator surfaces as `PreconditionFailedError` (412). Sends the * writable fields as the full body; omit a field to drop it (replace * semantics), so callers doing CAS pass every field forward. Returns the new * `ETag` and the fields written. * * This is the generic description-CAS primitive the key-epoch recipient * operations build on (add/remove a reader is a CAS of the `encryption` * descriptor); it is not epoch-specific. * * @param description {CollectionWritableFields} * @param options {object} * @param [options.ifMatch] {string} the prior `ETag`; the write applies only * if the description is unchanged * @returns {Promise<{ description: CollectionDescription; etag?: string }>} */ replaceDescription(description: CollectionWritableFields, options?: { ifMatch?: string; }): Promise<{ description: CollectionDescription; etag?: string; }>; /** * Deletes the whole collection. Idempotent. To delete a single resource, use * `collection.resource(id).delete()`. * * @returns {Promise} */ delete(): Promise; /** * Reads the Collection's metadata object (server-managed timestamps, * `createdBy` and the encrypted-`custom` key `epoch`, plus the user-writable * `custom` object). Returns `null` if the collection is missing or not * visible to you (404 conflation caveat). A server without Collection * metadata support surfaces its 501 as `NotImplementedError`. * * On an encrypted collection the stored `custom` is an opaque envelope; this * decodes it (decrypts, via the codec) so a caller always sees plaintext * `{ name, tags }`. A collection with no user metadata reports `custom` as * `{}`. * * Against a backend with the `conditional-writes` feature the result also * carries the metadata's current `etag` (the `/meta` `metaVersion` * validator) -- pass it as `setMeta(meta, { ifMatch })` for a * lost-update-safe metadata update. That validator is independent of the * Collection Description's ETag ({@link describeWithEtag}) and of every * Resource's versions: writing one never bumps the other. * * @returns {Promise<(CollectionMetadata & { etag?: string }) | null>} */ meta(): Promise<(CollectionMetadata & { etag?: string; }) | null>; /** * Replaces the Collection's user-writable metadata (`custom`). This is a full * replacement: any property omitted from `custom` is cleared, and an omitted * `custom` clears them all. Does not create the collection -- a `PUT` to the * metadata of a nonexistent collection throws `NotFoundError`. Servers * without Collection metadata support surface their 501 as * `NotImplementedError`. * * On an encrypted collection `custom` is encrypted into an opaque envelope by * the codec before it is sent, so `name` / `tags` are never stored as * server-visible plaintext -- transparently, the same call works on plaintext * and encrypted collections alike. * * Conditional metadata writes (the backend's `conditional-writes` feature): * pass `ifMatch` (the `etag` from a prior `meta()`) for an * update-if-unchanged, or `ifNoneMatch: true` for a * write-only-if-no-metadata. A failed precondition throws * `PreconditionFailedError` (412). The `/meta` ETag (`metaVersion`) is * independent of the Collection Description's ETag. Returns the new `etag`. * * @param meta {object} * @param [meta.custom] {ResourceMetadataCustomInput} the user-writable * properties; extra members beyond `name` / `tags` are admitted (this * Collection-level `custom` also carries the persisted `indexSchema`) * while `name` / `tags` themselves stay checked at their stored types * @param options {object} * @param [options.ifMatch] {string} update only if the `/meta` ETag matches * @param [options.ifNoneMatch] {boolean} write only if no metadata is set * @returns {Promise<{ etag?: string }>} the metadata's new ETag */ setMeta(meta?: { custom?: ResourceMetadataCustomInput; }, options?: { ifMatch?: string; ifNoneMatch?: boolean; }): Promise<{ etag?: string; }>; /** * Sets the Collection's metadata-level human-readable `name`, preserving any * existing `tags`. Convenience over `setMeta()`. The write is pinned to the * `etag` the `meta()` read returned (when the backend supports * `conditional-writes`), so a concurrent metadata write surfaces as * `PreconditionFailedError` instead of being silently erased by this * full-replacement write. * * On an encrypted collection this is the collection's client-encrypted name * surface: the codec seals it into the `custom` envelope, and by convention * the plaintext Description `name` is left unpopulated. On a plaintext * collection the two are separate labels -- space-level listings surface the * Description's `name` (set via `configure({ name })`), while this one is * metadata-level. * * @param name {string} * @returns {Promise} */ setName(name: string): Promise; /** * Sets the Collection's `tags`, preserving any existing `name`. Convenience * over `setMeta()`. Pinned to the `meta()` read's `etag` like * {@link setName}. * * @param tags {Record} * @returns {Promise} */ setTags(tags: Record): Promise; /** * Reads the Collection's persisted index schema: which attributes its * documents are searchable by, whether each index is unique, and the schema * revision each was added in. Any recipient that can decrypt the collection * can read it, which is the point of persisting it -- an app granted access * to an existing collection learns what is queryable with no out-of-band * coordination (the stored index tokens cannot teach it, being blinded). * * `addedIn` is the partial-coverage marker: documents written before an * attribute was declared carry no token for it, so they do not match a search * on it until they are rewritten. * * @returns {Promise} */ indexes(): Promise; /** * Declares an attribute searchable, persisting it in the Collection's * encrypted index schema and installing it on this handle's codec. * Idempotent: re-declaring an attribute already in the schema on the same * terms is a no-op write. * * Declarations are collection state, not app state: they are stored inside * the encrypted `/meta` envelope, so every recipient discovers them (see * {@link indexes}). Concurrent declarations from two clients are reconciled * with a compare-and-swap against the metadata's own `metaVersion` ETag and a * bounded retry, so neither is silently erased. * * A declaration is prospective: documents already written carry no token for * the new attribute and do not match a search on it until they are rewritten * (the backfill is a re-encrypt sweep of the collection). * * Pass an array of attribute names for a compound index. A compound index can * be searched by a leading prefix of its attributes; `unique` is enforced * only when a document carries the whole combination. * * @param options {object} * @param options.attribute {string | string[]} a dotted attribute path * rooted at `content` or `meta` (e.g. `content.type`), or an array of them * for a compound index * @param [options.unique] {boolean} reject a second document that carries * the same value (the server answers a colliding write with `409`) * @returns {Promise} the schema now in force */ declareIndex({ attribute, unique }: { attribute: string | string[]; unique?: boolean; }): Promise; /** * Searches the collection's encrypted documents by indexed attribute. The * terms are blinded client-side before they are sent, so the server matches * opaque tokens and learns neither the attribute names nor the values; the * documents it returns are decrypted here, exactly as `get()` decrypts one. * * Give either `equals` (attribute/value pairs a document must match -- an * array of objects is an OR of alternatives) or `has` (attribute names a * document must carry), not both. Every attribute named must already be in * the collection's schema ({@link declareIndex}); an undeclared one is * refused with `ValidationError` rather than silently matching nothing. * * Pass `count: true` for just the number of matches. Otherwise the result is * one page: `limit` caps its size (the server clamps its own maximum), and a * `hasMore` page carries the `cursor` to pass back for the next one. * * Requires the collection's backend to advertise the `blinded-index-query` * feature; a backend without it answers `501` (`NotImplementedError`). * * @param options {object} * @param [options.equals] {object | object[]} attribute/value pairs to match * @param [options.has] {string | string[]} attribute names to require * @param [options.count] {boolean} return `{ count }` instead of documents * @param [options.limit] {number} maximum documents in the page * @param [options.cursor] {string} continue from a previous page * @returns {Promise} */ find(options: { equals?: Record | Array>; has?: string | string[]; count?: boolean; limit?: number; cursor?: string; }): Promise; /** * Returns a lazy handle to a resource by id. No I/O. * * @param resourceId {string} * @param options {object} * @param [options.capability] {IZcap} * @param [options.encryption] {EncryptionOverride} per-resource encryption * override; wins over the Collection's codec and resolves a fresh one for * this resource (see {@link EncryptionOverride}) * @returns {Resource} */ resource(resourceId: string, options?: HandleOptions): Resource; /** * Adds a resource with a server-generated id. JSON for plain objects/arrays, * binary for `Blob`/`Uint8Array`. Throws `NotFoundError` if the collection * does not exist (WAS does not auto-create parents). * * On an encrypted collection a binary payload above the codec's * single-document threshold is auto-routed to the chunked-stream path, which * needs the backend's `chunked-streams` feature (`NotSupportedError` without * it, raised before anything is written). * * @param data {ResourceData} * @param options {object} * @param [options.contentType] {string} content-type for binary data * @returns {Promise} */ add(data: ResourceData, options?: { contentType?: string; }): Promise; /** * Reads a resource by id, auto-parsing JSON to an object and returning binary * as a `Blob`. Returns `null` on a missing/unauthorized resource (404 * conflation caveat). * * @param resourceId {string} * @returns {Promise} */ get(resourceId: string): Promise; /** * Creates or replaces a resource by id (upsert). Forwards the * conditional-write options (`ifMatch` / `ifNoneMatch`) to `Resource.put`; * see it for the `conditional-writes` semantics. Returns the stored * resource's new `etag`. * * @param resourceId {string} * @param data {ResourceData} * @param options {object} * @param [options.contentType] {string} content-type for binary data * @param [options.ifMatch] {string} update only if the ETag matches * @param [options.ifNoneMatch] {boolean} create only if absent * @returns {Promise<{ etag?: string }>} */ put(resourceId: string, data: ResourceData, options?: { contentType?: string; ifMatch?: string; ifNoneMatch?: boolean; }): Promise<{ etag?: string; }>; /** * Lists the items in the collection. Transparently follows the server's `next` * pagination links, buffering every page into a single list (the returned * envelope omits `next`). Convenient, but holds the whole collection in memory * -- for a large collection prefer `listPages()` or `listItems()`, which stream * one page at a time and allow stopping early. Returns `null` if the * collection is missing or not visible to you (404 conflation caveat). * * @returns {Promise} */ list(): Promise; /** * Lazily yields the listing one page at a time, following the server's `next` * links on demand (each page fetched with the same authorization). Use this * to stream a large collection in constant memory or to stop early. Yields * nothing if the collection is missing or not visible to you (404 conflation * caveat) -- unlike `list()`, the iterator does not distinguish that from an * empty collection. * * @returns {AsyncGenerator} */ listPages(): AsyncGenerator; /** * Lazily yields each item across every page, flattening `listPages()`. Yields * the listing's `ResourceSummary` entries (id / url / contentType / name), not * the resource bodies -- call `get(id)` to read a body. Yields nothing if the * collection is missing or not visible to you (404 conflation caveat). * * @returns {AsyncGenerator} */ listItems(): AsyncGenerator; /** * Reads one page of the collection's replication change feed (the `changes` * query profile): the JSON-document resources and tombstones changed strictly * after `checkpoint`, in change order, at most `limit` of them. With no * `checkpoint` the feed starts from the beginning. * * This is deliberately a single page, not an iterator: it is shaped for an * RxDB `pull.handler(checkpoint, batchSize)`, which owns the iteration and * persists the checkpoint between batches. Resume by passing the returned * `checkpoint` back; a page shorter than `limit` means you have caught up. * * Requires the collection's backend to advertise the `changes-query` feature * (see `backend()`); a backend without it answers `501`. On an encrypted * collection the documents' `data` / `custom` are the scheme's opaque * envelopes (an EDV encrypted document under the v1 `edv` scheme) -- this * method does not decrypt them, unlike `get()`. * * @param [options] {object} * @param [options.checkpoint] {ChangesCheckpoint} resume strictly after this * @param [options.limit] {number} max documents; the server clamps its own maximum * @returns {Promise} */ changes(options?: { checkpoint?: ChangesCheckpoint; limit?: number; }): Promise; /** * Delegates access to this collection. Prefills the grant `target` with this * collection's URL (and the bound `capability`, if any, for re-delegation). * * @param options {GrantOptions} * @returns {Promise} */ grant(options: GrantOptions): Promise; /** * Reads the collection's access-control policy. Returns `null` when no policy * is set (or it is not visible to you). Managing a policy is a * controller-level operation; a capability scoped to the collection does not * cover its policy sub-resource. * * @returns {Promise} */ getPolicy(): Promise; /** * Sets (creates or replaces) the collection's access-control policy. * * @param policy {PolicyDocument} * @returns {Promise} */ setPolicy(policy: PolicyDocument): Promise; /** * Returns `true` when this collection's policy is `PublicCanRead`. * * @returns {Promise} */ isPublic(): Promise; /** * Makes the collection world-readable: every resource in it becomes readable * without authorization (unless overridden by a more specific policy). Sugar * for `setPolicy({ type: 'PublicCanRead' })`. * * @returns {Promise} */ setPublic(): Promise; /** * Removes the collection's access-control policy, reverting it to * capability-only access. Idempotent. * * @returns {Promise} */ clearPolicy(): Promise; /** * Reads the collection's linkset (RFC9264 policy discovery). Returns `null` * if the collection is missing or not visible to you. * * @returns {Promise} */ linkset(): Promise; /** * Reads the storage backend this collection is stored on ("Collection Backend * Selected"). Returns `null` if the collection is missing or not visible to * you (404 conflation caveat). A server without backend support surfaces its * 501 as `NotImplementedError`. * * The descriptor's optional `features` array advertises optional server * affordances (e.g. `conditional-writes`, `blinded-index-query`, * `chunked-streams`); an absent token means the backend makes no claim to it, * so treat it as unsupported rather than assuming a default. (Client-side * encryption is not a backend feature -- it is a per-collection client concern * gated on the client's keys.) * * @returns {Promise} */ backend(): Promise; /** * Reads the collection's storage usage report, scoped to its backend (spec * "Quotas"). Returns `null` if the collection is missing or not visible to you * (404 conflation caveat). A backend that cannot account per-collection * surfaces its 501 as `NotImplementedError`. * * @returns {Promise} */ quota(): Promise; } //# sourceMappingURL=Collection.d.ts.map