/**
 * NOTE: Added reference types here to avoid a `pnpm` bug during build.
 * https://github.com/enboxorg/enbox/pull/507
 */
/// <reference types="@enbox/dwn-sdk-js" />
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { AnonymousDwnApi } from '@enbox/agent';
import { AuthManager } from '@enbox/auth/auth-manager';
import { EnboxRpcClient } from '@enbox/dwn-clients';
import { omitUndefined } from '@enbox/common';
import { DidDht, DidJwk, DidKey, DidResolverCacheMemory, DidWeb, UniversalResolver } from '@enbox/dids';
import { DidApi } from './did-api.js';
import { DwnApi } from './dwn-api.js';
import { DwnReaderApi } from './dwn-reader-api.js';
import { TypedEnbox } from './typed-enbox.js';
import { VcApi } from './vc-api.js';
/**
 * Module-level registry of in-flight {@link Enbox.connect} calls, keyed by
 * the resolved data path (or a sentinel for "default path").
 *
 * `AuthManager.create()` opens LevelDB handles at the agent's `dataPath`;
 * LevelDB enforces an exclusive lock per directory, so two parallel
 * `Enbox.connect()` invocations on the same path would race on the lock
 * and surface a cryptic `LEVEL_LOCKED` error to the caller. The registry
 * detects the race at the API boundary and throws a clear, domain-level
 * error instead. Custom `storage` adapters that don't share a path with
 * the agent's vault can still race below this guard — but for the default
 * path that every dapp uses, this catches the common case.
 */
const DEFAULT_DATA_PATH_KEY = '\x00default\x00';
const inflightConnects = new Map();
/**
 * The main Enbox API interface. It provides protocol-scoped access to
 * Decentralized Web Nodes (DWNs), Decentralized Identifiers (DIDs),
 * and Verifiable Credentials (VCs).
 *
 * For common app flows, use the asynchronous {@link Enbox.connect} helper.
 * For custom auth/session flows, use {@link Enbox.fromSession} with an
 * existing session, or the public constructor with raw `{ agent, connectedDid }`.
 *
 * @example
 * ```ts
 * import { Enbox } from '@enbox/api';
 *
 * const { enbox } = await Enbox.connect({
 *   createIdentity: true,
 *   sync: '15s',
 * });
 *
 * const social = enbox.using(SocialProtocol);
 * ```
 */
export class Enbox {
    constructor({ agent, connectedDid, delegateDid }) {
        /**
         * Cache of {@link TypedEnbox} instances keyed by protocol URI.
         *
         * Ensures that `enbox.using(Protocol)` returns the **same** `TypedEnbox`
         * instance for a given protocol across multiple call sites, avoiding
         * redundant protocol installations and duplicated internal state.
         */
        this._typedInstances = new Map();
        this.agent = agent;
        this.did = new DidApi({ agent, connectedDid });
        this._dwn = new DwnApi({ agent, connectedDid, delegateDid });
        this.vc = new VcApi({ agent, connectedDid });
    }
    /**
     * Returns a {@link TypedEnbox} instance scoped to the given protocol.
     *
     * This 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.
     *
     * Instances are **cached by protocol URI** — calling `using()` multiple
     * times with the same protocol returns the same `TypedEnbox` instance,
     * so auto-configure only runs once and all call sites share state.
     *
     * @param protocol - A typed protocol created via `defineProtocol()`.
     * @returns A `TypedEnbox` instance bound to the given protocol.
     *
     * @example
     * ```ts
     * const social = enbox.using(SocialProtocol);
     *
     * await social.configure();
     *
     * const { record } = await social.records.write('friend', {
     *   data: { did: 'did:example:alice', alias: 'Alice' },
     * });
     *
     * const { records } = await social.records.query('friend');
     * ```
     */
    using(protocol) {
        const uri = protocol.definition.protocol;
        const cached = this._typedInstances.get(uri);
        if (cached) {
            // The map stores a type-erased instance; restore the caller's generics.
            return cached;
        }
        const instance = new TypedEnbox(this._dwn, protocol);
        // Store with erased generics so the map value type stays uniform.
        this._typedInstances.set(uri, instance);
        return instance;
    }
    /**
     * Signs the user out and releases resources held by this Enbox instance.
     *
     * When this instance owns the underlying `AuthManager` (i.e. it was
     * created via `Enbox.connect()` with no caller-supplied `agent`), the
     * disconnect proceeds in three phases:
     *
     *   1. **Stop sync** — `agent.sync.stopSync(timeout)` halts the DWN
     *      sync engine. Always runs, regardless of ownership.
     *   2. **Sign out** — `AuthManager.disconnect()` sends delegate-grant
     *      revocations to the connected DWN endpoints, clears session
     *      restore markers (PREVIOUSLY_CONNECTED, ACTIVE_IDENTITY,
     *      delegate keys, etc.) from the StorageAdapter, and clears the
     *      in-memory delegate decryption key cache. **Without this step
     *      a later `Enbox.connect()` would silently restore the
     *      supposedly signed-out session from the persisted markers.**
     *   3. **Release resources** — `AuthManager.shutdown()` locks the
     *      vault, closes the sync engine, and closes the StorageAdapter.
     *
     * When the instance was created from a caller-owned session
     * ({@link Enbox.fromSession}), a raw agent (the public constructor),
     * or `Enbox.connect({ agent })`, only step 1 (stop sync) runs — the
     * caller's AuthManager / agent keep their lifecycle. The caller is
     * responsible for calling `auth.disconnect()` and `auth.shutdown()`
     * on their own handle when they're done.
     *
     * Idempotent: parallel calls share the same teardown promise. After
     * calling `disconnect()`, the `Enbox` instance should not be reused.
     *
     * @param timeout - Maximum milliseconds to wait for an in-progress sync
     *   cycle to finish before force-stopping. Defaults to `2000`.
     *
     * @example
     * ```ts
     * // High-level flow: a single disconnect() does sign-out + teardown.
     * const { enbox } = await Enbox.connect({ createIdentity: true });
     * // ... user uses the app ...
     * await enbox.disconnect();   // revoke grants, clear markers, close vault
     *
     * // Caller-owned auth: enbox.disconnect() only stops Enbox-side state.
     * const auth = await AuthManager.create({...});
     * const session = await auth.connectVault({ createIdentity: true });
     * const enbox = Enbox.fromSession(session);
     * await enbox.disconnect();   // stops sync + clears typed-enbox cache
     * await auth.disconnect();    // sign-out: caller's responsibility
     * await auth.shutdown();      // close resources: caller's responsibility
     * ```
     *
     * @beta
     */
    disconnect(timeout) {
        return __awaiter(this, void 0, void 0, function* () {
            // Memoize so parallel calls share the same teardown promise. Without
            // this, two concurrent disconnect()s would each invoke
            // agent.sync.stopSync (idempotent, but redundant) and could race on
            // _ownedAuth ownership transfer.
            if (this._disconnecting !== undefined) {
                return this._disconnecting;
            }
            this._disconnecting = this._doDisconnect(timeout);
            return this._disconnecting;
        });
    }
    _doDisconnect(timeout) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.agent.sync.stopSync(timeout);
            // Clear cached TypedEnbox instances so they are not accidentally reused.
            this._typedInstances.clear();
            // If this Enbox owns the AuthManager (created via Enbox.connect), the
            // sign-out + resource-teardown both fall on us:
            //
            //   1. `auth.disconnect()` — sign-out semantics: revoke delegate
            //      grants on the remote DWN, clear session restore markers
            //      (PREVIOUSLY_CONNECTED, ACTIVE_IDENTITY, delegate keys, etc.)
            //      from the StorageAdapter, and clear the in-memory delegate
            //      decryption key cache. MUST run while the agent's resources
            //      are still open (revocations need DWN access). Without this,
            //      a later `Enbox.connect()` would restore the supposedly
            //      signed-out session from the persisted markers.
            //   2. `auth.shutdown()` — resource teardown: lock the vault,
            //      close the sync engine, close the storage adapter. Runs
            //      after disconnect so revocation traffic completes first.
            //
            // Both are idempotent and best-effort; failures are logged but do
            // not propagate. We always attempt shutdown() even if disconnect()
            // throws, so resources still close.
            if (this._ownedAuth !== undefined) {
                const owned = this._ownedAuth;
                this._ownedAuth = undefined;
                try {
                    yield owned.disconnect({ timeout });
                }
                catch (error) {
                    console.warn('[@enbox/api] Enbox.disconnect: AuthManager.disconnect() failed', error);
                }
                try {
                    yield owned.shutdown({ timeout });
                }
                catch (error) {
                    console.warn('[@enbox/api] Enbox.disconnect: AuthManager.shutdown() failed', error);
                }
            }
        });
    }
    /**
     * Creates a lightweight, read-only Enbox instance for querying public DWN data.
     *
     * No identity, vault, password, or signing keys are required. The returned
     * API supports querying and reading published records and protocols from any
     * remote DWN, using **unsigned** (anonymous) DWN messages.
     *
     * @param options - Optional configuration overrides.
     * @returns An {@link EnboxAnonymousApi} with a read-only `dwn` property.
     *
     * @example
     * ```ts
     * const { dwn } = Enbox.anonymous();
     *
     * const { records } = await dwn.records.query({
     *   from: 'did:dht:alice...',
     *   filter: { protocol: 'https://social.example/posts', protocolPath: 'post' },
     * });
     *
     * for (const record of records) {
     *   console.log(record.id, await record.data.text());
     * }
     * ```
     *
     * @beta
     */
    static anonymous(options) {
        var _a;
        const didResolver = new UniversalResolver({
            didResolvers: (_a = options === null || options === void 0 ? void 0 : options.didResolvers) !== null && _a !== void 0 ? _a : [DidDht, DidJwk, DidKey, DidWeb],
            cache: new DidResolverCacheMemory(),
        });
        const rpcClient = new EnboxRpcClient();
        const anonymousDwn = new AnonymousDwnApi({ didResolver, rpcClient });
        return {
            dwn: new DwnReaderApi(anonymousDwn),
        };
    }
    /**
     * Creates an {@link Enbox} instance from a session-shaped object.
     *
     * Accepts `AuthSession`, `AgentSession`, or any compatible custom session
     * with `{ agent, did, delegateDid? }`. This is the right entry point
     * whenever you already hold an active session — including ones produced by
     * a caller-managed `AuthManager`.
     *
     * For raw `{ agent, connectedDid }` access (no session shape), use the
     * public constructor directly: `new Enbox({ agent, connectedDid })`.
     */
    static fromSession(session) {
        return new Enbox({
            agent: session.agent,
            connectedDid: session.did,
            delegateDid: session.delegateDid,
        });
    }
    /**
     * High-level entry point that creates an {@link AuthManager}, runs
     * `auth.connect()`, and returns the resulting `{ auth, session, enbox }`.
     *
     * For callers that already own an agent and DID, use the dedicated
     * synchronous entry points instead:
     * - `new Enbox({ agent, connectedDid })` for raw parameters
     * - {@link Enbox.fromSession} for session-shaped objects
     *
     * Routing happens at runtime inside `AuthManager.connect`: a recovery phrase
     * is treated as an explicit vault-restore action; otherwise, a non-empty
     * `protocols` array or a `connectHandler` selects the handler flow and
     * everything else routes to local. Local-style fields (`password`,
     * `dwnEndpoints`, etc.) are forwarded to both the manager (as defaults) and
     * the per-call payload, so behavior is consistent with restored sessions.
     *
     * If you need exact control of the `AuthManager.connect()` payload,
     * drop down one layer: create the `AuthManager` yourself with
     * `AuthManager.create()`, call `auth.connect(...)` with your exact
     * options, and pass the resulting session to `Enbox.fromSession`.
     *
     * @example
     * ```ts
     * const { enbox, session, auth } = await Enbox.connect({ createIdentity: true });
     * // ...
     * await enbox.disconnect();
     * await auth.shutdown(); // release vault + storage handles
     * ```
     */
    static connect() {
        return __awaiter(this, arguments, void 0, function* (options = {}) {
            var _a, _b;
            // Cross-instance concurrency guard. `AuthManager.create()` opens the
            // agent's LevelDB at `options.dataPath` (or the platform default), and
            // LevelDB enforces an exclusive `LOCK` file per directory — two
            // parallel calls on the same path would otherwise race and surface
            // `LEVEL_LOCKED` as a low-level error. The guard / ownership flag
            // tracks one question only: did we build the underlying agent? If
            // `options.agent` is supplied the caller owns the agent's lifecycle
            // and on-disk handles, so we skip both the guard (no new dataPath
            // race) and ownership cleanup (`disconnect()` must not tear down
            // the caller's agent). A custom `storage` adapter alone is NOT a
            // basis for skipping — Enbox still builds an agent that opens
            // LevelDB handles at `dataPath`, and the storage adapter governs
            // session-persistence keys, not the agent vault.
            //
            // Limitation: the key is the raw `options.dataPath` string, not its
            // resolved on-disk location. Two callers — one with `dataPath`
            // omitted and another passing the explicit platform default
            // ('DATA/AGENT' in browser, '~/.enbox' in CLI) — refer to the same
            // directory but produce different keys, so the guard won't catch
            // that race. Resolving the path here would require pulling in the
            // platform-default logic from `@enbox/agent`, which we deliberately
            // avoid to keep the helper layered above the agent. Pick one
            // convention per app: always omit `dataPath`, or always set it
            // explicitly.
            const shouldGuard = options.agent === undefined;
            const key = (_a = options.dataPath) !== null && _a !== void 0 ? _a : DEFAULT_DATA_PATH_KEY;
            if (shouldGuard && inflightConnects.has(key)) {
                throw new Error(`[@enbox/api] Enbox.connect() is already in progress for dataPath '${(_b = options.dataPath) !== null && _b !== void 0 ? _b : '<default>'}'. Await the in-flight call before starting another, or pass a custom dataPath.`);
            }
            const run = () => __awaiter(this, void 0, void 0, function* () {
                const auth = yield AuthManager.create(Enbox.toAuthManagerOptions(options));
                try {
                    const session = yield auth.connect(Enbox.toAuthConnectOptions(options));
                    const enbox = Enbox.fromSession(session);
                    // Take AuthManager ownership ONLY when we built the agent
                    // ourselves — `auth.shutdown()` will lock that agent's vault
                    // and close its sync engine. If the caller supplied
                    // `options.agent`, they retain agent ownership and must not
                    // have its lifecycle resources torn down by
                    // `enbox.disconnect()`. Callers that want explicit control
                    // (e.g. their own storage adapter without their own agent)
                    // can still grab the `auth` handle from the result and call
                    // `auth.shutdown()` themselves; we always include it in the
                    // returned `EnboxConnectResult`.
                    if (shouldGuard) {
                        enbox._ownedAuth = auth;
                    }
                    return { auth, enbox, session };
                }
                catch (error) {
                    try {
                        yield auth.shutdown();
                    }
                    catch (shutdownError) {
                        // Surface the recovery failure for diagnosis but preserve the
                        // original connect rejection on the rethrow path below.
                        console.warn('[@enbox/api] Enbox.connect: auth.shutdown() failed during error recovery', shutdownError);
                    }
                    throw error;
                }
            });
            if (!shouldGuard) {
                return run();
            }
            const promise = run();
            inflightConnects.set(key, promise);
            try {
                return yield promise;
            }
            finally {
                inflightConnects.delete(key);
            }
        });
    }
    /**
     * Split `EnboxConnectOptions` into the manager-wide defaults that
     * `AuthManager.create()` consumes.
     *
     * Implemented as an **explicit allowlist** for the same reason as
     * `toAuthConnectOptions`: a denylist would silently leak future
     * `HandlerConnectOptions` / `VaultConnectOptions` fields into the
     * manager-default record. The `satisfies Partial<AuthManagerOptions>`
     * annotation makes the compiler verify every key in the allowlist
     * exists on `AuthManagerOptions`, so a misspelled or stale field name
     * is a type error.
     */
    static toAuthManagerOptions(options) {
        // Explicit allowlist — every key must exist on `AuthManagerOptions`
        // (verified by `satisfies` below).
        const allowlisted = {
            agent: options.agent,
            agentVault: options.agentVault,
            localDwnStrategy: options.localDwnStrategy,
            dataPath: options.dataPath,
            storage: options.storage,
            password: options.password,
            passwordProvider: options.passwordProvider,
            sync: options.sync,
            identitySyncProtocols: options.identitySyncProtocols,
            dwnEndpoints: options.dwnEndpoints,
            registration: options.registration,
            connectHandler: options.connectHandler,
        };
        return omitUndefined(allowlisted);
    }
    /**
     * Split `EnboxConnectOptions` into the per-call payload that
     * `AuthManager.connect()` consumes.
     *
     * Implemented as an **explicit allowlist** of the fields declared on
     * {@link HandlerConnectOptions} ∪ {@link VaultConnectOptions}. A
     * denylist (strip manager-only fields, forward the rest) would
     * silently leak any future `AuthManagerOptions` field into
     * `AuthManager.connect()`; the allowlist makes the type boundary
     * explicit and the leak impossible. The trade-off is symmetric:
     * any new connect-options field requires an edit here. That's
     * acceptable — it's the same edit the public `ConnectOptions` type
     * already needs, just on one extra line.
     *
     * The `satisfies Partial<ConnectOptions>` annotation makes the
     * compiler verify every key in the allowlist exists on `ConnectOptions`,
     * so misspelling a field name is a type error rather than a silent
     * drop. Routing between restore, local, and handler flow happens inside
     * `AuthManager.connect`.
     *
     * `protocols: []` is normalized away — an empty array carries no
     * permission intent and would otherwise produce a zero-grant
     * "connected" handler session indistinguishable from a denied connect.
     */
    static toAuthConnectOptions(options) {
        // Explicit allowlist — every key must exist on the
        // `ConnectOptions` union (verified by `satisfies` below).
        const allowlisted = {
            // Shared across handler + local
            password: options.password,
            sync: options.sync,
            identitySyncProtocols: options.identitySyncProtocols,
            dwnEndpoints: options.dwnEndpoints,
            // Handler-only
            protocols: options.protocols,
            connectHandler: options.connectHandler,
            // Local-only
            recoveryPhrase: options.recoveryPhrase,
            createIdentity: options.createIdentity,
            metadata: options.metadata,
        };
        // Normalize `protocols: []` to undefined so omitUndefined strips it.
        const normalized = (Array.isArray(allowlisted.protocols) && allowlisted.protocols.length === 0)
            ? Object.assign(Object.assign({}, allowlisted), { protocols: undefined }) : allowlisted;
        const cleaned = omitUndefined(normalized);
        return Object.keys(cleaned).length === 0 ? undefined : cleaned;
    }
}
//# sourceMappingURL=enbox.js.map