@hypercerts-org/sdk-core 0.4.0-beta.0 → 0.6.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.
Files changed (62) hide show
  1. package/README.md +459 -79
  2. package/dist/index.cjs +128 -47
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.ts +28 -9
  5. package/dist/index.mjs +128 -47
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/types.cjs +3 -2
  8. package/dist/types.cjs.map +1 -1
  9. package/dist/types.d.ts +28 -9
  10. package/dist/types.mjs +3 -2
  11. package/dist/types.mjs.map +1 -1
  12. package/package.json +9 -5
  13. package/.turbo/turbo-build.log +0 -328
  14. package/.turbo/turbo-test.log +0 -118
  15. package/CHANGELOG.md +0 -22
  16. package/eslint.config.mjs +0 -22
  17. package/rollup.config.js +0 -75
  18. package/src/auth/OAuthClient.ts +0 -497
  19. package/src/core/SDK.ts +0 -410
  20. package/src/core/config.ts +0 -243
  21. package/src/core/errors.ts +0 -257
  22. package/src/core/interfaces.ts +0 -324
  23. package/src/core/types.ts +0 -281
  24. package/src/errors.ts +0 -57
  25. package/src/index.ts +0 -107
  26. package/src/lexicons.ts +0 -64
  27. package/src/repository/BlobOperationsImpl.ts +0 -199
  28. package/src/repository/CollaboratorOperationsImpl.ts +0 -396
  29. package/src/repository/HypercertOperationsImpl.ts +0 -1146
  30. package/src/repository/LexiconRegistry.ts +0 -332
  31. package/src/repository/OrganizationOperationsImpl.ts +0 -234
  32. package/src/repository/ProfileOperationsImpl.ts +0 -281
  33. package/src/repository/RecordOperationsImpl.ts +0 -340
  34. package/src/repository/Repository.ts +0 -482
  35. package/src/repository/interfaces.ts +0 -897
  36. package/src/repository/types.ts +0 -111
  37. package/src/services/hypercerts/types.ts +0 -87
  38. package/src/storage/InMemorySessionStore.ts +0 -127
  39. package/src/storage/InMemoryStateStore.ts +0 -146
  40. package/src/storage.ts +0 -63
  41. package/src/testing/index.ts +0 -67
  42. package/src/testing/mocks.ts +0 -142
  43. package/src/testing/stores.ts +0 -285
  44. package/src/testing.ts +0 -64
  45. package/src/types.ts +0 -86
  46. package/tests/auth/OAuthClient.test.ts +0 -164
  47. package/tests/core/SDK.test.ts +0 -176
  48. package/tests/core/errors.test.ts +0 -81
  49. package/tests/repository/BlobOperationsImpl.test.ts +0 -154
  50. package/tests/repository/CollaboratorOperationsImpl.test.ts +0 -438
  51. package/tests/repository/HypercertOperationsImpl.test.ts +0 -652
  52. package/tests/repository/LexiconRegistry.test.ts +0 -192
  53. package/tests/repository/OrganizationOperationsImpl.test.ts +0 -242
  54. package/tests/repository/ProfileOperationsImpl.test.ts +0 -254
  55. package/tests/repository/RecordOperationsImpl.test.ts +0 -375
  56. package/tests/repository/Repository.test.ts +0 -149
  57. package/tests/utils/fixtures.ts +0 -117
  58. package/tests/utils/mocks.ts +0 -109
  59. package/tests/utils/repository-fixtures.ts +0 -78
  60. package/tsconfig.json +0 -11
  61. package/tsconfig.tsbuildinfo +0 -1
  62. package/vitest.config.ts +0 -30
@@ -1,281 +0,0 @@
1
- /**
2
- * ProfileOperationsImpl - User profile operations.
3
- *
4
- * This module provides the implementation for AT Protocol profile
5
- * management, including fetching and updating user profiles.
6
- *
7
- * @packageDocumentation
8
- */
9
-
10
- import type { Agent } from "@atproto/api";
11
- import { NetworkError } from "../core/errors.js";
12
- import type { ProfileOperations } from "./interfaces.js";
13
- import type { UpdateResult } from "./types.js";
14
-
15
- /**
16
- * Implementation of profile operations for user profile management.
17
- *
18
- * Profiles in AT Protocol are stored as records in the `app.bsky.actor.profile`
19
- * collection with the special rkey "self". This class provides a convenient
20
- * API for reading and updating profile data.
21
- *
22
- * @remarks
23
- * This class is typically not instantiated directly. Access it through
24
- * {@link Repository.profile}.
25
- *
26
- * **Profile Fields**:
27
- * - `handle`: Read-only, managed by the PDS
28
- * - `displayName`: User's display name (max 64 chars typically)
29
- * - `description`: Profile bio (max 256 chars typically)
30
- * - `avatar`: Profile picture blob reference
31
- * - `banner`: Banner image blob reference
32
- * - `website`: User's website URL (may not be available on all servers)
33
- *
34
- * @example
35
- * ```typescript
36
- * // Get profile
37
- * const profile = await repo.profile.get();
38
- * console.log(`${profile.displayName} (@${profile.handle})`);
39
- *
40
- * // Update profile
41
- * await repo.profile.update({
42
- * displayName: "New Name",
43
- * description: "Updated bio",
44
- * });
45
- *
46
- * // Update with new avatar
47
- * const avatarBlob = new Blob([imageData], { type: "image/png" });
48
- * await repo.profile.update({ avatar: avatarBlob });
49
- *
50
- * // Remove a field
51
- * await repo.profile.update({ website: null });
52
- * ```
53
- *
54
- * @internal
55
- */
56
- export class ProfileOperationsImpl implements ProfileOperations {
57
- /**
58
- * Creates a new ProfileOperationsImpl.
59
- *
60
- * @param agent - AT Protocol Agent for making API calls
61
- * @param repoDid - DID of the repository/user
62
- * @param _serverUrl - Server URL (reserved for future use)
63
- *
64
- * @internal
65
- */
66
- constructor(
67
- private agent: Agent,
68
- private repoDid: string,
69
- private _serverUrl: string,
70
- ) {}
71
-
72
- /**
73
- * Gets the repository's profile.
74
- *
75
- * @returns Promise resolving to profile data
76
- * @throws {@link NetworkError} if the profile cannot be fetched
77
- *
78
- * @remarks
79
- * This method fetches the full profile using the `getProfile` API,
80
- * which includes resolved information like follower counts on some
81
- * servers. For hypercerts SDK usage, the basic profile fields are
82
- * returned.
83
- *
84
- * **Note**: The `website` field may not be available on all AT Protocol
85
- * servers. Standard Bluesky profiles don't include this field.
86
- *
87
- * @example
88
- * ```typescript
89
- * const profile = await repo.profile.get();
90
- *
91
- * console.log(`Handle: @${profile.handle}`);
92
- * console.log(`Name: ${profile.displayName || "(not set)"}`);
93
- * console.log(`Bio: ${profile.description || "(no bio)"}`);
94
- *
95
- * if (profile.avatar) {
96
- * // Avatar is a URL or blob reference
97
- * console.log(`Avatar: ${profile.avatar}`);
98
- * }
99
- * ```
100
- */
101
- async get(): Promise<{
102
- handle: string;
103
- displayName?: string;
104
- description?: string;
105
- avatar?: string;
106
- banner?: string;
107
- website?: string;
108
- }> {
109
- try {
110
- const result = await this.agent.getProfile({ actor: this.repoDid });
111
-
112
- if (!result.success) {
113
- throw new NetworkError("Failed to get profile");
114
- }
115
-
116
- return {
117
- handle: result.data.handle,
118
- displayName: result.data.displayName,
119
- description: result.data.description,
120
- avatar: result.data.avatar,
121
- banner: result.data.banner,
122
- // Note: website may not be available in standard profile
123
- };
124
- } catch (error) {
125
- if (error instanceof NetworkError) throw error;
126
- throw new NetworkError(
127
- `Failed to get profile: ${error instanceof Error ? error.message : "Unknown error"}`,
128
- error,
129
- );
130
- }
131
- }
132
-
133
- /**
134
- * Updates the repository's profile.
135
- *
136
- * @param params - Fields to update. Pass `null` to remove a field.
137
- * Omitted fields are preserved from the existing profile.
138
- * @returns Promise resolving to update result with new URI and CID
139
- * @throws {@link NetworkError} if the update fails
140
- *
141
- * @remarks
142
- * This method performs a read-modify-write operation:
143
- * 1. Fetches the existing profile record
144
- * 2. Merges in the provided updates
145
- * 3. Writes the updated profile back
146
- *
147
- * **Image Handling**: When providing `avatar` or `banner` as a Blob,
148
- * the image is automatically uploaded and the blob reference is stored
149
- * in the profile.
150
- *
151
- * **Field Removal**: Pass `null` to explicitly remove a field. Omitting
152
- * a field (not including it in params) preserves the existing value.
153
- *
154
- * @example Update display name and bio
155
- * ```typescript
156
- * await repo.profile.update({
157
- * displayName: "Alice",
158
- * description: "Building impact certificates",
159
- * });
160
- * ```
161
- *
162
- * @example Update avatar image
163
- * ```typescript
164
- * // From a file input
165
- * const file = document.getElementById("avatar").files[0];
166
- * await repo.profile.update({ avatar: file });
167
- *
168
- * // From raw data
169
- * const response = await fetch("https://example.com/my-avatar.png");
170
- * const blob = await response.blob();
171
- * await repo.profile.update({ avatar: blob });
172
- * ```
173
- *
174
- * @example Remove description
175
- * ```typescript
176
- * // Removes the description field entirely
177
- * await repo.profile.update({ description: null });
178
- * ```
179
- *
180
- * @example Multiple updates at once
181
- * ```typescript
182
- * const newAvatar = new Blob([avatarData], { type: "image/png" });
183
- * const newBanner = new Blob([bannerData], { type: "image/jpeg" });
184
- *
185
- * await repo.profile.update({
186
- * displayName: "New Name",
187
- * description: "New bio",
188
- * avatar: newAvatar,
189
- * banner: newBanner,
190
- * });
191
- * ```
192
- */
193
- async update(params: {
194
- displayName?: string | null;
195
- description?: string | null;
196
- avatar?: Blob | null;
197
- banner?: Blob | null;
198
- website?: string | null;
199
- }): Promise<UpdateResult> {
200
- try {
201
- // Get existing profile record
202
- const existing = await this.agent.com.atproto.repo.getRecord({
203
- repo: this.repoDid,
204
- collection: "app.bsky.actor.profile",
205
- rkey: "self",
206
- });
207
-
208
- const existingProfile = (existing.data.value as Record<string, unknown>) || {};
209
-
210
- // Build updated profile
211
- const updatedProfile: Record<string, unknown> = { ...existingProfile };
212
-
213
- if (params.displayName !== undefined) {
214
- if (params.displayName === null) {
215
- delete updatedProfile.displayName;
216
- } else {
217
- updatedProfile.displayName = params.displayName;
218
- }
219
- }
220
-
221
- if (params.description !== undefined) {
222
- if (params.description === null) {
223
- delete updatedProfile.description;
224
- } else {
225
- updatedProfile.description = params.description;
226
- }
227
- }
228
-
229
- // Handle avatar upload
230
- if (params.avatar !== undefined) {
231
- if (params.avatar === null) {
232
- delete updatedProfile.avatar;
233
- } else {
234
- const arrayBuffer = await params.avatar.arrayBuffer();
235
- const uint8Array = new Uint8Array(arrayBuffer);
236
- const uploadResult = await this.agent.com.atproto.repo.uploadBlob(uint8Array, {
237
- encoding: params.avatar.type || "image/jpeg",
238
- });
239
- if (uploadResult.success) {
240
- updatedProfile.avatar = uploadResult.data.blob;
241
- }
242
- }
243
- }
244
-
245
- // Handle banner upload
246
- if (params.banner !== undefined) {
247
- if (params.banner === null) {
248
- delete updatedProfile.banner;
249
- } else {
250
- const arrayBuffer = await params.banner.arrayBuffer();
251
- const uint8Array = new Uint8Array(arrayBuffer);
252
- const uploadResult = await this.agent.com.atproto.repo.uploadBlob(uint8Array, {
253
- encoding: params.banner.type || "image/jpeg",
254
- });
255
- if (uploadResult.success) {
256
- updatedProfile.banner = uploadResult.data.blob;
257
- }
258
- }
259
- }
260
-
261
- const result = await this.agent.com.atproto.repo.putRecord({
262
- repo: this.repoDid,
263
- collection: "app.bsky.actor.profile",
264
- rkey: "self",
265
- record: updatedProfile,
266
- });
267
-
268
- if (!result.success) {
269
- throw new NetworkError("Failed to update profile");
270
- }
271
-
272
- return { uri: result.data.uri, cid: result.data.cid };
273
- } catch (error) {
274
- if (error instanceof NetworkError) throw error;
275
- throw new NetworkError(
276
- `Failed to update profile: ${error instanceof Error ? error.message : "Unknown error"}`,
277
- error,
278
- );
279
- }
280
- }
281
- }
@@ -1,340 +0,0 @@
1
- /**
2
- * RecordOperationsImpl - Low-level record CRUD operations.
3
- *
4
- * This module provides the implementation for direct AT Protocol
5
- * record operations (create, read, update, delete, list).
6
- *
7
- * @packageDocumentation
8
- */
9
-
10
- import type { Agent } from "@atproto/api";
11
- import { NetworkError, ValidationError } from "../core/errors.js";
12
- import type { LexiconRegistry } from "./LexiconRegistry.js";
13
- import type { RecordOperations } from "./interfaces.js";
14
- import type { CreateResult, UpdateResult, PaginatedList } from "./types.js";
15
-
16
- /**
17
- * Implementation of low-level AT Protocol record operations.
18
- *
19
- * This class provides direct access to the AT Protocol repository API
20
- * for CRUD operations on records. It handles:
21
- *
22
- * - Lexicon validation before create/update operations
23
- * - Error mapping to SDK error types
24
- * - Response normalization
25
- *
26
- * @remarks
27
- * This class is typically not instantiated directly. Access it through
28
- * {@link Repository.records}.
29
- *
30
- * All operations are performed against the repository DID specified
31
- * at construction time. To operate on a different repository, create
32
- * a new Repository instance using {@link Repository.repo}.
33
- *
34
- * @example
35
- * ```typescript
36
- * // Access through Repository
37
- * const repo = sdk.repository(session);
38
- *
39
- * // Create a record
40
- * const { uri, cid } = await repo.records.create({
41
- * collection: "org.example.myRecord",
42
- * record: { title: "Hello", createdAt: new Date().toISOString() },
43
- * });
44
- *
45
- * // Update the record
46
- * const rkey = uri.split("/").pop()!;
47
- * await repo.records.update({
48
- * collection: "org.example.myRecord",
49
- * rkey,
50
- * record: { title: "Updated", createdAt: new Date().toISOString() },
51
- * });
52
- * ```
53
- *
54
- * @internal
55
- */
56
- export class RecordOperationsImpl implements RecordOperations {
57
- /**
58
- * Creates a new RecordOperationsImpl.
59
- *
60
- * @param agent - AT Protocol Agent for making API calls
61
- * @param repoDid - DID of the repository to operate on
62
- * @param lexiconRegistry - Registry for record validation
63
- *
64
- * @internal
65
- */
66
- constructor(
67
- private agent: Agent,
68
- private repoDid: string,
69
- private lexiconRegistry: LexiconRegistry,
70
- ) {}
71
-
72
- /**
73
- * Creates a new record in the specified collection.
74
- *
75
- * @param params - Creation parameters
76
- * @param params.collection - NSID of the collection (e.g., "org.hypercerts.hypercert")
77
- * @param params.record - Record data conforming to the collection's lexicon schema
78
- * @param params.rkey - Optional record key. If not provided, a TID (timestamp-based ID)
79
- * is automatically generated by the server.
80
- * @returns Promise resolving to the created record's URI and CID
81
- * @throws {@link ValidationError} if the record doesn't conform to the lexicon schema
82
- * @throws {@link NetworkError} if the API request fails
83
- *
84
- * @remarks
85
- * The record is validated against the collection's lexicon before sending
86
- * to the server. If no lexicon is registered for the collection, validation
87
- * is skipped (allowing custom record types).
88
- *
89
- * **AT-URI Format**: `at://{did}/{collection}/{rkey}`
90
- *
91
- * @example
92
- * ```typescript
93
- * const result = await repo.records.create({
94
- * collection: "org.hypercerts.hypercert",
95
- * record: {
96
- * title: "My Hypercert",
97
- * description: "...",
98
- * createdAt: new Date().toISOString(),
99
- * },
100
- * });
101
- * console.log(`Created: ${result.uri}`);
102
- * // Output: Created: at://did:plc:abc123/org.hypercerts.hypercert/xyz789
103
- * ```
104
- */
105
- async create(params: { collection: string; record: unknown; rkey?: string }): Promise<CreateResult> {
106
- const validation = this.lexiconRegistry.validate(params.collection, params.record);
107
- if (!validation.valid) {
108
- throw new ValidationError(`Invalid record for collection ${params.collection}: ${validation.error}`);
109
- }
110
-
111
- try {
112
- const result = await this.agent.com.atproto.repo.createRecord({
113
- repo: this.repoDid,
114
- collection: params.collection,
115
- record: params.record as Record<string, unknown>,
116
- rkey: params.rkey,
117
- });
118
-
119
- if (!result.success) {
120
- throw new NetworkError("Failed to create record");
121
- }
122
-
123
- return { uri: result.data.uri, cid: result.data.cid };
124
- } catch (error) {
125
- if (error instanceof ValidationError || error instanceof NetworkError) throw error;
126
- throw new NetworkError(
127
- `Failed to create record: ${error instanceof Error ? error.message : "Unknown error"}`,
128
- error,
129
- );
130
- }
131
- }
132
-
133
- /**
134
- * Updates an existing record (full replacement).
135
- *
136
- * @param params - Update parameters
137
- * @param params.collection - NSID of the collection
138
- * @param params.rkey - Record key (the last segment of the AT-URI)
139
- * @param params.record - New record data (completely replaces existing record)
140
- * @returns Promise resolving to the updated record's URI and new CID
141
- * @throws {@link ValidationError} if the record doesn't conform to the lexicon schema
142
- * @throws {@link NetworkError} if the API request fails
143
- *
144
- * @remarks
145
- * This is a full replacement operation, not a partial update. The entire
146
- * record is replaced with the new data. To preserve existing fields,
147
- * first fetch the record with {@link get}, modify it, then update.
148
- *
149
- * @example
150
- * ```typescript
151
- * // Get existing record
152
- * const existing = await repo.records.get({
153
- * collection: "org.hypercerts.hypercert",
154
- * rkey: "xyz789",
155
- * });
156
- *
157
- * // Update with modified data
158
- * const updated = await repo.records.update({
159
- * collection: "org.hypercerts.hypercert",
160
- * rkey: "xyz789",
161
- * record: {
162
- * ...existing.value,
163
- * title: "Updated Title",
164
- * },
165
- * });
166
- * ```
167
- */
168
- async update(params: { collection: string; rkey: string; record: unknown }): Promise<UpdateResult> {
169
- const validation = this.lexiconRegistry.validate(params.collection, params.record);
170
- if (!validation.valid) {
171
- throw new ValidationError(`Invalid record for collection ${params.collection}: ${validation.error}`);
172
- }
173
-
174
- try {
175
- const result = await this.agent.com.atproto.repo.putRecord({
176
- repo: this.repoDid,
177
- collection: params.collection,
178
- rkey: params.rkey,
179
- record: params.record as Record<string, unknown>,
180
- });
181
-
182
- if (!result.success) {
183
- throw new NetworkError("Failed to update record");
184
- }
185
-
186
- return { uri: result.data.uri, cid: result.data.cid };
187
- } catch (error) {
188
- if (error instanceof ValidationError || error instanceof NetworkError) throw error;
189
- throw new NetworkError(
190
- `Failed to update record: ${error instanceof Error ? error.message : "Unknown error"}`,
191
- error,
192
- );
193
- }
194
- }
195
-
196
- /**
197
- * Gets a single record by collection and key.
198
- *
199
- * @param params - Get parameters
200
- * @param params.collection - NSID of the collection
201
- * @param params.rkey - Record key
202
- * @returns Promise resolving to the record's URI, CID, and value
203
- * @throws {@link NetworkError} if the record is not found or request fails
204
- *
205
- * @example
206
- * ```typescript
207
- * const record = await repo.records.get({
208
- * collection: "org.hypercerts.hypercert",
209
- * rkey: "xyz789",
210
- * });
211
- *
212
- * console.log(record.uri); // at://did:plc:abc123/org.hypercerts.hypercert/xyz789
213
- * console.log(record.cid); // bafyrei...
214
- * console.log(record.value); // { title: "...", description: "...", ... }
215
- * ```
216
- */
217
- async get(params: { collection: string; rkey: string }): Promise<{ uri: string; cid: string; value: unknown }> {
218
- try {
219
- const result = await this.agent.com.atproto.repo.getRecord({
220
- repo: this.repoDid,
221
- collection: params.collection,
222
- rkey: params.rkey,
223
- });
224
-
225
- if (!result.success) {
226
- throw new NetworkError("Failed to get record");
227
- }
228
-
229
- return { uri: result.data.uri, cid: result.data.cid ?? "", value: result.data.value };
230
- } catch (error) {
231
- if (error instanceof NetworkError) throw error;
232
- throw new NetworkError(
233
- `Failed to get record: ${error instanceof Error ? error.message : "Unknown error"}`,
234
- error,
235
- );
236
- }
237
- }
238
-
239
- /**
240
- * Lists records in a collection with pagination.
241
- *
242
- * @param params - List parameters
243
- * @param params.collection - NSID of the collection
244
- * @param params.limit - Maximum number of records to return (server may impose its own limit)
245
- * @param params.cursor - Pagination cursor from a previous response
246
- * @returns Promise resolving to paginated list of records
247
- * @throws {@link NetworkError} if the request fails
248
- *
249
- * @remarks
250
- * Records are returned in reverse chronological order (newest first).
251
- * Use the `cursor` from the response to fetch subsequent pages.
252
- *
253
- * @example Paginating through all records
254
- * ```typescript
255
- * let cursor: string | undefined;
256
- * const allRecords = [];
257
- *
258
- * do {
259
- * const page = await repo.records.list({
260
- * collection: "org.hypercerts.hypercert",
261
- * limit: 100,
262
- * cursor,
263
- * });
264
- * allRecords.push(...page.records);
265
- * cursor = page.cursor;
266
- * } while (cursor);
267
- *
268
- * console.log(`Total records: ${allRecords.length}`);
269
- * ```
270
- */
271
- async list(params: {
272
- collection: string;
273
- limit?: number;
274
- cursor?: string;
275
- }): Promise<PaginatedList<{ uri: string; cid: string; value: unknown }>> {
276
- try {
277
- const result = await this.agent.com.atproto.repo.listRecords({
278
- repo: this.repoDid,
279
- collection: params.collection,
280
- limit: params.limit,
281
- cursor: params.cursor,
282
- });
283
-
284
- if (!result.success) {
285
- throw new NetworkError("Failed to list records");
286
- }
287
-
288
- return {
289
- records: result.data.records?.map((r) => ({ uri: r.uri, cid: r.cid, value: r.value })) || [],
290
- cursor: result.data.cursor ?? undefined,
291
- };
292
- } catch (error) {
293
- if (error instanceof NetworkError) throw error;
294
- throw new NetworkError(
295
- `Failed to list records: ${error instanceof Error ? error.message : "Unknown error"}`,
296
- error,
297
- );
298
- }
299
- }
300
-
301
- /**
302
- * Deletes a record from a collection.
303
- *
304
- * @param params - Delete parameters
305
- * @param params.collection - NSID of the collection
306
- * @param params.rkey - Record key to delete
307
- * @throws {@link NetworkError} if the deletion fails
308
- *
309
- * @remarks
310
- * Deletion is permanent. The record's AT-URI cannot be reused (the same
311
- * rkey can be used for a new record, but it will have a different CID).
312
- *
313
- * @example
314
- * ```typescript
315
- * await repo.records.delete({
316
- * collection: "org.hypercerts.hypercert",
317
- * rkey: "xyz789",
318
- * });
319
- * ```
320
- */
321
- async delete(params: { collection: string; rkey: string }): Promise<void> {
322
- try {
323
- const result = await this.agent.com.atproto.repo.deleteRecord({
324
- repo: this.repoDid,
325
- collection: params.collection,
326
- rkey: params.rkey,
327
- });
328
-
329
- if (!result.success) {
330
- throw new NetworkError("Failed to delete record");
331
- }
332
- } catch (error) {
333
- if (error instanceof NetworkError) throw error;
334
- throw new NetworkError(
335
- `Failed to delete record: ${error instanceof Error ? error.message : "Unknown error"}`,
336
- error,
337
- );
338
- }
339
- }
340
- }