@abraca/dabra 2.0.10 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -548,6 +548,25 @@ declare class AbracadabraClient {
548
548
  searchDocs(query: string, opts?: {
549
549
  limit?: number;
550
550
  }): Promise<DocSearchHit[]>;
551
+ /**
552
+ * Query for documents matching a structural predicate. v1 of the
553
+ * indexed-query layer described in
554
+ * `ARCHITECTURE/20-kickoff-phase3-queries.md`.
555
+ *
556
+ * The wire shape accepts a `where` field so v2 (meta-predicate
557
+ * filtering on top of the `doc_meta_index` projection) can light up
558
+ * without a compatibility break. Sending a non-empty `where` today
559
+ * returns a 400 — callers should leave it absent.
560
+ *
561
+ * Returns docs the requester can read at viewer-or-above, filtered
562
+ * through the cascading permission resolver server-side.
563
+ */
564
+ queryDocs(opts: {
565
+ type?: string;
566
+ parentId?: string;
567
+ labelContains?: string;
568
+ limit?: number;
569
+ }): Promise<DocumentMeta[]>;
551
570
  /**
552
571
  * List the direct children of a document, returning full metadata. Pass
553
572
  * no argument to list the children of the server root — what the
@@ -2254,6 +2273,113 @@ declare class RpcClient extends EventEmitter {
2254
2273
  private onCancel;
2255
2274
  }
2256
2275
  //#endregion
2276
+ //#region packages/provider/src/QueryClient.d.ts
2277
+ /**
2278
+ * V2 query layer — live subscription client.
2279
+ *
2280
+ * Mirrors the server in `crates/abracadabra/src/query_stream.rs`. Frames
2281
+ * travel as `MSG_STATELESS` payloads with the literal `query:v1:` prefix
2282
+ * followed by a JSON envelope.
2283
+ *
2284
+ * Each call to `subscribeQuery(...)` opens a long-lived subscription:
2285
+ * 1. The client sends `req` with a fresh correlation id.
2286
+ * 2. The server replies `ack` with an initial snapshot
2287
+ * (`DocumentMeta[]`).
2288
+ * 3. The server emits `delta` frames as the doc-tree projection
2289
+ * drifts under the predicate.
2290
+ * 4. The client sends `cancel` to tear it down (or relies on the
2291
+ * WebSocket close path to free server state).
2292
+ *
2293
+ * The shape of the `query` field mirrors the REST `POST /docs/query`
2294
+ * body — `type`, `parentId`, `labelContains`, `where`, `limit`.
2295
+ */
2296
+ declare const QUERY_PREFIX = "query:v1:";
2297
+ type QueryKind = "req" | "ack" | "delta" | "cancel" | "err";
2298
+ /** Mirrors `crate::query_stream::QuerySpec`. */
2299
+ interface QuerySpec {
2300
+ /** Match `documents.kind` exactly. */
2301
+ type?: string;
2302
+ /** Narrow to children of this doc. */
2303
+ parentId?: string;
2304
+ /** Case-insensitive substring on `documents.label`. */
2305
+ labelContains?: string;
2306
+ /** Maximum rows returned (default 50, hard cap 500 server-side). */
2307
+ limit?: number;
2308
+ /** Predicate AST. See `@abraca/schema/src/query.ts` for the shape. */
2309
+ where?: unknown;
2310
+ }
2311
+ interface DocumentMetaWire {
2312
+ id: string;
2313
+ parent_id?: string | null;
2314
+ label?: string | null;
2315
+ kind?: string | null;
2316
+ doc_type?: string | null;
2317
+ public_access?: string | null;
2318
+ description?: string | null;
2319
+ owner_id?: string | null;
2320
+ }
2321
+ interface QueryFrame {
2322
+ kind: QueryKind;
2323
+ id: string;
2324
+ query?: QuerySpec;
2325
+ initial?: DocumentMetaWire[];
2326
+ added?: DocumentMetaWire[];
2327
+ updated?: DocumentMetaWire[];
2328
+ removed?: string[];
2329
+ error?: {
2330
+ code: string;
2331
+ message: string;
2332
+ };
2333
+ }
2334
+ declare class QueryError extends Error {
2335
+ code: string;
2336
+ constructor(code: string, message: string);
2337
+ }
2338
+ interface QuerySubscriptionHandlers {
2339
+ /** Fired once with the initial snapshot from the server's `ack`. */
2340
+ onSnapshot?: (rows: DocumentMetaWire[]) => void;
2341
+ /**
2342
+ * Fired on every `delta` after the initial ack. `added` are rows
2343
+ * newly matching the predicate; `removed` are doc_ids that no
2344
+ * longer match (or are no longer readable). v2 does not emit
2345
+ * `updated` — clients see updates as remove + add today; that
2346
+ * gap closes in v3 when row-level updates are tracked.
2347
+ */
2348
+ onDelta?: (delta: {
2349
+ added: DocumentMetaWire[];
2350
+ removed: string[];
2351
+ }) => void;
2352
+ /** Fired on `err` frames from the server. */
2353
+ onError?: (err: QueryError) => void;
2354
+ }
2355
+ interface QuerySubscriptionHandle {
2356
+ /** Correlation id assigned when the request was sent. */
2357
+ readonly id: string;
2358
+ /** Send a `cancel` to the server and stop dispatching to handlers. */
2359
+ cancel(): void;
2360
+ }
2361
+ /**
2362
+ * Minimal transport surface — matches `RpcTransport` so the provider
2363
+ * can satisfy both without extra glue.
2364
+ */
2365
+ interface QueryTransport {
2366
+ sendStateless(payload: string): void;
2367
+ on(event: string, fn: Function): unknown;
2368
+ off(event: string, fn?: Function): unknown;
2369
+ }
2370
+ declare class QueryClient extends EventEmitter {
2371
+ private readonly transport;
2372
+ private readonly subs;
2373
+ private readonly onStatelessBound;
2374
+ private idCounter;
2375
+ constructor(transport: QueryTransport);
2376
+ destroy(): void;
2377
+ subscribeQuery(spec: QuerySpec, handlers?: QuerySubscriptionHandlers): QuerySubscriptionHandle;
2378
+ private nextId;
2379
+ private send;
2380
+ private receive;
2381
+ }
2382
+ //#endregion
2257
2383
  //#region packages/provider/src/AbracadabraBaseProvider.d.ts
2258
2384
  type AbracadabraBaseProviderConfiguration = Required<Pick<CompleteAbracadabraBaseProviderConfiguration, "name">> & Partial<CompleteAbracadabraBaseProviderConfiguration> & ((Required<Pick<CompleteAbracadabraWSConfiguration, "url">> & Partial<Pick<CompleteAbracadabraWSConfiguration, "preserveTrailingSlash">>) | Required<Pick<CompleteAbracadabraBaseProviderConfiguration, "websocketProvider">>);
2259
2385
  /** @deprecated Use AbracadabraBaseProviderConfiguration */
@@ -2340,6 +2466,20 @@ declare class AbracadabraBaseProvider extends EventEmitter {
2340
2466
  */
2341
2467
  private _rpc;
2342
2468
  get rpc(): RpcClient;
2469
+ /**
2470
+ * Lazily-constructed V2 query subscription client. See
2471
+ * `QueryClient`. Each call to `subscribeQuery(...)` opens a
2472
+ * long-lived subscription that fires `onSnapshot` with the initial
2473
+ * result set and `onDelta` whenever the projection moves.
2474
+ */
2475
+ private _queryClient;
2476
+ private getQueryClient;
2477
+ /**
2478
+ * Open a live query subscription. Mirrors the v1 REST shape
2479
+ * (`POST /docs/query`) but pushes deltas as the server-side
2480
+ * projection drifts under the predicate.
2481
+ */
2482
+ subscribeQuery(spec: QuerySpec, handlers?: QuerySubscriptionHandlers): QuerySubscriptionHandle;
2343
2483
  constructor(configuration: AbracadabraBaseProviderConfiguration);
2344
2484
  boundDocumentUpdateHandler: (update: Uint8Array, origin: any) => void;
2345
2485
  boundAwarenessUpdateHandler: ({
@@ -2404,6 +2544,127 @@ declare const HocuspocusProvider: typeof AbracadabraBaseProvider;
2404
2544
  /** @deprecated Use AbracadabraBaseProvider */
2405
2545
  type HocuspocusProvider = AbracadabraBaseProvider;
2406
2546
  //#endregion
2547
+ //#region packages/provider/src/TokenManager.d.ts
2548
+ /**
2549
+ * On-disk shape of a device session. The server returns these values from
2550
+ * `POST /auth/device-session`; persistence keeps them across reloads so a
2551
+ * warm boot can mint a fresh JWT without re-prompting for the passkey.
2552
+ */
2553
+ interface DeviceSessionRecord {
2554
+ sessionId: string;
2555
+ sessionToken: string;
2556
+ /** Unix seconds — matches what the server returns. */
2557
+ expiresAt: number;
2558
+ }
2559
+ interface DeviceSessionStorage {
2560
+ load(): DeviceSessionRecord | null;
2561
+ save(record: DeviceSessionRecord): void;
2562
+ clear(): void;
2563
+ }
2564
+ /** localStorage-backed storage; safe in SSR/Node (no-ops without localStorage). */
2565
+ declare class LocalStorageDeviceSessionStorage implements DeviceSessionStorage {
2566
+ private readonly key;
2567
+ constructor(key?: string);
2568
+ load(): DeviceSessionRecord | null;
2569
+ save(record: DeviceSessionRecord): void;
2570
+ clear(): void;
2571
+ }
2572
+ interface TokenManagerOptions {
2573
+ client: AbracadabraClient;
2574
+ /**
2575
+ * Where the device session record lives. Defaults to a localStorage
2576
+ * adapter under "abracadabra:device-session".
2577
+ */
2578
+ storage?: DeviceSessionStorage;
2579
+ /**
2580
+ * Refresh the JWT this many milliseconds before its `exp`. Defaults to
2581
+ * 5 minutes — well clear of clock skew without burning a refresh per
2582
+ * minute. Refreshes are also dedup'd via in-flight promise.
2583
+ */
2584
+ refreshLeadMs?: number;
2585
+ /**
2586
+ * Hook `document.visibilitychange` and `window.online` to opportunistically
2587
+ * refresh when the tab/network resumes. Default true. The proactive
2588
+ * `setTimeout` doesn't fire reliably while the tab is hidden or the
2589
+ * laptop is asleep, which is the actual breakage path being addressed —
2590
+ * the resume hook is what makes the system self-heal after a long sleep.
2591
+ */
2592
+ installResumeHandlers?: boolean;
2593
+ }
2594
+ /**
2595
+ * Manages the lifetime of the short-lived JWT against a long-lived device
2596
+ * session token.
2597
+ *
2598
+ * Flow:
2599
+ * - Bootstrap: if a stored device session exists, exchange it for a fresh
2600
+ * JWT immediately (`bootstrap()`). Otherwise the consumer must run a
2601
+ * full crypto-auth handshake and then call `attachSession(record)` once
2602
+ * the server returns a device-session token.
2603
+ * - Steady state: `getValidToken()` returns the JWT, refreshing if the
2604
+ * JWT is within `refreshLeadMs` of expiry. Concurrent callers share one
2605
+ * in-flight refresh.
2606
+ * - Background: a proactive timer fires `refreshLeadMs` before `exp`.
2607
+ * `visibilitychange` and `online` events trigger an opportunistic
2608
+ * refresh in case the timer was suppressed (hidden tab, sleeping
2609
+ * laptop). The combination is what restores the WS without a reload.
2610
+ * - Failure: refresh errors with status 401/403 mean the device session
2611
+ * itself is dead (revoked or 30 d expired). The record is cleared and
2612
+ * `session-expired` fires so the consumer can prompt re-auth. Other
2613
+ * errors (network) are transparent — the existing JWT is returned and
2614
+ * the caller's normal retry loop covers it.
2615
+ */
2616
+ declare class TokenManager extends EventEmitter {
2617
+ private readonly client;
2618
+ private readonly storage;
2619
+ private readonly refreshLeadMs;
2620
+ private session;
2621
+ private refreshing;
2622
+ private proactiveTimer;
2623
+ private resumeHandlersInstalled;
2624
+ private boundOnResume;
2625
+ private disposed;
2626
+ constructor(options: TokenManagerOptions);
2627
+ get hasSession(): boolean;
2628
+ get currentSession(): DeviceSessionRecord | null;
2629
+ /**
2630
+ * Persist a freshly-issued device session and exchange it immediately
2631
+ * for a JWT. Call this once after a successful crypto-auth login when
2632
+ * the server returns a device-session token.
2633
+ */
2634
+ attachSession(record: DeviceSessionRecord): Promise<string>;
2635
+ /**
2636
+ * Drop the local device session (no server call). Use after a logout
2637
+ * or when `session-expired` fires. To revoke server-side as well, call
2638
+ * `client.revokeDeviceSession(sessionId)` first.
2639
+ */
2640
+ clearSession(): void;
2641
+ /**
2642
+ * Return a valid JWT, refreshing if it is missing or within
2643
+ * `refreshLeadMs` of expiry. Safe to call concurrently — one
2644
+ * refresh is in flight at a time.
2645
+ *
2646
+ * Transient refresh errors (network) fall back to the cached JWT so
2647
+ * the WS auth attempt can still proceed; the server will reject and
2648
+ * the next reconnect tries again. Only 401/403 from the refresh
2649
+ * endpoint surfaces as a thrown error here, since those mean the
2650
+ * device session itself is dead.
2651
+ */
2652
+ getValidToken(): Promise<string>;
2653
+ /**
2654
+ * If a stored device session exists, exchange it for a fresh JWT now.
2655
+ * Returns the JWT (or null if there is no session to bootstrap from).
2656
+ * Throws on 401/403 — the stored session is invalid and the caller
2657
+ * should fall through to a full crypto-auth flow.
2658
+ */
2659
+ bootstrap(): Promise<string | null>;
2660
+ dispose(): void;
2661
+ private runRefresh;
2662
+ private tokenIsFresh;
2663
+ private parseExp;
2664
+ private scheduleProactiveRefresh;
2665
+ private installResumeHandlers;
2666
+ }
2667
+ //#endregion
2407
2668
  //#region packages/provider/src/auth.d.ts
2408
2669
  declare enum AuthMessageType {
2409
2670
  Token = 0,
@@ -2758,6 +3019,8 @@ declare class BackgroundSyncManager extends EventEmitter {
2758
3019
  private readonly persistence;
2759
3020
  private readonly semaphore;
2760
3021
  private readonly syncStates;
3022
+ /** Doc ids we've already warned about; keeps the log to one line per id. */
3023
+ private readonly _warnedInvalidIds;
2761
3024
  private _destroyed;
2762
3025
  private _initPromise;
2763
3026
  constructor(rootProvider: AbracadabraProvider, client: AbracadabraClient, fileBlobStore?: FileBlobStore | null, opts?: BackgroundSyncManagerOptions);
@@ -4295,6 +4558,98 @@ declare const PAGE_TYPES: Record<string, PageTypeInfo>;
4295
4558
  declare const TYPE_ALIASES: Record<string, string>;
4296
4559
  declare function resolvePageType(key: string | undefined): PageTypeInfo | undefined;
4297
4560
  //#endregion
4561
+ //#region packages/provider/src/SchemaTypes.d.ts
4562
+ /**
4563
+ * Structural shape of a typed schema registry. The `TMap` parameter is the
4564
+ * meta-type map keyed by doc-type name (e.g. `{ "kanban": KanbanMeta, ... }`).
4565
+ * `@abraca/schema`'s `SchemaRegistry<TMap>` satisfies this shape.
4566
+ */
4567
+ interface SchemaRegistryLike<TMap extends Record<string, unknown> = Record<string, unknown>> {
4568
+ readonly types: ReadonlyMap<string, unknown>;
4569
+ readonly __metaMap?: TMap;
4570
+ }
4571
+ /** Extract the doc-type names from a registry. */
4572
+ type SchemaDocTypeName<S> = S extends SchemaRegistryLike<infer M> ? keyof M & string : never;
4573
+ /** Extract the meta type for a given doc-type name from a registry. */
4574
+ type SchemaMetaOf<S, N extends string> = S extends SchemaRegistryLike<infer M> ? N extends keyof M ? M[N] : never : never;
4575
+ /**
4576
+ * A tree entry projected against a typed registry. The `meta` field is
4577
+ * narrowed to `TMap[N] | undefined` so consumers get full type inference
4578
+ * without an explicit cast.
4579
+ *
4580
+ * Note: `meta` is optional even when the registry mandates fields — the
4581
+ * tree may contain entries written before the schema was authored, and
4582
+ * Rule 4 means we don't reject them at read time.
4583
+ */
4584
+ interface TypedTreeEntry<TMap extends Record<string, unknown>, N extends keyof TMap & string> {
4585
+ readonly id: string;
4586
+ readonly type: N;
4587
+ readonly label: string;
4588
+ readonly parentId: string | null;
4589
+ readonly order: number;
4590
+ readonly meta: TMap[N] | undefined;
4591
+ readonly createdAt: number | undefined;
4592
+ readonly updatedAt: number | undefined;
4593
+ }
4594
+ /**
4595
+ * A typed surface bound to a schema. Returned by
4596
+ * `DocumentManager.docs(schema)` so the schema doesn't have to be repeated
4597
+ * at every call site.
4598
+ *
4599
+ * Read methods (`get`, `getEntry`, `narrow`) project the existing
4600
+ * untyped tree without performing schema validation. Write methods
4601
+ * (`update`, `set`, `clear`) delegate to `MetaManager` and are subject
4602
+ * to whatever validator was attached via `MetaManager.setSchema` —
4603
+ * they do NOT auto-attach the registry passed to `dm.docs()`.
4604
+ */
4605
+ interface TypedDocsClient<TMap extends Record<string, unknown>> {
4606
+ /**
4607
+ * Fetch a tree entry projected as the requested doc-type. Returns
4608
+ * `null` when the entry doesn't exist OR when its `type` field doesn't
4609
+ * match `expectedType`.
4610
+ */
4611
+ get<N extends keyof TMap & string>(expectedType: N, id: string): TypedTreeEntry<TMap, N> | null;
4612
+ /**
4613
+ * Fetch the raw, untyped entry (no type-narrowing applied). Useful when
4614
+ * the caller wants the underlying record without the projection.
4615
+ */
4616
+ getEntry(id: string): TreeEntry | null;
4617
+ /**
4618
+ * Discriminator helper: narrows an arbitrary `TreeEntry` to the
4619
+ * requested doc-type, returning a typed projection or `null` on mismatch.
4620
+ * No runtime read — pure type-guard helper.
4621
+ */
4622
+ narrow<N extends keyof TMap & string>(expectedType: N, entry: TreeEntry | null | undefined): TypedTreeEntry<TMap, N> | null;
4623
+ /**
4624
+ * Merge typed meta into a document's metadata. Throws
4625
+ * `TypedDocTypeMismatchError` when the stored entry's `type` doesn't
4626
+ * match `expectedType`; throws `MetaValidationError` when a validator
4627
+ * is attached and the merged meta fails validation.
4628
+ */
4629
+ update<N extends keyof TMap & string>(expectedType: N, id: string, meta: Partial<TMap[N]>): void;
4630
+ /**
4631
+ * Replace all metadata on a document with a fully-typed payload.
4632
+ * Same throw semantics as `update`.
4633
+ */
4634
+ set<N extends keyof TMap & string>(expectedType: N, id: string, meta: TMap[N]): void;
4635
+ /**
4636
+ * Delete specific metadata keys. Constrained to the typed key-set of
4637
+ * `TMap[N]` so typos surface at compile time.
4638
+ */
4639
+ clear<N extends keyof TMap & string>(expectedType: N, id: string, keys: ReadonlyArray<keyof TMap[N] & string>): void;
4640
+ }
4641
+ /**
4642
+ * Thrown by typed write methods on `TypedDocsClient` when the stored
4643
+ * entry's `type` doesn't match the caller's `expectedType`. Distinct from
4644
+ * `MetaValidationError` (which signals schema-content mismatches).
4645
+ */
4646
+ declare class TypedDocTypeMismatchError extends Error {
4647
+ readonly docId: string;
4648
+ readonly expectedType: string;
4649
+ readonly actualType: string | undefined;
4650
+ constructor(docId: string, expectedType: string, actualType: string | undefined);
4651
+ }
4652
+ //#endregion
4298
4653
  //#region packages/provider/src/TreeManager.d.ts
4299
4654
  declare class TreeManager {
4300
4655
  private dm;
@@ -4308,6 +4663,18 @@ declare class TreeManager {
4308
4663
  /** Build nested tree JSON. */
4309
4664
  buildTree(rootId?: string | null, maxDepth?: number): TreeNode[];
4310
4665
  private _buildTree;
4666
+ /**
4667
+ * Schema-typed lookup. Returns a `TypedTreeEntry<TMap, N>` when the
4668
+ * entry's `type` matches `expectedType`, else `null`. Pure projection
4669
+ * over the existing untyped tree — no schema validation is performed
4670
+ * here (the entry's data is whatever was last synced; meta correctness
4671
+ * is the writer's responsibility, optionally enforced via
4672
+ * `MetaManager.setSchema`).
4673
+ *
4674
+ * Rule 4 alignment: when the entry's type doesn't match, returns null
4675
+ * rather than throwing — callers branch on the result.
4676
+ */
4677
+ getTyped<TMap extends Record<string, unknown>, N extends keyof TMap & string>(_schema: SchemaRegistryLike<TMap>, expectedType: N, docId: string): TypedTreeEntry<TMap, N> | null;
4311
4678
  /** Find a single entry by ID. */
4312
4679
  get(docId: string): TreeEntry | null;
4313
4680
  /** Search by label (case-insensitive substring match). */
@@ -4459,25 +4826,75 @@ interface DocumentMetaInfo {
4459
4826
  type?: string;
4460
4827
  meta: PageMeta;
4461
4828
  }
4829
+ /**
4830
+ * Structural shape of a schema validator MetaManager can consult before
4831
+ * writes. Compatible with `@abraca/schema`'s `SchemaRegistry`.
4832
+ *
4833
+ * Implementations MUST treat unknown doc-types as unconstrained
4834
+ * (return `{ ok: true }`) — Rule 4: existing-app traffic for doc-types
4835
+ * not in the validator's bundle is never rejected.
4836
+ */
4837
+ interface SchemaValidatorLike {
4838
+ validateMeta(docType: string, meta: unknown): {
4839
+ ok: true;
4840
+ value?: unknown;
4841
+ } | {
4842
+ ok: false;
4843
+ errors: ReadonlyArray<{
4844
+ path: ReadonlyArray<PropertyKey>;
4845
+ message: string;
4846
+ code?: string;
4847
+ }>;
4848
+ };
4849
+ }
4850
+ declare class MetaValidationError extends Error {
4851
+ readonly docId: string;
4852
+ readonly docType: string;
4853
+ readonly errors: ReadonlyArray<{
4854
+ path: ReadonlyArray<PropertyKey>;
4855
+ message: string;
4856
+ code?: string;
4857
+ }>;
4858
+ constructor(docId: string, docType: string, errors: ReadonlyArray<{
4859
+ path: ReadonlyArray<PropertyKey>;
4860
+ message: string;
4861
+ code?: string;
4862
+ }>);
4863
+ }
4462
4864
  declare class MetaManager {
4463
4865
  private dm;
4866
+ private schema;
4464
4867
  constructor(dm: DocumentManager);
4868
+ /**
4869
+ * Attach (or detach with `null`) a schema validator. When set, every
4870
+ * `update` / `set` validates the post-write meta against the entry's
4871
+ * declared `type` before writing to the doc-tree. Entries without a
4872
+ * `type` field pass through unconditionally.
4873
+ */
4874
+ setSchema(schema: SchemaValidatorLike | null): void;
4465
4875
  /** Read metadata for a document. Returns null if not found. */
4466
4876
  get(docId: string): DocumentMetaInfo | null;
4467
4877
  /**
4468
4878
  * Merge fields into a document's metadata.
4469
4879
  * Existing keys not in the update are preserved.
4880
+ *
4881
+ * @throws {MetaValidationError} when a schema is attached and the
4882
+ * merged meta fails validation for the entry's declared type.
4470
4883
  */
4471
4884
  update(docId: string, meta: Partial<PageMeta>): void;
4472
4885
  /**
4473
4886
  * Replace all metadata on a document.
4474
4887
  * This overwrites the entire meta object.
4888
+ *
4889
+ * @throws {MetaValidationError} when a schema is attached and the
4890
+ * replacement meta fails validation for the entry's declared type.
4475
4891
  */
4476
4892
  set(docId: string, meta: PageMeta): void;
4477
4893
  /**
4478
4894
  * Clear specific metadata keys (set them to null/undefined).
4479
4895
  */
4480
4896
  clear(docId: string, keys: string[]): void;
4897
+ private validateOrThrow;
4481
4898
  }
4482
4899
  //#endregion
4483
4900
  //#region packages/provider/src/DocumentManager.d.ts
@@ -4533,6 +4950,20 @@ declare class DocumentManager {
4533
4950
  private _rootProvider;
4534
4951
  private childCache;
4535
4952
  constructor(config: DocumentManagerConfig);
4953
+ /**
4954
+ * Bind a schema registry to a typed accessor surface. The returned
4955
+ * client offers `.get(type, id)` with the registry's meta types
4956
+ * inferred at the call site, removing the need to pass `schema`
4957
+ * to every lookup. The provider remains schema-free at runtime —
4958
+ * `schema` is only used to drive type inference (see
4959
+ * `SchemaRegistryLike` for the structural witness).
4960
+ *
4961
+ * @example
4962
+ * const docs = dm.docs(kanbanSchema);
4963
+ * const board = docs.get("kanban", id);
4964
+ * if (board) console.log(board.meta.kanbanColumnWidth); // typed
4965
+ */
4966
+ docs<TMap extends Record<string, unknown>>(_schema: SchemaRegistryLike<TMap>): TypedDocsClient<TMap>;
4536
4967
  get displayName(): string;
4537
4968
  get displayColor(): string;
4538
4969
  get serverInfo(): ServerInfo | null;
@@ -4600,4 +5031,4 @@ declare function normalizeRootId(id: string | null | undefined, rootDocId: strin
4600
5031
  */
4601
5032
  declare function toPlain(val: unknown): unknown;
4602
5033
  //#endregion
4603
- export { AbracadabraBaseProvider, AbracadabraBaseProviderConfiguration, AbracadabraClient, AbracadabraClientConfig, AbracadabraOutgoingMessageArguments, AbracadabraProvider, AbracadabraProviderConfiguration, AbracadabraWS, AbracadabraWSConfiguration, AbracadabraWebRTC, type AbracadabraWebRTCConfiguration, AbracadabraWebSocketConn, AdminConfigField, AdminConfigOriginKind, AuditLogEntry, AuditQueryOpts, AuditVerifyResult, AuthFailureContext, AuthFailureReason, AuthMessageType, AuthorizedScope, AwarenessError, BackgroundSyncManager, type BackgroundSyncManagerOptions, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, ChannelKeyResolver, type ChatChannel, ChatClient, type ChatClientTransport, type MarkReadInput as ChatMarkReadInput, type ChatMessage, type ChatReadCursor, type ChatReadReceipt, type ChatTypingEvent, CloseEvent, CompleteAbracadabraBaseProviderConfiguration, CompleteAbracadabraWSConfiguration, CompleteHocuspocusProviderConfiguration, CompleteHocuspocusProviderWebsocketConfiguration, ConnectionTimeout, Constructable, ConstructableOutgoingMessage, ContentManager, type CreateNotificationInput, CryptoIdentity, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, type DeleteMessageInput, DevicePairingChannel, type DevicePairingConfig, DeviceRegistrationService, type DeviceServerStatus, type DeviceTier, type DocEncryptionInfo, DocKeyManager, DocSearchHit, type DocSyncState, type DocumentBlock, DocumentCache, type DocumentCacheOptions, type DocumentContent, DocumentManager, type DocumentManagerConfig, DocumentMeta, type DocumentMetaInfo, E2EAbracadabraProvider, E2EEChannel, type E2EEIdentity, E2EOfflineStore, type EditMessageInput, EffectivePermissionEntry, EffectivePermissionsResponse, EffectiveRole, EncryptedChatClient, EncryptedYMap, EncryptedYText, EnvSnapshotExtension, EnvSnapshotItem, EnvSnapshotResponse, type FetchInboxInput, type FetchNotificationsInput, FileBlobStore, FileTransferChannel, FileTransferHandle, type FileTransferMeta, type FileTransferStatus, type FoldedMessage, Forbidden, GEO_TYPE_META_SCHEMAS, type GetChatHistoryInput, HealthStatus, HocusPocusWebSocket, HocuspocusProvider, HocuspocusProviderConfiguration, HocuspocusProviderWebsocket, HocuspocusProviderWebsocketConfiguration, HocuspocusWebSocket, type IdentityDeviceEntry, type IdentityDocConfiguration, IdentityDocProvider, type IdentityProfile, type IdentityServerEntry, type IdentitySpaceEntry, type InboxEntry, type MarkReadInput$1 as InboxMarkReadInput, InviteRow, KEY_EXCHANGE_CHANNEL, Kind, ManualSignaling, type ManualSignalingBlob, type MessageRecord, MessageTooBig, MessageType, MetaFieldType, MetaManager, type NotificationReadUpdate, type NotificationRecord, NotificationsClient, OfflineStore, OutgoingMessageArguments, OutgoingMessageInterface, PAGE_TYPES, PageMeta, PageTypeInfo, PageTypeMetaField, type PairingRequest, type PairingResult, PeerConnection, type PeerInfo, type PeerState, PendingSubdoc, PermissionEntry, PublicKeyInfo, RPC_PREFIX, ReadyzStatus, ResetConnection, type RpcCallHandle, type RpcCallOptions, RpcClient, RpcError, type RpcErrorCode, type RpcErrorPayload, type RpcFrame, type RpcHandler, type RpcHandlerContext, type RpcKind, type RpcTarget, type RpcTransport, SERVER_ROOT_ID, SearchIndex, SearchResult, type SendChatMessageInput, type SendMessageInput, type SendMessageResult, ServerInfo, type SignalingIncoming, type SignalingOutgoing, SignalingSocket, SnapshotCreateResult, SnapshotData, SnapshotFileEntry, SnapshotForkResult, SnapshotMeta, SnapshotRestoreResult, StatesArray, SubdocMessage, SubdocRegisteredEvent, TYPE_ALIASES, TreeEntry, TreeManager, TreeNode, TreeSearchResult, Unauthorized, UploadInfo, UploadMeta, UploadQueueEntry, UploadQueueStatus, UserMetaField, UserProfile, WebSocketStatus, WsReadyStates, YjsDataChannel, attachUpdatedAtObserver, awarenessStatesToArray, wordlist as bip39Wordlist, buildBlockquoteElement, buildBlocksFromMarkdown, buildBulletListElement, buildCodeBlockElement, buildHeadingElement, buildHorizontalRuleElement, buildOrderedListElement, buildParagraphElement, buildTaskListElement, decryptChatContent, decryptField, deriveIdentityDocId, deriveSeedWrappingKey, encryptChatContent, encryptField, filenameToLabel, foldRecords, generateMnemonic, isEncryptedContent, makeEncryptedYMap, makeEncryptedYText, mnemonicToEd25519Seed, mnemonicToKeyPair, normalizeRootId, onAuthenticatedParameters, onAuthenticationFailedParameters, onAwarenessChangeParameters, onAwarenessUpdateParameters, onCloseParameters, onCompactedParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onServerErrorParameters, onStatelessParameters, onStatusParameters, onSubdocLoadedParameters, onSubdocRegisteredParameters, onSyncedParameters, onUnsyncedChangesParameters, parseFrontmatter, populateYDocFromMarkdown, readAuthMessage, readBlocksFromFragment, recordFromYAny, resolvePageType, toPlain, unwrapSeed, validateMnemonic, waitForSync, withTimeout, wrapSeed, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest, yjsToMarkdown };
5034
+ export { AbracadabraBaseProvider, AbracadabraBaseProviderConfiguration, AbracadabraClient, AbracadabraClientConfig, AbracadabraOutgoingMessageArguments, AbracadabraProvider, AbracadabraProviderConfiguration, AbracadabraWS, AbracadabraWSConfiguration, AbracadabraWebRTC, type AbracadabraWebRTCConfiguration, AbracadabraWebSocketConn, AdminConfigField, AdminConfigOriginKind, AuditLogEntry, AuditQueryOpts, AuditVerifyResult, AuthFailureContext, AuthFailureReason, AuthMessageType, AuthorizedScope, AwarenessError, BackgroundSyncManager, type BackgroundSyncManagerOptions, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, ChannelKeyResolver, type ChatChannel, ChatClient, type ChatClientTransport, type MarkReadInput as ChatMarkReadInput, type ChatMessage, type ChatReadCursor, type ChatReadReceipt, type ChatTypingEvent, CloseEvent, CompleteAbracadabraBaseProviderConfiguration, CompleteAbracadabraWSConfiguration, CompleteHocuspocusProviderConfiguration, CompleteHocuspocusProviderWebsocketConfiguration, ConnectionTimeout, Constructable, ConstructableOutgoingMessage, ContentManager, type CreateNotificationInput, CryptoIdentity, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, type DeleteMessageInput, DevicePairingChannel, type DevicePairingConfig, DeviceRegistrationService, type DeviceServerStatus, type DeviceSessionRecord, type DeviceSessionStorage, type DeviceTier, type DocEncryptionInfo, DocKeyManager, DocSearchHit, type DocSyncState, type DocumentBlock, DocumentCache, type DocumentCacheOptions, type DocumentContent, DocumentManager, type DocumentManagerConfig, DocumentMeta, type DocumentMetaInfo, type DocumentMetaWire, E2EAbracadabraProvider, E2EEChannel, type E2EEIdentity, E2EOfflineStore, type EditMessageInput, EffectivePermissionEntry, EffectivePermissionsResponse, EffectiveRole, EncryptedChatClient, EncryptedYMap, EncryptedYText, EnvSnapshotExtension, EnvSnapshotItem, EnvSnapshotResponse, type FetchInboxInput, type FetchNotificationsInput, FileBlobStore, FileTransferChannel, FileTransferHandle, type FileTransferMeta, type FileTransferStatus, type FoldedMessage, Forbidden, GEO_TYPE_META_SCHEMAS, type GetChatHistoryInput, HealthStatus, HocusPocusWebSocket, HocuspocusProvider, HocuspocusProviderConfiguration, HocuspocusProviderWebsocket, HocuspocusProviderWebsocketConfiguration, HocuspocusWebSocket, type IdentityDeviceEntry, type IdentityDocConfiguration, IdentityDocProvider, type IdentityProfile, type IdentityServerEntry, type IdentitySpaceEntry, type InboxEntry, type MarkReadInput$1 as InboxMarkReadInput, InviteRow, KEY_EXCHANGE_CHANNEL, Kind, LocalStorageDeviceSessionStorage, ManualSignaling, type ManualSignalingBlob, type MessageRecord, MessageTooBig, MessageType, MetaFieldType, MetaManager, MetaValidationError, type NotificationReadUpdate, type NotificationRecord, NotificationsClient, OfflineStore, OutgoingMessageArguments, OutgoingMessageInterface, PAGE_TYPES, PageMeta, PageTypeInfo, PageTypeMetaField, type PairingRequest, type PairingResult, PeerConnection, type PeerInfo, type PeerState, PendingSubdoc, PermissionEntry, PublicKeyInfo, QUERY_PREFIX, QueryClient, QueryError, type QueryFrame, type QueryKind, type QuerySpec, type QuerySubscriptionHandle, type QuerySubscriptionHandlers, type QueryTransport, RPC_PREFIX, ReadyzStatus, ResetConnection, type RpcCallHandle, type RpcCallOptions, RpcClient, RpcError, type RpcErrorCode, type RpcErrorPayload, type RpcFrame, type RpcHandler, type RpcHandlerContext, type RpcKind, type RpcTarget, type RpcTransport, SERVER_ROOT_ID, type SchemaDocTypeName, type SchemaMetaOf, type SchemaRegistryLike, type SchemaValidatorLike, SearchIndex, SearchResult, type SendChatMessageInput, type SendMessageInput, type SendMessageResult, ServerInfo, type SignalingIncoming, type SignalingOutgoing, SignalingSocket, SnapshotCreateResult, SnapshotData, SnapshotFileEntry, SnapshotForkResult, SnapshotMeta, SnapshotRestoreResult, StatesArray, SubdocMessage, SubdocRegisteredEvent, TYPE_ALIASES, TokenManager, type TokenManagerOptions, TreeEntry, TreeManager, TreeNode, TreeSearchResult, TypedDocTypeMismatchError, type TypedDocsClient, type TypedTreeEntry, Unauthorized, UploadInfo, UploadMeta, UploadQueueEntry, UploadQueueStatus, UserMetaField, UserProfile, WebSocketStatus, WsReadyStates, YjsDataChannel, attachUpdatedAtObserver, awarenessStatesToArray, wordlist as bip39Wordlist, buildBlockquoteElement, buildBlocksFromMarkdown, buildBulletListElement, buildCodeBlockElement, buildHeadingElement, buildHorizontalRuleElement, buildOrderedListElement, buildParagraphElement, buildTaskListElement, decryptChatContent, decryptField, deriveIdentityDocId, deriveSeedWrappingKey, encryptChatContent, encryptField, filenameToLabel, foldRecords, generateMnemonic, isEncryptedContent, makeEncryptedYMap, makeEncryptedYText, mnemonicToEd25519Seed, mnemonicToKeyPair, normalizeRootId, onAuthenticatedParameters, onAuthenticationFailedParameters, onAwarenessChangeParameters, onAwarenessUpdateParameters, onCloseParameters, onCompactedParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onServerErrorParameters, onStatelessParameters, onStatusParameters, onSubdocLoadedParameters, onSubdocRegisteredParameters, onSyncedParameters, onUnsyncedChangesParameters, parseFrontmatter, populateYDocFromMarkdown, readAuthMessage, readBlocksFromFragment, recordFromYAny, resolvePageType, toPlain, unwrapSeed, validateMnemonic, waitForSync, withTimeout, wrapSeed, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest, yjsToMarkdown };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abraca/dabra",
3
- "version": "2.0.10",
3
+ "version": "2.4.0",
4
4
  "description": "abracadabra provider",
5
5
  "keywords": [
6
6
  "abracadabra",
@@ -39,5 +39,11 @@
39
39
  "peerDependencies": {
40
40
  "y-protocols": "^1.0.6",
41
41
  "yjs": "^13.6.8"
42
+ },
43
+ "devDependencies": {
44
+ "@abraca/schema": "2.4.0"
45
+ },
46
+ "scripts": {
47
+ "test": "node --no-warnings --conditions=source --experimental-transform-types --test 'tests/*.test.ts'"
42
48
  }
43
- }
49
+ }
@@ -12,6 +12,8 @@ import { AuthenticationMessage } from "./OutgoingMessages/AuthenticationMessage.
12
12
  import { AwarenessMessage } from "./OutgoingMessages/AwarenessMessage.ts";
13
13
  import { StatelessMessage } from "./OutgoingMessages/StatelessMessage.ts";
14
14
  import { RpcClient } from "./RpcClient.ts";
15
+ import { QueryClient } from "./QueryClient.ts";
16
+ import type { QuerySpec, QuerySubscriptionHandlers, QuerySubscriptionHandle } from "./QueryClient.ts";
15
17
  import { SyncStepOneMessage } from "./OutgoingMessages/SyncStepOneMessage.ts";
16
18
  import { UpdateMessage } from "./OutgoingMessages/UpdateMessage.ts";
17
19
  import type {
@@ -181,6 +183,32 @@ export class AbracadabraBaseProvider extends EventEmitter {
181
183
  return this._rpc;
182
184
  }
183
185
 
186
+ /**
187
+ * Lazily-constructed V2 query subscription client. See
188
+ * `QueryClient`. Each call to `subscribeQuery(...)` opens a
189
+ * long-lived subscription that fires `onSnapshot` with the initial
190
+ * result set and `onDelta` whenever the projection moves.
191
+ */
192
+ private _queryClient: QueryClient | undefined;
193
+ private getQueryClient(): QueryClient {
194
+ if (!this._queryClient) {
195
+ this._queryClient = new QueryClient(this);
196
+ }
197
+ return this._queryClient;
198
+ }
199
+
200
+ /**
201
+ * Open a live query subscription. Mirrors the v1 REST shape
202
+ * (`POST /docs/query`) but pushes deltas as the server-side
203
+ * projection drifts under the predicate.
204
+ */
205
+ subscribeQuery(
206
+ spec: QuerySpec,
207
+ handlers: QuerySubscriptionHandlers = {},
208
+ ): QuerySubscriptionHandle {
209
+ return this.getQueryClient().subscribeQuery(spec, handlers);
210
+ }
211
+
184
212
  constructor(configuration: AbracadabraBaseProviderConfiguration) {
185
213
  super();
186
214
  this.setConfiguration(configuration);
@@ -603,6 +603,38 @@ export class AbracadabraClient {
603
603
  return res.results;
604
604
  }
605
605
 
606
+ /**
607
+ * Query for documents matching a structural predicate. v1 of the
608
+ * indexed-query layer described in
609
+ * `ARCHITECTURE/20-kickoff-phase3-queries.md`.
610
+ *
611
+ * The wire shape accepts a `where` field so v2 (meta-predicate
612
+ * filtering on top of the `doc_meta_index` projection) can light up
613
+ * without a compatibility break. Sending a non-empty `where` today
614
+ * returns a 400 — callers should leave it absent.
615
+ *
616
+ * Returns docs the requester can read at viewer-or-above, filtered
617
+ * through the cascading permission resolver server-side.
618
+ */
619
+ async queryDocs(opts: {
620
+ type?: string;
621
+ parentId?: string;
622
+ labelContains?: string;
623
+ limit?: number;
624
+ }): Promise<DocumentMeta[]> {
625
+ const body: Record<string, unknown> = {};
626
+ if (opts.type != null) body.type = opts.type;
627
+ if (opts.parentId != null) body.parent_id = opts.parentId;
628
+ if (opts.labelContains != null) body.label_contains = opts.labelContains;
629
+ if (opts.limit != null) body.limit = opts.limit;
630
+ const res = await this.request<{ documents: DocumentMeta[]; v: number }>(
631
+ "POST",
632
+ "/docs/query",
633
+ { body },
634
+ );
635
+ return res.documents;
636
+ }
637
+
606
638
  /**
607
639
  * List the direct children of a document, returning full metadata. Pass
608
640
  * no argument to list the children of the server root — what the