@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.
Files changed (83) hide show
  1. package/.turbo/turbo-build.log +328 -0
  2. package/.turbo/turbo-test.log +118 -0
  3. package/CHANGELOG.md +16 -0
  4. package/LICENSE +21 -0
  5. package/README.md +100 -0
  6. package/dist/errors.cjs +260 -0
  7. package/dist/errors.cjs.map +1 -0
  8. package/dist/errors.d.ts +233 -0
  9. package/dist/errors.mjs +253 -0
  10. package/dist/errors.mjs.map +1 -0
  11. package/dist/index.cjs +4531 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.d.ts +3430 -0
  14. package/dist/index.mjs +4448 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/lexicons.cjs +420 -0
  17. package/dist/lexicons.cjs.map +1 -0
  18. package/dist/lexicons.d.ts +227 -0
  19. package/dist/lexicons.mjs +410 -0
  20. package/dist/lexicons.mjs.map +1 -0
  21. package/dist/storage.cjs +270 -0
  22. package/dist/storage.cjs.map +1 -0
  23. package/dist/storage.d.ts +474 -0
  24. package/dist/storage.mjs +267 -0
  25. package/dist/storage.mjs.map +1 -0
  26. package/dist/testing.cjs +415 -0
  27. package/dist/testing.cjs.map +1 -0
  28. package/dist/testing.d.ts +928 -0
  29. package/dist/testing.mjs +410 -0
  30. package/dist/testing.mjs.map +1 -0
  31. package/dist/types.cjs +220 -0
  32. package/dist/types.cjs.map +1 -0
  33. package/dist/types.d.ts +2118 -0
  34. package/dist/types.mjs +212 -0
  35. package/dist/types.mjs.map +1 -0
  36. package/eslint.config.mjs +22 -0
  37. package/package.json +90 -0
  38. package/rollup.config.js +75 -0
  39. package/src/auth/OAuthClient.ts +497 -0
  40. package/src/core/SDK.ts +410 -0
  41. package/src/core/config.ts +243 -0
  42. package/src/core/errors.ts +257 -0
  43. package/src/core/interfaces.ts +324 -0
  44. package/src/core/types.ts +281 -0
  45. package/src/errors.ts +57 -0
  46. package/src/index.ts +107 -0
  47. package/src/lexicons.ts +64 -0
  48. package/src/repository/BlobOperationsImpl.ts +199 -0
  49. package/src/repository/CollaboratorOperationsImpl.ts +288 -0
  50. package/src/repository/HypercertOperationsImpl.ts +1146 -0
  51. package/src/repository/LexiconRegistry.ts +332 -0
  52. package/src/repository/OrganizationOperationsImpl.ts +234 -0
  53. package/src/repository/ProfileOperationsImpl.ts +281 -0
  54. package/src/repository/RecordOperationsImpl.ts +340 -0
  55. package/src/repository/Repository.ts +482 -0
  56. package/src/repository/interfaces.ts +868 -0
  57. package/src/repository/types.ts +111 -0
  58. package/src/services/hypercerts/types.ts +87 -0
  59. package/src/storage/InMemorySessionStore.ts +127 -0
  60. package/src/storage/InMemoryStateStore.ts +146 -0
  61. package/src/storage.ts +63 -0
  62. package/src/testing/index.ts +67 -0
  63. package/src/testing/mocks.ts +142 -0
  64. package/src/testing/stores.ts +285 -0
  65. package/src/testing.ts +64 -0
  66. package/src/types.ts +86 -0
  67. package/tests/auth/OAuthClient.test.ts +164 -0
  68. package/tests/core/SDK.test.ts +176 -0
  69. package/tests/core/errors.test.ts +81 -0
  70. package/tests/repository/BlobOperationsImpl.test.ts +154 -0
  71. package/tests/repository/CollaboratorOperationsImpl.test.ts +323 -0
  72. package/tests/repository/HypercertOperationsImpl.test.ts +652 -0
  73. package/tests/repository/LexiconRegistry.test.ts +192 -0
  74. package/tests/repository/OrganizationOperationsImpl.test.ts +242 -0
  75. package/tests/repository/ProfileOperationsImpl.test.ts +254 -0
  76. package/tests/repository/RecordOperationsImpl.test.ts +375 -0
  77. package/tests/repository/Repository.test.ts +149 -0
  78. package/tests/utils/fixtures.ts +117 -0
  79. package/tests/utils/mocks.ts +109 -0
  80. package/tests/utils/repository-fixtures.ts +78 -0
  81. package/tsconfig.json +11 -0
  82. package/tsconfig.tsbuildinfo +1 -0
  83. package/vitest.config.ts +30 -0
@@ -0,0 +1,420 @@
1
+ 'use strict';
2
+
3
+ var lexicon$1 = require('@atproto/lexicon');
4
+ var lexicon = require('@hypercerts-org/lexicon');
5
+
6
+ /**
7
+ * Base error class for all SDK errors.
8
+ *
9
+ * All errors thrown by the Hypercerts SDK extend this class, making it easy
10
+ * to catch and handle SDK-specific errors.
11
+ *
12
+ * @example Catching all SDK errors
13
+ * ```typescript
14
+ * try {
15
+ * await sdk.authorize("user.bsky.social");
16
+ * } catch (error) {
17
+ * if (error instanceof ATProtoSDKError) {
18
+ * console.error(`SDK Error [${error.code}]: ${error.message}`);
19
+ * console.error(`HTTP Status: ${error.status}`);
20
+ * }
21
+ * }
22
+ * ```
23
+ *
24
+ * @example Checking error codes
25
+ * ```typescript
26
+ * try {
27
+ * await repo.records.get(collection, rkey);
28
+ * } catch (error) {
29
+ * if (error instanceof ATProtoSDKError) {
30
+ * switch (error.code) {
31
+ * case "AUTHENTICATION_ERROR":
32
+ * // Redirect to login
33
+ * break;
34
+ * case "VALIDATION_ERROR":
35
+ * // Show form errors
36
+ * break;
37
+ * case "NETWORK_ERROR":
38
+ * // Retry or show offline message
39
+ * break;
40
+ * }
41
+ * }
42
+ * }
43
+ * ```
44
+ */
45
+ class ATProtoSDKError extends Error {
46
+ /**
47
+ * Creates a new SDK error.
48
+ *
49
+ * @param message - Human-readable error description
50
+ * @param code - Machine-readable error code for programmatic handling
51
+ * @param status - HTTP status code associated with this error type
52
+ * @param cause - The underlying error that caused this error, if any
53
+ */
54
+ constructor(message, code, status, cause) {
55
+ super(message);
56
+ this.code = code;
57
+ this.status = status;
58
+ this.cause = cause;
59
+ this.name = "ATProtoSDKError";
60
+ Error.captureStackTrace?.(this, this.constructor);
61
+ }
62
+ }
63
+ /**
64
+ * Error thrown when input validation fails.
65
+ *
66
+ * This error indicates that provided data doesn't meet the required format
67
+ * or constraints. Common causes:
68
+ * - Missing required fields
69
+ * - Invalid URL formats
70
+ * - Invalid DID format
71
+ * - Schema validation failures for records
72
+ * - Invalid configuration values
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * try {
77
+ * await sdk.authorize(""); // Empty identifier
78
+ * } catch (error) {
79
+ * if (error instanceof ValidationError) {
80
+ * console.error("Invalid input:", error.message);
81
+ * // Show validation error to user
82
+ * }
83
+ * }
84
+ * ```
85
+ *
86
+ * @example With Zod validation cause
87
+ * ```typescript
88
+ * try {
89
+ * await repo.records.create(collection, record);
90
+ * } catch (error) {
91
+ * if (error instanceof ValidationError && error.cause) {
92
+ * // error.cause may be a ZodError with detailed field errors
93
+ * const zodError = error.cause as ZodError;
94
+ * zodError.errors.forEach(e => {
95
+ * console.error(`Field ${e.path.join(".")}: ${e.message}`);
96
+ * });
97
+ * }
98
+ * }
99
+ * ```
100
+ */
101
+ class ValidationError extends ATProtoSDKError {
102
+ /**
103
+ * Creates a validation error.
104
+ *
105
+ * @param message - Description of what validation failed
106
+ * @param cause - The underlying validation error (e.g., ZodError)
107
+ */
108
+ constructor(message, cause) {
109
+ super(message, "VALIDATION_ERROR", 400, cause);
110
+ this.name = "ValidationError";
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Registry for managing and validating AT Protocol lexicon schemas.
116
+ *
117
+ * Lexicons are schema definitions that describe the structure of records
118
+ * in the AT Protocol. This registry allows you to:
119
+ *
120
+ * - Register custom lexicons for your application's record types
121
+ * - Validate records against their lexicon schemas
122
+ * - Extend the AT Protocol Agent with custom lexicon support
123
+ *
124
+ * @remarks
125
+ * The SDK automatically registers hypercert lexicons when creating a Repository.
126
+ * You only need to use this class directly if you're working with custom
127
+ * record types.
128
+ *
129
+ * **Lexicon IDs** follow the NSID (Namespaced Identifier) format:
130
+ * `{authority}.{name}` (e.g., `org.hypercerts.hypercert`)
131
+ *
132
+ * @example Registering custom lexicons
133
+ * ```typescript
134
+ * const registry = sdk.getLexiconRegistry();
135
+ *
136
+ * // Register a single lexicon
137
+ * registry.register({
138
+ * lexicon: 1,
139
+ * id: "org.example.myRecord",
140
+ * defs: {
141
+ * main: {
142
+ * type: "record",
143
+ * key: "tid",
144
+ * record: {
145
+ * type: "object",
146
+ * required: ["title", "createdAt"],
147
+ * properties: {
148
+ * title: { type: "string" },
149
+ * description: { type: "string" },
150
+ * createdAt: { type: "string", format: "datetime" },
151
+ * },
152
+ * },
153
+ * },
154
+ * },
155
+ * });
156
+ *
157
+ * // Register multiple lexicons at once
158
+ * registry.registerMany([lexicon1, lexicon2, lexicon3]);
159
+ * ```
160
+ *
161
+ * @example Validating records
162
+ * ```typescript
163
+ * const result = registry.validate("org.example.myRecord", {
164
+ * title: "Test",
165
+ * createdAt: new Date().toISOString(),
166
+ * });
167
+ *
168
+ * if (!result.valid) {
169
+ * console.error(`Validation failed: ${result.error}`);
170
+ * }
171
+ * ```
172
+ *
173
+ * @see https://atproto.com/specs/lexicon for the Lexicon specification
174
+ */
175
+ class LexiconRegistry {
176
+ /**
177
+ * Creates a new LexiconRegistry.
178
+ *
179
+ * The registry starts empty. Use {@link register} or {@link registerMany}
180
+ * to add lexicons.
181
+ */
182
+ constructor() {
183
+ /** Map of lexicon ID to lexicon document */
184
+ this.lexicons = new Map();
185
+ this.lexiconsCollection = new lexicon$1.Lexicons();
186
+ }
187
+ /**
188
+ * Registers a single lexicon schema.
189
+ *
190
+ * @param lexicon - The lexicon document to register
191
+ * @throws {@link ValidationError} if the lexicon doesn't have an `id` field
192
+ *
193
+ * @remarks
194
+ * If a lexicon with the same ID is already registered, it will be
195
+ * replaced with the new definition. This is useful for testing but
196
+ * should generally be avoided in production.
197
+ *
198
+ * @example
199
+ * ```typescript
200
+ * registry.register({
201
+ * lexicon: 1,
202
+ * id: "org.example.post",
203
+ * defs: {
204
+ * main: {
205
+ * type: "record",
206
+ * key: "tid",
207
+ * record: {
208
+ * type: "object",
209
+ * required: ["text", "createdAt"],
210
+ * properties: {
211
+ * text: { type: "string", maxLength: 300 },
212
+ * createdAt: { type: "string", format: "datetime" },
213
+ * },
214
+ * },
215
+ * },
216
+ * },
217
+ * });
218
+ * ```
219
+ */
220
+ register(lexicon) {
221
+ if (!lexicon.id) {
222
+ throw new ValidationError("Lexicon must have an 'id' field");
223
+ }
224
+ // Remove existing lexicon if present (to allow overwriting)
225
+ if (this.lexicons.has(lexicon.id)) {
226
+ // Lexicons collection doesn't support removal, so we create a new one
227
+ // This is a limitation - in practice, lexicons shouldn't be overwritten
228
+ // But we allow it for testing and flexibility
229
+ const existingLexicon = this.lexicons.get(lexicon.id);
230
+ if (existingLexicon) {
231
+ // Try to remove from collection (may fail if not supported)
232
+ try {
233
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
234
+ this.lexiconsCollection.remove?.(lexicon.id);
235
+ }
236
+ catch {
237
+ // If removal fails, create a new collection
238
+ this.lexiconsCollection = new lexicon$1.Lexicons();
239
+ // Re-register all other lexicons
240
+ for (const [id, lex] of this.lexicons.entries()) {
241
+ if (id !== lexicon.id) {
242
+ this.lexiconsCollection.add(lex);
243
+ }
244
+ }
245
+ }
246
+ }
247
+ }
248
+ this.lexicons.set(lexicon.id, lexicon);
249
+ this.lexiconsCollection.add(lexicon);
250
+ }
251
+ /**
252
+ * Registers multiple lexicons at once.
253
+ *
254
+ * @param lexicons - Array of lexicon documents to register
255
+ *
256
+ * @example
257
+ * ```typescript
258
+ * import { HYPERCERT_LEXICONS } from "@hypercerts-org/sdk/lexicons";
259
+ *
260
+ * registry.registerMany(HYPERCERT_LEXICONS);
261
+ * ```
262
+ */
263
+ registerMany(lexicons) {
264
+ for (const lexicon of lexicons) {
265
+ this.register(lexicon);
266
+ }
267
+ }
268
+ /**
269
+ * Gets a lexicon document by ID.
270
+ *
271
+ * @param id - The lexicon NSID (e.g., "org.hypercerts.hypercert")
272
+ * @returns The lexicon document, or `undefined` if not registered
273
+ *
274
+ * @example
275
+ * ```typescript
276
+ * const lexicon = registry.get("org.hypercerts.hypercert");
277
+ * if (lexicon) {
278
+ * console.log(`Found lexicon: ${lexicon.id}`);
279
+ * }
280
+ * ```
281
+ */
282
+ get(id) {
283
+ return this.lexicons.get(id);
284
+ }
285
+ /**
286
+ * Validates a record against a collection's lexicon schema.
287
+ *
288
+ * @param collection - The collection NSID (same as lexicon ID)
289
+ * @param record - The record data to validate
290
+ * @returns Validation result with `valid` boolean and optional `error` message
291
+ *
292
+ * @remarks
293
+ * - If no lexicon is registered for the collection, validation passes
294
+ * (we can't validate against unknown schemas)
295
+ * - Validation checks required fields and type constraints defined
296
+ * in the lexicon schema
297
+ *
298
+ * @example
299
+ * ```typescript
300
+ * const result = registry.validate("org.hypercerts.hypercert", {
301
+ * title: "My Hypercert",
302
+ * description: "Description...",
303
+ * // ... other fields
304
+ * });
305
+ *
306
+ * if (!result.valid) {
307
+ * throw new Error(`Invalid record: ${result.error}`);
308
+ * }
309
+ * ```
310
+ */
311
+ validate(collection, record) {
312
+ // Check if we have a lexicon registered for this collection
313
+ // Collection format is typically "namespace.collection" (e.g., "app.bsky.feed.post")
314
+ // Lexicon ID format is the same
315
+ const lexiconId = collection;
316
+ const lexicon = this.lexicons.get(lexiconId);
317
+ if (!lexicon) {
318
+ // No lexicon registered - validation passes (can't validate unknown schemas)
319
+ return { valid: true };
320
+ }
321
+ // Check required fields if the lexicon defines them
322
+ const recordDef = lexicon.defs?.record;
323
+ if (recordDef && typeof recordDef === "object" && "record" in recordDef) {
324
+ const recordSchema = recordDef.record;
325
+ if (typeof recordSchema === "object" && "required" in recordSchema && Array.isArray(recordSchema.required)) {
326
+ const recordObj = record;
327
+ for (const requiredField of recordSchema.required) {
328
+ if (typeof requiredField === "string" && !(requiredField in recordObj)) {
329
+ return {
330
+ valid: false,
331
+ error: `Missing required field: ${requiredField}`,
332
+ };
333
+ }
334
+ }
335
+ }
336
+ }
337
+ try {
338
+ this.lexiconsCollection.assertValidRecord(collection, record);
339
+ return { valid: true };
340
+ }
341
+ catch (error) {
342
+ // If error indicates lexicon not found, treat as validation pass
343
+ // (the lexicon might exist in Agent's collection but not ours)
344
+ const errorMessage = error instanceof Error ? error.message : String(error);
345
+ if (errorMessage.includes("not found") || errorMessage.includes("Lexicon not found")) {
346
+ return { valid: true };
347
+ }
348
+ return {
349
+ valid: false,
350
+ error: errorMessage,
351
+ };
352
+ }
353
+ }
354
+ /**
355
+ * Adds all registered lexicons to an AT Protocol Agent instance.
356
+ *
357
+ * This allows the Agent to understand custom lexicon types when making
358
+ * API requests.
359
+ *
360
+ * @param agent - The Agent instance to extend
361
+ *
362
+ * @remarks
363
+ * This is called automatically when creating a Repository. You typically
364
+ * don't need to call this directly unless you're using the Agent
365
+ * independently.
366
+ *
367
+ * @internal
368
+ */
369
+ addToAgent(agent) {
370
+ // Access the internal lexicons collection and merge our lexicons
371
+ // The Agent's lex property is a Lexicons instance
372
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
373
+ const agentLex = agent.lex;
374
+ // Add each registered lexicon to the agent
375
+ for (const lexicon of this.lexicons.values()) {
376
+ agentLex.add(lexicon);
377
+ }
378
+ }
379
+ /**
380
+ * Gets all registered lexicon IDs.
381
+ *
382
+ * @returns Array of lexicon NSIDs
383
+ *
384
+ * @example
385
+ * ```typescript
386
+ * const ids = registry.getRegisteredIds();
387
+ * console.log(`Registered lexicons: ${ids.join(", ")}`);
388
+ * ```
389
+ */
390
+ getRegisteredIds() {
391
+ return Array.from(this.lexicons.keys());
392
+ }
393
+ /**
394
+ * Checks if a lexicon is registered.
395
+ *
396
+ * @param id - The lexicon NSID to check
397
+ * @returns `true` if the lexicon is registered
398
+ *
399
+ * @example
400
+ * ```typescript
401
+ * if (registry.has("org.hypercerts.hypercert")) {
402
+ * // Hypercert lexicon is available
403
+ * }
404
+ * ```
405
+ */
406
+ has(id) {
407
+ return this.lexicons.has(id);
408
+ }
409
+ }
410
+
411
+ Object.defineProperty(exports, "HYPERCERT_COLLECTIONS", {
412
+ enumerable: true,
413
+ get: function () { return lexicon.HYPERCERT_COLLECTIONS; }
414
+ });
415
+ Object.defineProperty(exports, "HYPERCERT_LEXICONS", {
416
+ enumerable: true,
417
+ get: function () { return lexicon.HYPERCERT_LEXICONS; }
418
+ });
419
+ exports.LexiconRegistry = LexiconRegistry;
420
+ //# sourceMappingURL=lexicons.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lexicons.cjs","sources":["../src/core/errors.ts","../src/repository/LexiconRegistry.ts"],"sourcesContent":["/**\n * Base error class for all SDK errors.\n *\n * All errors thrown by the Hypercerts SDK extend this class, making it easy\n * to catch and handle SDK-specific errors.\n *\n * @example Catching all SDK errors\n * ```typescript\n * try {\n * await sdk.authorize(\"user.bsky.social\");\n * } catch (error) {\n * if (error instanceof ATProtoSDKError) {\n * console.error(`SDK Error [${error.code}]: ${error.message}`);\n * console.error(`HTTP Status: ${error.status}`);\n * }\n * }\n * ```\n *\n * @example Checking error codes\n * ```typescript\n * try {\n * await repo.records.get(collection, rkey);\n * } catch (error) {\n * if (error instanceof ATProtoSDKError) {\n * switch (error.code) {\n * case \"AUTHENTICATION_ERROR\":\n * // Redirect to login\n * break;\n * case \"VALIDATION_ERROR\":\n * // Show form errors\n * break;\n * case \"NETWORK_ERROR\":\n * // Retry or show offline message\n * break;\n * }\n * }\n * }\n * ```\n */\nexport class ATProtoSDKError extends Error {\n /**\n * Creates a new SDK error.\n *\n * @param message - Human-readable error description\n * @param code - Machine-readable error code for programmatic handling\n * @param status - HTTP status code associated with this error type\n * @param cause - The underlying error that caused this error, if any\n */\n constructor(\n message: string,\n public code: string,\n public status?: number,\n public cause?: unknown,\n ) {\n super(message);\n this.name = \"ATProtoSDKError\";\n Error.captureStackTrace?.(this, this.constructor);\n }\n}\n\n/**\n * Error thrown when authentication fails.\n *\n * This error indicates problems with the OAuth flow, invalid credentials,\n * or failed token exchanges. Common causes:\n * - Invalid authorization code\n * - Expired or invalid state parameter\n * - Revoked or invalid tokens\n * - User denied authorization\n *\n * @example\n * ```typescript\n * try {\n * const session = await sdk.callback(params);\n * } catch (error) {\n * if (error instanceof AuthenticationError) {\n * // Clear any stored state and redirect to login\n * console.error(\"Authentication failed:\", error.message);\n * }\n * }\n * ```\n */\nexport class AuthenticationError extends ATProtoSDKError {\n /**\n * Creates an authentication error.\n *\n * @param message - Description of what went wrong during authentication\n * @param cause - The underlying error (e.g., from the OAuth client)\n */\n constructor(message: string, cause?: unknown) {\n super(message, \"AUTHENTICATION_ERROR\", 401, cause);\n this.name = \"AuthenticationError\";\n }\n}\n\n/**\n * Error thrown when a session has expired and cannot be refreshed.\n *\n * This typically occurs when:\n * - The refresh token has expired (usually after extended inactivity)\n * - The user has revoked access to your application\n * - The PDS has invalidated all sessions for the user\n *\n * When this error occurs, the user must re-authenticate.\n *\n * @example\n * ```typescript\n * try {\n * const session = await sdk.restoreSession(did);\n * } catch (error) {\n * if (error instanceof SessionExpiredError) {\n * // Clear stored session and prompt user to log in again\n * localStorage.removeItem(\"userDid\");\n * window.location.href = \"/login\";\n * }\n * }\n * ```\n */\nexport class SessionExpiredError extends ATProtoSDKError {\n /**\n * Creates a session expired error.\n *\n * @param message - Description of why the session expired\n * @param cause - The underlying error from the token refresh attempt\n */\n constructor(message: string = \"Session expired\", cause?: unknown) {\n super(message, \"SESSION_EXPIRED\", 401, cause);\n this.name = \"SessionExpiredError\";\n }\n}\n\n/**\n * Error thrown when input validation fails.\n *\n * This error indicates that provided data doesn't meet the required format\n * or constraints. Common causes:\n * - Missing required fields\n * - Invalid URL formats\n * - Invalid DID format\n * - Schema validation failures for records\n * - Invalid configuration values\n *\n * @example\n * ```typescript\n * try {\n * await sdk.authorize(\"\"); // Empty identifier\n * } catch (error) {\n * if (error instanceof ValidationError) {\n * console.error(\"Invalid input:\", error.message);\n * // Show validation error to user\n * }\n * }\n * ```\n *\n * @example With Zod validation cause\n * ```typescript\n * try {\n * await repo.records.create(collection, record);\n * } catch (error) {\n * if (error instanceof ValidationError && error.cause) {\n * // error.cause may be a ZodError with detailed field errors\n * const zodError = error.cause as ZodError;\n * zodError.errors.forEach(e => {\n * console.error(`Field ${e.path.join(\".\")}: ${e.message}`);\n * });\n * }\n * }\n * ```\n */\nexport class ValidationError extends ATProtoSDKError {\n /**\n * Creates a validation error.\n *\n * @param message - Description of what validation failed\n * @param cause - The underlying validation error (e.g., ZodError)\n */\n constructor(message: string, cause?: unknown) {\n super(message, \"VALIDATION_ERROR\", 400, cause);\n this.name = \"ValidationError\";\n }\n}\n\n/**\n * Error thrown when a network request fails.\n *\n * This error indicates connectivity issues or server unavailability.\n * Common causes:\n * - No internet connection\n * - DNS resolution failure\n * - Server timeout\n * - Server returned 5xx error\n * - TLS/SSL errors\n *\n * These errors are typically transient and may succeed on retry.\n *\n * @example\n * ```typescript\n * try {\n * await repo.records.list(collection);\n * } catch (error) {\n * if (error instanceof NetworkError) {\n * // Implement retry logic or show offline indicator\n * console.error(\"Network error:\", error.message);\n * await retryWithBackoff(() => repo.records.list(collection));\n * }\n * }\n * ```\n */\nexport class NetworkError extends ATProtoSDKError {\n /**\n * Creates a network error.\n *\n * @param message - Description of the network failure\n * @param cause - The underlying error (e.g., fetch error, timeout)\n */\n constructor(message: string, cause?: unknown) {\n super(message, \"NETWORK_ERROR\", 503, cause);\n this.name = \"NetworkError\";\n }\n}\n\n/**\n * Error thrown when an SDS-only operation is attempted on a PDS.\n *\n * Certain operations are only available on Shared Data Servers (SDS),\n * such as collaborator management and organization operations.\n * This error is thrown when these operations are attempted on a\n * Personal Data Server (PDS).\n *\n * @example\n * ```typescript\n * const pdsRepo = sdk.repository(session); // Default is PDS\n *\n * try {\n * // This will throw SDSRequiredError\n * await pdsRepo.collaborators.list();\n * } catch (error) {\n * if (error instanceof SDSRequiredError) {\n * // Switch to SDS for this operation\n * const sdsRepo = sdk.repository(session, { server: \"sds\" });\n * const collaborators = await sdsRepo.collaborators.list();\n * }\n * }\n * ```\n */\nexport class SDSRequiredError extends ATProtoSDKError {\n /**\n * Creates an SDS required error.\n *\n * @param message - Description of which operation requires SDS\n * @param cause - Any underlying error\n */\n constructor(message: string = \"This operation requires a Shared Data Server (SDS)\", cause?: unknown) {\n super(message, \"SDS_REQUIRED\", 400, cause);\n this.name = \"SDSRequiredError\";\n }\n}\n","import type { Agent } from \"@atproto/api\";\nimport type { LexiconDoc } from \"@atproto/lexicon\";\nimport { Lexicons } from \"@atproto/lexicon\";\nimport { ValidationError } from \"../core/errors.js\";\n\n/**\n * Result of validating a record against a lexicon schema.\n */\nexport interface ValidationResult {\n /**\n * Whether the record is valid according to the lexicon schema.\n */\n valid: boolean;\n\n /**\n * Error message if validation failed.\n *\n * Only present when `valid` is `false`.\n */\n error?: string;\n}\n\n/**\n * Registry for managing and validating AT Protocol lexicon schemas.\n *\n * Lexicons are schema definitions that describe the structure of records\n * in the AT Protocol. This registry allows you to:\n *\n * - Register custom lexicons for your application's record types\n * - Validate records against their lexicon schemas\n * - Extend the AT Protocol Agent with custom lexicon support\n *\n * @remarks\n * The SDK automatically registers hypercert lexicons when creating a Repository.\n * You only need to use this class directly if you're working with custom\n * record types.\n *\n * **Lexicon IDs** follow the NSID (Namespaced Identifier) format:\n * `{authority}.{name}` (e.g., `org.hypercerts.hypercert`)\n *\n * @example Registering custom lexicons\n * ```typescript\n * const registry = sdk.getLexiconRegistry();\n *\n * // Register a single lexicon\n * registry.register({\n * lexicon: 1,\n * id: \"org.example.myRecord\",\n * defs: {\n * main: {\n * type: \"record\",\n * key: \"tid\",\n * record: {\n * type: \"object\",\n * required: [\"title\", \"createdAt\"],\n * properties: {\n * title: { type: \"string\" },\n * description: { type: \"string\" },\n * createdAt: { type: \"string\", format: \"datetime\" },\n * },\n * },\n * },\n * },\n * });\n *\n * // Register multiple lexicons at once\n * registry.registerMany([lexicon1, lexicon2, lexicon3]);\n * ```\n *\n * @example Validating records\n * ```typescript\n * const result = registry.validate(\"org.example.myRecord\", {\n * title: \"Test\",\n * createdAt: new Date().toISOString(),\n * });\n *\n * if (!result.valid) {\n * console.error(`Validation failed: ${result.error}`);\n * }\n * ```\n *\n * @see https://atproto.com/specs/lexicon for the Lexicon specification\n */\nexport class LexiconRegistry {\n /** Map of lexicon ID to lexicon document */\n private lexicons = new Map<string, LexiconDoc>();\n\n /** Lexicons collection for validation */\n private lexiconsCollection: Lexicons;\n\n /**\n * Creates a new LexiconRegistry.\n *\n * The registry starts empty. Use {@link register} or {@link registerMany}\n * to add lexicons.\n */\n constructor() {\n this.lexiconsCollection = new Lexicons();\n }\n\n /**\n * Registers a single lexicon schema.\n *\n * @param lexicon - The lexicon document to register\n * @throws {@link ValidationError} if the lexicon doesn't have an `id` field\n *\n * @remarks\n * If a lexicon with the same ID is already registered, it will be\n * replaced with the new definition. This is useful for testing but\n * should generally be avoided in production.\n *\n * @example\n * ```typescript\n * registry.register({\n * lexicon: 1,\n * id: \"org.example.post\",\n * defs: {\n * main: {\n * type: \"record\",\n * key: \"tid\",\n * record: {\n * type: \"object\",\n * required: [\"text\", \"createdAt\"],\n * properties: {\n * text: { type: \"string\", maxLength: 300 },\n * createdAt: { type: \"string\", format: \"datetime\" },\n * },\n * },\n * },\n * },\n * });\n * ```\n */\n register(lexicon: LexiconDoc): void {\n if (!lexicon.id) {\n throw new ValidationError(\"Lexicon must have an 'id' field\");\n }\n\n // Remove existing lexicon if present (to allow overwriting)\n if (this.lexicons.has(lexicon.id)) {\n // Lexicons collection doesn't support removal, so we create a new one\n // This is a limitation - in practice, lexicons shouldn't be overwritten\n // But we allow it for testing and flexibility\n const existingLexicon = this.lexicons.get(lexicon.id);\n if (existingLexicon) {\n // Try to remove from collection (may fail if not supported)\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this.lexiconsCollection as any).remove?.(lexicon.id);\n } catch {\n // If removal fails, create a new collection\n this.lexiconsCollection = new Lexicons();\n // Re-register all other lexicons\n for (const [id, lex] of this.lexicons.entries()) {\n if (id !== lexicon.id) {\n this.lexiconsCollection.add(lex);\n }\n }\n }\n }\n }\n\n this.lexicons.set(lexicon.id, lexicon);\n this.lexiconsCollection.add(lexicon);\n }\n\n /**\n * Registers multiple lexicons at once.\n *\n * @param lexicons - Array of lexicon documents to register\n *\n * @example\n * ```typescript\n * import { HYPERCERT_LEXICONS } from \"@hypercerts-org/sdk/lexicons\";\n *\n * registry.registerMany(HYPERCERT_LEXICONS);\n * ```\n */\n registerMany(lexicons: LexiconDoc[]): void {\n for (const lexicon of lexicons) {\n this.register(lexicon);\n }\n }\n\n /**\n * Gets a lexicon document by ID.\n *\n * @param id - The lexicon NSID (e.g., \"org.hypercerts.hypercert\")\n * @returns The lexicon document, or `undefined` if not registered\n *\n * @example\n * ```typescript\n * const lexicon = registry.get(\"org.hypercerts.hypercert\");\n * if (lexicon) {\n * console.log(`Found lexicon: ${lexicon.id}`);\n * }\n * ```\n */\n get(id: string): LexiconDoc | undefined {\n return this.lexicons.get(id);\n }\n\n /**\n * Validates a record against a collection's lexicon schema.\n *\n * @param collection - The collection NSID (same as lexicon ID)\n * @param record - The record data to validate\n * @returns Validation result with `valid` boolean and optional `error` message\n *\n * @remarks\n * - If no lexicon is registered for the collection, validation passes\n * (we can't validate against unknown schemas)\n * - Validation checks required fields and type constraints defined\n * in the lexicon schema\n *\n * @example\n * ```typescript\n * const result = registry.validate(\"org.hypercerts.hypercert\", {\n * title: \"My Hypercert\",\n * description: \"Description...\",\n * // ... other fields\n * });\n *\n * if (!result.valid) {\n * throw new Error(`Invalid record: ${result.error}`);\n * }\n * ```\n */\n validate(collection: string, record: unknown): ValidationResult {\n // Check if we have a lexicon registered for this collection\n // Collection format is typically \"namespace.collection\" (e.g., \"app.bsky.feed.post\")\n // Lexicon ID format is the same\n const lexiconId = collection;\n const lexicon = this.lexicons.get(lexiconId);\n if (!lexicon) {\n // No lexicon registered - validation passes (can't validate unknown schemas)\n return { valid: true };\n }\n\n // Check required fields if the lexicon defines them\n const recordDef = lexicon.defs?.record;\n if (recordDef && typeof recordDef === \"object\" && \"record\" in recordDef) {\n const recordSchema = recordDef.record;\n if (typeof recordSchema === \"object\" && \"required\" in recordSchema && Array.isArray(recordSchema.required)) {\n const recordObj = record as Record<string, unknown>;\n for (const requiredField of recordSchema.required) {\n if (typeof requiredField === \"string\" && !(requiredField in recordObj)) {\n return {\n valid: false,\n error: `Missing required field: ${requiredField}`,\n };\n }\n }\n }\n }\n\n try {\n this.lexiconsCollection.assertValidRecord(collection, record);\n return { valid: true };\n } catch (error) {\n // If error indicates lexicon not found, treat as validation pass\n // (the lexicon might exist in Agent's collection but not ours)\n const errorMessage = error instanceof Error ? error.message : String(error);\n if (errorMessage.includes(\"not found\") || errorMessage.includes(\"Lexicon not found\")) {\n return { valid: true };\n }\n return {\n valid: false,\n error: errorMessage,\n };\n }\n }\n\n /**\n * Adds all registered lexicons to an AT Protocol Agent instance.\n *\n * This allows the Agent to understand custom lexicon types when making\n * API requests.\n *\n * @param agent - The Agent instance to extend\n *\n * @remarks\n * This is called automatically when creating a Repository. You typically\n * don't need to call this directly unless you're using the Agent\n * independently.\n *\n * @internal\n */\n addToAgent(agent: Agent): void {\n // Access the internal lexicons collection and merge our lexicons\n // The Agent's lex property is a Lexicons instance\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const agentLex = (agent as any).lex as Lexicons;\n\n // Add each registered lexicon to the agent\n for (const lexicon of this.lexicons.values()) {\n agentLex.add(lexicon);\n }\n }\n\n /**\n * Gets all registered lexicon IDs.\n *\n * @returns Array of lexicon NSIDs\n *\n * @example\n * ```typescript\n * const ids = registry.getRegisteredIds();\n * console.log(`Registered lexicons: ${ids.join(\", \")}`);\n * ```\n */\n getRegisteredIds(): string[] {\n return Array.from(this.lexicons.keys());\n }\n\n /**\n * Checks if a lexicon is registered.\n *\n * @param id - The lexicon NSID to check\n * @returns `true` if the lexicon is registered\n *\n * @example\n * ```typescript\n * if (registry.has(\"org.hypercerts.hypercert\")) {\n * // Hypercert lexicon is available\n * }\n * ```\n */\n has(id: string): boolean {\n return this.lexicons.has(id);\n }\n}\n"],"names":["Lexicons"],"mappings":";;;;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCG;AACG,MAAO,eAAgB,SAAQ,KAAK,CAAA;AACxC;;;;;;;AAOG;AACH,IAAA,WAAA,CACE,OAAe,EACR,IAAY,EACZ,MAAe,EACf,KAAe,EAAA;QAEtB,KAAK,CAAC,OAAO,CAAC;QAJP,IAAA,CAAA,IAAI,GAAJ,IAAI;QACJ,IAAA,CAAA,MAAM,GAAN,MAAM;QACN,IAAA,CAAA,KAAK,GAAL,KAAK;AAGZ,QAAA,IAAI,CAAC,IAAI,GAAG,iBAAiB;QAC7B,KAAK,CAAC,iBAAiB,GAAG,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC;IACnD;AACD;AAyED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCG;AACG,MAAO,eAAgB,SAAQ,eAAe,CAAA;AAClD;;;;;AAKG;IACH,WAAA,CAAY,OAAe,EAAE,KAAe,EAAA;QAC1C,KAAK,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,EAAE,KAAK,CAAC;AAC9C,QAAA,IAAI,CAAC,IAAI,GAAG,iBAAiB;IAC/B;AACD;;AC9JD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DG;MACU,eAAe,CAAA;AAO1B;;;;;AAKG;AACH,IAAA,WAAA,GAAA;;AAXQ,QAAA,IAAA,CAAA,QAAQ,GAAG,IAAI,GAAG,EAAsB;AAY9C,QAAA,IAAI,CAAC,kBAAkB,GAAG,IAAIA,kBAAQ,EAAE;IAC1C;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCG;AACH,IAAA,QAAQ,CAAC,OAAmB,EAAA;AAC1B,QAAA,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE;AACf,YAAA,MAAM,IAAI,eAAe,CAAC,iCAAiC,CAAC;QAC9D;;QAGA,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;;;;AAIjC,YAAA,MAAM,eAAe,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACrD,IAAI,eAAe,EAAE;;AAEnB,gBAAA,IAAI;;oBAED,IAAI,CAAC,kBAA0B,CAAC,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;gBACvD;AAAE,gBAAA,MAAM;;AAEN,oBAAA,IAAI,CAAC,kBAAkB,GAAG,IAAIA,kBAAQ,EAAE;;AAExC,oBAAA,KAAK,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE;AAC/C,wBAAA,IAAI,EAAE,KAAK,OAAO,CAAC,EAAE,EAAE;AACrB,4BAAA,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC;wBAClC;oBACF;gBACF;YACF;QACF;QAEA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC;AACtC,QAAA,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC;IACtC;AAEA;;;;;;;;;;;AAWG;AACH,IAAA,YAAY,CAAC,QAAsB,EAAA;AACjC,QAAA,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;AAC9B,YAAA,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;QACxB;IACF;AAEA;;;;;;;;;;;;;AAaG;AACH,IAAA,GAAG,CAAC,EAAU,EAAA;QACZ,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9B;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;IACH,QAAQ,CAAC,UAAkB,EAAE,MAAe,EAAA;;;;QAI1C,MAAM,SAAS,GAAG,UAAU;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC;QAC5C,IAAI,CAAC,OAAO,EAAE;;AAEZ,YAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE;QACxB;;AAGA,QAAA,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,EAAE,MAAM;QACtC,IAAI,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,QAAQ,IAAI,SAAS,EAAE;AACvE,YAAA,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM;AACrC,YAAA,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,UAAU,IAAI,YAAY,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE;gBAC1G,MAAM,SAAS,GAAG,MAAiC;AACnD,gBAAA,KAAK,MAAM,aAAa,IAAI,YAAY,CAAC,QAAQ,EAAE;AACjD,oBAAA,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,EAAE,aAAa,IAAI,SAAS,CAAC,EAAE;wBACtE,OAAO;AACL,4BAAA,KAAK,EAAE,KAAK;4BACZ,KAAK,EAAE,CAAA,wBAAA,EAA2B,aAAa,CAAA,CAAE;yBAClD;oBACH;gBACF;YACF;QACF;AAEA,QAAA,IAAI;YACF,IAAI,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,UAAU,EAAE,MAAM,CAAC;AAC7D,YAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE;QACxB;QAAE,OAAO,KAAK,EAAE;;;AAGd,YAAA,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,GAAG,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC;AAC3E,YAAA,IAAI,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,YAAY,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE;AACpF,gBAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE;YACxB;YACA,OAAO;AACL,gBAAA,KAAK,EAAE,KAAK;AACZ,gBAAA,KAAK,EAAE,YAAY;aACpB;QACH;IACF;AAEA;;;;;;;;;;;;;;AAcG;AACH,IAAA,UAAU,CAAC,KAAY,EAAA;;;;AAIrB,QAAA,MAAM,QAAQ,GAAI,KAAa,CAAC,GAAe;;QAG/C,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE;AAC5C,YAAA,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC;QACvB;IACF;AAEA;;;;;;;;;;AAUG;IACH,gBAAgB,GAAA;QACd,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACzC;AAEA;;;;;;;;;;;;AAYG;AACH,IAAA,GAAG,CAAC,EAAU,EAAA;QACZ,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9B;AACD;;;;;;;;;;;;"}
@@ -0,0 +1,227 @@
1
+ import { Agent } from '@atproto/api';
2
+ import { LexiconDoc } from '@atproto/lexicon';
3
+ export { HYPERCERT_COLLECTIONS, HYPERCERT_LEXICONS } from '@hypercerts-org/lexicon';
4
+
5
+ /**
6
+ * Result of validating a record against a lexicon schema.
7
+ */
8
+ interface ValidationResult {
9
+ /**
10
+ * Whether the record is valid according to the lexicon schema.
11
+ */
12
+ valid: boolean;
13
+ /**
14
+ * Error message if validation failed.
15
+ *
16
+ * Only present when `valid` is `false`.
17
+ */
18
+ error?: string;
19
+ }
20
+ /**
21
+ * Registry for managing and validating AT Protocol lexicon schemas.
22
+ *
23
+ * Lexicons are schema definitions that describe the structure of records
24
+ * in the AT Protocol. This registry allows you to:
25
+ *
26
+ * - Register custom lexicons for your application's record types
27
+ * - Validate records against their lexicon schemas
28
+ * - Extend the AT Protocol Agent with custom lexicon support
29
+ *
30
+ * @remarks
31
+ * The SDK automatically registers hypercert lexicons when creating a Repository.
32
+ * You only need to use this class directly if you're working with custom
33
+ * record types.
34
+ *
35
+ * **Lexicon IDs** follow the NSID (Namespaced Identifier) format:
36
+ * `{authority}.{name}` (e.g., `org.hypercerts.hypercert`)
37
+ *
38
+ * @example Registering custom lexicons
39
+ * ```typescript
40
+ * const registry = sdk.getLexiconRegistry();
41
+ *
42
+ * // Register a single lexicon
43
+ * registry.register({
44
+ * lexicon: 1,
45
+ * id: "org.example.myRecord",
46
+ * defs: {
47
+ * main: {
48
+ * type: "record",
49
+ * key: "tid",
50
+ * record: {
51
+ * type: "object",
52
+ * required: ["title", "createdAt"],
53
+ * properties: {
54
+ * title: { type: "string" },
55
+ * description: { type: "string" },
56
+ * createdAt: { type: "string", format: "datetime" },
57
+ * },
58
+ * },
59
+ * },
60
+ * },
61
+ * });
62
+ *
63
+ * // Register multiple lexicons at once
64
+ * registry.registerMany([lexicon1, lexicon2, lexicon3]);
65
+ * ```
66
+ *
67
+ * @example Validating records
68
+ * ```typescript
69
+ * const result = registry.validate("org.example.myRecord", {
70
+ * title: "Test",
71
+ * createdAt: new Date().toISOString(),
72
+ * });
73
+ *
74
+ * if (!result.valid) {
75
+ * console.error(`Validation failed: ${result.error}`);
76
+ * }
77
+ * ```
78
+ *
79
+ * @see https://atproto.com/specs/lexicon for the Lexicon specification
80
+ */
81
+ declare class LexiconRegistry {
82
+ /** Map of lexicon ID to lexicon document */
83
+ private lexicons;
84
+ /** Lexicons collection for validation */
85
+ private lexiconsCollection;
86
+ /**
87
+ * Creates a new LexiconRegistry.
88
+ *
89
+ * The registry starts empty. Use {@link register} or {@link registerMany}
90
+ * to add lexicons.
91
+ */
92
+ constructor();
93
+ /**
94
+ * Registers a single lexicon schema.
95
+ *
96
+ * @param lexicon - The lexicon document to register
97
+ * @throws {@link ValidationError} if the lexicon doesn't have an `id` field
98
+ *
99
+ * @remarks
100
+ * If a lexicon with the same ID is already registered, it will be
101
+ * replaced with the new definition. This is useful for testing but
102
+ * should generally be avoided in production.
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * registry.register({
107
+ * lexicon: 1,
108
+ * id: "org.example.post",
109
+ * defs: {
110
+ * main: {
111
+ * type: "record",
112
+ * key: "tid",
113
+ * record: {
114
+ * type: "object",
115
+ * required: ["text", "createdAt"],
116
+ * properties: {
117
+ * text: { type: "string", maxLength: 300 },
118
+ * createdAt: { type: "string", format: "datetime" },
119
+ * },
120
+ * },
121
+ * },
122
+ * },
123
+ * });
124
+ * ```
125
+ */
126
+ register(lexicon: LexiconDoc): void;
127
+ /**
128
+ * Registers multiple lexicons at once.
129
+ *
130
+ * @param lexicons - Array of lexicon documents to register
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * import { HYPERCERT_LEXICONS } from "@hypercerts-org/sdk/lexicons";
135
+ *
136
+ * registry.registerMany(HYPERCERT_LEXICONS);
137
+ * ```
138
+ */
139
+ registerMany(lexicons: LexiconDoc[]): void;
140
+ /**
141
+ * Gets a lexicon document by ID.
142
+ *
143
+ * @param id - The lexicon NSID (e.g., "org.hypercerts.hypercert")
144
+ * @returns The lexicon document, or `undefined` if not registered
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * const lexicon = registry.get("org.hypercerts.hypercert");
149
+ * if (lexicon) {
150
+ * console.log(`Found lexicon: ${lexicon.id}`);
151
+ * }
152
+ * ```
153
+ */
154
+ get(id: string): LexiconDoc | undefined;
155
+ /**
156
+ * Validates a record against a collection's lexicon schema.
157
+ *
158
+ * @param collection - The collection NSID (same as lexicon ID)
159
+ * @param record - The record data to validate
160
+ * @returns Validation result with `valid` boolean and optional `error` message
161
+ *
162
+ * @remarks
163
+ * - If no lexicon is registered for the collection, validation passes
164
+ * (we can't validate against unknown schemas)
165
+ * - Validation checks required fields and type constraints defined
166
+ * in the lexicon schema
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * const result = registry.validate("org.hypercerts.hypercert", {
171
+ * title: "My Hypercert",
172
+ * description: "Description...",
173
+ * // ... other fields
174
+ * });
175
+ *
176
+ * if (!result.valid) {
177
+ * throw new Error(`Invalid record: ${result.error}`);
178
+ * }
179
+ * ```
180
+ */
181
+ validate(collection: string, record: unknown): ValidationResult;
182
+ /**
183
+ * Adds all registered lexicons to an AT Protocol Agent instance.
184
+ *
185
+ * This allows the Agent to understand custom lexicon types when making
186
+ * API requests.
187
+ *
188
+ * @param agent - The Agent instance to extend
189
+ *
190
+ * @remarks
191
+ * This is called automatically when creating a Repository. You typically
192
+ * don't need to call this directly unless you're using the Agent
193
+ * independently.
194
+ *
195
+ * @internal
196
+ */
197
+ addToAgent(agent: Agent): void;
198
+ /**
199
+ * Gets all registered lexicon IDs.
200
+ *
201
+ * @returns Array of lexicon NSIDs
202
+ *
203
+ * @example
204
+ * ```typescript
205
+ * const ids = registry.getRegisteredIds();
206
+ * console.log(`Registered lexicons: ${ids.join(", ")}`);
207
+ * ```
208
+ */
209
+ getRegisteredIds(): string[];
210
+ /**
211
+ * Checks if a lexicon is registered.
212
+ *
213
+ * @param id - The lexicon NSID to check
214
+ * @returns `true` if the lexicon is registered
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * if (registry.has("org.hypercerts.hypercert")) {
219
+ * // Hypercert lexicon is available
220
+ * }
221
+ * ```
222
+ */
223
+ has(id: string): boolean;
224
+ }
225
+
226
+ export { LexiconRegistry };
227
+ export type { ValidationResult };