@hypercerts-org/sdk-core 0.2.0-beta.0 → 0.5.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 +13 -301
- package/.turbo/turbo-test.log +30 -29
- package/CHANGELOG.md +49 -3
- package/README.md +473 -50
- package/dist/index.cjs +232 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +57 -19
- package/dist/index.mjs +232 -55
- package/dist/index.mjs.map +1 -1
- package/dist/types.cjs +3 -2
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.ts +57 -19
- package/dist/types.mjs +3 -2
- package/dist/types.mjs.map +1 -1
- package/package.json +2 -2
- package/src/core/types.ts +3 -2
- package/src/repository/BlobOperationsImpl.ts +1 -1
- package/src/repository/CollaboratorOperationsImpl.ts +184 -30
- package/src/repository/HypercertOperationsImpl.ts +3 -3
- package/src/repository/OrganizationOperationsImpl.ts +70 -22
- package/src/repository/interfaces.ts +45 -4
- package/src/repository/types.ts +1 -1
- package/tests/repository/BlobOperationsImpl.test.ts +10 -9
- package/tests/repository/CollaboratorOperationsImpl.test.ts +138 -23
- package/tests/repository/OrganizationOperationsImpl.test.ts +18 -20
|
@@ -30,9 +30,11 @@ import type { RepositoryRole, RepositoryAccessGrant } from "./types.js";
|
|
|
30
30
|
* - `owner`: Full control including ownership management
|
|
31
31
|
*
|
|
32
32
|
* **SDS API Endpoints Used**:
|
|
33
|
-
* - `com.
|
|
34
|
-
* - `com.
|
|
35
|
-
* - `com.
|
|
33
|
+
* - `com.sds.repo.grantAccess`: Grant access to a user
|
|
34
|
+
* - `com.sds.repo.revokeAccess`: Revoke access from a user
|
|
35
|
+
* - `com.sds.repo.listCollaborators`: List all collaborators
|
|
36
|
+
* - `com.sds.repo.getPermissions`: Get current user's permissions
|
|
37
|
+
* - `com.sds.repo.transferOwnership`: Transfer repository ownership
|
|
36
38
|
*
|
|
37
39
|
* @example
|
|
38
40
|
* ```typescript
|
|
@@ -105,6 +107,27 @@ export class CollaboratorOperationsImpl implements CollaboratorOperations {
|
|
|
105
107
|
return "viewer";
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Converts a permission string array to a permissions object.
|
|
112
|
+
*
|
|
113
|
+
* The SDS API returns permissions as an array of strings (e.g., ["read", "create"]).
|
|
114
|
+
* This method converts them to the boolean flag format used by the SDK.
|
|
115
|
+
*
|
|
116
|
+
* @param permissionArray - Array of permission strings from SDS API
|
|
117
|
+
* @returns Permission flags object
|
|
118
|
+
* @internal
|
|
119
|
+
*/
|
|
120
|
+
private parsePermissions(permissionArray: string[]): CollaboratorPermissions {
|
|
121
|
+
return {
|
|
122
|
+
read: permissionArray.includes("read"),
|
|
123
|
+
create: permissionArray.includes("create"),
|
|
124
|
+
update: permissionArray.includes("update"),
|
|
125
|
+
delete: permissionArray.includes("delete"),
|
|
126
|
+
admin: permissionArray.includes("admin"),
|
|
127
|
+
owner: permissionArray.includes("owner"),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
108
131
|
/**
|
|
109
132
|
* Grants repository access to a user.
|
|
110
133
|
*
|
|
@@ -135,7 +158,7 @@ export class CollaboratorOperationsImpl implements CollaboratorOperations {
|
|
|
135
158
|
async grant(params: { userDid: string; role: RepositoryRole }): Promise<void> {
|
|
136
159
|
const permissions = this.roleToPermissions(params.role);
|
|
137
160
|
|
|
138
|
-
const response = await this.session.fetchHandler(`${this.serverUrl}/xrpc/com.
|
|
161
|
+
const response = await this.session.fetchHandler(`${this.serverUrl}/xrpc/com.sds.repo.grantAccess`, {
|
|
139
162
|
method: "POST",
|
|
140
163
|
headers: { "Content-Type": "application/json" },
|
|
141
164
|
body: JSON.stringify({
|
|
@@ -169,7 +192,7 @@ export class CollaboratorOperationsImpl implements CollaboratorOperations {
|
|
|
169
192
|
* ```
|
|
170
193
|
*/
|
|
171
194
|
async revoke(params: { userDid: string }): Promise<void> {
|
|
172
|
-
const response = await this.session.fetchHandler(`${this.serverUrl}/xrpc/com.
|
|
195
|
+
const response = await this.session.fetchHandler(`${this.serverUrl}/xrpc/com.sds.repo.revokeAccess`, {
|
|
173
196
|
method: "POST",
|
|
174
197
|
headers: { "Content-Type": "application/json" },
|
|
175
198
|
body: JSON.stringify({
|
|
@@ -186,7 +209,10 @@ export class CollaboratorOperationsImpl implements CollaboratorOperations {
|
|
|
186
209
|
/**
|
|
187
210
|
* Lists all collaborators on the repository.
|
|
188
211
|
*
|
|
189
|
-
* @
|
|
212
|
+
* @param params - Optional pagination parameters
|
|
213
|
+
* @param params.limit - Maximum number of results (1-100, default 50)
|
|
214
|
+
* @param params.cursor - Pagination cursor from previous response
|
|
215
|
+
* @returns Promise resolving to collaborators and optional cursor
|
|
190
216
|
* @throws {@link NetworkError} if the list operation fails
|
|
191
217
|
*
|
|
192
218
|
* @remarks
|
|
@@ -195,23 +221,37 @@ export class CollaboratorOperationsImpl implements CollaboratorOperations {
|
|
|
195
221
|
*
|
|
196
222
|
* @example
|
|
197
223
|
* ```typescript
|
|
198
|
-
*
|
|
224
|
+
* // Get first page
|
|
225
|
+
* const page1 = await repo.collaborators.list({ limit: 10 });
|
|
226
|
+
* console.log(`Found ${page1.collaborators.length} collaborators`);
|
|
227
|
+
*
|
|
228
|
+
* // Get next page if available
|
|
229
|
+
* if (page1.cursor) {
|
|
230
|
+
* const page2 = await repo.collaborators.list({ limit: 10, cursor: page1.cursor });
|
|
231
|
+
* }
|
|
199
232
|
*
|
|
200
233
|
* // 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
|
-
* };
|
|
234
|
+
* const active = page1.collaborators.filter(c => !c.revokedAt);
|
|
210
235
|
* ```
|
|
211
236
|
*/
|
|
212
|
-
async list(): Promise<
|
|
237
|
+
async list(params?: { limit?: number; cursor?: string }): Promise<{
|
|
238
|
+
collaborators: RepositoryAccessGrant[];
|
|
239
|
+
cursor?: string;
|
|
240
|
+
}> {
|
|
241
|
+
const queryParams = new URLSearchParams({
|
|
242
|
+
repo: this.repoDid,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (params?.limit !== undefined) {
|
|
246
|
+
queryParams.set("limit", params.limit.toString());
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (params?.cursor) {
|
|
250
|
+
queryParams.set("cursor", params.cursor);
|
|
251
|
+
}
|
|
252
|
+
|
|
213
253
|
const response = await this.session.fetchHandler(
|
|
214
|
-
`${this.serverUrl}/xrpc/com.
|
|
254
|
+
`${this.serverUrl}/xrpc/com.sds.repo.listCollaborators?${queryParams.toString()}`,
|
|
215
255
|
{ method: "GET" },
|
|
216
256
|
);
|
|
217
257
|
|
|
@@ -220,22 +260,30 @@ export class CollaboratorOperationsImpl implements CollaboratorOperations {
|
|
|
220
260
|
}
|
|
221
261
|
|
|
222
262
|
const data = await response.json();
|
|
223
|
-
|
|
263
|
+
const collaborators = (data.collaborators || []).map(
|
|
224
264
|
(c: {
|
|
225
265
|
userDid: string;
|
|
226
|
-
permissions:
|
|
266
|
+
permissions: string[]; // SDS API returns string array
|
|
227
267
|
grantedBy: string;
|
|
228
268
|
grantedAt: string;
|
|
229
269
|
revokedAt?: string;
|
|
230
|
-
}) =>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
270
|
+
}) => {
|
|
271
|
+
const permissions = this.parsePermissions(c.permissions);
|
|
272
|
+
return {
|
|
273
|
+
userDid: c.userDid,
|
|
274
|
+
role: this.permissionsToRole(permissions),
|
|
275
|
+
permissions: permissions,
|
|
276
|
+
grantedBy: c.grantedBy,
|
|
277
|
+
grantedAt: c.grantedAt,
|
|
278
|
+
revokedAt: c.revokedAt,
|
|
279
|
+
};
|
|
280
|
+
},
|
|
238
281
|
);
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
collaborators,
|
|
285
|
+
cursor: data.cursor,
|
|
286
|
+
};
|
|
239
287
|
}
|
|
240
288
|
|
|
241
289
|
/**
|
|
@@ -259,7 +307,7 @@ export class CollaboratorOperationsImpl implements CollaboratorOperations {
|
|
|
259
307
|
*/
|
|
260
308
|
async hasAccess(userDid: string): Promise<boolean> {
|
|
261
309
|
try {
|
|
262
|
-
const collaborators = await this.list();
|
|
310
|
+
const { collaborators } = await this.list();
|
|
263
311
|
return collaborators.some((c) => c.userDid === userDid && !c.revokedAt);
|
|
264
312
|
} catch {
|
|
265
313
|
return false;
|
|
@@ -281,8 +329,114 @@ export class CollaboratorOperationsImpl implements CollaboratorOperations {
|
|
|
281
329
|
* ```
|
|
282
330
|
*/
|
|
283
331
|
async getRole(userDid: string): Promise<RepositoryRole | null> {
|
|
284
|
-
const collaborators = await this.list();
|
|
332
|
+
const { collaborators } = await this.list();
|
|
285
333
|
const collab = collaborators.find((c) => c.userDid === userDid && !c.revokedAt);
|
|
286
334
|
return collab?.role ?? null;
|
|
287
335
|
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Gets the current user's permissions for this repository.
|
|
339
|
+
*
|
|
340
|
+
* @returns Promise resolving to the permission flags
|
|
341
|
+
* @throws {@link NetworkError} if the request fails
|
|
342
|
+
*
|
|
343
|
+
* @remarks
|
|
344
|
+
* This is useful for checking what actions the current user can perform
|
|
345
|
+
* before attempting operations that might fail due to insufficient permissions.
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```typescript
|
|
349
|
+
* const permissions = await repo.collaborators.getPermissions();
|
|
350
|
+
*
|
|
351
|
+
* if (permissions.admin) {
|
|
352
|
+
* // Show admin UI
|
|
353
|
+
* console.log("You can manage collaborators");
|
|
354
|
+
* }
|
|
355
|
+
*
|
|
356
|
+
* if (permissions.create) {
|
|
357
|
+
* console.log("You can create records");
|
|
358
|
+
* }
|
|
359
|
+
* ```
|
|
360
|
+
*
|
|
361
|
+
* @example Conditional UI rendering
|
|
362
|
+
* ```typescript
|
|
363
|
+
* const permissions = await repo.collaborators.getPermissions();
|
|
364
|
+
*
|
|
365
|
+
* // Show/hide UI elements based on permissions
|
|
366
|
+
* const canEdit = permissions.update;
|
|
367
|
+
* const canDelete = permissions.delete;
|
|
368
|
+
* const isAdmin = permissions.admin;
|
|
369
|
+
* const isOwner = permissions.owner;
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
async getPermissions(): Promise<CollaboratorPermissions> {
|
|
373
|
+
const response = await this.session.fetchHandler(
|
|
374
|
+
`${this.serverUrl}/xrpc/com.sds.repo.getPermissions?repo=${encodeURIComponent(this.repoDid)}`,
|
|
375
|
+
{ method: "GET" },
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
if (!response.ok) {
|
|
379
|
+
throw new NetworkError(`Failed to get permissions: ${response.statusText}`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const data = await response.json();
|
|
383
|
+
return data.permissions as CollaboratorPermissions;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Transfers repository ownership to another user.
|
|
388
|
+
*
|
|
389
|
+
* @param params - Transfer parameters
|
|
390
|
+
* @param params.newOwnerDid - DID of the user to transfer ownership to
|
|
391
|
+
* @throws {@link NetworkError} if the transfer fails
|
|
392
|
+
*
|
|
393
|
+
* @remarks
|
|
394
|
+
* **IMPORTANT**: This action is irreversible. Once ownership is transferred:
|
|
395
|
+
* - The new owner gains full control of the repository
|
|
396
|
+
* - Your role will be changed to admin (or specified role)
|
|
397
|
+
* - You cannot transfer ownership back without the new owner's approval
|
|
398
|
+
*
|
|
399
|
+
* **Requirements**:
|
|
400
|
+
* - You must be the current owner
|
|
401
|
+
* - The new owner must have an existing account
|
|
402
|
+
* - The new owner will be notified of the ownership transfer
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* ```typescript
|
|
406
|
+
* // Transfer ownership to another user
|
|
407
|
+
* await repo.collaborators.transferOwnership({
|
|
408
|
+
* newOwnerDid: "did:plc:new-owner",
|
|
409
|
+
* });
|
|
410
|
+
*
|
|
411
|
+
* console.log("Ownership transferred successfully");
|
|
412
|
+
* // You are now an admin, not the owner
|
|
413
|
+
* ```
|
|
414
|
+
*
|
|
415
|
+
* @example With confirmation
|
|
416
|
+
* ```typescript
|
|
417
|
+
* const confirmTransfer = await askUser(
|
|
418
|
+
* "Are you sure you want to transfer ownership? This cannot be undone."
|
|
419
|
+
* );
|
|
420
|
+
*
|
|
421
|
+
* if (confirmTransfer) {
|
|
422
|
+
* await repo.collaborators.transferOwnership({
|
|
423
|
+
* newOwnerDid: "did:plc:new-owner",
|
|
424
|
+
* });
|
|
425
|
+
* }
|
|
426
|
+
* ```
|
|
427
|
+
*/
|
|
428
|
+
async transferOwnership(params: { newOwnerDid: string }): Promise<void> {
|
|
429
|
+
const response = await this.session.fetchHandler(`${this.serverUrl}/xrpc/com.sds.repo.transferOwnership`, {
|
|
430
|
+
method: "POST",
|
|
431
|
+
headers: { "Content-Type": "application/json" },
|
|
432
|
+
body: JSON.stringify({
|
|
433
|
+
repo: this.repoDid,
|
|
434
|
+
newOwner: params.newOwnerDid,
|
|
435
|
+
}),
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
if (!response.ok) {
|
|
439
|
+
throw new NetworkError(`Failed to transfer ownership: ${response.statusText}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
288
442
|
}
|
|
@@ -208,7 +208,7 @@ export class HypercertOperationsImpl extends EventEmitter<HypercertEvents> imple
|
|
|
208
208
|
if (uploadResult.success) {
|
|
209
209
|
imageBlobRef = {
|
|
210
210
|
$type: "blob",
|
|
211
|
-
ref: uploadResult.data.blob.ref,
|
|
211
|
+
ref: { $link: uploadResult.data.blob.ref.toString() },
|
|
212
212
|
mimeType: uploadResult.data.blob.mimeType,
|
|
213
213
|
size: uploadResult.data.blob.size,
|
|
214
214
|
};
|
|
@@ -676,7 +676,7 @@ export class HypercertOperationsImpl extends EventEmitter<HypercertEvents> imple
|
|
|
676
676
|
if (uploadResult.success) {
|
|
677
677
|
locationValue = {
|
|
678
678
|
$type: "blob",
|
|
679
|
-
ref: uploadResult.data.blob.ref,
|
|
679
|
+
ref: { $link: uploadResult.data.blob.ref.toString() },
|
|
680
680
|
mimeType: uploadResult.data.blob.mimeType,
|
|
681
681
|
size: uploadResult.data.blob.size,
|
|
682
682
|
};
|
|
@@ -999,7 +999,7 @@ export class HypercertOperationsImpl extends EventEmitter<HypercertEvents> imple
|
|
|
999
999
|
if (uploadResult.success) {
|
|
1000
1000
|
coverPhotoRef = {
|
|
1001
1001
|
$type: "blob",
|
|
1002
|
-
ref: uploadResult.data.blob.ref,
|
|
1002
|
+
ref: { $link: uploadResult.data.blob.ref.toString() },
|
|
1003
1003
|
mimeType: uploadResult.data.blob.mimeType,
|
|
1004
1004
|
size: uploadResult.data.blob.size,
|
|
1005
1005
|
};
|
|
@@ -29,8 +29,8 @@ import type { OrganizationInfo } from "./types.js";
|
|
|
29
29
|
* {@link Repository.organizations} on an SDS-connected repository.
|
|
30
30
|
*
|
|
31
31
|
* **SDS API Endpoints Used**:
|
|
32
|
-
* - `com.
|
|
33
|
-
* - `com.
|
|
32
|
+
* - `com.sds.organization.create`: Create a new organization
|
|
33
|
+
* - `com.sds.organization.list`: List accessible organizations
|
|
34
34
|
*
|
|
35
35
|
* **Access Types**:
|
|
36
36
|
* - `"owner"`: User created or owns the organization
|
|
@@ -109,10 +109,18 @@ export class OrganizationOperationsImpl implements OrganizationOperations {
|
|
|
109
109
|
* ```
|
|
110
110
|
*/
|
|
111
111
|
async create(params: { name: string; description?: string; handle?: string }): Promise<OrganizationInfo> {
|
|
112
|
-
const
|
|
112
|
+
const userDid = this.session.did || this.session.sub;
|
|
113
|
+
if (!userDid) {
|
|
114
|
+
throw new NetworkError("No authenticated user found");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const response = await this.session.fetchHandler(`${this.serverUrl}/xrpc/com.sds.organization.create`, {
|
|
113
118
|
method: "POST",
|
|
114
119
|
headers: { "Content-Type": "application/json" },
|
|
115
|
-
body: JSON.stringify(
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
...params,
|
|
122
|
+
creatorDid: userDid,
|
|
123
|
+
}),
|
|
116
124
|
});
|
|
117
125
|
|
|
118
126
|
if (!response.ok) {
|
|
@@ -126,8 +134,15 @@ export class OrganizationOperationsImpl implements OrganizationOperations {
|
|
|
126
134
|
name: data.name,
|
|
127
135
|
description: data.description,
|
|
128
136
|
createdAt: data.createdAt || new Date().toISOString(),
|
|
129
|
-
accessType: "owner",
|
|
130
|
-
permissions:
|
|
137
|
+
accessType: data.accessType || "owner",
|
|
138
|
+
permissions: data.permissions || {
|
|
139
|
+
read: true,
|
|
140
|
+
create: true,
|
|
141
|
+
update: true,
|
|
142
|
+
delete: true,
|
|
143
|
+
admin: true,
|
|
144
|
+
owner: true,
|
|
145
|
+
},
|
|
131
146
|
};
|
|
132
147
|
}
|
|
133
148
|
|
|
@@ -155,8 +170,8 @@ export class OrganizationOperationsImpl implements OrganizationOperations {
|
|
|
155
170
|
*/
|
|
156
171
|
async get(did: string): Promise<OrganizationInfo | null> {
|
|
157
172
|
try {
|
|
158
|
-
const
|
|
159
|
-
return
|
|
173
|
+
const { organizations } = await this.list();
|
|
174
|
+
return organizations.find((o) => o.did === did) ?? null;
|
|
160
175
|
} catch {
|
|
161
176
|
return null;
|
|
162
177
|
}
|
|
@@ -165,7 +180,10 @@ export class OrganizationOperationsImpl implements OrganizationOperations {
|
|
|
165
180
|
/**
|
|
166
181
|
* Lists organizations the current user has access to.
|
|
167
182
|
*
|
|
168
|
-
* @
|
|
183
|
+
* @param params - Optional pagination parameters
|
|
184
|
+
* @param params.limit - Maximum number of results (1-100, default 50)
|
|
185
|
+
* @param params.cursor - Pagination cursor from previous response
|
|
186
|
+
* @returns Promise resolving to organizations and optional cursor
|
|
169
187
|
* @throws {@link NetworkError} if the list operation fails
|
|
170
188
|
*
|
|
171
189
|
* @remarks
|
|
@@ -177,21 +195,25 @@ export class OrganizationOperationsImpl implements OrganizationOperations {
|
|
|
177
195
|
*
|
|
178
196
|
* @example
|
|
179
197
|
* ```typescript
|
|
180
|
-
*
|
|
198
|
+
* // Get first page
|
|
199
|
+
* const page1 = await repo.organizations.list({ limit: 20 });
|
|
200
|
+
* console.log(`Found ${page1.organizations.length} organizations`);
|
|
181
201
|
*
|
|
182
|
-
* //
|
|
183
|
-
*
|
|
184
|
-
*
|
|
202
|
+
* // Get next page if available
|
|
203
|
+
* if (page1.cursor) {
|
|
204
|
+
* const page2 = await repo.organizations.list({ limit: 20, cursor: page1.cursor });
|
|
205
|
+
* }
|
|
185
206
|
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
207
|
+
* // Filter by access type
|
|
208
|
+
* const owned = page1.organizations.filter(o => o.accessType === "owner");
|
|
209
|
+
* const shared = page1.organizations.filter(o => o.accessType === "shared");
|
|
188
210
|
* ```
|
|
189
211
|
*
|
|
190
212
|
* @example Display organization details
|
|
191
213
|
* ```typescript
|
|
192
|
-
* const
|
|
214
|
+
* const { organizations } = await repo.organizations.list();
|
|
193
215
|
*
|
|
194
|
-
* for (const org of
|
|
216
|
+
* for (const org of organizations) {
|
|
195
217
|
* console.log(`${org.name} (@${org.handle})`);
|
|
196
218
|
* console.log(` DID: ${org.did}`);
|
|
197
219
|
* console.log(` Access: ${org.accessType}`);
|
|
@@ -201,9 +223,29 @@ export class OrganizationOperationsImpl implements OrganizationOperations {
|
|
|
201
223
|
* }
|
|
202
224
|
* ```
|
|
203
225
|
*/
|
|
204
|
-
async list(): Promise<
|
|
226
|
+
async list(params?: { limit?: number; cursor?: string }): Promise<{
|
|
227
|
+
organizations: OrganizationInfo[];
|
|
228
|
+
cursor?: string;
|
|
229
|
+
}> {
|
|
230
|
+
const userDid = this.session.did || this.session.sub;
|
|
231
|
+
if (!userDid) {
|
|
232
|
+
throw new NetworkError("No authenticated user found");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const queryParams = new URLSearchParams({
|
|
236
|
+
userDid,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
if (params?.limit !== undefined) {
|
|
240
|
+
queryParams.set("limit", params.limit.toString());
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (params?.cursor) {
|
|
244
|
+
queryParams.set("cursor", params.cursor);
|
|
245
|
+
}
|
|
246
|
+
|
|
205
247
|
const response = await this.session.fetchHandler(
|
|
206
|
-
`${this.serverUrl}/xrpc/com.
|
|
248
|
+
`${this.serverUrl}/xrpc/com.sds.organization.list?${queryParams.toString()}`,
|
|
207
249
|
{ method: "GET" },
|
|
208
250
|
);
|
|
209
251
|
|
|
@@ -212,23 +254,29 @@ export class OrganizationOperationsImpl implements OrganizationOperations {
|
|
|
212
254
|
}
|
|
213
255
|
|
|
214
256
|
const data = await response.json();
|
|
215
|
-
|
|
257
|
+
const organizations = (data.organizations || []).map(
|
|
216
258
|
(r: {
|
|
217
259
|
did: string;
|
|
218
260
|
handle: string;
|
|
219
261
|
name: string;
|
|
220
262
|
description?: string;
|
|
221
|
-
|
|
263
|
+
createdAt?: string;
|
|
264
|
+
accessType: "owner" | "shared" | "none";
|
|
222
265
|
permissions: CollaboratorPermissions;
|
|
223
266
|
}) => ({
|
|
224
267
|
did: r.did,
|
|
225
268
|
handle: r.handle,
|
|
226
269
|
name: r.name,
|
|
227
270
|
description: r.description,
|
|
228
|
-
createdAt: new Date().toISOString(),
|
|
271
|
+
createdAt: r.createdAt || new Date().toISOString(),
|
|
229
272
|
accessType: r.accessType,
|
|
230
273
|
permissions: r.permissions,
|
|
231
274
|
}),
|
|
232
275
|
);
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
organizations,
|
|
279
|
+
cursor: data.cursor,
|
|
280
|
+
};
|
|
233
281
|
}
|
|
234
282
|
}
|
|
@@ -773,6 +773,17 @@ export interface HypercertOperations extends EventEmitter<HypercertEvents> {
|
|
|
773
773
|
* const hasAccess = await repo.collaborators.hasAccess("did:plc:someone");
|
|
774
774
|
* const role = await repo.collaborators.getRole("did:plc:someone");
|
|
775
775
|
*
|
|
776
|
+
* // Get current user permissions
|
|
777
|
+
* const permissions = await repo.collaborators.getPermissions();
|
|
778
|
+
* if (permissions.admin) {
|
|
779
|
+
* // Can manage collaborators
|
|
780
|
+
* }
|
|
781
|
+
*
|
|
782
|
+
* // Transfer ownership
|
|
783
|
+
* await repo.collaborators.transferOwnership({
|
|
784
|
+
* newOwnerDid: "did:plc:new-owner",
|
|
785
|
+
* });
|
|
786
|
+
*
|
|
776
787
|
* // Revoke access
|
|
777
788
|
* await repo.collaborators.revoke({ userDid: "did:plc:former-user" });
|
|
778
789
|
* ```
|
|
@@ -798,9 +809,15 @@ export interface CollaboratorOperations {
|
|
|
798
809
|
/**
|
|
799
810
|
* Lists all collaborators on the repository.
|
|
800
811
|
*
|
|
801
|
-
* @
|
|
812
|
+
* @param params - Optional pagination parameters
|
|
813
|
+
* @param params.limit - Maximum number of results (1-100, default 50)
|
|
814
|
+
* @param params.cursor - Pagination cursor from previous response
|
|
815
|
+
* @returns Promise resolving to collaborators and optional cursor
|
|
802
816
|
*/
|
|
803
|
-
list(): Promise<
|
|
817
|
+
list(params?: { limit?: number; cursor?: string }): Promise<{
|
|
818
|
+
collaborators: RepositoryAccessGrant[];
|
|
819
|
+
cursor?: string;
|
|
820
|
+
}>;
|
|
804
821
|
|
|
805
822
|
/**
|
|
806
823
|
* Checks if a user has any access to the repository.
|
|
@@ -817,6 +834,24 @@ export interface CollaboratorOperations {
|
|
|
817
834
|
* @returns Promise resolving to role, or `null` if no access
|
|
818
835
|
*/
|
|
819
836
|
getRole(userDid: string): Promise<RepositoryRole | null>;
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Gets the current user's permissions for this repository.
|
|
840
|
+
*
|
|
841
|
+
* @returns Promise resolving to permission flags
|
|
842
|
+
*/
|
|
843
|
+
getPermissions(): Promise<import("../core/types.js").CollaboratorPermissions>;
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Transfers repository ownership to another user.
|
|
847
|
+
*
|
|
848
|
+
* **WARNING**: This action is irreversible. The new owner will have
|
|
849
|
+
* full control of the repository.
|
|
850
|
+
*
|
|
851
|
+
* @param params - Transfer parameters
|
|
852
|
+
* @param params.newOwnerDid - DID of the user to transfer ownership to
|
|
853
|
+
*/
|
|
854
|
+
transferOwnership(params: { newOwnerDid: string }): Promise<void>;
|
|
820
855
|
}
|
|
821
856
|
|
|
822
857
|
/**
|
|
@@ -862,7 +897,13 @@ export interface OrganizationOperations {
|
|
|
862
897
|
/**
|
|
863
898
|
* Lists organizations the current user has access to.
|
|
864
899
|
*
|
|
865
|
-
* @
|
|
900
|
+
* @param params - Optional pagination parameters
|
|
901
|
+
* @param params.limit - Maximum number of results (1-100, default 50)
|
|
902
|
+
* @param params.cursor - Pagination cursor from previous response
|
|
903
|
+
* @returns Promise resolving to organizations and optional cursor
|
|
866
904
|
*/
|
|
867
|
-
list(): Promise<
|
|
905
|
+
list(params?: { limit?: number; cursor?: string }): Promise<{
|
|
906
|
+
organizations: OrganizationInfo[];
|
|
907
|
+
cursor?: string;
|
|
908
|
+
}>;
|
|
868
909
|
}
|
package/src/repository/types.ts
CHANGED
|
@@ -85,7 +85,7 @@ export interface OrganizationInfo {
|
|
|
85
85
|
name: string;
|
|
86
86
|
description?: string;
|
|
87
87
|
createdAt: string;
|
|
88
|
-
accessType: "owner" | "
|
|
88
|
+
accessType: "owner" | "shared" | "none";
|
|
89
89
|
permissions: CollaboratorPermissions;
|
|
90
90
|
collaboratorCount?: number;
|
|
91
91
|
profile?: {
|
|
@@ -28,11 +28,14 @@ describe("BlobOperationsImpl", () => {
|
|
|
28
28
|
describe("upload", () => {
|
|
29
29
|
it("should upload a blob successfully", async () => {
|
|
30
30
|
const mockBlob = new Blob(["test content"], { type: "text/plain" });
|
|
31
|
+
const mockCID = {
|
|
32
|
+
toString: () => "bafyrei123",
|
|
33
|
+
};
|
|
31
34
|
mockAgent.com.atproto.repo.uploadBlob.mockResolvedValue({
|
|
32
35
|
success: true,
|
|
33
36
|
data: {
|
|
34
37
|
blob: {
|
|
35
|
-
ref:
|
|
38
|
+
ref: mockCID,
|
|
36
39
|
mimeType: "text/plain",
|
|
37
40
|
size: 12,
|
|
38
41
|
},
|
|
@@ -61,10 +64,9 @@ describe("BlobOperationsImpl", () => {
|
|
|
61
64
|
|
|
62
65
|
await blobOps.upload(mockBlob);
|
|
63
66
|
|
|
64
|
-
expect(mockAgent.com.atproto.repo.uploadBlob).toHaveBeenCalledWith(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
);
|
|
67
|
+
expect(mockAgent.com.atproto.repo.uploadBlob).toHaveBeenCalledWith(expect.any(Uint8Array), {
|
|
68
|
+
encoding: "image/png",
|
|
69
|
+
});
|
|
68
70
|
});
|
|
69
71
|
|
|
70
72
|
it("should default to application/octet-stream for blobs without type", async () => {
|
|
@@ -82,10 +84,9 @@ describe("BlobOperationsImpl", () => {
|
|
|
82
84
|
|
|
83
85
|
await blobOps.upload(mockBlob);
|
|
84
86
|
|
|
85
|
-
expect(mockAgent.com.atproto.repo.uploadBlob).toHaveBeenCalledWith(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
);
|
|
87
|
+
expect(mockAgent.com.atproto.repo.uploadBlob).toHaveBeenCalledWith(expect.any(Uint8Array), {
|
|
88
|
+
encoding: "application/octet-stream",
|
|
89
|
+
});
|
|
89
90
|
});
|
|
90
91
|
|
|
91
92
|
it("should throw NetworkError when API returns success: false", async () => {
|