@hypercerts-org/sdk-core 0.4.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.
@@ -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 userDid = this.session.did || this.session.sub;
113
+ if (!userDid) {
114
+ throw new NetworkError("No authenticated user found");
115
+ }
116
+
112
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(params),
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: { read: true, create: true, update: true, delete: true, admin: true, owner: true },
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 orgs = await this.list();
159
- return orgs.find((o) => o.did === did) ?? null;
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
- * @returns Promise resolving to array of organization info
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
- * const orgs = await repo.organizations.list();
198
+ * // Get first page
199
+ * const page1 = await repo.organizations.list({ limit: 20 });
200
+ * console.log(`Found ${page1.organizations.length} organizations`);
181
201
  *
182
- * // Filter by access type
183
- * const owned = orgs.filter(o => o.accessType === "owner");
184
- * const collaborated = orgs.filter(o => o.accessType === "collaborator");
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
- * console.log(`You own ${owned.length} organizations`);
187
- * console.log(`You collaborate on ${collaborated.length} organizations`);
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 orgs = await repo.organizations.list();
214
+ * const { organizations } = await repo.organizations.list();
193
215
  *
194
- * for (const org of orgs) {
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<OrganizationInfo[]> {
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.sds.organization.list?userDid=${encodeURIComponent(this.session.did || this.session.sub)}`,
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
- return (data.repositories || []).map(
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
- accessType: "owner" | "collaborator";
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(), // SDS may not return this
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
  }
@@ -809,9 +809,15 @@ export interface CollaboratorOperations {
809
809
  /**
810
810
  * Lists all collaborators on the repository.
811
811
  *
812
- * @returns Promise resolving to array of access grants
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
813
816
  */
814
- list(): Promise<RepositoryAccessGrant[]>;
817
+ list(params?: { limit?: number; cursor?: string }): Promise<{
818
+ collaborators: RepositoryAccessGrant[];
819
+ cursor?: string;
820
+ }>;
815
821
 
816
822
  /**
817
823
  * Checks if a user has any access to the repository.
@@ -891,7 +897,13 @@ export interface OrganizationOperations {
891
897
  /**
892
898
  * Lists organizations the current user has access to.
893
899
  *
894
- * @returns Promise resolving to array of organization info
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
895
904
  */
896
- list(): Promise<OrganizationInfo[]>;
905
+ list(params?: { limit?: number; cursor?: string }): Promise<{
906
+ organizations: OrganizationInfo[];
907
+ cursor?: string;
908
+ }>;
897
909
  }
@@ -85,7 +85,7 @@ export interface OrganizationInfo {
85
85
  name: string;
86
86
  description?: string;
87
87
  createdAt: string;
88
- accessType: "owner" | "collaborator";
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: { $link: "bafyrei123" },
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
- expect.any(Uint8Array),
66
- { encoding: "image/png" },
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
- expect.any(Uint8Array),
87
- { encoding: "application/octet-stream" },
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 () => {
@@ -131,13 +131,13 @@ describe("CollaboratorOperationsImpl", () => {
131
131
  collaborators: [
132
132
  {
133
133
  userDid: "did:plc:user1",
134
- permissions: { read: true, create: true, update: true, delete: false, admin: false, owner: false },
134
+ permissions: ["read", "create", "update"],
135
135
  grantedBy: "did:plc:owner",
136
136
  grantedAt: "2024-01-01T00:00:00Z",
137
137
  },
138
138
  {
139
139
  userDid: "did:plc:user2",
140
- permissions: { read: true, create: false, update: false, delete: false, admin: false, owner: false },
140
+ permissions: ["read"],
141
141
  grantedBy: "did:plc:owner",
142
142
  grantedAt: "2024-01-02T00:00:00Z",
143
143
  },
@@ -147,10 +147,10 @@ describe("CollaboratorOperationsImpl", () => {
147
147
 
148
148
  const result = await collaboratorOps.list();
149
149
 
150
- expect(result).toHaveLength(2);
151
- expect(result[0].userDid).toBe("did:plc:user1");
152
- expect(result[0].role).toBe("editor");
153
- expect(result[1].role).toBe("viewer");
150
+ expect(result.collaborators).toHaveLength(2);
151
+ expect(result.collaborators[0].userDid).toBe("did:plc:user1");
152
+ expect(result.collaborators[0].role).toBe("editor");
153
+ expect(result.collaborators[1].role).toBe("viewer");
154
154
  });
155
155
 
156
156
  it("should handle empty collaborators list", async () => {
@@ -161,7 +161,7 @@ describe("CollaboratorOperationsImpl", () => {
161
161
 
162
162
  const result = await collaboratorOps.list();
163
163
 
164
- expect(result).toHaveLength(0);
164
+ expect(result.collaborators).toHaveLength(0);
165
165
  });
166
166
 
167
167
  it("should correctly map permissions to roles", async () => {
@@ -171,13 +171,13 @@ describe("CollaboratorOperationsImpl", () => {
171
171
  collaborators: [
172
172
  {
173
173
  userDid: "did:plc:owner",
174
- permissions: { read: true, create: true, update: true, delete: true, admin: true, owner: true },
174
+ permissions: ["read", "create", "update", "delete", "admin", "owner"],
175
175
  grantedBy: "did:plc:system",
176
176
  grantedAt: "2024-01-01T00:00:00Z",
177
177
  },
178
178
  {
179
179
  userDid: "did:plc:admin",
180
- permissions: { read: true, create: true, update: true, delete: true, admin: true, owner: false },
180
+ permissions: ["read", "create", "update", "delete", "admin"],
181
181
  grantedBy: "did:plc:owner",
182
182
  grantedAt: "2024-01-01T00:00:00Z",
183
183
  },
@@ -187,8 +187,8 @@ describe("CollaboratorOperationsImpl", () => {
187
187
 
188
188
  const result = await collaboratorOps.list();
189
189
 
190
- expect(result[0].role).toBe("owner");
191
- expect(result[1].role).toBe("admin");
190
+ expect(result.collaborators[0].role).toBe("owner");
191
+ expect(result.collaborators[1].role).toBe("admin");
192
192
  });
193
193
 
194
194
  it("should throw NetworkError on failure", async () => {
@@ -209,7 +209,7 @@ describe("CollaboratorOperationsImpl", () => {
209
209
  collaborators: [
210
210
  {
211
211
  userDid: "did:plc:activeuser",
212
- permissions: { read: true, create: false, update: false, delete: false, admin: false, owner: false },
212
+ permissions: ["read"],
213
213
  grantedBy: "did:plc:owner",
214
214
  grantedAt: "2024-01-01T00:00:00Z",
215
215
  },
@@ -240,7 +240,7 @@ describe("CollaboratorOperationsImpl", () => {
240
240
  collaborators: [
241
241
  {
242
242
  userDid: "did:plc:revokeduser",
243
- permissions: { read: true, create: false, update: false, delete: false, admin: false, owner: false },
243
+ permissions: ["read"],
244
244
  grantedBy: "did:plc:owner",
245
245
  grantedAt: "2024-01-01T00:00:00Z",
246
246
  revokedAt: "2024-02-01T00:00:00Z",
@@ -271,7 +271,7 @@ describe("CollaboratorOperationsImpl", () => {
271
271
  collaborators: [
272
272
  {
273
273
  userDid: "did:plc:editor",
274
- permissions: { read: true, create: true, update: true, delete: false, admin: false, owner: false },
274
+ permissions: ["read", "create", "update"],
275
275
  grantedBy: "did:plc:owner",
276
276
  grantedAt: "2024-01-01T00:00:00Z",
277
277
  },
@@ -302,7 +302,7 @@ describe("CollaboratorOperationsImpl", () => {
302
302
  collaborators: [
303
303
  {
304
304
  userDid: "did:plc:revoked",
305
- permissions: { read: true, create: true, update: true, delete: false, admin: false, owner: false },
305
+ permissions: ["read", "create", "update"],
306
306
  grantedBy: "did:plc:owner",
307
307
  grantedAt: "2024-01-01T00:00:00Z",
308
308
  revokedAt: "2024-02-01T00:00:00Z",
@@ -74,9 +74,7 @@ describe("OrganizationOperationsImpl", () => {
74
74
  statusText: "Conflict",
75
75
  });
76
76
 
77
- await expect(
78
- orgOps.create({ name: "Test Org" }),
79
- ).rejects.toThrow(NetworkError);
77
+ await expect(orgOps.create({ name: "Test Org" })).rejects.toThrow(NetworkError);
80
78
  });
81
79
  });
82
80
 
@@ -85,7 +83,7 @@ describe("OrganizationOperationsImpl", () => {
85
83
  mockSession.fetchHandler.mockResolvedValue({
86
84
  ok: true,
87
85
  json: async () => ({
88
- repositories: [
86
+ organizations: [
89
87
  {
90
88
  did: "did:plc:org1",
91
89
  handle: "org1.example.com",
@@ -98,7 +96,7 @@ describe("OrganizationOperationsImpl", () => {
98
96
  did: "did:plc:org2",
99
97
  handle: "org2.example.com",
100
98
  name: "Organization 2",
101
- accessType: "collaborator",
99
+ accessType: "shared",
102
100
  permissions: { read: true, create: true, update: true, delete: false, admin: false, owner: false },
103
101
  },
104
102
  ],
@@ -116,7 +114,7 @@ describe("OrganizationOperationsImpl", () => {
116
114
  mockSession.fetchHandler.mockResolvedValue({
117
115
  ok: true,
118
116
  json: async () => ({
119
- repositories: [],
117
+ organizations: [],
120
118
  }),
121
119
  });
122
120
 
@@ -139,7 +137,7 @@ describe("OrganizationOperationsImpl", () => {
139
137
  mockSession.fetchHandler.mockResolvedValue({
140
138
  ok: true,
141
139
  json: async () => ({
142
- repositories: [
140
+ organizations: [
143
141
  {
144
142
  did: "did:plc:org1",
145
143
  handle: "org1.example.com",
@@ -152,7 +150,7 @@ describe("OrganizationOperationsImpl", () => {
152
150
  handle: "org2.example.com",
153
151
  name: "Organization 2",
154
152
  description: "Second org",
155
- accessType: "collaborator",
153
+ accessType: "shared",
156
154
  permissions: { read: true, create: true, update: false, delete: false, admin: false, owner: false },
157
155
  },
158
156
  ],
@@ -161,28 +159,28 @@ describe("OrganizationOperationsImpl", () => {
161
159
 
162
160
  const result = await orgOps.list();
163
161
 
164
- expect(result).toHaveLength(2);
165
- expect(result[0].did).toBe("did:plc:org1");
166
- expect(result[0].accessType).toBe("owner");
167
- expect(result[1].did).toBe("did:plc:org2");
168
- expect(result[1].accessType).toBe("collaborator");
162
+ expect(result.organizations).toHaveLength(2);
163
+ expect(result.organizations[0].did).toBe("did:plc:org1");
164
+ expect(result.organizations[0].accessType).toBe("owner");
165
+ expect(result.organizations[1].did).toBe("did:plc:org2");
166
+ expect(result.organizations[1].accessType).toBe("shared");
169
167
  });
170
168
 
171
169
  it("should handle empty repositories list", async () => {
172
170
  mockSession.fetchHandler.mockResolvedValue({
173
171
  ok: true,
174
- json: async () => ({ repositories: [] }),
172
+ json: async () => ({ organizations: [] }),
175
173
  });
176
174
 
177
175
  const result = await orgOps.list();
178
176
 
179
- expect(result).toHaveLength(0);
177
+ expect(result.organizations).toHaveLength(0);
180
178
  });
181
179
 
182
180
  it("should use session DID in query", async () => {
183
181
  mockSession.fetchHandler.mockResolvedValue({
184
182
  ok: true,
185
- json: async () => ({ repositories: [] }),
183
+ json: async () => ({ organizations: [] }),
186
184
  });
187
185
 
188
186
  await orgOps.list();
@@ -197,7 +195,7 @@ describe("OrganizationOperationsImpl", () => {
197
195
  mockSession.did = undefined;
198
196
  mockSession.fetchHandler.mockResolvedValue({
199
197
  ok: true,
200
- json: async () => ({ repositories: [] }),
198
+ json: async () => ({ organizations: [] }),
201
199
  });
202
200
 
203
201
  await orgOps.list();
@@ -221,7 +219,7 @@ describe("OrganizationOperationsImpl", () => {
221
219
  mockSession.fetchHandler.mockResolvedValue({
222
220
  ok: true,
223
221
  json: async () => ({
224
- repositories: [
222
+ organizations: [
225
223
  {
226
224
  did: "did:plc:org",
227
225
  handle: "org.example.com",
@@ -236,7 +234,7 @@ describe("OrganizationOperationsImpl", () => {
236
234
 
237
235
  const result = await orgOps.list();
238
236
 
239
- expect(result[0].createdAt).toBeDefined();
237
+ expect(result.organizations[0].createdAt).toBeDefined();
240
238
  });
241
239
  });
242
240
  });