@abraca/dabra 1.9.1 → 2.0.1

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/src/types.ts CHANGED
@@ -183,20 +183,68 @@ export interface UserProfile {
183
183
  role: string;
184
184
  /** Account-level Ed25519 public key (base64url). Canonical user identity. */
185
185
  publicKey: string | null;
186
+ /**
187
+ * Whether the user has a password set. `false` for key-based soft
188
+ * identities until they opt in via {@link AbracadabraClient.setPassword}.
189
+ * Drives the UI's "Set password" vs "Change password" branching.
190
+ */
191
+ hasPassword?: boolean;
186
192
  }
187
193
 
188
194
  export interface DocumentMeta {
189
195
  id: string;
190
196
  parent_id: string | null;
197
+ /**
198
+ * Renderer hint — `"kanban"`, `"checklist"`, `"outline"`, `"graph"`,
199
+ * `"sheet"`, `"mindmap"`, `"doc"`, etc. Drives the dashboard's choice of
200
+ * Vue component for this doc. Orthogonal to {@link kind}.
201
+ */
191
202
  doc_type?: string | null;
192
203
  label?: string | null;
193
204
  description?: string | null;
194
205
  public_access?: string | null;
195
206
  owner_id?: string | null;
196
- is_hub?: boolean;
207
+ /**
208
+ * Structural role hint — `"server"` for the reserved server-root doc,
209
+ * `"space"` for top-level workspace containers, `null`/absent otherwise.
210
+ * Distinct from {@link doc_type}: a Space (`kind: "space"`) can contain
211
+ * many Kanban boards (each `doc_type: "kanban"`).
212
+ */
213
+ kind?: string | null;
197
214
  updated_at?: number | null;
198
215
  }
199
216
 
217
+ /**
218
+ * Well-known structural roles for the {@link DocumentMeta.kind} field. These
219
+ * are conventions only — the server treats `kind` as an opaque string. Use
220
+ * these constants instead of string literals to avoid typos.
221
+ */
222
+ export const Kind = {
223
+ /** The reserved server root document. There is exactly one per server. */
224
+ Server: "server",
225
+ /** A top-level workspace container — what the dashboard calls a "Space". */
226
+ Space: "space",
227
+ /** A regular content page rendered by the editor. */
228
+ Page: "page",
229
+ /** A group chat container under a Space. */
230
+ Channel: "channel",
231
+ /** A direct-message container at the server root with two-member permissions. */
232
+ Dm: "dm",
233
+ /** A child of channel/dm holding the messages Y.Array. */
234
+ ChannelPeriod: "channel-period",
235
+ /** A per-user inbox doc holding inbox entries; child of server root. */
236
+ Inbox: "inbox",
237
+ } as const;
238
+ export type Kind = typeof Kind[keyof typeof Kind];
239
+
240
+ /**
241
+ * Hardcoded UUID of the reserved server root document. Every doc in the tree
242
+ * chains up to this row, so a permission grant on this doc cascades into
243
+ * every descendant. Mirrors the constant in the Rust crate
244
+ * (`crate::types::SERVER_ROOT_ID_STR`).
245
+ */
246
+ export const SERVER_ROOT_ID = "00000000-0000-0000-0000-000000000000";
247
+
200
248
  export interface UploadMeta {
201
249
  id: string;
202
250
  doc_id: string;
@@ -250,10 +298,30 @@ export interface SnapshotMeta {
250
298
  label?: string | null;
251
299
  created_by?: string | null;
252
300
  created_at: number;
301
+ /** Number of uploads referenced by this snapshot's CRDT state.
302
+ * Always 0 on servers older than the `snapshot_files` migration, or for
303
+ * snapshots created before that migration ran (until the operator runs
304
+ * `POST /admin/snapshots/backfill-refs`). */
305
+ file_count?: number;
306
+ /** Signed byte delta vs. the previous-version snapshot for this doc.
307
+ * `null` for the first snapshot of a doc (or when the previous version
308
+ * has been pruned). Lets the UI render `+12 KB` / `−3 KB`. */
309
+ delta_bytes?: number | null;
310
+ }
311
+
312
+ export interface SnapshotFileEntry {
313
+ id: string;
314
+ doc_id: string;
315
+ filename: string;
316
+ mime_type?: string | null;
317
+ size?: number | null;
253
318
  }
254
319
 
255
320
  export interface SnapshotData extends SnapshotMeta {
256
321
  data: string;
322
+ /** Populated only when fetched with `?include=files`. Each entry is an
323
+ * upload referenced by this snapshot's CRDT state. */
324
+ files?: SnapshotFileEntry[];
257
325
  }
258
326
 
259
327
  export interface SnapshotCreateResult {
@@ -285,10 +353,23 @@ export interface ServerInfo {
285
353
  version?: string;
286
354
  /** Hocuspocus wire protocol version (currently 2). */
287
355
  protocol_version?: number;
288
- /** Entry-point document ID advertised by the server, if configured. */
289
- index_doc_id?: string;
290
- /** Default role assigned to users without explicit permissions. */
291
- default_role?: string;
356
+ /**
357
+ * The reserved server root document id. Every doc in the tree chains up
358
+ * to this row; granting Admin here cascades to every doc. Hardcoded by
359
+ * the server — clients can also use {@link SERVER_ROOT_ID} directly.
360
+ */
361
+ root_doc_id?: string;
362
+ /**
363
+ * Server-wide access policy. Drives client-side decisions like "show the
364
+ * sign-up CTA?" (anonymous mode) or "warn before publishing?" (anonymous
365
+ * grants).
366
+ */
367
+ access?: {
368
+ /** `"none"` rejects unauthenticated requests; `"observer"` allows public read. */
369
+ anonymous?: "none" | "observer";
370
+ /** Server-wide floor for authed users with no explicit grant. */
371
+ authenticated?: "none" | "observer" | "viewer" | "editor";
372
+ };
292
373
  /** Enabled auth methods (e.g. ["crypto", "jwt"]). */
293
374
  auth_methods?: string[];
294
375
  /** Whether open registration is enabled. */
@@ -297,6 +378,13 @@ export interface ServerInfo {
297
378
  invite_only?: boolean;
298
379
  /** Server encryption configuration. */
299
380
  encryption?: { default_mode?: string; minimum_mode?: string };
381
+ /**
382
+ * Ed25519 public key (base64url, no padding) the server uses to sign every
383
+ * accepted `messages:*` record (`server_sig`). Clients can verify message
384
+ * placement / ordering by checking each record's `server_sig` against this
385
+ * key over the canonical `{ msg_id, period_id, ts, client_sig }` payload.
386
+ */
387
+ messages_signer_pubkey?: string;
300
388
  }
301
389
 
302
390
  // ── Search ───────────────────────────────────────────────────────────────────
@@ -307,21 +395,152 @@ export interface SearchResult {
307
395
  score: number;
308
396
  }
309
397
 
310
- // ── Spaces ───────────────────────────────────────────────────────────────────
398
+ /**
399
+ * A single hit returned by the server's full-text search endpoint
400
+ * (`GET /docs/search`). Distinct from {@link SearchResult}, which is the
401
+ * client-side trigram index in `SearchIndex.ts`.
402
+ *
403
+ * `snippet` is server-rendered HTML with `<mark>` wrappers around the
404
+ * matched tokens — safe to inject after standard sanitization. `rank` is
405
+ * a backend-specific score (SQLite bm25 vs Postgres ts_rank); the only
406
+ * guarantee is that hits arrive in best-first order.
407
+ */
408
+ export interface DocSearchHit {
409
+ doc_id: string;
410
+ parent_id: string | null;
411
+ label: string | null;
412
+ kind: string | null;
413
+ /** HTML-safe snippet with `<mark>` markers around the matched tokens. */
414
+ snippet: string;
415
+ rank: number;
416
+ }
311
417
 
312
- export interface SpaceMeta {
418
+ // ── Audit log (admin) ────────────────────────────────────────────────────────
419
+
420
+ /**
421
+ * A single row from `GET /admin/audit`. Mirrors the server's audit log
422
+ * row format (see `audit::AuditLogRow`). `metadata` is the parsed JSON
423
+ * object the server stored, or `null` if the row had no metadata. Each
424
+ * row carries an internal `prev_hash` / `row_hash` chain that admins
425
+ * verify with {@link AuditVerifyResult}.
426
+ */
427
+ export interface AuditLogEntry {
313
428
  id: string;
314
- doc_id: string;
315
- name: string;
316
- description: string | null;
317
- visibility: "public" | "private" | "invite";
318
- is_hub: boolean;
319
- owner_id: string | null;
320
- created_at: number;
321
- updated_at: number;
322
- public_access?: string | null;
429
+ ts: number;
430
+ event_type: string;
431
+ actor_user_id: string | null;
432
+ actor_ip: string | null;
433
+ request_id: string | null;
434
+ target_type: string | null;
435
+ target_id: string | null;
436
+ metadata: Record<string, unknown> | null;
437
+ outcome: string;
438
+ }
439
+
440
+ /** Filters for `GET /admin/audit`. All optional and AND-combined. */
441
+ export interface AuditQueryOpts {
442
+ event_type?: string;
443
+ actor_user_id?: string;
444
+ target_type?: string;
445
+ target_id?: string;
446
+ since_ts?: number;
447
+ until_ts?: number;
448
+ limit?: number;
449
+ offset?: number;
450
+ }
451
+
452
+ /** Response of `GET /admin/audit/verify`. `status: "broken"` carries `break`. */
453
+ export interface AuditVerifyResult {
454
+ status: "ok" | "broken";
455
+ message?: string;
456
+ break?: { rows_checked: number; row_id: string; reason: string };
323
457
  }
324
458
 
459
+ // ── Admin: runtime config store ───────────────────────────────────────────────
460
+
461
+ /**
462
+ * Where the current value of a config field came from.
463
+ *
464
+ * * `default` — compiled-in default; nobody touched it.
465
+ * * `env` — set at startup by an `ABRA_*` env var.
466
+ * * `global_override` — admin runtime override via `/admin/config`.
467
+ * * `route_override` — per-route override (Phase 3).
468
+ */
469
+ export type AdminConfigOriginKind =
470
+ | "default"
471
+ | "env"
472
+ | "global_override"
473
+ | "route_override";
474
+
475
+ /** One entry returned by `GET /admin/config` and `GET /admin/config/fields/:path`. */
476
+ export interface AdminConfigField {
477
+ path: string;
478
+ section: string;
479
+ description: string;
480
+ env_var: string;
481
+ runtime_mutable: boolean;
482
+ value: unknown;
483
+ origin_kind: AdminConfigOriginKind;
484
+ /** Provenance — present only when origin_kind is a `*_override`. */
485
+ set_at?: number;
486
+ set_by?: string;
487
+ route?: string;
488
+ }
489
+
490
+ /**
491
+ * One row from `GET /admin/config/env-snapshot.items[]`. Reports whether
492
+ * each registry-known `ABRA_*` env var was set in the running process at
493
+ * boot, with secrets reported as `value: null` and `redacted: true`.
494
+ */
495
+ export interface EnvSnapshotItem {
496
+ env_var: string;
497
+ path: string;
498
+ section: string;
499
+ present: boolean;
500
+ /** Verbatim env value, or `null` for redacted (secrets) or unset. */
501
+ value: string | null;
502
+ redacted: boolean;
503
+ }
504
+
505
+ /**
506
+ * One row from `GET /admin/config/env-snapshot.extensions[]`. Extension
507
+ * overrides (`ABRA_EXT_<name>_<field>`) sit outside the registry and are
508
+ * reported by name with values inline.
509
+ */
510
+ export interface EnvSnapshotExtension {
511
+ env_var: string;
512
+ value: string;
513
+ }
514
+
515
+ /**
516
+ * `GET /admin/config/env-snapshot` response.
517
+ *
518
+ * `unknown` lists `ABRA_*` env vars present in the process that don't map
519
+ * to any registered field — i.e. typos. Operators that see their tweak
520
+ * land here instead of in `items` know they misspelled the var.
521
+ */
522
+ export interface EnvSnapshotResponse {
523
+ items: EnvSnapshotItem[];
524
+ extensions: EnvSnapshotExtension[];
525
+ unknown: string[];
526
+ }
527
+
528
+ // ── Readiness ────────────────────────────────────────────────────────────────
529
+
530
+ /** Response of `GET /readyz`. HTTP 200 when ready, 503 when not. */
531
+ export interface ReadyzStatus {
532
+ status: "ready" | "unready";
533
+ version: string;
534
+ checks: { database: "ok" | "error" };
535
+ }
536
+
537
+ // ── Spaces ───────────────────────────────────────────────────────────────────
538
+ //
539
+ // Spaces are documents whose `parent_id` is the server root and whose `kind`
540
+ // is `"space"`. Listing them is `client.listSpaces()`; they come back as
541
+ // regular `DocumentMeta` objects. Visibility is encoded in `public_access`:
542
+ // `null` (or `"none"`) = private, `"observer"` = public read-only.
543
+
325
544
  // ── Invites ──────────────────────────────────────────────────────────────────
326
545
 
327
546
  export interface InviteRow {
@@ -527,7 +527,7 @@ export class AbracadabraWebRTC extends EventEmitter {
527
527
  pendingKeyExchangeChannel = channel;
528
528
  return;
529
529
  }
530
- channel.send(e2ee.getKeyExchangeMessage());
530
+ channel.send(e2ee.getKeyExchangeMessage() as unknown as ArrayBuffer);
531
531
  });
532
532
 
533
533
  this.resolveE2ee().then(async (identity) => {
@@ -541,7 +541,7 @@ export class AbracadabraWebRTC extends EventEmitter {
541
541
 
542
542
  // Drain buffered key-exchange channel open
543
543
  if (pendingKeyExchangeChannel) {
544
- pendingKeyExchangeChannel.send(e2ee.getKeyExchangeMessage());
544
+ pendingKeyExchangeChannel.send(e2ee.getKeyExchangeMessage() as unknown as ArrayBuffer);
545
545
  }
546
546
  // Drain buffered messages
547
547
  for (const msg of pendingMessages) {
@@ -100,9 +100,9 @@ export class DataChannelRouter extends EventEmitter {
100
100
 
101
101
  if (this.encryptor?.isEstablished && !this.plaintextChannels.has(name)) {
102
102
  const encrypted = await this.encryptor.encrypt(data);
103
- channel.send(encrypted);
103
+ channel.send(encrypted as unknown as ArrayBuffer);
104
104
  } else {
105
- channel.send(data);
105
+ channel.send(data as unknown as ArrayBuffer);
106
106
  }
107
107
  return true;
108
108
  }
@@ -98,7 +98,7 @@ export class E2EEChannel extends EventEmitter {
98
98
 
99
99
  this.sessionKey = await crypto.subtle.importKey(
100
100
  "raw",
101
- keyBytes,
101
+ keyBytes as BufferSource,
102
102
  { name: "AES-GCM" },
103
103
  false,
104
104
  ["encrypt", "decrypt"],
@@ -130,9 +130,9 @@ export class E2EEChannel extends EventEmitter {
130
130
  const nonce = crypto.getRandomValues(new Uint8Array(NONCE_BYTES));
131
131
  const ciphertext = new Uint8Array(
132
132
  await crypto.subtle.encrypt(
133
- { name: "AES-GCM", iv: nonce },
133
+ { name: "AES-GCM", iv: nonce as BufferSource },
134
134
  this.sessionKey,
135
- plaintext,
135
+ plaintext as BufferSource,
136
136
  ),
137
137
  );
138
138
 
@@ -45,6 +45,13 @@ export class FileTransferHandle extends EventEmitter {
45
45
  _setStatus(s: FileTransferStatus): void {
46
46
  this.status = s;
47
47
  }
48
+
49
+ /** @internal — public alias for the protected emit so the parent
50
+ * `FileTransferChannel` (a different class instance) can dispatch events
51
+ * on this handle. Protected access is by-class, not by-hierarchy. */
52
+ _emit(event: string, ...args: unknown[]): void {
53
+ this.emit(event, ...args);
54
+ }
48
55
  }
49
56
 
50
57
  interface ReceiveState {
@@ -102,7 +109,7 @@ export class FileTransferChannel extends EventEmitter {
102
109
  const channel = this.router.getChannel(CHANNEL_NAMES.FILE_TRANSFER);
103
110
  if (!channel || channel.readyState !== "open") {
104
111
  handle._setStatus("error");
105
- handle.emit("error", new Error("File transfer channel not open"));
112
+ handle._emit("error", new Error("File transfer channel not open"));
106
113
  return handle;
107
114
  }
108
115
 
@@ -174,7 +181,7 @@ export class FileTransferChannel extends EventEmitter {
174
181
  channel.send(completeMsg);
175
182
 
176
183
  handle._setStatus("complete");
177
- handle.emit("complete");
184
+ handle._emit("complete");
178
185
 
179
186
  return handle;
180
187
  }