@abraca/nuxt 2.4.0 → 2.5.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.
Files changed (96) hide show
  1. package/README.md +25 -6
  2. package/dist/module.d.mts +4 -2
  3. package/dist/module.json +1 -1
  4. package/dist/runtime/components/ACodeEditor.d.vue.ts +1 -1
  5. package/dist/runtime/components/ACodeEditor.vue.d.ts +1 -1
  6. package/dist/runtime/components/AConnectServerModal.d.vue.ts +2 -2
  7. package/dist/runtime/components/AConnectServerModal.vue +1 -1
  8. package/dist/runtime/components/AConnectServerModal.vue.d.ts +2 -2
  9. package/dist/runtime/components/ADocumentTree.d.vue.ts +8 -1
  10. package/dist/runtime/components/ADocumentTree.vue +6 -3
  11. package/dist/runtime/components/ADocumentTree.vue.d.ts +8 -1
  12. package/dist/runtime/components/AEditor.d.vue.ts +7 -7
  13. package/dist/runtime/components/AEditor.vue.d.ts +7 -7
  14. package/dist/runtime/components/AEmailVerifyConfirmModal.d.vue.ts +2 -2
  15. package/dist/runtime/components/AEmailVerifyConfirmModal.vue.d.ts +2 -2
  16. package/dist/runtime/components/AIdentityModal.d.vue.ts +2 -2
  17. package/dist/runtime/components/AIdentityModal.vue.d.ts +2 -2
  18. package/dist/runtime/components/AInviteRedeemModal.vue +15 -11
  19. package/dist/runtime/components/AMnemonicLoginModal.d.vue.ts +2 -2
  20. package/dist/runtime/components/AMnemonicLoginModal.vue.d.ts +2 -2
  21. package/dist/runtime/components/APasswordChangeModal.d.vue.ts +3 -3
  22. package/dist/runtime/components/APasswordChangeModal.vue.d.ts +3 -3
  23. package/dist/runtime/components/APasswordLoginModal.d.vue.ts +4 -4
  24. package/dist/runtime/components/APasswordLoginModal.vue.d.ts +4 -4
  25. package/dist/runtime/components/APasswordRegisterModal.d.vue.ts +3 -3
  26. package/dist/runtime/components/APasswordRegisterModal.vue.d.ts +3 -3
  27. package/dist/runtime/components/APasswordResetConfirmModal.d.vue.ts +3 -3
  28. package/dist/runtime/components/APasswordResetConfirmModal.vue.d.ts +3 -3
  29. package/dist/runtime/components/APasswordResetRequestModal.d.vue.ts +2 -2
  30. package/dist/runtime/components/APasswordResetRequestModal.vue.d.ts +2 -2
  31. package/dist/runtime/components/ASetPasswordCard.d.vue.ts +1 -1
  32. package/dist/runtime/components/ASetPasswordCard.vue.d.ts +1 -1
  33. package/dist/runtime/components/aware/AArea.d.vue.ts +1 -1
  34. package/dist/runtime/components/aware/AArea.vue.d.ts +1 -1
  35. package/dist/runtime/components/aware/ACalendar.d.vue.ts +1 -1
  36. package/dist/runtime/components/aware/ACalendar.vue.d.ts +1 -1
  37. package/dist/runtime/components/aware/AFileUpload.d.vue.ts +1 -1
  38. package/dist/runtime/components/aware/AFileUpload.vue.d.ts +1 -1
  39. package/dist/runtime/components/aware/AInput.d.vue.ts +1 -1
  40. package/dist/runtime/components/aware/AInput.vue.d.ts +1 -1
  41. package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
  42. package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
  43. package/dist/runtime/components/aware/AScroll.d.vue.ts +1 -1
  44. package/dist/runtime/components/aware/AScroll.vue.d.ts +1 -1
  45. package/dist/runtime/components/aware/ASlider.d.vue.ts +1 -1
  46. package/dist/runtime/components/aware/ASlider.vue.d.ts +1 -1
  47. package/dist/runtime/components/aware/ATable.d.vue.ts +1 -1
  48. package/dist/runtime/components/aware/ATable.vue.d.ts +1 -1
  49. package/dist/runtime/components/aware/ATabs.d.vue.ts +1 -1
  50. package/dist/runtime/components/aware/ATabs.vue.d.ts +1 -1
  51. package/dist/runtime/components/aware/ATextarea.d.vue.ts +1 -1
  52. package/dist/runtime/components/aware/ATextarea.vue.d.ts +1 -1
  53. package/dist/runtime/components/aware/ATree.d.vue.ts +1 -1
  54. package/dist/runtime/components/aware/ATree.vue.d.ts +1 -1
  55. package/dist/runtime/components/docs/ADocsNavigation.d.vue.ts +1 -1
  56. package/dist/runtime/components/docs/ADocsNavigation.vue.d.ts +1 -1
  57. package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +4 -4
  58. package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +4 -4
  59. package/dist/runtime/components/docs/ADocsSearchButton.d.vue.ts +2 -2
  60. package/dist/runtime/components/docs/ADocsSearchButton.vue.d.ts +2 -2
  61. package/dist/runtime/components/docs/ADocsToc.d.vue.ts +1 -1
  62. package/dist/runtime/components/docs/ADocsToc.vue.d.ts +1 -1
  63. package/dist/runtime/components/editor/AColorPalettePopover.d.vue.ts +2 -2
  64. package/dist/runtime/components/editor/AColorPalettePopover.vue.d.ts +2 -2
  65. package/dist/runtime/components/editor/AIconPickerPopover.d.vue.ts +2 -2
  66. package/dist/runtime/components/editor/AIconPickerPopover.vue.d.ts +2 -2
  67. package/dist/runtime/components/editor/ALocationPickerPopover.d.vue.ts +2 -2
  68. package/dist/runtime/components/editor/ALocationPickerPopover.vue.d.ts +2 -2
  69. package/dist/runtime/components/registry/APluginCapabilityDialog.d.vue.ts +4 -4
  70. package/dist/runtime/components/registry/APluginCapabilityDialog.vue +1 -1
  71. package/dist/runtime/components/registry/APluginCapabilityDialog.vue.d.ts +4 -4
  72. package/dist/runtime/components/renderers/AProseRenderer.d.vue.ts +4 -4
  73. package/dist/runtime/components/renderers/AProseRenderer.vue.d.ts +4 -4
  74. package/dist/runtime/components/renderers/sheets/ASheetsCell.d.vue.ts +4 -4
  75. package/dist/runtime/components/renderers/sheets/ASheetsCell.vue.d.ts +4 -4
  76. package/dist/runtime/components/renderers/sheets/ASheetsGrid.d.vue.ts +2 -2
  77. package/dist/runtime/components/renderers/sheets/ASheetsGrid.vue.d.ts +2 -2
  78. package/dist/runtime/components/renderers/sheets/ASheetsMobileActionBar.d.vue.ts +4 -4
  79. package/dist/runtime/components/renderers/sheets/ASheetsMobileActionBar.vue.d.ts +4 -4
  80. package/dist/runtime/components/shell/ADocPanelSettings.d.vue.ts +1 -1
  81. package/dist/runtime/components/shell/ADocPanelSettings.vue.d.ts +1 -1
  82. package/dist/runtime/components/shell/AUserMenu.d.vue.ts +2 -2
  83. package/dist/runtime/components/shell/AUserMenu.vue.d.ts +2 -2
  84. package/dist/runtime/components/shell/AUserProfilePopover.d.vue.ts +1 -1
  85. package/dist/runtime/components/shell/AUserProfilePopover.vue.d.ts +1 -1
  86. package/dist/runtime/components/shell/AWelcomeScreen.d.vue.ts +4 -4
  87. package/dist/runtime/components/shell/AWelcomeScreen.vue +77 -58
  88. package/dist/runtime/components/shell/AWelcomeScreen.vue.d.ts +4 -4
  89. package/dist/runtime/composables/useAbraAdmin.d.ts +131 -4
  90. package/dist/runtime/composables/useAbraAdmin.js +136 -36
  91. package/dist/runtime/composables/useInvites.d.ts +7 -0
  92. package/dist/runtime/plugin-abracadabra.client.js +44 -3
  93. package/dist/runtime/server/api/_abracadabra/spaces.get.d.ts +1 -1
  94. package/dist/runtime/types.d.ts +2 -0
  95. package/dist/types.d.mts +6 -2
  96. package/package.json +20 -20
@@ -35,7 +35,7 @@ type __VLS_ModelProps = {
35
35
  * Ported from cou-sh/app/components/WelcomeScreen.vue — decoupled from
36
36
  * useAbracadabra, useSpaceRegistry, and all app-specific modals.
37
37
  */
38
- 'open'?: boolean;
38
+ "open"?: boolean;
39
39
  };
40
40
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
41
41
  declare var __VLS_15: {}, __VLS_22: {}, __VLS_24: {}, __VLS_57: {}, __VLS_69: {}, __VLS_76: {}, __VLS_78: {};
@@ -55,8 +55,8 @@ type __VLS_Slots = {} & {
55
55
  logo?: (props: typeof __VLS_78) => any;
56
56
  };
57
57
  declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
58
- done: () => any;
59
58
  "update:open": (value: boolean) => any;
59
+ done: () => any;
60
60
  "set-name": (name: string) => any;
61
61
  "set-color": (color: string) => any;
62
62
  "set-neutral": (color: string) => any;
@@ -64,8 +64,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
64
64
  "connect-server": (url: string) => any;
65
65
  "claim-passkey": () => any;
66
66
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
67
- onDone?: (() => any) | undefined;
68
67
  "onUpdate:open"?: ((value: boolean) => any) | undefined;
68
+ onDone?: (() => any) | undefined;
69
69
  "onSet-name"?: ((name: string) => any) | undefined;
70
70
  "onSet-color"?: ((color: string) => any) | undefined;
71
71
  "onSet-neutral"?: ((color: string) => any) | undefined;
@@ -74,9 +74,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
74
74
  "onClaim-passkey"?: (() => any) | undefined;
75
75
  }>, {
76
76
  connecting: boolean;
77
+ isClaimed: boolean;
77
78
  userName: string;
78
79
  currentColor: string;
79
- isClaimed: boolean;
80
80
  avatarStyle: string;
81
81
  connectionError: string;
82
82
  colors: string[];
@@ -1,9 +1,14 @@
1
1
  /**
2
2
  * useAbraAdmin
3
3
  *
4
- * Admin-only composable. Wraps the `/admin/*` REST endpoints (audit log,
5
- * backup dump, user lockout). All methods reject with `Error('not admin')`
6
- * when `isAdmin` is false UI should gate route entry on the same flag.
4
+ * Admin-only composable. Typed facade over the full `/admin/*` REST
5
+ * surface (users, audit, storage, uploads, runtime config, backup).
6
+ * Every method rejects with the server error when the caller lacks an
7
+ * elevated role — UI should still gate route entry on `isAdmin`.
8
+ *
9
+ * This is the single typed entry point for admin tooling (e.g.
10
+ * abracadabra-arcana). Consumers must never reach for
11
+ * `client as any` — if a method is missing here, add it here.
7
12
  *
8
13
  * Usage:
9
14
  * const admin = useAbraAdmin()
@@ -11,11 +16,95 @@
11
16
  * const rows = await admin.auditList({ event_type: 'auth.login_failed' })
12
17
  * }
13
18
  */
14
- import type { AuditLogEntry, AuditQueryOpts, AuditVerifyResult } from '@abraca/dabra';
19
+ import type { AdminConfigField, AuditLogEntry, AuditQueryOpts, AuditVerifyResult, EnvSnapshotResponse, UserProfile } from '@abraca/dabra';
20
+ /** One row from `GET /admin/users` — a profile plus admin-only fields. */
21
+ export type AdminUserRow = UserProfile & {
22
+ revoked: boolean;
23
+ deviceKeys: string[];
24
+ };
25
+ /** One row from `GET /admin/uploads` (uploads ⨝ documents ⨝ file_blobs). */
26
+ export interface AdminUploadRow {
27
+ id: string;
28
+ doc_id: string;
29
+ doc_label: string | null;
30
+ filename: string;
31
+ mime_type: string | null;
32
+ size: number | null;
33
+ content_hash: string | null;
34
+ /** Number of uploads pointing at the same content-addressed blob. */
35
+ ref_count: number | null;
36
+ owner_id: string;
37
+ created_at: number;
38
+ }
39
+ /** `GET /admin/uploads` response. */
40
+ export interface AdminUploadList {
41
+ uploads: AdminUploadRow[];
42
+ total: number;
43
+ }
44
+ /** One document a user owns or has an explicit grant on
45
+ * (`GET /admin/users/:id/docs`). Label is best-effort (CRDT-resident). */
46
+ export interface AdminUserDocRow {
47
+ id: string;
48
+ label: string | null;
49
+ kind: string | null;
50
+ doc_type: string | null;
51
+ parent_id: string | null;
52
+ source: 'owner' | 'grant';
53
+ role: string | null;
54
+ }
55
+ /** `GET /admin/storage/stats` — aggregate storage figures. */
56
+ export interface AdminStorageStats {
57
+ uploadCount: number;
58
+ blobCount: number;
59
+ /** Sum of upload sizes as users see them. */
60
+ logicalBytes: number;
61
+ /** On-disk after content-addressed dedup. */
62
+ physicalBytes: number;
63
+ /** `logicalBytes - physicalBytes`. */
64
+ dedupSaved: number;
65
+ }
15
66
  declare function auditList(opts?: AuditQueryOpts): Promise<AuditLogEntry[]>;
16
67
  declare function auditExport(opts?: Omit<AuditQueryOpts, 'limit' | 'offset'>): Promise<string>;
17
68
  declare function auditVerify(): Promise<AuditVerifyResult>;
69
+ declare function listUsers(): Promise<AdminUserRow[]>;
70
+ declare function promoteUser(userId: string): Promise<void>;
71
+ declare function demoteUser(userId: string): Promise<void>;
18
72
  declare function unlockUser(userId: string): Promise<void>;
73
+ declare function userDocs(userId: string, opts?: {
74
+ limit?: number;
75
+ }): Promise<AdminUserDocRow[]>;
76
+ declare function listUploads(opts?: {
77
+ q?: string;
78
+ docId?: string;
79
+ limit?: number;
80
+ offset?: number;
81
+ }): Promise<AdminUploadList>;
82
+ declare function storageStats(): Promise<AdminStorageStats>;
83
+ declare function storageSweep(): Promise<{
84
+ blobsDeleted: number;
85
+ }>;
86
+ declare function storageRepair(): Promise<{
87
+ refCountsRepaired: number;
88
+ blobsSwept: number;
89
+ }>;
90
+ declare function snapshotsBackfillRefs(): Promise<{
91
+ snapshotsScanned: number;
92
+ refsWritten: number;
93
+ }>;
94
+ declare function snapshotsBackfillBlobs(): Promise<{
95
+ snapshotsMigrated: number;
96
+ blobsAfter: number;
97
+ totalBytesAfter: number;
98
+ }>;
99
+ declare function configList(): Promise<AdminConfigField[]>;
100
+ declare function configGet(path: string): Promise<AdminConfigField>;
101
+ declare function configSet(path: string, value: unknown): Promise<AdminConfigField>;
102
+ declare function configUnset(path: string): Promise<boolean>;
103
+ declare function configEnvSnapshot(): Promise<EnvSnapshotResponse>;
104
+ declare function configListRoutes(): Promise<string[]>;
105
+ declare function configGetRoute(route: string, path: string): Promise<AdminConfigField>;
106
+ declare function configSetRoute(route: string, path: string, value: unknown): Promise<AdminConfigField>;
107
+ declare function configUnsetRoute(route: string, path: string): Promise<boolean>;
19
108
  declare function backupDump(opts?: {
20
109
  includeAudit?: boolean;
21
110
  }): Promise<Blob>;
@@ -44,8 +133,46 @@ export declare function useAbraAdmin(): {
44
133
  auditExport: typeof auditExport;
45
134
  /** GET /admin/audit/verify — returns the result whether ok or broken. */
46
135
  auditVerify: typeof auditVerify;
136
+ /** GET /admin/users — profiles + revoked + device keys + effective role. */
137
+ listUsers: typeof listUsers;
138
+ /** POST /admin/users/:id/admin (service identity only). */
139
+ promoteUser: typeof promoteUser;
140
+ /** DELETE /admin/users/:id/admin (service identity only). */
141
+ demoteUser: typeof demoteUser;
47
142
  /** POST /admin/users/:id/unlock. */
48
143
  unlockUser: typeof unlockUser;
144
+ /** GET /admin/users/:id/docs — owned + explicitly-granted docs. */
145
+ userDocs: typeof userDocs;
146
+ /** GET /admin/uploads — paginated/filterable, with dedup ref counts. */
147
+ listUploads: typeof listUploads;
148
+ /** GET /admin/storage/stats. */
149
+ storageStats: typeof storageStats;
150
+ /** POST /admin/storage/sweep — GC orphaned blobs. */
151
+ storageSweep: typeof storageSweep;
152
+ /** POST /admin/storage/repair — repair ref-counts + sweep. */
153
+ storageRepair: typeof storageRepair;
154
+ /** POST /admin/snapshots/backfill-refs — populate snapshot_files. */
155
+ snapshotsBackfillRefs: typeof snapshotsBackfillRefs;
156
+ /** POST /admin/snapshots/backfill-blobs — migrate to dedup store. */
157
+ snapshotsBackfillBlobs: typeof snapshotsBackfillBlobs;
158
+ /** GET /admin/config. */
159
+ configList: typeof configList;
160
+ /** GET /admin/config/fields/:path. */
161
+ configGet: typeof configGet;
162
+ /** PUT /admin/config/fields/:path. */
163
+ configSet: typeof configSet;
164
+ /** DELETE /admin/config/fields/:path — revert to base. */
165
+ configUnset: typeof configUnset;
166
+ /** GET /admin/config/env-snapshot. */
167
+ configEnvSnapshot: typeof configEnvSnapshot;
168
+ /** GET /admin/config/routes — routes with ≥1 override. */
169
+ configListRoutes: typeof configListRoutes;
170
+ /** GET /admin/config/routes/:route/fields/:path. */
171
+ configGetRoute: typeof configGetRoute;
172
+ /** PUT /admin/config/routes/:route/fields/:path. */
173
+ configSetRoute: typeof configSetRoute;
174
+ /** DELETE /admin/config/routes/:route/fields/:path. */
175
+ configUnsetRoute: typeof configUnsetRoute;
49
176
  /** GET /admin/backup/dump — returns the tar Blob. */
50
177
  backupDump: typeof backupDump;
51
178
  /** Convenience: backupDump + browser download. */
@@ -13,62 +13,119 @@ function _client() {
13
13
  function _isAdminRole(role) {
14
14
  return role === "admin" || role === "service" || role === "owner";
15
15
  }
16
- async function auditList(opts) {
17
- const { client } = _client();
16
+ async function _run(label, fn) {
18
17
  isLoading.value = true;
19
18
  error.value = null;
20
19
  try {
21
- const rows = await client.adminAuditList(opts);
22
- auditEntries.value = rows;
23
- return rows;
20
+ return await fn();
24
21
  } catch (e) {
25
- error.value = e instanceof Error ? e.message : "Failed to load audit log";
22
+ error.value = e instanceof Error ? e.message : label;
26
23
  throw e;
27
24
  } finally {
28
25
  isLoading.value = false;
29
26
  }
30
27
  }
28
+ async function auditList(opts) {
29
+ const { client } = _client();
30
+ const rows = await _run("Failed to load audit log", () => client.adminAuditList(opts));
31
+ auditEntries.value = rows;
32
+ return rows;
33
+ }
31
34
  async function auditExport(opts) {
32
35
  const { client } = _client();
33
- error.value = null;
34
- try {
35
- return await client.adminAuditExport(opts);
36
- } catch (e) {
37
- error.value = e instanceof Error ? e.message : "Failed to export audit log";
38
- throw e;
39
- }
36
+ return _run("Failed to export audit log", () => client.adminAuditExport(opts));
40
37
  }
41
38
  async function auditVerify() {
42
39
  const { client } = _client();
43
- error.value = null;
44
- try {
45
- const result = await client.adminAuditVerify();
46
- auditVerifyResult.value = result;
47
- return result;
48
- } catch (e) {
49
- error.value = e instanceof Error ? e.message : "Audit verification failed";
50
- throw e;
51
- }
40
+ const result = await _run("Audit verification failed", () => client.adminAuditVerify());
41
+ auditVerifyResult.value = result;
42
+ return result;
43
+ }
44
+ async function listUsers() {
45
+ const { client } = _client();
46
+ const res = await _run("Failed to load users", () => client.adminListUsers());
47
+ return res?.users ?? [];
48
+ }
49
+ async function promoteUser(userId) {
50
+ const { client } = _client();
51
+ await _run("Failed to promote user", () => client.adminPromote(userId));
52
+ }
53
+ async function demoteUser(userId) {
54
+ const { client } = _client();
55
+ await _run("Failed to demote user", () => client.adminDemote(userId));
52
56
  }
53
57
  async function unlockUser(userId) {
54
58
  const { client } = _client();
55
- error.value = null;
56
- try {
57
- await client.adminUnlockUser(userId);
58
- } catch (e) {
59
- error.value = e instanceof Error ? e.message : "Failed to unlock user";
60
- throw e;
61
- }
59
+ await _run("Failed to unlock user", () => client.adminUnlockUser(userId));
60
+ }
61
+ async function userDocs(userId, opts = {}) {
62
+ const { client } = _client();
63
+ const res = await _run("Failed to load user documents", () => client.adminUserDocs(userId, opts));
64
+ return res?.docs ?? [];
65
+ }
66
+ async function listUploads(opts = {}) {
67
+ const { client } = _client();
68
+ return _run("Failed to load uploads", () => client.adminListUploads(opts));
69
+ }
70
+ async function storageStats() {
71
+ const { client } = _client();
72
+ return _run("Failed to load storage stats", () => client.adminStorageStats());
73
+ }
74
+ async function storageSweep() {
75
+ const { client } = _client();
76
+ return _run("Storage sweep failed", () => client.adminStorageSweep());
77
+ }
78
+ async function storageRepair() {
79
+ const { client } = _client();
80
+ return _run("Storage repair failed", () => client.adminStorageRepair());
81
+ }
82
+ async function snapshotsBackfillRefs() {
83
+ const { client } = _client();
84
+ return _run("Snapshot ref backfill failed", () => client.adminSnapshotsBackfillRefs());
85
+ }
86
+ async function snapshotsBackfillBlobs() {
87
+ const { client } = _client();
88
+ return _run("Snapshot blob backfill failed", () => client.adminSnapshotsBackfillBlobs());
89
+ }
90
+ async function configList() {
91
+ const { client } = _client();
92
+ return _run("Failed to load config", () => client.adminConfigList());
93
+ }
94
+ async function configGet(path) {
95
+ const { client } = _client();
96
+ return _run(`Failed to read ${path}`, () => client.adminConfigGet(path));
97
+ }
98
+ async function configSet(path, value) {
99
+ const { client } = _client();
100
+ return _run(`Failed to set ${path}`, () => client.adminConfigSet(path, value));
101
+ }
102
+ async function configUnset(path) {
103
+ const { client } = _client();
104
+ return _run(`Failed to reset ${path}`, () => client.adminConfigUnset(path));
105
+ }
106
+ async function configEnvSnapshot() {
107
+ const { client } = _client();
108
+ return _run("Failed to load env snapshot", () => client.adminConfigEnvSnapshot());
109
+ }
110
+ async function configListRoutes() {
111
+ const { client } = _client();
112
+ return _run("Failed to load route overrides", () => client.adminConfigListRoutes());
113
+ }
114
+ async function configGetRoute(route, path) {
115
+ const { client } = _client();
116
+ return _run(`Failed to read ${path} for ${route}`, () => client.adminConfigGetRoute(route, path));
117
+ }
118
+ async function configSetRoute(route, path, value) {
119
+ const { client } = _client();
120
+ return _run(`Failed to set ${path} for ${route}`, () => client.adminConfigSetRoute(route, path, value));
121
+ }
122
+ async function configUnsetRoute(route, path) {
123
+ const { client } = _client();
124
+ return _run(`Failed to clear ${path} for ${route}`, () => client.adminConfigUnsetRoute(route, path));
62
125
  }
63
126
  async function backupDump(opts) {
64
127
  const { client } = _client();
65
- error.value = null;
66
- try {
67
- return await client.adminBackupDump(opts);
68
- } catch (e) {
69
- error.value = e instanceof Error ? e.message : "Backup dump failed";
70
- throw e;
71
- }
128
+ return _run("Backup dump failed", () => client.adminBackupDump(opts));
72
129
  }
73
130
  async function downloadBackup(opts) {
74
131
  const { abra } = _client();
@@ -99,14 +156,57 @@ export function useAbraAdmin() {
99
156
  isLoading,
100
157
  /** Last error message, or null. */
101
158
  error,
159
+ /* Audit */
102
160
  /** GET /admin/audit. */
103
161
  auditList,
104
162
  /** GET /admin/audit/export — returns NDJSON as a string. */
105
163
  auditExport,
106
164
  /** GET /admin/audit/verify — returns the result whether ok or broken. */
107
165
  auditVerify,
166
+ /* Users */
167
+ /** GET /admin/users — profiles + revoked + device keys + effective role. */
168
+ listUsers,
169
+ /** POST /admin/users/:id/admin (service identity only). */
170
+ promoteUser,
171
+ /** DELETE /admin/users/:id/admin (service identity only). */
172
+ demoteUser,
108
173
  /** POST /admin/users/:id/unlock. */
109
174
  unlockUser,
175
+ /** GET /admin/users/:id/docs — owned + explicitly-granted docs. */
176
+ userDocs,
177
+ /* Storage & uploads */
178
+ /** GET /admin/uploads — paginated/filterable, with dedup ref counts. */
179
+ listUploads,
180
+ /** GET /admin/storage/stats. */
181
+ storageStats,
182
+ /** POST /admin/storage/sweep — GC orphaned blobs. */
183
+ storageSweep,
184
+ /** POST /admin/storage/repair — repair ref-counts + sweep. */
185
+ storageRepair,
186
+ /** POST /admin/snapshots/backfill-refs — populate snapshot_files. */
187
+ snapshotsBackfillRefs,
188
+ /** POST /admin/snapshots/backfill-blobs — migrate to dedup store. */
189
+ snapshotsBackfillBlobs,
190
+ /* Runtime config */
191
+ /** GET /admin/config. */
192
+ configList,
193
+ /** GET /admin/config/fields/:path. */
194
+ configGet,
195
+ /** PUT /admin/config/fields/:path. */
196
+ configSet,
197
+ /** DELETE /admin/config/fields/:path — revert to base. */
198
+ configUnset,
199
+ /** GET /admin/config/env-snapshot. */
200
+ configEnvSnapshot,
201
+ /** GET /admin/config/routes — routes with ≥1 override. */
202
+ configListRoutes,
203
+ /** GET /admin/config/routes/:route/fields/:path. */
204
+ configGetRoute,
205
+ /** PUT /admin/config/routes/:route/fields/:path. */
206
+ configSetRoute,
207
+ /** DELETE /admin/config/routes/:route/fields/:path. */
208
+ configUnsetRoute,
209
+ /* Backup */
110
210
  /** GET /admin/backup/dump — returns the tar Blob. */
111
211
  backupDump,
112
212
  /** Convenience: backupDump + browser download. */
@@ -14,6 +14,9 @@ export interface Invite {
14
14
  max_uses: number | null;
15
15
  expires_at: number | null;
16
16
  created_at: number;
17
+ created_by: string | null;
18
+ /** True once the code has been revoked — it can no longer be redeemed. */
19
+ revoked: boolean;
17
20
  }
18
21
  declare function refresh(): Promise<void>;
19
22
  declare function create(opts?: {
@@ -32,6 +35,8 @@ export declare function useInvites(): {
32
35
  max_uses: number | null;
33
36
  expires_at: number | null;
34
37
  created_at: number;
38
+ created_by: string | null;
39
+ revoked: boolean;
35
40
  }[], Invite[] | {
36
41
  code: string;
37
42
  role: string;
@@ -39,6 +44,8 @@ export declare function useInvites(): {
39
44
  max_uses: number | null;
40
45
  expires_at: number | null;
41
46
  created_at: number;
47
+ created_by: string | null;
48
+ revoked: boolean;
42
49
  }[]>;
43
50
  /** Whether invite list is loading. */
44
51
  loading: import("vue").Ref<boolean, boolean>;
@@ -768,13 +768,25 @@ export default defineNuxtPlugin({
768
768
  }
769
769
  }
770
770
  }
771
+ function _normalizeInvite(r) {
772
+ return {
773
+ code: r.code,
774
+ role: r.role,
775
+ max_uses: r.maxUses ?? 0,
776
+ uses: r.useCount ?? 0,
777
+ created_at: r.createdAt ?? 0,
778
+ expires_at: r.expiresAt ?? null,
779
+ created_by: r.createdBy ?? null,
780
+ revoked: !!r.revoked
781
+ };
782
+ }
771
783
  async function createInvite(opts) {
772
784
  if (!client.value) throw new Error("Not connected");
773
- return client.value.createInvite(opts);
785
+ return _normalizeInvite(await client.value.createInvite(opts));
774
786
  }
775
787
  async function listInvites() {
776
788
  if (!client.value) throw new Error("Not connected");
777
- return client.value.listInvites();
789
+ return (await client.value.listInvites()).map(_normalizeInvite);
778
790
  }
779
791
  async function revokeInvite(code) {
780
792
  if (!client.value) throw new Error("Not connected");
@@ -1062,6 +1074,12 @@ export default defineNuxtPlugin({
1062
1074
  document: _doc,
1063
1075
  websocketProvider: wsp,
1064
1076
  client: _client,
1077
+ // The provider lives for the whole app lifetime on one socket
1078
+ // that rarely drops, so it never re-issues sync-step-1 on its
1079
+ // own — doc-tree entries written by an MCP / sibling client
1080
+ // stay invisible until a full page reload. Periodically re-diff
1081
+ // against the server to surface them.
1082
+ forceSyncInterval: 3e4,
1065
1083
  // Route every WS auth handshake (initial + reconnects) through
1066
1084
  // the TokenManager so an expired JWT is silently refreshed via
1067
1085
  // the device session before being sent. Without this, a tab
@@ -1352,6 +1370,13 @@ export default defineNuxtPlugin({
1352
1370
  _wsp.connect();
1353
1371
  } catch {
1354
1372
  }
1373
+ return;
1374
+ }
1375
+ if (status.value === "connected") {
1376
+ try {
1377
+ provider.value?.forceSync();
1378
+ } catch {
1379
+ }
1355
1380
  }
1356
1381
  }
1357
1382
  window.addEventListener("focus", _kickConnection);
@@ -1360,10 +1385,26 @@ export default defineNuxtPlugin({
1360
1385
  if (document.visibilityState === "visible") _kickConnection();
1361
1386
  });
1362
1387
  loadServers();
1388
+ let deepLinkServer = "";
1389
+ try {
1390
+ const qp = new URLSearchParams(window.location.search);
1391
+ const hp = new URLSearchParams(window.location.hash.replace(/^#/, ""));
1392
+ const raw = (qp.get("server") || hp.get("server") || "").trim();
1393
+ if (raw) {
1394
+ let u = raw;
1395
+ if (!u.startsWith("http://") && !u.startsWith("https://")) u = "https://" + u;
1396
+ deepLinkServer = u.replace(/\/$/, "");
1397
+ }
1398
+ } catch {
1399
+ }
1363
1400
  const storedServer = localStorage.getItem(CURRENT_SERVER_KEY);
1364
- const initialUrl = storedServer && savedServers.value.some((s) => s.url === storedServer) ? storedServer : currentServerUrl.value || savedServers.value[0]?.url || defaultUrl;
1401
+ const initialUrl = deepLinkServer || (storedServer && savedServers.value.some((s) => s.url === storedServer) ? storedServer : currentServerUrl.value || savedServers.value[0]?.url || defaultUrl);
1365
1402
  if (initialUrl !== currentServerUrl.value) currentServerUrl.value = initialUrl;
1366
1403
  _initPromise = init(initialUrl);
1404
+ if (deepLinkServer && !savedServers.value.some((s) => s.url === deepLinkServer)) {
1405
+ void addServer(deepLinkServer).catch(() => {
1406
+ });
1407
+ }
1367
1408
  _initPromise.then(async () => {
1368
1409
  if (!provider.value) return;
1369
1410
  const ctx = { abracadabra: abra };
@@ -1,2 +1,2 @@
1
- declare const _default: import("h3").EventHandler<Omit<import("h3").EventHandlerRequest, "body">, Promise<unknown[] | never[]>>;
1
+ declare const _default: import("h3").EventHandler<Omit<import("h3").EventHandlerRequest, "body">, Promise<never[] | unknown[]>>;
2
2
  export default _default;
@@ -341,6 +341,8 @@ export interface InviteInfo {
341
341
  created_at: number;
342
342
  expires_at: number | null;
343
343
  created_by: string | null;
344
+ /** True once the code has been revoked — it can no longer be redeemed. */
345
+ revoked: boolean;
344
346
  }
345
347
  export interface SavedServer {
346
348
  url: string;
package/dist/types.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- export { type AbracadabraPlugin, type AbracadabraState, type CachedTreeNode, type DocCacheAPI, type DocPageMeta, type RunnerCleanup, type ServerRunnerContext, type ServerRunnerDefinition, type TreeEntry, type UserMetaField } from '../dist/runtime/types.js'
1
+ export { type AbracadabraPlugin, type AbracadabraState, type CachedTreeNode, type DocCacheAPI, type DocPageMeta, type InviteInfo, type RunnerCleanup, type ServerRunnerContext, type ServerRunnerDefinition, type SpaceMeta, type TreeEntry, type UserMetaField } from '../dist/runtime/types.js'
2
2
 
3
3
  export { type AbracadabraLocale } from '../dist/runtime/locale.js'
4
4
 
@@ -12,10 +12,14 @@ export { type ParsedMessage, type TextSegment, type parseMessageContent, type pr
12
12
 
13
13
  export { type DupEntry, type duplicateDocContent, type duplicateDocTree } from '../dist/runtime/utils/duplicateDocContent.js'
14
14
 
15
- export { type AuditLogEntry, type AuditQueryOpts, type AuditVerifyResult, type DocSearchHit, type ReadyzStatus } from '@abraca/dabra'
15
+ export { type AdminConfigField, type AuditLogEntry, type AuditQueryOpts, type AuditVerifyResult, type DocSearchHit, type EnvSnapshotResponse, type ReadyzStatus } from '@abraca/dabra'
16
16
 
17
17
  export { type ServerTrashEntry } from '../dist/runtime/composables/useServerTrash.js'
18
18
 
19
+ export { type Invite } from '../dist/runtime/composables/useInvites.js'
20
+
21
+ export { type AdminStorageStats, type AdminUploadList, type AdminUploadRow, type AdminUserDocRow, type AdminUserRow } from '../dist/runtime/composables/useAbraAdmin.js'
22
+
19
23
  export { type SlugMap, type SlugMapEntry, type buildSlugMap, type isUUID, type slugify } from '../dist/runtime/utils/slugify.js'
20
24
 
21
25
  export { type avatarBorderStyle, type avatarGradient, type avatarStyleFromName } from '../dist/runtime/utils/avatarStyle.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abraca/nuxt",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "First-class Nuxt module for the Abracadabra CRDT collaboration platform",
5
5
  "repository": "abracadabra/abracadabra-nuxt",
6
6
  "license": "MIT",
@@ -26,16 +26,16 @@
26
26
  "access": "public"
27
27
  },
28
28
  "dependencies": {
29
- "@nuxt/kit": "^4.4.4",
29
+ "@nuxt/kit": "^4.4.6",
30
30
  "@vueuse/core": "^14.3.0",
31
31
  "defu": "^6.1.7",
32
32
  "nanoevents": "^9.1.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "@abraca/dabra": "^2.3.0",
36
- "@abraca/convert": "^2.3.0",
37
- "@abraca/plugin": "^2.3.0",
38
- "@abraca/schema": "^2.3.0",
35
+ "@abraca/convert": "^2.5.0",
36
+ "@abraca/dabra": "^2.5.0",
37
+ "@abraca/plugin": "^2.5.0",
38
+ "@abraca/schema": "^2.5.0",
39
39
  "@codemirror/autocomplete": "^6.18.0",
40
40
  "@codemirror/commands": "^6.7.0",
41
41
  "@codemirror/lang-css": "^6.3.1",
@@ -46,7 +46,6 @@
46
46
  "@codemirror/search": "^6.5.0",
47
47
  "@codemirror/state": "^6.6.0",
48
48
  "@codemirror/view": "^6.42.0",
49
- "y-codemirror.next": "^0.3.5",
50
49
  "@noble/ed25519": "^3.1.0",
51
50
  "@noble/hashes": "^2.2.0",
52
51
  "@nuxt/ui": "^3.0.0",
@@ -78,6 +77,7 @@
78
77
  "nuxt": "^4.0.0",
79
78
  "three": "^0.184.0",
80
79
  "vue": "^3.4.0",
80
+ "y-codemirror.next": "^0.3.5",
81
81
  "yjs": "^13.0.0"
82
82
  },
83
83
  "peerDependenciesMeta": {
@@ -137,9 +137,9 @@
137
137
  }
138
138
  },
139
139
  "devDependencies": {
140
- "@abraca/dabra": "^2.3.0",
141
- "@abraca/convert": "^2.3.0",
142
- "@abraca/plugin": "^2.3.0",
140
+ "@abraca/convert": "^2.5.0",
141
+ "@abraca/dabra": "^2.5.0",
142
+ "@abraca/plugin": "^2.5.0",
143
143
  "@codemirror/autocomplete": "^6.18.0",
144
144
  "@codemirror/commands": "^6.7.0",
145
145
  "@codemirror/lang-css": "^6.3.1",
@@ -150,35 +150,35 @@
150
150
  "@codemirror/search": "^6.5.0",
151
151
  "@codemirror/state": "^6.6.0",
152
152
  "@codemirror/view": "^6.42.0",
153
- "y-codemirror.next": "^0.3.5",
154
- "@iconify-json/lucide": "^1.2.105",
153
+ "@iconify-json/lucide": "^1.2.108",
155
154
  "@noble/ed25519": "^3.1.0",
156
155
  "@noble/hashes": "^2.2.0",
157
156
  "@nuxt/eslint-config": "^1.15.2",
158
157
  "@nuxt/module-builder": "^1.0.2",
159
- "@nuxt/schema": "^4.4.4",
158
+ "@nuxt/schema": "^4.4.6",
160
159
  "@nuxt/test-utils": "^4.0.3",
161
160
  "@nuxt/ui": "^4.7.1",
162
161
  "@playwright/test": "^1.59.1",
163
- "@tiptap/core": "^3.22.5",
164
- "@tiptap/extension-collaboration": "^3.22.5",
165
- "@tiptap/extension-collaboration-caret": "^3.22.5",
166
- "@tiptap/vue-3": "^3.22.5",
162
+ "@tiptap/core": "^3.23.5",
163
+ "@tiptap/extension-collaboration": "^3.23.5",
164
+ "@tiptap/extension-collaboration-caret": "^3.23.5",
165
+ "@tiptap/vue-3": "^3.23.5",
167
166
  "@types/d3-force": "^3.0.10",
168
167
  "@types/node": "latest",
169
168
  "@types/ws": "^8.18.1",
170
169
  "@vue/test-utils": "^2.4.10",
171
170
  "changelogen": "^0.6.2",
172
171
  "d3-force": "^3.0.0",
173
- "eslint": "^10.3.0",
172
+ "eslint": "^10.4.0",
174
173
  "happy-dom": "^20.9.0",
175
- "nuxt": "^4.4.4",
174
+ "nuxt": "^4.4.6",
176
175
  "tslib": "^2.8.1",
177
176
  "typescript": "~6.0.3",
178
177
  "vitest": "^4.1.5",
179
178
  "vue": "^3.5.34",
180
- "vue-tsc": "^3.2.8",
179
+ "vue-tsc": "^3.3.1",
181
180
  "ws": "^8.20.0",
181
+ "y-codemirror.next": "^0.3.5",
182
182
  "yjs": "^13.6.30"
183
183
  },
184
184
  "scripts": {