@hypercerts-org/sdk-core 0.2.0-beta.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/.turbo/turbo-build.log +328 -0
- package/.turbo/turbo-test.log +118 -0
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +100 -0
- package/dist/errors.cjs +260 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.ts +233 -0
- package/dist/errors.mjs +253 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.cjs +4531 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +3430 -0
- package/dist/index.mjs +4448 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lexicons.cjs +420 -0
- package/dist/lexicons.cjs.map +1 -0
- package/dist/lexicons.d.ts +227 -0
- package/dist/lexicons.mjs +410 -0
- package/dist/lexicons.mjs.map +1 -0
- package/dist/storage.cjs +270 -0
- package/dist/storage.cjs.map +1 -0
- package/dist/storage.d.ts +474 -0
- package/dist/storage.mjs +267 -0
- package/dist/storage.mjs.map +1 -0
- package/dist/testing.cjs +415 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.ts +928 -0
- package/dist/testing.mjs +410 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/types.cjs +220 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.ts +2118 -0
- package/dist/types.mjs +212 -0
- package/dist/types.mjs.map +1 -0
- package/eslint.config.mjs +22 -0
- package/package.json +90 -0
- package/rollup.config.js +75 -0
- package/src/auth/OAuthClient.ts +497 -0
- package/src/core/SDK.ts +410 -0
- package/src/core/config.ts +243 -0
- package/src/core/errors.ts +257 -0
- package/src/core/interfaces.ts +324 -0
- package/src/core/types.ts +281 -0
- package/src/errors.ts +57 -0
- package/src/index.ts +107 -0
- package/src/lexicons.ts +64 -0
- package/src/repository/BlobOperationsImpl.ts +199 -0
- package/src/repository/CollaboratorOperationsImpl.ts +288 -0
- package/src/repository/HypercertOperationsImpl.ts +1146 -0
- package/src/repository/LexiconRegistry.ts +332 -0
- package/src/repository/OrganizationOperationsImpl.ts +234 -0
- package/src/repository/ProfileOperationsImpl.ts +281 -0
- package/src/repository/RecordOperationsImpl.ts +340 -0
- package/src/repository/Repository.ts +482 -0
- package/src/repository/interfaces.ts +868 -0
- package/src/repository/types.ts +111 -0
- package/src/services/hypercerts/types.ts +87 -0
- package/src/storage/InMemorySessionStore.ts +127 -0
- package/src/storage/InMemoryStateStore.ts +146 -0
- package/src/storage.ts +63 -0
- package/src/testing/index.ts +67 -0
- package/src/testing/mocks.ts +142 -0
- package/src/testing/stores.ts +285 -0
- package/src/testing.ts +64 -0
- package/src/types.ts +86 -0
- package/tests/auth/OAuthClient.test.ts +164 -0
- package/tests/core/SDK.test.ts +176 -0
- package/tests/core/errors.test.ts +81 -0
- package/tests/repository/BlobOperationsImpl.test.ts +154 -0
- package/tests/repository/CollaboratorOperationsImpl.test.ts +323 -0
- package/tests/repository/HypercertOperationsImpl.test.ts +652 -0
- package/tests/repository/LexiconRegistry.test.ts +192 -0
- package/tests/repository/OrganizationOperationsImpl.test.ts +242 -0
- package/tests/repository/ProfileOperationsImpl.test.ts +254 -0
- package/tests/repository/RecordOperationsImpl.test.ts +375 -0
- package/tests/repository/Repository.test.ts +149 -0
- package/tests/utils/fixtures.ts +117 -0
- package/tests/utils/mocks.ts +109 -0
- package/tests/utils/repository-fixtures.ts +78 -0
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +30 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CollaboratorOperationsImpl - SDS collaborator management operations.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the implementation for managing collaborator
|
|
5
|
+
* access on Shared Data Server (SDS) repositories.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { NetworkError } from "../core/errors.js";
|
|
11
|
+
import type { CollaboratorPermissions, Session } from "../core/types.js";
|
|
12
|
+
import type { CollaboratorOperations } from "./interfaces.js";
|
|
13
|
+
import type { RepositoryRole, RepositoryAccessGrant } from "./types.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Implementation of collaborator operations for SDS access control.
|
|
17
|
+
*
|
|
18
|
+
* This class manages access permissions for shared repositories on
|
|
19
|
+
* Shared Data Servers (SDS). It provides role-based access control
|
|
20
|
+
* with predefined permission sets.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* This class is typically not instantiated directly. Access it through
|
|
24
|
+
* {@link Repository.collaborators} on an SDS-connected repository.
|
|
25
|
+
*
|
|
26
|
+
* **Role Hierarchy**:
|
|
27
|
+
* - `viewer`: Read-only access
|
|
28
|
+
* - `editor`: Read + Create + Update
|
|
29
|
+
* - `admin`: All permissions except ownership transfer
|
|
30
|
+
* - `owner`: Full control including ownership management
|
|
31
|
+
*
|
|
32
|
+
* **SDS API Endpoints Used**:
|
|
33
|
+
* - `com.atproto.sds.grantAccess`: Grant access to a user
|
|
34
|
+
* - `com.atproto.sds.revokeAccess`: Revoke access from a user
|
|
35
|
+
* - `com.atproto.sds.listCollaborators`: List all collaborators
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* // Get SDS repository
|
|
40
|
+
* const sdsRepo = sdk.repository(session, { server: "sds" });
|
|
41
|
+
*
|
|
42
|
+
* // Grant editor access
|
|
43
|
+
* await sdsRepo.collaborators.grant({
|
|
44
|
+
* userDid: "did:plc:new-user",
|
|
45
|
+
* role: "editor",
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* // List all collaborators
|
|
49
|
+
* const collaborators = await sdsRepo.collaborators.list();
|
|
50
|
+
*
|
|
51
|
+
* // Check specific user
|
|
52
|
+
* const hasAccess = await sdsRepo.collaborators.hasAccess("did:plc:someone");
|
|
53
|
+
* const role = await sdsRepo.collaborators.getRole("did:plc:someone");
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
export class CollaboratorOperationsImpl implements CollaboratorOperations {
|
|
59
|
+
/**
|
|
60
|
+
* Creates a new CollaboratorOperationsImpl.
|
|
61
|
+
*
|
|
62
|
+
* @param session - Authenticated OAuth session with fetchHandler
|
|
63
|
+
* @param repoDid - DID of the repository to manage
|
|
64
|
+
* @param serverUrl - SDS server URL
|
|
65
|
+
*
|
|
66
|
+
* @internal
|
|
67
|
+
*/
|
|
68
|
+
constructor(
|
|
69
|
+
private session: Session,
|
|
70
|
+
private repoDid: string,
|
|
71
|
+
private serverUrl: string,
|
|
72
|
+
) {}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Converts a role to its corresponding permissions object.
|
|
76
|
+
*
|
|
77
|
+
* @param role - The role to convert
|
|
78
|
+
* @returns Permission flags for the role
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
private roleToPermissions(role: RepositoryRole): CollaboratorPermissions {
|
|
82
|
+
switch (role) {
|
|
83
|
+
case "viewer":
|
|
84
|
+
return { read: true, create: false, update: false, delete: false, admin: false, owner: false };
|
|
85
|
+
case "editor":
|
|
86
|
+
return { read: true, create: true, update: true, delete: false, admin: false, owner: false };
|
|
87
|
+
case "admin":
|
|
88
|
+
return { read: true, create: true, update: true, delete: true, admin: true, owner: false };
|
|
89
|
+
case "owner":
|
|
90
|
+
return { read: true, create: true, update: true, delete: true, admin: true, owner: true };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Determines the role from a permissions object.
|
|
96
|
+
*
|
|
97
|
+
* @param permissions - The permissions to analyze
|
|
98
|
+
* @returns The highest role matching the permissions
|
|
99
|
+
* @internal
|
|
100
|
+
*/
|
|
101
|
+
private permissionsToRole(permissions: CollaboratorPermissions): RepositoryRole {
|
|
102
|
+
if (permissions.owner) return "owner";
|
|
103
|
+
if (permissions.admin) return "admin";
|
|
104
|
+
if (permissions.create || permissions.update) return "editor";
|
|
105
|
+
return "viewer";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Grants repository access to a user.
|
|
110
|
+
*
|
|
111
|
+
* @param params - Grant parameters
|
|
112
|
+
* @param params.userDid - DID of the user to grant access to
|
|
113
|
+
* @param params.role - Role to assign (determines permissions)
|
|
114
|
+
* @throws {@link NetworkError} if the grant operation fails
|
|
115
|
+
*
|
|
116
|
+
* @remarks
|
|
117
|
+
* If the user already has access, their permissions are updated
|
|
118
|
+
* to the new role.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* // Grant viewer access
|
|
123
|
+
* await repo.collaborators.grant({
|
|
124
|
+
* userDid: "did:plc:viewer-user",
|
|
125
|
+
* role: "viewer",
|
|
126
|
+
* });
|
|
127
|
+
*
|
|
128
|
+
* // Upgrade to editor
|
|
129
|
+
* await repo.collaborators.grant({
|
|
130
|
+
* userDid: "did:plc:viewer-user",
|
|
131
|
+
* role: "editor",
|
|
132
|
+
* });
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
async grant(params: { userDid: string; role: RepositoryRole }): Promise<void> {
|
|
136
|
+
const permissions = this.roleToPermissions(params.role);
|
|
137
|
+
|
|
138
|
+
const response = await this.session.fetchHandler(`${this.serverUrl}/xrpc/com.atproto.sds.grantAccess`, {
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers: { "Content-Type": "application/json" },
|
|
141
|
+
body: JSON.stringify({
|
|
142
|
+
repo: this.repoDid,
|
|
143
|
+
userDid: params.userDid,
|
|
144
|
+
permissions,
|
|
145
|
+
}),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
throw new NetworkError(`Failed to grant access: ${response.statusText}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Revokes repository access from a user.
|
|
155
|
+
*
|
|
156
|
+
* @param params - Revoke parameters
|
|
157
|
+
* @param params.userDid - DID of the user to revoke access from
|
|
158
|
+
* @throws {@link NetworkError} if the revoke operation fails
|
|
159
|
+
*
|
|
160
|
+
* @remarks
|
|
161
|
+
* - Cannot revoke access from the repository owner
|
|
162
|
+
* - Revoked access is recorded with a `revokedAt` timestamp
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* await repo.collaborators.revoke({
|
|
167
|
+
* userDid: "did:plc:former-collaborator",
|
|
168
|
+
* });
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
async revoke(params: { userDid: string }): Promise<void> {
|
|
172
|
+
const response = await this.session.fetchHandler(`${this.serverUrl}/xrpc/com.atproto.sds.revokeAccess`, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: { "Content-Type": "application/json" },
|
|
175
|
+
body: JSON.stringify({
|
|
176
|
+
repo: this.repoDid,
|
|
177
|
+
userDid: params.userDid,
|
|
178
|
+
}),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
throw new NetworkError(`Failed to revoke access: ${response.statusText}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Lists all collaborators on the repository.
|
|
188
|
+
*
|
|
189
|
+
* @returns Promise resolving to array of access grants
|
|
190
|
+
* @throws {@link NetworkError} if the list operation fails
|
|
191
|
+
*
|
|
192
|
+
* @remarks
|
|
193
|
+
* The list includes both active and revoked collaborators.
|
|
194
|
+
* Check `revokedAt` to filter active collaborators.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```typescript
|
|
198
|
+
* const collaborators = await repo.collaborators.list();
|
|
199
|
+
*
|
|
200
|
+
* // Filter active collaborators
|
|
201
|
+
* const active = collaborators.filter(c => !c.revokedAt);
|
|
202
|
+
*
|
|
203
|
+
* // Group by role
|
|
204
|
+
* const byRole = {
|
|
205
|
+
* owners: active.filter(c => c.role === "owner"),
|
|
206
|
+
* admins: active.filter(c => c.role === "admin"),
|
|
207
|
+
* editors: active.filter(c => c.role === "editor"),
|
|
208
|
+
* viewers: active.filter(c => c.role === "viewer"),
|
|
209
|
+
* };
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
async list(): Promise<RepositoryAccessGrant[]> {
|
|
213
|
+
const response = await this.session.fetchHandler(
|
|
214
|
+
`${this.serverUrl}/xrpc/com.atproto.sds.listCollaborators?repo=${encodeURIComponent(this.repoDid)}`,
|
|
215
|
+
{ method: "GET" },
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
if (!response.ok) {
|
|
219
|
+
throw new NetworkError(`Failed to list collaborators: ${response.statusText}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const data = await response.json();
|
|
223
|
+
return (data.collaborators || []).map(
|
|
224
|
+
(c: {
|
|
225
|
+
userDid: string;
|
|
226
|
+
permissions: CollaboratorPermissions;
|
|
227
|
+
grantedBy: string;
|
|
228
|
+
grantedAt: string;
|
|
229
|
+
revokedAt?: string;
|
|
230
|
+
}) => ({
|
|
231
|
+
userDid: c.userDid,
|
|
232
|
+
role: this.permissionsToRole(c.permissions),
|
|
233
|
+
permissions: c.permissions,
|
|
234
|
+
grantedBy: c.grantedBy,
|
|
235
|
+
grantedAt: c.grantedAt,
|
|
236
|
+
revokedAt: c.revokedAt,
|
|
237
|
+
}),
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Checks if a user has any access to the repository.
|
|
243
|
+
*
|
|
244
|
+
* @param userDid - DID of the user to check
|
|
245
|
+
* @returns Promise resolving to `true` if user has active access
|
|
246
|
+
*
|
|
247
|
+
* @remarks
|
|
248
|
+
* Returns `false` if:
|
|
249
|
+
* - User was never granted access
|
|
250
|
+
* - User's access was revoked
|
|
251
|
+
* - The list operation fails (error is suppressed)
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* ```typescript
|
|
255
|
+
* if (await repo.collaborators.hasAccess("did:plc:someone")) {
|
|
256
|
+
* console.log("User has access");
|
|
257
|
+
* }
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
async hasAccess(userDid: string): Promise<boolean> {
|
|
261
|
+
try {
|
|
262
|
+
const collaborators = await this.list();
|
|
263
|
+
return collaborators.some((c) => c.userDid === userDid && !c.revokedAt);
|
|
264
|
+
} catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Gets the role assigned to a user.
|
|
271
|
+
*
|
|
272
|
+
* @param userDid - DID of the user to check
|
|
273
|
+
* @returns Promise resolving to the user's role, or `null` if no active access
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* ```typescript
|
|
277
|
+
* const role = await repo.collaborators.getRole("did:plc:someone");
|
|
278
|
+
* if (role === "admin" || role === "owner") {
|
|
279
|
+
* // User can manage other collaborators
|
|
280
|
+
* }
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
283
|
+
async getRole(userDid: string): Promise<RepositoryRole | null> {
|
|
284
|
+
const collaborators = await this.list();
|
|
285
|
+
const collab = collaborators.find((c) => c.userDid === userDid && !c.revokedAt);
|
|
286
|
+
return collab?.role ?? null;
|
|
287
|
+
}
|
|
288
|
+
}
|