/**
 * A protocol-scoped API returned by {@link Enbox.using}.
 *
 * `TypedEnbox` is the **primary developer interface** for interacting with
 * protocol-backed records. It auto-injects the protocol URI, protocolPath,
 * and schema into every operation, and provides compile-time path
 * autocompletion plus typed data payloads via the schema map.
 *
 * All record-returning methods wrap the underlying `Record` instances in
 * {@link TypedRecord} so that type information flows through reads, queries,
 * updates, and subscriptions without manual casts.
 *
 * @example
 * ```ts
 * const social = enbox.using(SocialProtocol);
 *
 * // Install the protocol
 * await social.configure();
 *
 * // Create — path and data type are checked at compile time
 * const { record } = await social.records.create('thread', {
 *   data: { title: 'Hello World', body: '...' },
 * });
 * // record is TypedRecord<ThreadData>
 *
 * const data = await record.data.json(); // ThreadData — no cast needed
 *
 * // Query — protocol and protocolPath are auto-injected
 * const { records } = await social.records.query('thread');
 * // records is TypedRecord<ThreadData>[]
 *
 * // Subscribe — real-time changes via TypedLiveQuery
 * const { liveQuery } = await social.records.subscribe('thread/reply');
 * liveQuery.on('create', (record) => {
 *   // record is TypedRecord<ReplyData>
 * });
 * ```
 */
import type { DwnApi } from './dwn-api.js';
import type { LiveQuery } from './live-query.js';
import type { Protocol } from './protocol.js';
import type { DateSort, ProtocolDefinition, ProtocolType, RecordsFilter } from '@enbox/dwn-sdk-js';
import type { DwnPaginationCursor, DwnResponseStatus } from '@enbox/agent';
import type { ProtocolPaths, SchemaMap, TypedProtocol, TypeNameAtPath } from './protocol-types.js';
import { TypedLiveQuery } from './typed-live-query.js';
import { TypedRecord } from './typed-record.js';
/**
 * Resolves the TypeScript data type for a given protocol path.
 *
 * If the schema map contains a mapping for the type name at the given path,
 * that type is returned. Otherwise falls back to `unknown`.
 */
export type DataForPath<_D extends ProtocolDefinition, M extends SchemaMap, Path extends string> = TypeNameAtPath<Path> extends keyof M ? M[TypeNameAtPath<Path>] : unknown;
/**
 * Resolves the `ProtocolType` entry for a given protocol path.
 */
type ProtocolTypeForPath<D extends ProtocolDefinition, Path extends string> = TypeNameAtPath<Path> extends keyof D['types'] ? D['types'][TypeNameAtPath<Path>] extends ProtocolType ? D['types'][TypeNameAtPath<Path>] : undefined : undefined;
/**
 * Resolves a `dataFormat` string literal union for a path, or `string` if none.
 */
type DataFormatForPath<D extends ProtocolDefinition, Path extends string> = ProtocolTypeForPath<D, Path> extends {
    dataFormats: infer F;
} ? F extends readonly string[] ? F[number] : string : string;
/**
 * Options for {@link TypedEnbox} `records.create()`.
 *
 * The `data` field is type-checked against the protocol's schema map for
 * the given path, providing compile-time safety for record payloads.
 *
 * @typeParam D - The protocol definition type.
 * @typeParam M - The schema map mapping type names to TypeScript types.
 * @typeParam Path - The protocol path string literal.
 *
 * @example
 * ```ts
 * await proto.records.create('notebook/page', {
 *   data: { title: 'My Page', body: '...' },       // type-checked as PageData
 *   parentContextId: notebook.contextId,             // link to parent
 *   tags: { category: 'draft' },
 * });
 * ```
 */
export type TypedCreateRequest<D extends ProtocolDefinition, M extends SchemaMap, Path extends string> = {
    /** The data payload. Type-checked against the schema map for the given path. */
    data: DataForPath<D, M, Path>;
    /**
     * The context ID of the parent record.
     *
     * Required when creating a child record under a parent in a hierarchical
     * protocol structure. Use the parent record's {@link TypedRecord.contextId | contextId}
     * as this value.
     *
     * @example
     * ```ts
     * const { record: notebook } = await proto.records.create('notebook', {
     *   data: { name: 'My Notebook' },
     * });
     *
     * // Create a page under the notebook
     * await proto.records.create('notebook/page', {
     *   data: { title: 'Page 1' },
     *   parentContextId: notebook.contextId,
     * });
     * ```
     */
    parentContextId?: string;
    /**
     * Whether the record should be publicly published.
     *
     * Published records can be read by anyone without authorization.
     */
    published?: boolean;
    /**
     * ISO 8601 timestamp for when the record is considered published.
     * Only meaningful when `published` is `true`.
     */
    datePublished?: string;
    /**
     * The DID of the intended recipient.
     *
     * Sets the recipient for permission-scoped records. The recipient may
     * have special read/write permissions as defined by the protocol's
     * `$actions` rules.
     */
    recipient?: string;
    /**
     * The protocol role under which to create this record.
     *
     * The DWN will verify that the author is authorized to write under
     * this role per the protocol's `$actions` configuration.
     */
    protocolRole?: string;
    /**
     * The MIME type / data format for the record.
     *
     * If omitted, defaults to the first entry in the protocol type's
     * `dataFormats` array (typically `'application/json'`).
     */
    dataFormat?: DataFormatForPath<D, Path>;
    /**
     * Key-value metadata tags to attach to the record.
     *
     * Tags are indexed by the DWN and can be used in query filters for
     * efficient lookups. Values can be strings, numbers, booleans, or
     * arrays of strings/numbers.
     */
    tags?: globalThis.Record<string, string | number | boolean | string[] | number[]>;
    /**
     * Whether to persist the record to the local DWN immediately.
     *
     * Defaults to `true`. Set to `false` to create the record in memory
     * only — you can call {@link TypedRecord.store | record.store()} later.
     */
    store?: boolean;
    /**
     * Whether to auto-encrypt the record.
     *
     * If omitted, encryption follows the protocol definition. Set to `true`
     * to force encryption or `false` to skip it.
     */
    encryption?: boolean;
};
/**
 * Response from {@link TypedEnbox} `records.create()`.
 *
 * Uses a discriminated union so that TypeScript narrows `record` to
 * `TypedRecord<T>` after a `status.code` check:
 *
 * ```ts
 * const result = await proto.records.create('notebook', { data });
 * if (result.record) {
 *   // TypeScript knows `record` is TypedRecord<NotebookData> here
 *   console.log(result.record.id);
 * }
 * ```
 *
 * @typeParam T - The data type of the created record.
 */
export type TypedCreateResponse<T = unknown> = (DwnResponseStatus & {
    record: TypedRecord<T>;
}) | (DwnResponseStatus & {
    record: undefined;
});
/**
 * Filter options for {@link TypedEnbox} `records.query()` and `records.subscribe()`.
 *
 * The `protocol`, `protocolPath`, and `schema` fields are automatically
 * injected by {@link TypedEnbox} — you only need to supply additional
 * filter criteria.
 *
 * Common filter fields inherited from `RecordsFilter`:
 *
 * - **`parentId`** — Filter by parent context ID. Despite the name, this
 *   filters on the parent record's **context ID** (i.e. pass
 *   `parent.contextId`, not `parent.id`). Use this to find child records
 *   under a specific parent in a hierarchical protocol.
 * - **`recordId`** — Match a specific record by its unique ID.
 * - **`recipient`** — Filter by recipient DID.
 * - **`dataFormat`** — Filter by MIME type.
 * - **`dateCreated`** — Range filter on creation date.
 * - **`contextId`** — Filter by context ID directly.
 *
 * @example
 * ```ts
 * // Find pages under a specific notebook
 * const { records } = await proto.records.query('notebook/page', {
 *   filter: {
 *     parentId: notebook.contextId,  // filters by parent's context ID
 *     tags: { status: 'published' },
 *   },
 * });
 * ```
 */
export type TypedQueryFilter = Omit<RecordsFilter, 'protocol' | 'protocolPath' | 'schema'> & {
    /**
     * Filter records by tag values.
     *
     * Only records whose tags match **all** specified key-value pairs are
     * returned. Array values match if the record's tag contains any of the
     * specified values.
     */
    tags?: globalThis.Record<string, string | number | boolean | (string | number)[]>;
    /** Alias for `parentId` — filters records by parent context ID. */
    parentContextId?: string;
};
/**
 * Options for {@link TypedEnbox} `records.query()`.
 *
 * All fields are optional — calling `query(path)` with no request object
 * returns all records at that path.
 *
 * @example
 * ```ts
 * const { records, cursor } = await proto.records.query('notebook', {
 *   filter: { tags: { archived: false } },
 *   dateSort: DateSort.CreatedDescending,
 *   pagination: { limit: 25 },
 * });
 *
 * // Paginate for more
 * if (cursor) {
 *   const { records: next } = await proto.records.query('notebook', {
 *     pagination: { limit: 25, cursor },
 *   });
 * }
 * ```
 */
export type TypedQueryRequest = {
    /**
     * A remote DWN DID to query from.
     *
     * When set, the query is sent to the specified DID's remote DWN instead
     * of the local DWN.
     */
    from?: string;
    /**
     * Filter criteria for the query.
     *
     * The `protocol`, `protocolPath`, and `schema` fields are auto-injected.
     * See {@link TypedQueryFilter} for available filter fields.
     */
    filter?: TypedQueryFilter;
    /**
     * Sort order for the returned records.
     *
     * Use `DateSort.CreatedAscending`, `DateSort.CreatedDescending`,
     * `DateSort.PublishedAscending`, or `DateSort.PublishedDescending`.
     */
    dateSort?: DateSort;
    /**
     * Pagination options.
     *
     * - `limit` — Maximum number of records to return.
     * - `cursor` — A pagination cursor from a previous query response to
     *   resume from where the last page left off.
     */
    pagination?: {
        limit?: number;
        cursor?: DwnPaginationCursor;
    };
    /**
     * The protocol role under which to execute the query.
     *
     * Required when the protocol's `$actions` rules restrict read access
     * to specific roles.
     */
    protocolRole?: string;
    /**
     * When `true`, automatically decrypts encrypted records in the results.
     *
     * If omitted, encrypted records are returned as-is (data accessors will
     * return encrypted bytes).
     */
    encryption?: boolean;
};
/**
 * Response from {@link TypedEnbox} `records.query()`.
 *
 * @typeParam T - The data type of the queried records.
 */
export type TypedQueryResponse<T = unknown> = DwnResponseStatus & {
    /**
     * The matching records, each wrapped as {@link TypedRecord | TypedRecord<T>}.
     *
     * The array is empty if no records match the filter criteria.
     */
    records: TypedRecord<T>[];
    /**
     * A pagination cursor for fetching the next page of results.
     *
     * Pass this to a subsequent `query()` call's `pagination.cursor` to
     * continue from where this page ended. `undefined` when there are no
     * more results.
     */
    cursor?: DwnPaginationCursor;
};
/**
 * Options for {@link TypedEnbox} `records.read()`.
 *
 * A `filter` is required to identify which record to read. The most common
 * approach is to filter by `recordId`.
 *
 * @example
 * ```ts
 * // Read a specific record by ID
 * const { record } = await proto.records.read('notebook', {
 *   filter: { recordId: notebookId },
 * });
 *
 * // Read from a remote DWN
 * const { record: remote } = await proto.records.read('notebook', {
 *   from: 'did:example:alice',
 *   filter: { recordId: notebookId },
 * });
 * ```
 */
export type TypedReadRequest = {
    /**
     * A remote DWN DID to read from.
     *
     * When set, the read is performed against the specified DID's remote
     * DWN instead of the local DWN.
     */
    from?: string;
    /**
     * Filter to identify the record to read.
     *
     * The `protocol`, `protocolPath`, and `schema` fields are auto-injected.
     * Typically you filter by `recordId` to read a specific record. Other
     * fields from `RecordsFilter` (like `contextId`, `parentId`, `recipient`)
     * are also available.
     */
    filter: Omit<RecordsFilter, 'protocol' | 'protocolPath' | 'schema'> & {
        /** Alias for `parentId` — filters records by parent context ID. */
        parentContextId?: string;
    };
    /**
     * When `true`, automatically decrypts the record if it is encrypted.
     *
     * If omitted, encrypted records are returned as-is.
     */
    encryption?: boolean;
};
/**
 * Response from {@link TypedEnbox} `records.read()`.
 *
 * Uses a discriminated union so that TypeScript narrows `record` to
 * `TypedRecord<T>` after a truthiness check:
 *
 * ```ts
 * const result = await proto.records.read('notebook', { filter: { recordId } });
 * if (result.record) {
 *   const data = await result.record.data.json(); // NotebookData
 * }
 * ```
 *
 * @typeParam T - The data type of the read record.
 */
export type TypedReadResponse<T = unknown> = (DwnResponseStatus & {
    record: TypedRecord<T>;
}) | (DwnResponseStatus & {
    record: undefined;
});
/**
 * Options for {@link TypedEnbox} `records.delete()`.
 *
 * @example
 * ```ts
 * const { status } = await proto.records.delete('notebook', {
 *   recordId: notebook.id,
 * });
 * ```
 */
export type TypedDeleteRequest = {
    /**
     * A remote DWN DID to delete from.
     *
     * When set, the delete is performed on the specified DID's remote DWN.
     */
    from?: string;
    /**
     * The unique `recordId` of the record to delete.
     *
     * Use {@link TypedRecord.id | record.id} to obtain this value.
     */
    recordId: string;
};
/**
 * Options for {@link TypedEnbox} `records.subscribe()`.
 *
 * @example
 * ```ts
 * const { liveQuery } = await proto.records.subscribe('notebook/page', {
 *   filter: { parentId: notebook.contextId },
 * });
 *
 * liveQuery.on('create', (record) => {
 *   console.log('New page:', await record.data.json());
 * });
 * ```
 */
export type TypedSubscribeRequest = {
    /**
     * A remote DWN DID to subscribe to.
     *
     * When set, the subscription listens to the specified DID's remote DWN.
     */
    from?: string;
    /**
     * Filter criteria for the subscription.
     *
     * The `protocol`, `protocolPath`, and `schema` fields are auto-injected.
     * See {@link TypedQueryFilter} for available filter fields.
     */
    filter?: TypedQueryFilter;
    /**
     * The protocol role under which to subscribe.
     *
     * Required when the protocol's `$actions` rules restrict read access
     * to specific roles.
     */
    protocolRole?: string;
};
/**
 * Response from {@link TypedEnbox} `records.subscribe()`.
 *
 * @typeParam T - The data type of records in the subscription.
 */
export type TypedSubscribeResponse<T = unknown> = DwnResponseStatus & {
    /**
     * The typed live query instance for receiving real-time record changes.
     *
     * `undefined` if the subscription request failed (check `status.code`).
     * When defined, use `liveQuery.on('create' | 'update' | 'delete', callback)`
     * to react to changes. Call `liveQuery.close()` to stop the subscription.
     */
    liveQuery?: TypedLiveQuery<T>;
};
/**
 * A protocol-scoped API that auto-injects `protocol`, `protocolPath`, and
 * `schema` into every DWN operation.
 *
 * All record-returning methods wrap results in {@link TypedRecord} so that
 * the data type `T` (resolved from the schema map) flows end-to-end — from
 * write through read, query, update, and subscribe — without manual casts.
 *
 * Obtain an instance via `enbox.using(typedProtocol)`.
 *
 * @example
 * ```ts
 * const social = enbox.using(SocialProtocol);
 *
 * await social.configure();
 *
 * const { record } = await social.records.create('friend', {
 *   data: { did: 'did:example:alice', alias: 'Alice' },
 * });
 * const data = await record.data.json(); // FriendData — no cast
 *
 * const { records } = await social.records.query('friend', {
 *   filter: { tags: { did: 'did:example:alice' } },
 * });
 * for (const r of records) {
 *   const d = await r.data.json(); // FriendData
 * }
 * ```
 */
export declare class TypedEnbox<D extends ProtocolDefinition = ProtocolDefinition, M extends SchemaMap = SchemaMap> {
    /** @internal */
    private readonly _dwn;
    /** @internal */
    private readonly _definition;
    /** @internal */
    private _configured;
    /** @internal */
    private _ensureReadyPromise;
    /** @internal */
    private readonly _validPaths;
    /** @internal */
    private _records?;
    /** @internal — cached result of the `hasEncryptedTypes` scan (definition is immutable). */
    private readonly _hasEncryptedTypes;
    /**
     * @internal Create a new `TypedEnbox` instance. Use `enbox.using(protocol)` instead.
     * @param dwn - The underlying DWN API instance.
     * @param protocol - The typed protocol containing the definition and schema map.
     */
    constructor(dwn: DwnApi, protocol: TypedProtocol<D, M>);
    /**
     * The protocol URI string (e.g. `'https://example.com/social'`).
     *
     * This is the globally unique identifier for the protocol and is
     * auto-injected into every record operation.
     */
    get protocol(): string;
    /**
     * The raw protocol definition object.
     *
     * Contains the full `protocol`, `types`, and `structure` that define
     * the protocol's schema and permission rules.
     */
    get definition(): D;
    /**
     * Configures (installs) this protocol on the local DWN.
     *
     * If the protocol is already installed with an identical definition,
     * this is a no-op and returns the existing protocol with status `200`.
     * If the definition has changed (e.g. new types, modified structure),
     * the protocol is re-configured with the updated definition and returns
     * status `202`.
     *
     * **Must be called before any record operations.** Methods like
     * `records.create()`, `records.query()`, etc. will throw if the protocol
     * has not been configured.
     *
     * @param options - Optional configuration overrides.
     * @param options.encryption - Whether to enable auto-encryption for the
     *   protocol. If omitted, follows the protocol definition defaults.
     * @returns The DWN response status and the installed protocol object.
     *
     * @example
     * ```ts
     * const proto = enbox.using(NotebookProtocol);
     *
     * const { status, protocol } = await proto.configure();
     * console.log(status.code); // 202 (first install) or 200 (already installed)
     *
     * // Now you can use records.create(), records.query(), etc.
     * ```
     */
    configure(options?: {
        encryption?: boolean;
    }): Promise<DwnResponseStatus & {
        protocol?: Protocol;
    }>;
    /**
     * Whether the protocol has been configured (installed) on the local DWN.
     *
     * Returns `true` after a successful call to {@link TypedEnbox.configure | configure()}.
     * Record operations will throw if this is `false`.
     */
    get isConfigured(): boolean;
    /**
     * Subscribe to **all** record changes across the entire protocol,
     * regardless of protocolPath.
     *
     * Unlike `records.subscribe(path)` which scopes to a single path
     * (e.g. `'notebook'`), this method catches creates, updates, and
     * deletes at every level of the protocol hierarchy — ideal for a
     * "refresh everything" strategy when any record changes.
     *
     * Returns a {@link LiveQuery} (untyped, since events span multiple
     * paths/types) or `undefined` if the subscription could not be created.
     * Call `liveQuery.close()` to stop listening.
     *
     * @example
     * ```ts
     * const typed = enbox.using(NotebookProtocol);
     * const liveQuery = await typed.subscribe();
     *
     * liveQuery?.on('change', () => {
     *   // A record somewhere in the protocol changed — re-query as needed.
     * });
     * ```
     */
    subscribe(request?: {
        from?: string;
    }): Promise<LiveQuery | undefined>;
    /**
     * Validates that the path is recognized.
     * Throws a descriptive error if the path is not a valid protocol path.
     */
    private _assertValidPath;
    /**
     * Ensures the protocol is configured before performing record operations.
     *
     * On first call, queries for an existing protocol installation:
     * - If found with an identical definition → marks as configured.
     * - If found with a different definition → marks as configured but warns
     *   that the local definition differs from the installed one.
     * - If not found → installs the protocol via `protocols.configure()`.
     *
     * Concurrent calls are deduplicated via a shared Promise so the network
     * call only happens once.
     */
    private _ensureReady;
    /**
     * Performs the one-time auto-configuration check. Called at most once;
     * subsequent calls reuse the same Promise via `_ensureReadyPromise`.
     */
    private _autoConfigureOnce;
    /**
     * For delegates: fetch the owner's protocol definition (with `$encryption`
     * keys) from the remote DWN and install it locally.
     *
     * Returns `true` if the remote definition was successfully installed.
     */
    private _autoConfigureDelegateProtocol;
    /**
     * Protocol-scoped record operations.
     *
     * Every method auto-injects the `protocol`, `protocolPath`, and `schema`
     * from the protocol definition — you never need to specify them manually.
     * Path parameters provide **compile-time autocompletion** via
     * `ProtocolPaths<D>`, and data types are resolved from the schema map.
     *
     * All methods return {@link TypedRecord} or {@link TypedLiveQuery} instances
     * that carry the resolved data type from the schema map, providing
     * end-to-end type safety.
     *
     * Available methods:
     * - {@link TypedEnbox.records.create | create(path, request)} — Create a new record
     * - {@link TypedEnbox.records.query | query(path, request?)} — Query records with filters
     * - {@link TypedEnbox.records.read | read(path, request)} — Read a single record
     * - {@link TypedEnbox.records.delete | delete(path, request)} — Delete a record by ID
     * - {@link TypedEnbox.records.subscribe | subscribe(path, request?)} — Real-time subscription
     */
    get records(): {
        create: <Path extends ProtocolPaths<D> & string>(path: Path, request: TypedCreateRequest<D, M, Path>) => Promise<TypedCreateResponse<DataForPath<D, M, Path>>>;
        query: <Path extends ProtocolPaths<D> & string>(path: Path, request?: TypedQueryRequest) => Promise<TypedQueryResponse<DataForPath<D, M, Path>>>;
        read: <Path extends ProtocolPaths<D> & string>(path: Path, request: TypedReadRequest) => Promise<TypedReadResponse<DataForPath<D, M, Path>>>;
        delete: <Path extends ProtocolPaths<D> & string>(path: Path, request: TypedDeleteRequest) => Promise<DwnResponseStatus>;
        subscribe: <Path extends ProtocolPaths<D> & string>(path: Path, request?: TypedSubscribeRequest) => Promise<TypedSubscribeResponse<DataForPath<D, M, Path>>>;
    };
}
/**
 * Compares two protocol definitions for **logical** equality using
 * deterministic JSON serialization with `$encryption` blocks stripped.
 *
 * When a protocol is installed with `encryption: true`, the agent injects
 * `$encryption` blocks into the `structure` containing derived public keys.
 * These blocks are operational metadata — not part of the developer-authored
 * definition — so they must be ignored during equality checks. Otherwise,
 * the installed definition (with `$encryption`) would always differ from
 * the source definition (without `$encryption`), causing false drift
 * detection and spurious reconfigure warnings.
 *
 * Keys are sorted recursively so that semantically identical definitions
 * with different key ordering are treated as equal.
 */
export declare function definitionsEqual(a: unknown, b: unknown): boolean;
export {};
//# sourceMappingURL=typed-enbox.d.ts.map