@abraca/dabra 2.4.0 → 2.6.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/README.md +50 -0
- package/dist/abracadabra-provider.cjs +534 -142
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +529 -153
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +248 -2
- package/package.json +2 -2
- package/src/AbracadabraClient.ts +273 -13
- package/src/AbracadabraProvider.ts +25 -16
- package/src/ContentManager.ts +19 -11
- package/src/DocUtils.ts +62 -0
- package/src/DocumentManager.ts +18 -0
- package/src/FileBlobStore.ts +43 -6
- package/src/MetaManager.ts +4 -7
- package/src/TreeManager.ts +343 -47
- package/src/TreeTimestamps.ts +6 -2
- package/src/index.ts +9 -1
package/dist/index.d.ts
CHANGED
|
@@ -289,6 +289,11 @@ declare class AbracadabraClient {
|
|
|
289
289
|
private readonly _fetch;
|
|
290
290
|
readonly cache: DocumentCache | null;
|
|
291
291
|
private readonly _onAuthFailed;
|
|
292
|
+
/** Single-flight for root listings (`/docs?root=true`) — the per-row
|
|
293
|
+
* permission cascade makes this a multi-second / hundreds-of-KB call on
|
|
294
|
+
* a busy server, and a connect burst fires several identical ones.
|
|
295
|
+
* Keyed by the kind filter; cleared when the request settles. */
|
|
296
|
+
private readonly rootListInflight;
|
|
292
297
|
constructor(config: AbracadabraClientConfig);
|
|
293
298
|
get token(): string | null;
|
|
294
299
|
set token(value: string | null);
|
|
@@ -576,7 +581,9 @@ declare class AbracadabraClient {
|
|
|
576
581
|
* recursive tree walks; callers that need it can read `meta.id` from
|
|
577
582
|
* the returned metas.
|
|
578
583
|
*/
|
|
579
|
-
listChildren(parentId?: string
|
|
584
|
+
listChildren(parentId?: string, opts?: {
|
|
585
|
+
kind?: string;
|
|
586
|
+
}): Promise<DocumentMeta[]>;
|
|
580
587
|
/**
|
|
581
588
|
* Create a child document under a parent (requires write permission).
|
|
582
589
|
*
|
|
@@ -729,6 +736,64 @@ declare class AbracadabraClient {
|
|
|
729
736
|
refCountsRepaired: number;
|
|
730
737
|
blobsSwept: number;
|
|
731
738
|
}>;
|
|
739
|
+
/**
|
|
740
|
+
* Admin one-shot: populate the `snapshot_files` table for snapshots
|
|
741
|
+
* created before that migration ran, so `SnapshotMeta.file_count` and
|
|
742
|
+
* upload-ref tracking become accurate. Idempotent (insert-or-ignore).
|
|
743
|
+
* Requires elevated role.
|
|
744
|
+
*/
|
|
745
|
+
adminSnapshotsBackfillRefs(): Promise<{
|
|
746
|
+
snapshotsScanned: number;
|
|
747
|
+
refsWritten: number;
|
|
748
|
+
}>;
|
|
749
|
+
/**
|
|
750
|
+
* Admin one-shot: migrate pre-dedup inline snapshot data into the
|
|
751
|
+
* content-addressed `snapshot_blobs` store. Idempotent (only migrates
|
|
752
|
+
* rows with `data_hash IS NULL`). Requires elevated role.
|
|
753
|
+
*/
|
|
754
|
+
adminSnapshotsBackfillBlobs(): Promise<{
|
|
755
|
+
snapshotsMigrated: number;
|
|
756
|
+
blobsAfter: number;
|
|
757
|
+
totalBytesAfter: number;
|
|
758
|
+
}>;
|
|
759
|
+
/**
|
|
760
|
+
* Admin: server-wide upload listing, joined with the owning document
|
|
761
|
+
* (label is best-effort — labels live in the CRDT, not the SQL row)
|
|
762
|
+
* and the content-addressed blob (`ref_count` exposes dedup).
|
|
763
|
+
* Server-side paginated + filtered. Requires elevated role.
|
|
764
|
+
*/
|
|
765
|
+
adminListUploads(opts?: {
|
|
766
|
+
q?: string;
|
|
767
|
+
docId?: string;
|
|
768
|
+
limit?: number;
|
|
769
|
+
offset?: number;
|
|
770
|
+
}): Promise<{
|
|
771
|
+
uploads: Array<{
|
|
772
|
+
id: string;
|
|
773
|
+
doc_id: string;
|
|
774
|
+
doc_label: string | null;
|
|
775
|
+
filename: string;
|
|
776
|
+
mime_type: string | null;
|
|
777
|
+
size: number | null;
|
|
778
|
+
content_hash: string | null;
|
|
779
|
+
ref_count: number | null;
|
|
780
|
+
owner_id: string;
|
|
781
|
+
created_at: number;
|
|
782
|
+
}>;
|
|
783
|
+
total: number;
|
|
784
|
+
}>;
|
|
785
|
+
/**
|
|
786
|
+
* Admin: aggregate storage figures. `logicalBytes` is what users
|
|
787
|
+
* uploaded; `physicalBytes` is on-disk after content-addressed dedup;
|
|
788
|
+
* `dedupSaved` is the difference. Requires elevated role.
|
|
789
|
+
*/
|
|
790
|
+
adminStorageStats(): Promise<{
|
|
791
|
+
uploadCount: number;
|
|
792
|
+
blobCount: number;
|
|
793
|
+
logicalBytes: number;
|
|
794
|
+
physicalBytes: number;
|
|
795
|
+
dedupSaved: number;
|
|
796
|
+
}>;
|
|
732
797
|
/**
|
|
733
798
|
* Clear the lockout state on a user account: zeroes the failed-login
|
|
734
799
|
* counter and `locked_until`. Requires elevated role (Admin or
|
|
@@ -736,6 +801,82 @@ declare class AbracadabraClient {
|
|
|
736
801
|
* `admin.user_unlock`.
|
|
737
802
|
*/
|
|
738
803
|
adminUnlockUser(userId: string): Promise<void>;
|
|
804
|
+
/**
|
|
805
|
+
* Admin: every non-deleted document the user owns (`source: "owner"`)
|
|
806
|
+
* or has an explicit permission grant on (`source: "grant"`, with
|
|
807
|
+
* `role`). Answers "what does this identity touch" without an N+1
|
|
808
|
+
* client tree walk. Labels are best-effort (they live in the CRDT).
|
|
809
|
+
* Requires elevated role.
|
|
810
|
+
*/
|
|
811
|
+
adminUserDocs(userId: string, opts?: {
|
|
812
|
+
limit?: number;
|
|
813
|
+
}): Promise<{
|
|
814
|
+
docs: Array<{
|
|
815
|
+
id: string;
|
|
816
|
+
label: string | null;
|
|
817
|
+
kind: string | null;
|
|
818
|
+
doc_type: string | null;
|
|
819
|
+
parent_id: string | null;
|
|
820
|
+
source: "owner" | "grant";
|
|
821
|
+
role: string | null;
|
|
822
|
+
}>;
|
|
823
|
+
}>;
|
|
824
|
+
/**
|
|
825
|
+
* List `service`-role users (runners, demo seeders, automation
|
|
826
|
+
* identities). Requires Service role. Admins can see service users via
|
|
827
|
+
* `/admin/users` too but cannot mint or rotate them.
|
|
828
|
+
*/
|
|
829
|
+
adminListServiceAccounts(): Promise<{
|
|
830
|
+
items: Array<{
|
|
831
|
+
id: string;
|
|
832
|
+
username: string;
|
|
833
|
+
public_key: string | null;
|
|
834
|
+
revoked: boolean;
|
|
835
|
+
display_name: string | null;
|
|
836
|
+
}>;
|
|
837
|
+
}>;
|
|
838
|
+
/**
|
|
839
|
+
* Create a new `service`-role user. When `public_key` is omitted the
|
|
840
|
+
* server generates a keypair and returns the private half in the
|
|
841
|
+
* response — show it to the operator **once** and discard; the server
|
|
842
|
+
* never persists it. Requires Service role.
|
|
843
|
+
*/
|
|
844
|
+
adminCreateServiceAccount(body: {
|
|
845
|
+
username: string;
|
|
846
|
+
public_key?: string;
|
|
847
|
+
}): Promise<{
|
|
848
|
+
id: string;
|
|
849
|
+
username: string;
|
|
850
|
+
public_key: string;
|
|
851
|
+
role: string;
|
|
852
|
+
private_key?: string;
|
|
853
|
+
}>;
|
|
854
|
+
/**
|
|
855
|
+
* Rotate the active keypair on a service account. Old JWTs are
|
|
856
|
+
* invalidated; old device keys are marked revoked; the canonical
|
|
857
|
+
* `users.public_key` swaps to the new value. `users.id` stays put so
|
|
858
|
+
* existing permission rows keep matching. Returns the new pubkey (and
|
|
859
|
+
* private half when the server generated it). Requires Service role.
|
|
860
|
+
*/
|
|
861
|
+
adminRotateServiceAccountKey(userId: string, body?: {
|
|
862
|
+
public_key?: string;
|
|
863
|
+
}): Promise<{
|
|
864
|
+
id: string;
|
|
865
|
+
public_key: string;
|
|
866
|
+
private_key?: string;
|
|
867
|
+
}>;
|
|
868
|
+
/**
|
|
869
|
+
* Lock a service account and revoke all of its device keys. Idempotent.
|
|
870
|
+
* Refuses targets whose `users.role` isn't `"service"`. Requires
|
|
871
|
+
* Service role.
|
|
872
|
+
*/
|
|
873
|
+
adminRevokeServiceAccount(userId: string): Promise<void>;
|
|
874
|
+
/**
|
|
875
|
+
* Revoke a single device key on a user (any role). Bumps
|
|
876
|
+
* `tokens_invalid_before` so open WS sessions tied to the key must
|
|
877
|
+
* re-auth. Requires elevated role (Service or Admin@root).
|
|
878
|
+
*/
|
|
879
|
+
adminRevokeDeviceKey(userId: string, keyId: string): Promise<void>;
|
|
739
880
|
/**
|
|
740
881
|
* Page through the audit log. Filters AND-combine; `limit` defaults to
|
|
741
882
|
* 100 server-side. Requires elevated role.
|
|
@@ -799,6 +940,22 @@ declare class AbracadabraClient {
|
|
|
799
940
|
* Secrets are redacted (`value: null`, `redacted: true`).
|
|
800
941
|
*/
|
|
801
942
|
adminConfigEnvSnapshot(): Promise<EnvSnapshotResponse>;
|
|
943
|
+
/**
|
|
944
|
+
* List every route pattern that currently has at least one per-route
|
|
945
|
+
* config override. Use {@link adminConfigGetRoute} to read individual
|
|
946
|
+
* fields. Requires elevated role.
|
|
947
|
+
*/
|
|
948
|
+
adminConfigListRoutes(): Promise<string[]>;
|
|
949
|
+
/**
|
|
950
|
+
* Read a field's effective value scoped to `route`, falling back to
|
|
951
|
+
* the global value when no per-route override exists. `origin_kind`
|
|
952
|
+
* is `"route_override"` only when an override is actually set.
|
|
953
|
+
*/
|
|
954
|
+
adminConfigGetRoute(route: string, path: string): Promise<AdminConfigField>;
|
|
955
|
+
/** Set or replace a per-route override. Mirrors {@link adminConfigSet}. */
|
|
956
|
+
adminConfigSetRoute(route: string, path: string, value: unknown): Promise<AdminConfigField>;
|
|
957
|
+
/** Clear a per-route override (falls back to global). True if one existed. */
|
|
958
|
+
adminConfigUnsetRoute(route: string, path: string): Promise<boolean>;
|
|
802
959
|
/** List snapshot metadata for a document. */
|
|
803
960
|
listSnapshots(docId: string, opts?: {
|
|
804
961
|
limit?: number;
|
|
@@ -4651,17 +4808,67 @@ declare class TypedDocTypeMismatchError extends Error {
|
|
|
4651
4808
|
}
|
|
4652
4809
|
//#endregion
|
|
4653
4810
|
//#region packages/provider/src/TreeManager.d.ts
|
|
4811
|
+
interface ChildrenPage {
|
|
4812
|
+
entries: TreeEntry[];
|
|
4813
|
+
/** Pass back as `cursor` for the next page; `null` when exhausted. */
|
|
4814
|
+
nextCursor: string | null;
|
|
4815
|
+
}
|
|
4654
4816
|
declare class TreeManager {
|
|
4655
4817
|
private dm;
|
|
4656
4818
|
constructor(dm: DocumentManager);
|
|
4819
|
+
private _idxMap;
|
|
4820
|
+
private _idxObserver;
|
|
4821
|
+
private _idxDirty;
|
|
4822
|
+
private _byId;
|
|
4823
|
+
private _childrenByParent;
|
|
4824
|
+
/**
|
|
4825
|
+
* Ensure the index is enabled, bound to the current root doc's tree
|
|
4826
|
+
* map, and fresh. Returns `false` when the index is disabled or there
|
|
4827
|
+
* is no tree map yet — callers then use the legacy scan path.
|
|
4828
|
+
*/
|
|
4829
|
+
private ensureIndex;
|
|
4830
|
+
private unbindIndex;
|
|
4831
|
+
private rebuildIndex;
|
|
4832
|
+
/**
|
|
4833
|
+
* Release the deep observer. Optional — the observer is auto-rebound
|
|
4834
|
+
* on space switch and becomes moot when the root Y.Doc is GC'd — but
|
|
4835
|
+
* available for consumers that want deterministic teardown.
|
|
4836
|
+
*/
|
|
4837
|
+
dispose(): void;
|
|
4657
4838
|
/** Read all tree entries as plain objects. */
|
|
4658
4839
|
readEntries(): TreeEntry[];
|
|
4840
|
+
/**
|
|
4841
|
+
* Like {@link readEntries} but with every entry's *stored* parentId
|
|
4842
|
+
* run through {@link normalizeRootId} (parentId === rootDocId → null),
|
|
4843
|
+
* so a cou-sh / orphan-rescue top-level doc (parentId === spaceRoot)
|
|
4844
|
+
* resolves to top-level identically to a provider-created one
|
|
4845
|
+
* (parentId: null). Without this, the raw `parentId === spaceRoot`
|
|
4846
|
+
* form never matches the normalized `null` query and such docs are
|
|
4847
|
+
* silently invisible cross-client. Mirrors the Rust provider's
|
|
4848
|
+
* `normalized_entries`. readEntries/get keep raw values for
|
|
4849
|
+
* round-trip consumers; only tree-walk reads use this.
|
|
4850
|
+
*/
|
|
4851
|
+
private normalizedEntries;
|
|
4659
4852
|
/** Get immediate children of a parent (sorted by order). */
|
|
4660
4853
|
childrenOf(parentId: string | null): TreeEntry[];
|
|
4854
|
+
/**
|
|
4855
|
+
* Paginated immediate children — the Path-1 surface for large fan-out
|
|
4856
|
+
* parents. Walks the same stable (order,id) sibling order as
|
|
4857
|
+
* {@link childrenOf}; `cursor` is opaque (round-trip `nextCursor`).
|
|
4858
|
+
* `limit` defaults to 100. A stale/garbage cursor restarts from the
|
|
4859
|
+
* head rather than throwing. Cursor stability is exact when the index
|
|
4860
|
+
* is enabled; on the legacy scan path siblings with equal `order`
|
|
4861
|
+
* may shift between calls.
|
|
4862
|
+
*/
|
|
4863
|
+
childrenOfPage(parentId: string | null, opts?: {
|
|
4864
|
+
limit?: number;
|
|
4865
|
+
cursor?: string | null;
|
|
4866
|
+
}): ChildrenPage;
|
|
4661
4867
|
/** Get all descendants recursively. */
|
|
4662
4868
|
descendantsOf(parentId: string | null): TreeEntry[];
|
|
4663
4869
|
/** Build nested tree JSON. */
|
|
4664
4870
|
buildTree(rootId?: string | null, maxDepth?: number): TreeNode[];
|
|
4871
|
+
private _buildTreeIndexed;
|
|
4665
4872
|
private _buildTree;
|
|
4666
4873
|
/**
|
|
4667
4874
|
* Schema-typed lookup. Returns a `TypedTreeEntry<TMap, N>` when the
|
|
@@ -4933,6 +5140,16 @@ interface DocumentManagerConfig {
|
|
|
4933
5140
|
* the entry-point docId is already known.
|
|
4934
5141
|
*/
|
|
4935
5142
|
rootDocId?: string;
|
|
5143
|
+
/**
|
|
5144
|
+
* PROTOTYPE (Path-1 doctree scaling): opt into TreeManager's in-memory
|
|
5145
|
+
* parent→children index. When `false` (default), every tree walk
|
|
5146
|
+
* re-scans the whole `doc-tree` Y.Map (O(n) per call, O(n²) recursive
|
|
5147
|
+
* traversal) — the historical behaviour, byte-for-byte. When `true`,
|
|
5148
|
+
* walks resolve against a lazily-rebuilt adjacency index (O(k) per
|
|
5149
|
+
* lookup, O(result) traversal) and `tree.childrenOfPage()` becomes
|
|
5150
|
+
* usable. Behind a flag so it ships dark until benchmarked.
|
|
5151
|
+
*/
|
|
5152
|
+
treeIndex?: boolean;
|
|
4936
5153
|
}
|
|
4937
5154
|
declare class DocumentManager {
|
|
4938
5155
|
readonly client: AbracadabraClient;
|
|
@@ -4968,6 +5185,11 @@ declare class DocumentManager {
|
|
|
4968
5185
|
get displayColor(): string;
|
|
4969
5186
|
get serverInfo(): ServerInfo | null;
|
|
4970
5187
|
get rootDocId(): string | null;
|
|
5188
|
+
/**
|
|
5189
|
+
* Whether the TreeManager in-memory index is enabled (Path-1 prototype).
|
|
5190
|
+
* Off by default — see {@link DocumentManagerConfig.treeIndex}.
|
|
5191
|
+
*/
|
|
5192
|
+
get treeIndexEnabled(): boolean;
|
|
4971
5193
|
get rootDocument(): Y.Doc | null;
|
|
4972
5194
|
get rootProvider(): AbracadabraProvider | null;
|
|
4973
5195
|
/**
|
|
@@ -5030,5 +5252,29 @@ declare function normalizeRootId(id: string | null | undefined, rootDocId: strin
|
|
|
5030
5252
|
* Safely read a tree map value, converting Y.Map to plain object if needed.
|
|
5031
5253
|
*/
|
|
5032
5254
|
declare function toPlain(val: unknown): unknown;
|
|
5255
|
+
/**
|
|
5256
|
+
* Build a tree/trash entry as a nested `Y.Map`. Use for a brand-new or
|
|
5257
|
+
* re-created key (create / duplicate / restore) where no concurrent
|
|
5258
|
+
* writer exists, so a whole-value write is safe. `undefined` fields are
|
|
5259
|
+
* omitted; `null` is kept (a real value, e.g. top-level `parentId`).
|
|
5260
|
+
*/
|
|
5261
|
+
declare function makeEntryMap(fields: Record<string, unknown>): Y.Map<unknown>;
|
|
5262
|
+
/**
|
|
5263
|
+
* Patch an EXISTING entry's fields per-key on its nested `Y.Map`, so a
|
|
5264
|
+
* concurrent edit to a *different* field by a peer is preserved instead
|
|
5265
|
+
* of being clobbered by a whole-entry write — the whole-entry-LWW fix
|
|
5266
|
+
* (audit ⑦), the mirror of the Rust provider's `with_entry_mut`.
|
|
5267
|
+
*
|
|
5268
|
+
* - nested `Y.Map` entry → set/delete only the touched keys in place;
|
|
5269
|
+
* - legacy opaque (plain-object) entry → migrated once to a `Y.Map`;
|
|
5270
|
+
* - missing entry → created from the patch (lenient; matches the prior
|
|
5271
|
+
* call-site behaviour of spreading `undefined`).
|
|
5272
|
+
*
|
|
5273
|
+
* A patch value of `undefined` deletes the key; `null` is written.
|
|
5274
|
+
* Self-transacting: it batches its writes in one `Y.Doc` transaction
|
|
5275
|
+
* (a safe reentrant no-op join when already inside one), so callers
|
|
5276
|
+
* don't need to pass or own a transaction.
|
|
5277
|
+
*/
|
|
5278
|
+
declare function patchEntry(treeMap: Y.Map<unknown>, id: string, patch: Record<string, unknown>, removeKeys?: string[]): void;
|
|
5033
5279
|
//#endregion
|
|
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 };
|
|
5280
|
+
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, type ChildrenPage, 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, makeEntryMap, mnemonicToEd25519Seed, mnemonicToKeyPair, normalizeRootId, onAuthenticatedParameters, onAuthenticationFailedParameters, onAwarenessChangeParameters, onAwarenessUpdateParameters, onCloseParameters, onCompactedParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onServerErrorParameters, onStatelessParameters, onStatusParameters, onSubdocLoadedParameters, onSubdocRegisteredParameters, onSyncedParameters, onUnsyncedChangesParameters, parseFrontmatter, patchEntry, 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.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "abracadabra provider",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"abracadabra",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"yjs": "^13.6.8"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@abraca/schema": "2.
|
|
44
|
+
"@abraca/schema": "2.6.0"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"test": "node --no-warnings --conditions=source --experimental-transform-types --test 'tests/*.test.ts'"
|
package/src/AbracadabraClient.ts
CHANGED
|
@@ -95,6 +95,14 @@ export class AbracadabraClient {
|
|
|
95
95
|
private readonly _fetch: typeof globalThis.fetch;
|
|
96
96
|
readonly cache: DocumentCache | null;
|
|
97
97
|
private readonly _onAuthFailed: ((ctx: AuthFailureContext) => void) | null;
|
|
98
|
+
/** Single-flight for root listings (`/docs?root=true`) — the per-row
|
|
99
|
+
* permission cascade makes this a multi-second / hundreds-of-KB call on
|
|
100
|
+
* a busy server, and a connect burst fires several identical ones.
|
|
101
|
+
* Keyed by the kind filter; cleared when the request settles. */
|
|
102
|
+
private readonly rootListInflight = new Map<
|
|
103
|
+
string,
|
|
104
|
+
Promise<DocumentMeta[]>
|
|
105
|
+
>();
|
|
98
106
|
|
|
99
107
|
constructor(config: AbracadabraClientConfig) {
|
|
100
108
|
this.baseUrl = config.url.replace(/\/+$/, "");
|
|
@@ -644,18 +652,50 @@ export class AbracadabraClient {
|
|
|
644
652
|
* recursive tree walks; callers that need it can read `meta.id` from
|
|
645
653
|
* the returned metas.
|
|
646
654
|
*/
|
|
647
|
-
async listChildren(
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
655
|
+
async listChildren(
|
|
656
|
+
parentId?: string,
|
|
657
|
+
opts?: { kind?: string },
|
|
658
|
+
): Promise<DocumentMeta[]> {
|
|
659
|
+
const kind = opts?.kind;
|
|
660
|
+
if (parentId) {
|
|
661
|
+
const res = await this.request<{
|
|
662
|
+
documents: DocumentMeta[];
|
|
663
|
+
children?: string[];
|
|
664
|
+
}>("GET", `/docs/${encodeURIComponent(parentId)}/children`);
|
|
665
|
+
if (this.cache && res.children) {
|
|
666
|
+
await this.cache.setChildren(parentId, res.children).catch(() => null);
|
|
667
|
+
}
|
|
668
|
+
return kind
|
|
669
|
+
? res.documents.filter((d) => d.kind === kind)
|
|
670
|
+
: res.documents;
|
|
657
671
|
}
|
|
658
|
-
|
|
672
|
+
// Root listing. The server runs the per-row permission cascade for
|
|
673
|
+
// every top-level doc, so this is the expensive call. Send the
|
|
674
|
+
// optional server-side `kind` filter — a fixed server skips the
|
|
675
|
+
// cascade for non-matching docs; an old server ignores the unknown
|
|
676
|
+
// param and we still filter client-side below, so the result is
|
|
677
|
+
// identical either way. Concurrent identical root listings (a
|
|
678
|
+
// connect fans out several) share one in-flight request.
|
|
679
|
+
const key = kind ?? "";
|
|
680
|
+
const existing = this.rootListInflight.get(key);
|
|
681
|
+
const docs = existing
|
|
682
|
+
? await existing
|
|
683
|
+
: await (() => {
|
|
684
|
+
const p = this.request<{
|
|
685
|
+
documents: DocumentMeta[];
|
|
686
|
+
children?: string[];
|
|
687
|
+
}>(
|
|
688
|
+
"GET",
|
|
689
|
+
kind
|
|
690
|
+
? `/docs?root=true&kind=${encodeURIComponent(kind)}`
|
|
691
|
+
: "/docs?root=true",
|
|
692
|
+
)
|
|
693
|
+
.then((res) => res.documents)
|
|
694
|
+
.finally(() => this.rootListInflight.delete(key));
|
|
695
|
+
this.rootListInflight.set(key, p);
|
|
696
|
+
return p;
|
|
697
|
+
})();
|
|
698
|
+
return kind ? docs.filter((d) => d.kind === kind) : docs;
|
|
659
699
|
}
|
|
660
700
|
|
|
661
701
|
/**
|
|
@@ -889,8 +929,7 @@ export class AbracadabraClient {
|
|
|
889
929
|
* spaces resolving to any role; anonymous users see public ones.
|
|
890
930
|
*/
|
|
891
931
|
async listSpaces(): Promise<DocumentMeta[]> {
|
|
892
|
-
|
|
893
|
-
return docs.filter((d) => d.kind === Kind.Space);
|
|
932
|
+
return this.listChildren(undefined, { kind: Kind.Space });
|
|
894
933
|
}
|
|
895
934
|
|
|
896
935
|
/**
|
|
@@ -1044,6 +1083,79 @@ export class AbracadabraClient {
|
|
|
1044
1083
|
return this.request("POST", "/admin/storage/repair");
|
|
1045
1084
|
}
|
|
1046
1085
|
|
|
1086
|
+
/**
|
|
1087
|
+
* Admin one-shot: populate the `snapshot_files` table for snapshots
|
|
1088
|
+
* created before that migration ran, so `SnapshotMeta.file_count` and
|
|
1089
|
+
* upload-ref tracking become accurate. Idempotent (insert-or-ignore).
|
|
1090
|
+
* Requires elevated role.
|
|
1091
|
+
*/
|
|
1092
|
+
async adminSnapshotsBackfillRefs(): Promise<{
|
|
1093
|
+
snapshotsScanned: number;
|
|
1094
|
+
refsWritten: number;
|
|
1095
|
+
}> {
|
|
1096
|
+
return this.request("POST", "/admin/snapshots/backfill-refs");
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* Admin one-shot: migrate pre-dedup inline snapshot data into the
|
|
1101
|
+
* content-addressed `snapshot_blobs` store. Idempotent (only migrates
|
|
1102
|
+
* rows with `data_hash IS NULL`). Requires elevated role.
|
|
1103
|
+
*/
|
|
1104
|
+
async adminSnapshotsBackfillBlobs(): Promise<{
|
|
1105
|
+
snapshotsMigrated: number;
|
|
1106
|
+
blobsAfter: number;
|
|
1107
|
+
totalBytesAfter: number;
|
|
1108
|
+
}> {
|
|
1109
|
+
return this.request("POST", "/admin/snapshots/backfill-blobs");
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Admin: server-wide upload listing, joined with the owning document
|
|
1114
|
+
* (label is best-effort — labels live in the CRDT, not the SQL row)
|
|
1115
|
+
* and the content-addressed blob (`ref_count` exposes dedup).
|
|
1116
|
+
* Server-side paginated + filtered. Requires elevated role.
|
|
1117
|
+
*/
|
|
1118
|
+
async adminListUploads(
|
|
1119
|
+
opts: { q?: string; docId?: string; limit?: number; offset?: number } = {},
|
|
1120
|
+
): Promise<{
|
|
1121
|
+
uploads: Array<{
|
|
1122
|
+
id: string;
|
|
1123
|
+
doc_id: string;
|
|
1124
|
+
doc_label: string | null;
|
|
1125
|
+
filename: string;
|
|
1126
|
+
mime_type: string | null;
|
|
1127
|
+
size: number | null;
|
|
1128
|
+
content_hash: string | null;
|
|
1129
|
+
ref_count: number | null;
|
|
1130
|
+
owner_id: string;
|
|
1131
|
+
created_at: number;
|
|
1132
|
+
}>;
|
|
1133
|
+
total: number;
|
|
1134
|
+
}> {
|
|
1135
|
+
const p = new URLSearchParams();
|
|
1136
|
+
if (opts.q) p.set("q", opts.q);
|
|
1137
|
+
if (opts.docId) p.set("doc_id", opts.docId);
|
|
1138
|
+
if (opts.limit != null) p.set("limit", String(opts.limit));
|
|
1139
|
+
if (opts.offset != null) p.set("offset", String(opts.offset));
|
|
1140
|
+
const qs = p.toString();
|
|
1141
|
+
return this.request("GET", `/admin/uploads${qs ? `?${qs}` : ""}`);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Admin: aggregate storage figures. `logicalBytes` is what users
|
|
1146
|
+
* uploaded; `physicalBytes` is on-disk after content-addressed dedup;
|
|
1147
|
+
* `dedupSaved` is the difference. Requires elevated role.
|
|
1148
|
+
*/
|
|
1149
|
+
async adminStorageStats(): Promise<{
|
|
1150
|
+
uploadCount: number;
|
|
1151
|
+
blobCount: number;
|
|
1152
|
+
logicalBytes: number;
|
|
1153
|
+
physicalBytes: number;
|
|
1154
|
+
dedupSaved: number;
|
|
1155
|
+
}> {
|
|
1156
|
+
return this.request("GET", "/admin/storage/stats");
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1047
1159
|
/**
|
|
1048
1160
|
* Clear the lockout state on a user account: zeroes the failed-login
|
|
1049
1161
|
* counter and `locked_until`. Requires elevated role (Admin or
|
|
@@ -1054,6 +1166,112 @@ export class AbracadabraClient {
|
|
|
1054
1166
|
await this.request("POST", `/admin/users/${encodeURIComponent(userId)}/unlock`);
|
|
1055
1167
|
}
|
|
1056
1168
|
|
|
1169
|
+
/**
|
|
1170
|
+
* Admin: every non-deleted document the user owns (`source: "owner"`)
|
|
1171
|
+
* or has an explicit permission grant on (`source: "grant"`, with
|
|
1172
|
+
* `role`). Answers "what does this identity touch" without an N+1
|
|
1173
|
+
* client tree walk. Labels are best-effort (they live in the CRDT).
|
|
1174
|
+
* Requires elevated role.
|
|
1175
|
+
*/
|
|
1176
|
+
async adminUserDocs(
|
|
1177
|
+
userId: string,
|
|
1178
|
+
opts: { limit?: number } = {},
|
|
1179
|
+
): Promise<{
|
|
1180
|
+
docs: Array<{
|
|
1181
|
+
id: string;
|
|
1182
|
+
label: string | null;
|
|
1183
|
+
kind: string | null;
|
|
1184
|
+
doc_type: string | null;
|
|
1185
|
+
parent_id: string | null;
|
|
1186
|
+
source: "owner" | "grant";
|
|
1187
|
+
role: string | null;
|
|
1188
|
+
}>;
|
|
1189
|
+
}> {
|
|
1190
|
+
const qs = opts.limit != null ? `?limit=${opts.limit}` : "";
|
|
1191
|
+
return this.request(
|
|
1192
|
+
"GET",
|
|
1193
|
+
`/admin/users/${encodeURIComponent(userId)}/docs${qs}`,
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
/**
|
|
1198
|
+
* List `service`-role users (runners, demo seeders, automation
|
|
1199
|
+
* identities). Requires Service role. Admins can see service users via
|
|
1200
|
+
* `/admin/users` too but cannot mint or rotate them.
|
|
1201
|
+
*/
|
|
1202
|
+
async adminListServiceAccounts(): Promise<{
|
|
1203
|
+
items: Array<{
|
|
1204
|
+
id: string;
|
|
1205
|
+
username: string;
|
|
1206
|
+
public_key: string | null;
|
|
1207
|
+
revoked: boolean;
|
|
1208
|
+
display_name: string | null;
|
|
1209
|
+
}>;
|
|
1210
|
+
}> {
|
|
1211
|
+
return this.request("GET", "/admin/service-accounts");
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* Create a new `service`-role user. When `public_key` is omitted the
|
|
1216
|
+
* server generates a keypair and returns the private half in the
|
|
1217
|
+
* response — show it to the operator **once** and discard; the server
|
|
1218
|
+
* never persists it. Requires Service role.
|
|
1219
|
+
*/
|
|
1220
|
+
async adminCreateServiceAccount(body: {
|
|
1221
|
+
username: string;
|
|
1222
|
+
public_key?: string;
|
|
1223
|
+
}): Promise<{
|
|
1224
|
+
id: string;
|
|
1225
|
+
username: string;
|
|
1226
|
+
public_key: string;
|
|
1227
|
+
role: string;
|
|
1228
|
+
private_key?: string;
|
|
1229
|
+
}> {
|
|
1230
|
+
return this.request("POST", "/admin/service-accounts", { body });
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/**
|
|
1234
|
+
* Rotate the active keypair on a service account. Old JWTs are
|
|
1235
|
+
* invalidated; old device keys are marked revoked; the canonical
|
|
1236
|
+
* `users.public_key` swaps to the new value. `users.id` stays put so
|
|
1237
|
+
* existing permission rows keep matching. Returns the new pubkey (and
|
|
1238
|
+
* private half when the server generated it). Requires Service role.
|
|
1239
|
+
*/
|
|
1240
|
+
async adminRotateServiceAccountKey(
|
|
1241
|
+
userId: string,
|
|
1242
|
+
body: { public_key?: string } = {},
|
|
1243
|
+
): Promise<{ id: string; public_key: string; private_key?: string }> {
|
|
1244
|
+
return this.request(
|
|
1245
|
+
"POST",
|
|
1246
|
+
`/admin/service-accounts/${encodeURIComponent(userId)}/rotate-key`,
|
|
1247
|
+
{ body },
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* Lock a service account and revoke all of its device keys. Idempotent.
|
|
1253
|
+
* Refuses targets whose `users.role` isn't `"service"`. Requires
|
|
1254
|
+
* Service role.
|
|
1255
|
+
*/
|
|
1256
|
+
async adminRevokeServiceAccount(userId: string): Promise<void> {
|
|
1257
|
+
await this.request(
|
|
1258
|
+
"DELETE",
|
|
1259
|
+
`/admin/service-accounts/${encodeURIComponent(userId)}`,
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/**
|
|
1264
|
+
* Revoke a single device key on a user (any role). Bumps
|
|
1265
|
+
* `tokens_invalid_before` so open WS sessions tied to the key must
|
|
1266
|
+
* re-auth. Requires elevated role (Service or Admin@root).
|
|
1267
|
+
*/
|
|
1268
|
+
async adminRevokeDeviceKey(userId: string, keyId: string): Promise<void> {
|
|
1269
|
+
await this.request(
|
|
1270
|
+
"POST",
|
|
1271
|
+
`/admin/users/${encodeURIComponent(userId)}/device-keys/${encodeURIComponent(keyId)}/revoke`,
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1057
1275
|
/**
|
|
1058
1276
|
* Page through the audit log. Filters AND-combine; `limit` defaults to
|
|
1059
1277
|
* 100 server-side. Requires elevated role.
|
|
@@ -1203,6 +1421,48 @@ export class AbracadabraClient {
|
|
|
1203
1421
|
return this.request<EnvSnapshotResponse>("GET", "/admin/config/env-snapshot");
|
|
1204
1422
|
}
|
|
1205
1423
|
|
|
1424
|
+
/**
|
|
1425
|
+
* List every route pattern that currently has at least one per-route
|
|
1426
|
+
* config override. Use {@link adminConfigGetRoute} to read individual
|
|
1427
|
+
* fields. Requires elevated role.
|
|
1428
|
+
*/
|
|
1429
|
+
async adminConfigListRoutes(): Promise<string[]> {
|
|
1430
|
+
const res = await this.request<{ routes: string[] }>(
|
|
1431
|
+
"GET", "/admin/config/routes",
|
|
1432
|
+
);
|
|
1433
|
+
return res.routes;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
/**
|
|
1437
|
+
* Read a field's effective value scoped to `route`, falling back to
|
|
1438
|
+
* the global value when no per-route override exists. `origin_kind`
|
|
1439
|
+
* is `"route_override"` only when an override is actually set.
|
|
1440
|
+
*/
|
|
1441
|
+
async adminConfigGetRoute(route: string, path: string): Promise<AdminConfigField> {
|
|
1442
|
+
return this.request<AdminConfigField>(
|
|
1443
|
+
"GET",
|
|
1444
|
+
`/admin/config/routes/${encodeURIComponent(route)}/fields/${encodeURIComponent(path)}`,
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
/** Set or replace a per-route override. Mirrors {@link adminConfigSet}. */
|
|
1449
|
+
async adminConfigSetRoute(route: string, path: string, value: unknown): Promise<AdminConfigField> {
|
|
1450
|
+
return this.request<AdminConfigField>(
|
|
1451
|
+
"PUT",
|
|
1452
|
+
`/admin/config/routes/${encodeURIComponent(route)}/fields/${encodeURIComponent(path)}`,
|
|
1453
|
+
{ body: { value } },
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
/** Clear a per-route override (falls back to global). True if one existed. */
|
|
1458
|
+
async adminConfigUnsetRoute(route: string, path: string): Promise<boolean> {
|
|
1459
|
+
const res = await this.request<{ existed: boolean }>(
|
|
1460
|
+
"DELETE",
|
|
1461
|
+
`/admin/config/routes/${encodeURIComponent(route)}/fields/${encodeURIComponent(path)}`,
|
|
1462
|
+
);
|
|
1463
|
+
return res.existed;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1206
1466
|
// ── Snapshots ────────────────────────────────────────────────────────────
|
|
1207
1467
|
|
|
1208
1468
|
/** List snapshot metadata for a document. */
|