@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,410 @@
1
+ /**
2
+ * Mock factories for testing.
3
+ *
4
+ * This module provides factory functions to create mock objects
5
+ * for testing SDK functionality without real AT Protocol connections.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ /**
10
+ * Creates a mock OAuth session for testing.
11
+ *
12
+ * The mock session includes all required properties and a mock
13
+ * `fetchHandler` that returns empty successful responses by default.
14
+ *
15
+ * @param overrides - Partial session object to override default values
16
+ * @returns A mock Session object suitable for testing
17
+ *
18
+ * @remarks
19
+ * The mock session is cast to `Session` type for compatibility.
20
+ * In real usage, sessions come from the OAuth flow and contain
21
+ * actual tokens and a real fetch handler.
22
+ *
23
+ * **Default Values**:
24
+ * - `did`: `"did:plc:test123"`
25
+ * - `handle`: `"test.bsky.social"`
26
+ * - `fetchHandler`: Returns `Response` with `{}` body
27
+ *
28
+ * @example Basic mock session
29
+ * ```typescript
30
+ * import { createMockSession } from "@hypercerts-org/sdk/testing";
31
+ *
32
+ * const session = createMockSession();
33
+ * const repo = sdk.repository(session);
34
+ * ```
35
+ *
36
+ * @example With custom DID
37
+ * ```typescript
38
+ * const session = createMockSession({
39
+ * did: "did:plc:custom-test-user",
40
+ * handle: "custom.bsky.social",
41
+ * });
42
+ * ```
43
+ *
44
+ * @example With custom fetch handler
45
+ * ```typescript
46
+ * const session = createMockSession({
47
+ * fetchHandler: async (url, init) => {
48
+ * // Custom response logic
49
+ * return new Response(JSON.stringify({ success: true }));
50
+ * },
51
+ * });
52
+ * ```
53
+ */
54
+ function createMockSession(overrides = {}) {
55
+ const mockSession = {
56
+ did: "did:plc:test123",
57
+ sub: "did:plc:test123",
58
+ handle: "test.bsky.social",
59
+ accessJwt: "mock-access-jwt",
60
+ refreshJwt: "mock-refresh-jwt",
61
+ active: true,
62
+ fetchHandler: async (_input, _init) => {
63
+ return new Response(JSON.stringify({}), {
64
+ status: 200,
65
+ headers: { "Content-Type": "application/json" },
66
+ });
67
+ },
68
+ ...overrides,
69
+ };
70
+ return mockSession;
71
+ }
72
+ /**
73
+ * Creates a mock SDK configuration for testing.
74
+ *
75
+ * The configuration includes all required OAuth settings with
76
+ * placeholder values suitable for testing (not real credentials).
77
+ *
78
+ * @param overrides - Partial configuration to override default values
79
+ * @returns A complete ATProtoSDKConfig suitable for testing
80
+ *
81
+ * @remarks
82
+ * The default configuration uses example.com domains and a minimal
83
+ * JWK structure. This is sufficient for unit tests but won't work
84
+ * for integration tests that require real OAuth flows.
85
+ *
86
+ * **Default Values**:
87
+ * - `clientId`: `"https://test.example.com/client-metadata.json"`
88
+ * - `pds`: `"https://bsky.social"`
89
+ * - `sds`: `"https://sds.example.com"`
90
+ *
91
+ * @example Basic test config
92
+ * ```typescript
93
+ * import { createTestConfig } from "@hypercerts-org/sdk/testing";
94
+ *
95
+ * const config = createTestConfig();
96
+ * const sdk = new ATProtoSDK(config);
97
+ * ```
98
+ *
99
+ * @example With custom PDS
100
+ * ```typescript
101
+ * const config = createTestConfig({
102
+ * servers: {
103
+ * pds: "https://custom-pds.example.com",
104
+ * },
105
+ * });
106
+ * ```
107
+ *
108
+ * @example With logger for debugging tests
109
+ * ```typescript
110
+ * const config = createTestConfig({
111
+ * logger: console,
112
+ * });
113
+ * ```
114
+ */
115
+ function createTestConfig(overrides = {}) {
116
+ return {
117
+ oauth: {
118
+ clientId: "https://test.example.com/client-metadata.json",
119
+ redirectUri: "https://test.example.com/callback",
120
+ scope: "atproto transition:generic",
121
+ jwksUri: "https://test.example.com/jwks.json",
122
+ jwkPrivate: JSON.stringify({
123
+ kty: "EC",
124
+ crv: "P-256",
125
+ x: "test",
126
+ y: "test",
127
+ d: "test",
128
+ }),
129
+ },
130
+ servers: {
131
+ pds: "https://bsky.social",
132
+ sds: "https://sds.example.com",
133
+ },
134
+ ...overrides,
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Mock storage implementations for testing.
140
+ *
141
+ * This module provides mock implementations of SessionStore and StateStore
142
+ * that track all operations for verification in tests.
143
+ *
144
+ * @packageDocumentation
145
+ */
146
+ /**
147
+ * Mock session store that tracks all operations.
148
+ *
149
+ * This implementation stores sessions in memory and records all
150
+ * method calls for verification in tests.
151
+ *
152
+ * @remarks
153
+ * Use this in tests to:
154
+ * - Verify that sessions are being stored correctly
155
+ * - Check what DIDs have been accessed
156
+ * - Assert on the number and order of operations
157
+ * - Pre-populate sessions for testing restore flows
158
+ *
159
+ * @example Basic usage
160
+ * ```typescript
161
+ * import { MockSessionStore } from "@hypercerts-org/sdk/testing";
162
+ *
163
+ * const sessionStore = new MockSessionStore();
164
+ * const sdk = new ATProtoSDK({
165
+ * ...config,
166
+ * storage: { sessionStore },
167
+ * });
168
+ *
169
+ * // After some operations...
170
+ * expect(sessionStore.setCalls).toHaveLength(1);
171
+ * expect(sessionStore.getCalls).toContain("did:plc:test123");
172
+ * ```
173
+ *
174
+ * @example Pre-populating for tests
175
+ * ```typescript
176
+ * const sessionStore = new MockSessionStore();
177
+ *
178
+ * // Pre-populate a session
179
+ * await sessionStore.set("did:plc:existing", mockSessionData);
180
+ *
181
+ * // Reset tracking (keeps the data)
182
+ * sessionStore.getCalls = [];
183
+ * sessionStore.setCalls = [];
184
+ *
185
+ * // Now test restore behavior
186
+ * const session = await sdk.restoreSession("did:plc:existing");
187
+ * expect(sessionStore.getCalls).toContain("did:plc:existing");
188
+ * ```
189
+ *
190
+ * @example Asserting on operations
191
+ * ```typescript
192
+ * const sessionStore = new MockSessionStore();
193
+ *
194
+ * // ... perform operations ...
195
+ *
196
+ * // Verify session was stored for correct DID
197
+ * expect(sessionStore.setCalls[0].did).toBe("did:plc:expected");
198
+ *
199
+ * // Verify session was deleted on logout
200
+ * expect(sessionStore.delCalls).toContain("did:plc:logged-out");
201
+ * ```
202
+ */
203
+ class MockSessionStore {
204
+ constructor() {
205
+ /**
206
+ * Internal storage for sessions.
207
+ * @internal
208
+ */
209
+ this.store = new Map();
210
+ /**
211
+ * Record of all `get()` calls made to this store.
212
+ *
213
+ * Each entry is the DID that was requested.
214
+ */
215
+ this.getCalls = [];
216
+ /**
217
+ * Record of all `set()` calls made to this store.
218
+ *
219
+ * Each entry contains the DID and session that was stored.
220
+ */
221
+ this.setCalls = [];
222
+ /**
223
+ * Record of all `del()` calls made to this store.
224
+ *
225
+ * Each entry is the DID that was deleted.
226
+ */
227
+ this.delCalls = [];
228
+ }
229
+ /**
230
+ * Retrieves a session by DID.
231
+ *
232
+ * Records the call in `getCalls`.
233
+ *
234
+ * @param did - The DID to look up
235
+ * @returns The stored session or undefined
236
+ */
237
+ async get(did) {
238
+ this.getCalls.push(did);
239
+ return this.store.get(did);
240
+ }
241
+ /**
242
+ * Stores a session.
243
+ *
244
+ * Records the call in `setCalls`.
245
+ *
246
+ * @param did - The DID to store under
247
+ * @param session - The session data to store
248
+ */
249
+ async set(did, session) {
250
+ this.setCalls.push({ did, session });
251
+ this.store.set(did, session);
252
+ }
253
+ /**
254
+ * Deletes a session.
255
+ *
256
+ * Records the call in `delCalls`.
257
+ *
258
+ * @param did - The DID to delete
259
+ */
260
+ async del(did) {
261
+ this.delCalls.push(did);
262
+ this.store.delete(did);
263
+ }
264
+ /**
265
+ * Resets the store to initial state.
266
+ *
267
+ * Clears all stored sessions and all recorded calls.
268
+ * Call this in `beforeEach` or `afterEach` to ensure test isolation.
269
+ *
270
+ * @example
271
+ * ```typescript
272
+ * beforeEach(() => {
273
+ * sessionStore.reset();
274
+ * });
275
+ * ```
276
+ */
277
+ reset() {
278
+ this.store.clear();
279
+ this.getCalls = [];
280
+ this.setCalls = [];
281
+ this.delCalls = [];
282
+ }
283
+ }
284
+ /**
285
+ * Mock state store that tracks all operations.
286
+ *
287
+ * This implementation stores OAuth state in memory and records all
288
+ * method calls for verification in tests.
289
+ *
290
+ * @remarks
291
+ * Use this in tests to:
292
+ * - Verify OAuth state is being created during authorization
293
+ * - Check that state is retrieved during callback
294
+ * - Assert that state is cleaned up after use
295
+ * - Test error handling for missing/invalid state
296
+ *
297
+ * @example Basic usage
298
+ * ```typescript
299
+ * import { MockStateStore } from "@hypercerts-org/sdk/testing";
300
+ *
301
+ * const stateStore = new MockStateStore();
302
+ * const sdk = new ATProtoSDK({
303
+ * ...config,
304
+ * storage: { stateStore },
305
+ * });
306
+ *
307
+ * // After authorize()
308
+ * expect(stateStore.setCalls).toHaveLength(1);
309
+ *
310
+ * // After callback()
311
+ * expect(stateStore.getCalls).toHaveLength(1);
312
+ * expect(stateStore.delCalls).toHaveLength(1);
313
+ * ```
314
+ *
315
+ * @example Testing invalid state
316
+ * ```typescript
317
+ * const stateStore = new MockStateStore();
318
+ * // Don't pre-populate - state will be missing
319
+ *
320
+ * // This should fail because state doesn't exist
321
+ * await expect(sdk.callback(params)).rejects.toThrow();
322
+ *
323
+ * // Verify the lookup was attempted
324
+ * expect(stateStore.getCalls).toContain(stateKey);
325
+ * ```
326
+ */
327
+ class MockStateStore {
328
+ constructor() {
329
+ /**
330
+ * Internal storage for OAuth state.
331
+ * @internal
332
+ */
333
+ this.store = new Map();
334
+ /**
335
+ * Record of all `get()` calls made to this store.
336
+ *
337
+ * Each entry is the state key that was requested.
338
+ */
339
+ this.getCalls = [];
340
+ /**
341
+ * Record of all `set()` calls made to this store.
342
+ *
343
+ * Each entry contains the key and state that was stored.
344
+ */
345
+ this.setCalls = [];
346
+ /**
347
+ * Record of all `del()` calls made to this store.
348
+ *
349
+ * Each entry is the state key that was deleted.
350
+ */
351
+ this.delCalls = [];
352
+ }
353
+ /**
354
+ * Retrieves OAuth state by key.
355
+ *
356
+ * Records the call in `getCalls`.
357
+ *
358
+ * @param key - The state key to look up
359
+ * @returns The stored state or undefined
360
+ */
361
+ async get(key) {
362
+ this.getCalls.push(key);
363
+ return this.store.get(key);
364
+ }
365
+ /**
366
+ * Stores OAuth state.
367
+ *
368
+ * Records the call in `setCalls`.
369
+ *
370
+ * @param key - The state key to store under
371
+ * @param state - The OAuth state data to store
372
+ */
373
+ async set(key, state) {
374
+ this.setCalls.push({ key, state });
375
+ this.store.set(key, state);
376
+ }
377
+ /**
378
+ * Deletes OAuth state.
379
+ *
380
+ * Records the call in `delCalls`.
381
+ *
382
+ * @param key - The state key to delete
383
+ */
384
+ async del(key) {
385
+ this.delCalls.push(key);
386
+ this.store.delete(key);
387
+ }
388
+ /**
389
+ * Resets the store to initial state.
390
+ *
391
+ * Clears all stored state and all recorded calls.
392
+ * Call this in `beforeEach` or `afterEach` to ensure test isolation.
393
+ *
394
+ * @example
395
+ * ```typescript
396
+ * beforeEach(() => {
397
+ * stateStore.reset();
398
+ * });
399
+ * ```
400
+ */
401
+ reset() {
402
+ this.store.clear();
403
+ this.getCalls = [];
404
+ this.setCalls = [];
405
+ this.delCalls = [];
406
+ }
407
+ }
408
+
409
+ export { MockSessionStore, MockStateStore, createMockSession, createTestConfig };
410
+ //# sourceMappingURL=testing.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.mjs","sources":["../src/testing/mocks.ts","../src/testing/stores.ts"],"sourcesContent":["/**\n * Mock factories for testing.\n *\n * This module provides factory functions to create mock objects\n * for testing SDK functionality without real AT Protocol connections.\n *\n * @packageDocumentation\n */\n\nimport type { Session } from \"../core/types.js\";\nimport type { ATProtoSDKConfig } from \"../core/config.js\";\n\n/**\n * Creates a mock OAuth session for testing.\n *\n * The mock session includes all required properties and a mock\n * `fetchHandler` that returns empty successful responses by default.\n *\n * @param overrides - Partial session object to override default values\n * @returns A mock Session object suitable for testing\n *\n * @remarks\n * The mock session is cast to `Session` type for compatibility.\n * In real usage, sessions come from the OAuth flow and contain\n * actual tokens and a real fetch handler.\n *\n * **Default Values**:\n * - `did`: `\"did:plc:test123\"`\n * - `handle`: `\"test.bsky.social\"`\n * - `fetchHandler`: Returns `Response` with `{}` body\n *\n * @example Basic mock session\n * ```typescript\n * import { createMockSession } from \"@hypercerts-org/sdk/testing\";\n *\n * const session = createMockSession();\n * const repo = sdk.repository(session);\n * ```\n *\n * @example With custom DID\n * ```typescript\n * const session = createMockSession({\n * did: \"did:plc:custom-test-user\",\n * handle: \"custom.bsky.social\",\n * });\n * ```\n *\n * @example With custom fetch handler\n * ```typescript\n * const session = createMockSession({\n * fetchHandler: async (url, init) => {\n * // Custom response logic\n * return new Response(JSON.stringify({ success: true }));\n * },\n * });\n * ```\n */\nexport function createMockSession(overrides: Partial<Session> = {}): Session {\n const mockSession = {\n did: \"did:plc:test123\",\n sub: \"did:plc:test123\",\n handle: \"test.bsky.social\",\n accessJwt: \"mock-access-jwt\",\n refreshJwt: \"mock-refresh-jwt\",\n active: true,\n fetchHandler: async (_input: RequestInfo | URL, _init?: RequestInit) => {\n return new Response(JSON.stringify({}), {\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n });\n },\n ...overrides,\n } as unknown as Session;\n\n return mockSession;\n}\n\n/**\n * Creates a mock SDK configuration for testing.\n *\n * The configuration includes all required OAuth settings with\n * placeholder values suitable for testing (not real credentials).\n *\n * @param overrides - Partial configuration to override default values\n * @returns A complete ATProtoSDKConfig suitable for testing\n *\n * @remarks\n * The default configuration uses example.com domains and a minimal\n * JWK structure. This is sufficient for unit tests but won't work\n * for integration tests that require real OAuth flows.\n *\n * **Default Values**:\n * - `clientId`: `\"https://test.example.com/client-metadata.json\"`\n * - `pds`: `\"https://bsky.social\"`\n * - `sds`: `\"https://sds.example.com\"`\n *\n * @example Basic test config\n * ```typescript\n * import { createTestConfig } from \"@hypercerts-org/sdk/testing\";\n *\n * const config = createTestConfig();\n * const sdk = new ATProtoSDK(config);\n * ```\n *\n * @example With custom PDS\n * ```typescript\n * const config = createTestConfig({\n * servers: {\n * pds: \"https://custom-pds.example.com\",\n * },\n * });\n * ```\n *\n * @example With logger for debugging tests\n * ```typescript\n * const config = createTestConfig({\n * logger: console,\n * });\n * ```\n */\nexport function createTestConfig(overrides: Partial<ATProtoSDKConfig> = {}): ATProtoSDKConfig {\n return {\n oauth: {\n clientId: \"https://test.example.com/client-metadata.json\",\n redirectUri: \"https://test.example.com/callback\",\n scope: \"atproto transition:generic\",\n jwksUri: \"https://test.example.com/jwks.json\",\n jwkPrivate: JSON.stringify({\n kty: \"EC\",\n crv: \"P-256\",\n x: \"test\",\n y: \"test\",\n d: \"test\",\n }),\n },\n servers: {\n pds: \"https://bsky.social\",\n sds: \"https://sds.example.com\",\n },\n ...overrides,\n };\n}\n","/**\n * Mock storage implementations for testing.\n *\n * This module provides mock implementations of SessionStore and StateStore\n * that track all operations for verification in tests.\n *\n * @packageDocumentation\n */\n\nimport type { SessionStore, StateStore } from \"../core/interfaces.js\";\nimport type { NodeSavedSession, NodeSavedState } from \"@atproto/oauth-client-node\";\n\n/**\n * Mock session store that tracks all operations.\n *\n * This implementation stores sessions in memory and records all\n * method calls for verification in tests.\n *\n * @remarks\n * Use this in tests to:\n * - Verify that sessions are being stored correctly\n * - Check what DIDs have been accessed\n * - Assert on the number and order of operations\n * - Pre-populate sessions for testing restore flows\n *\n * @example Basic usage\n * ```typescript\n * import { MockSessionStore } from \"@hypercerts-org/sdk/testing\";\n *\n * const sessionStore = new MockSessionStore();\n * const sdk = new ATProtoSDK({\n * ...config,\n * storage: { sessionStore },\n * });\n *\n * // After some operations...\n * expect(sessionStore.setCalls).toHaveLength(1);\n * expect(sessionStore.getCalls).toContain(\"did:plc:test123\");\n * ```\n *\n * @example Pre-populating for tests\n * ```typescript\n * const sessionStore = new MockSessionStore();\n *\n * // Pre-populate a session\n * await sessionStore.set(\"did:plc:existing\", mockSessionData);\n *\n * // Reset tracking (keeps the data)\n * sessionStore.getCalls = [];\n * sessionStore.setCalls = [];\n *\n * // Now test restore behavior\n * const session = await sdk.restoreSession(\"did:plc:existing\");\n * expect(sessionStore.getCalls).toContain(\"did:plc:existing\");\n * ```\n *\n * @example Asserting on operations\n * ```typescript\n * const sessionStore = new MockSessionStore();\n *\n * // ... perform operations ...\n *\n * // Verify session was stored for correct DID\n * expect(sessionStore.setCalls[0].did).toBe(\"did:plc:expected\");\n *\n * // Verify session was deleted on logout\n * expect(sessionStore.delCalls).toContain(\"did:plc:logged-out\");\n * ```\n */\nexport class MockSessionStore implements SessionStore {\n /**\n * Internal storage for sessions.\n * @internal\n */\n private store = new Map<string, NodeSavedSession>();\n\n /**\n * Record of all `get()` calls made to this store.\n *\n * Each entry is the DID that was requested.\n */\n public getCalls: string[] = [];\n\n /**\n * Record of all `set()` calls made to this store.\n *\n * Each entry contains the DID and session that was stored.\n */\n public setCalls: Array<{ did: string; session: NodeSavedSession }> = [];\n\n /**\n * Record of all `del()` calls made to this store.\n *\n * Each entry is the DID that was deleted.\n */\n public delCalls: string[] = [];\n\n /**\n * Retrieves a session by DID.\n *\n * Records the call in `getCalls`.\n *\n * @param did - The DID to look up\n * @returns The stored session or undefined\n */\n async get(did: string): Promise<NodeSavedSession | undefined> {\n this.getCalls.push(did);\n return this.store.get(did);\n }\n\n /**\n * Stores a session.\n *\n * Records the call in `setCalls`.\n *\n * @param did - The DID to store under\n * @param session - The session data to store\n */\n async set(did: string, session: NodeSavedSession): Promise<void> {\n this.setCalls.push({ did, session });\n this.store.set(did, session);\n }\n\n /**\n * Deletes a session.\n *\n * Records the call in `delCalls`.\n *\n * @param did - The DID to delete\n */\n async del(did: string): Promise<void> {\n this.delCalls.push(did);\n this.store.delete(did);\n }\n\n /**\n * Resets the store to initial state.\n *\n * Clears all stored sessions and all recorded calls.\n * Call this in `beforeEach` or `afterEach` to ensure test isolation.\n *\n * @example\n * ```typescript\n * beforeEach(() => {\n * sessionStore.reset();\n * });\n * ```\n */\n reset(): void {\n this.store.clear();\n this.getCalls = [];\n this.setCalls = [];\n this.delCalls = [];\n }\n}\n\n/**\n * Mock state store that tracks all operations.\n *\n * This implementation stores OAuth state in memory and records all\n * method calls for verification in tests.\n *\n * @remarks\n * Use this in tests to:\n * - Verify OAuth state is being created during authorization\n * - Check that state is retrieved during callback\n * - Assert that state is cleaned up after use\n * - Test error handling for missing/invalid state\n *\n * @example Basic usage\n * ```typescript\n * import { MockStateStore } from \"@hypercerts-org/sdk/testing\";\n *\n * const stateStore = new MockStateStore();\n * const sdk = new ATProtoSDK({\n * ...config,\n * storage: { stateStore },\n * });\n *\n * // After authorize()\n * expect(stateStore.setCalls).toHaveLength(1);\n *\n * // After callback()\n * expect(stateStore.getCalls).toHaveLength(1);\n * expect(stateStore.delCalls).toHaveLength(1);\n * ```\n *\n * @example Testing invalid state\n * ```typescript\n * const stateStore = new MockStateStore();\n * // Don't pre-populate - state will be missing\n *\n * // This should fail because state doesn't exist\n * await expect(sdk.callback(params)).rejects.toThrow();\n *\n * // Verify the lookup was attempted\n * expect(stateStore.getCalls).toContain(stateKey);\n * ```\n */\nexport class MockStateStore implements StateStore {\n /**\n * Internal storage for OAuth state.\n * @internal\n */\n private store = new Map<string, NodeSavedState>();\n\n /**\n * Record of all `get()` calls made to this store.\n *\n * Each entry is the state key that was requested.\n */\n public getCalls: string[] = [];\n\n /**\n * Record of all `set()` calls made to this store.\n *\n * Each entry contains the key and state that was stored.\n */\n public setCalls: Array<{ key: string; state: NodeSavedState }> = [];\n\n /**\n * Record of all `del()` calls made to this store.\n *\n * Each entry is the state key that was deleted.\n */\n public delCalls: string[] = [];\n\n /**\n * Retrieves OAuth state by key.\n *\n * Records the call in `getCalls`.\n *\n * @param key - The state key to look up\n * @returns The stored state or undefined\n */\n async get(key: string): Promise<NodeSavedState | undefined> {\n this.getCalls.push(key);\n return this.store.get(key);\n }\n\n /**\n * Stores OAuth state.\n *\n * Records the call in `setCalls`.\n *\n * @param key - The state key to store under\n * @param state - The OAuth state data to store\n */\n async set(key: string, state: NodeSavedState): Promise<void> {\n this.setCalls.push({ key, state });\n this.store.set(key, state);\n }\n\n /**\n * Deletes OAuth state.\n *\n * Records the call in `delCalls`.\n *\n * @param key - The state key to delete\n */\n async del(key: string): Promise<void> {\n this.delCalls.push(key);\n this.store.delete(key);\n }\n\n /**\n * Resets the store to initial state.\n *\n * Clears all stored state and all recorded calls.\n * Call this in `beforeEach` or `afterEach` to ensure test isolation.\n *\n * @example\n * ```typescript\n * beforeEach(() => {\n * stateStore.reset();\n * });\n * ```\n */\n reset(): void {\n this.store.clear();\n this.getCalls = [];\n this.setCalls = [];\n this.delCalls = [];\n }\n}\n"],"names":[],"mappings":"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"}
package/dist/types.cjs ADDED
@@ -0,0 +1,220 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+
5
+ /**
6
+ * Zod schema for OAuth configuration validation.
7
+ *
8
+ * @remarks
9
+ * All URLs must be valid and use HTTPS in production. The `jwkPrivate` field
10
+ * should contain the private key in JWK (JSON Web Key) format as a string.
11
+ */
12
+ const OAuthConfigSchema = zod.z.object({
13
+ /**
14
+ * URL to the OAuth client metadata JSON document.
15
+ * This document describes your application to the authorization server.
16
+ *
17
+ * @see https://atproto.com/specs/oauth#client-metadata
18
+ */
19
+ clientId: zod.z.string().url(),
20
+ /**
21
+ * URL where users are redirected after authentication.
22
+ * Must match one of the redirect URIs in your client metadata.
23
+ */
24
+ redirectUri: zod.z.string().url(),
25
+ /**
26
+ * OAuth scopes to request, space-separated.
27
+ * Common scopes: "atproto", "transition:generic"
28
+ */
29
+ scope: zod.z.string(),
30
+ /**
31
+ * URL to your public JWKS (JSON Web Key Set) endpoint.
32
+ * Used by the authorization server to verify your client's signatures.
33
+ */
34
+ jwksUri: zod.z.string().url(),
35
+ /**
36
+ * Private JWK (JSON Web Key) as a JSON string.
37
+ * Used for signing DPoP proofs and client assertions.
38
+ *
39
+ * @remarks
40
+ * This should be kept secret and never exposed to clients.
41
+ * Typically loaded from environment variables or a secrets manager.
42
+ */
43
+ jwkPrivate: zod.z.string(),
44
+ });
45
+ /**
46
+ * Zod schema for server URL configuration.
47
+ *
48
+ * @remarks
49
+ * At least one server (PDS or SDS) should be configured for the SDK to be useful.
50
+ */
51
+ const ServerConfigSchema = zod.z.object({
52
+ /**
53
+ * Personal Data Server URL - the user's own AT Protocol server.
54
+ * This is the primary server for user data operations.
55
+ *
56
+ * @example "https://bsky.social"
57
+ */
58
+ pds: zod.z.string().url().optional(),
59
+ /**
60
+ * Shared Data Server URL - for collaborative data storage.
61
+ * Required for collaborator and organization operations.
62
+ *
63
+ * @example "https://sds.hypercerts.org"
64
+ */
65
+ sds: zod.z.string().url().optional(),
66
+ });
67
+ /**
68
+ * Zod schema for timeout configuration.
69
+ *
70
+ * @remarks
71
+ * All timeout values are in milliseconds.
72
+ */
73
+ const TimeoutConfigSchema = zod.z.object({
74
+ /**
75
+ * Timeout for fetching PDS metadata during identity resolution.
76
+ * @default 5000 (5 seconds, set by OAuthClient)
77
+ */
78
+ pdsMetadata: zod.z.number().positive().optional(),
79
+ /**
80
+ * Timeout for general API requests to PDS/SDS.
81
+ * @default 30000 (30 seconds)
82
+ */
83
+ apiRequests: zod.z.number().positive().optional(),
84
+ });
85
+ /**
86
+ * Zod schema for SDK configuration validation.
87
+ *
88
+ * @remarks
89
+ * This schema validates only the primitive/serializable parts of the configuration.
90
+ * Storage interfaces ({@link SessionStore}, {@link StateStore}) cannot be validated
91
+ * with Zod as they are runtime objects.
92
+ */
93
+ const ATProtoSDKConfigSchema = zod.z.object({
94
+ oauth: OAuthConfigSchema,
95
+ servers: ServerConfigSchema.optional(),
96
+ timeouts: TimeoutConfigSchema.optional(),
97
+ });
98
+
99
+ /**
100
+ * Zod schema for collaborator permissions in SDS repositories.
101
+ *
102
+ * Defines the granular permissions a collaborator can have on a shared repository.
103
+ * Permissions follow a hierarchical model where higher-level permissions
104
+ * typically imply lower-level ones.
105
+ */
106
+ const CollaboratorPermissionsSchema = zod.z.object({
107
+ /**
108
+ * Can read/view records in the repository.
109
+ * This is the most basic permission level.
110
+ */
111
+ read: zod.z.boolean(),
112
+ /**
113
+ * Can create new records in the repository.
114
+ * Typically implies `read` permission.
115
+ */
116
+ create: zod.z.boolean(),
117
+ /**
118
+ * Can modify existing records in the repository.
119
+ * Typically implies `read` and `create` permissions.
120
+ */
121
+ update: zod.z.boolean(),
122
+ /**
123
+ * Can delete records from the repository.
124
+ * Typically implies `read`, `create`, and `update` permissions.
125
+ */
126
+ delete: zod.z.boolean(),
127
+ /**
128
+ * Can manage collaborators and their permissions.
129
+ * Administrative permission that allows inviting/removing collaborators.
130
+ */
131
+ admin: zod.z.boolean(),
132
+ /**
133
+ * Full ownership of the repository.
134
+ * Owners have all permissions and cannot be removed by other admins.
135
+ * There must always be at least one owner.
136
+ */
137
+ owner: zod.z.boolean(),
138
+ });
139
+ /**
140
+ * Zod schema for SDS organization data.
141
+ *
142
+ * Organizations are top-level entities in SDS that can own repositories
143
+ * and have multiple collaborators with different permission levels.
144
+ */
145
+ const OrganizationSchema = zod.z.object({
146
+ /**
147
+ * The organization's DID - unique identifier.
148
+ * Format: "did:plc:..." or "did:web:..."
149
+ */
150
+ did: zod.z.string(),
151
+ /**
152
+ * The organization's handle - human-readable identifier.
153
+ * Format: "orgname.sds.hypercerts.org" or similar
154
+ */
155
+ handle: zod.z.string(),
156
+ /**
157
+ * Display name for the organization.
158
+ */
159
+ name: zod.z.string(),
160
+ /**
161
+ * Optional description of the organization's purpose.
162
+ */
163
+ description: zod.z.string().optional(),
164
+ /**
165
+ * ISO 8601 timestamp when the organization was created.
166
+ * Format: "2024-01-15T10:30:00.000Z"
167
+ */
168
+ createdAt: zod.z.string(),
169
+ /**
170
+ * The current user's permissions within this organization.
171
+ */
172
+ permissions: CollaboratorPermissionsSchema,
173
+ /**
174
+ * How the current user relates to this organization.
175
+ * - `"owner"`: User created or owns the organization
176
+ * - `"collaborator"`: User was invited to collaborate
177
+ */
178
+ accessType: zod.z.enum(["owner", "collaborator"]),
179
+ });
180
+ /**
181
+ * Zod schema for collaborator data.
182
+ *
183
+ * Represents a user who has been granted access to a shared repository
184
+ * or organization with specific permissions.
185
+ */
186
+ const CollaboratorSchema = zod.z.object({
187
+ /**
188
+ * The collaborator's DID - their unique identifier.
189
+ * Format: "did:plc:..." or "did:web:..."
190
+ */
191
+ userDid: zod.z.string(),
192
+ /**
193
+ * The permissions granted to this collaborator.
194
+ */
195
+ permissions: CollaboratorPermissionsSchema,
196
+ /**
197
+ * DID of the user who granted these permissions.
198
+ * Useful for audit trails.
199
+ */
200
+ grantedBy: zod.z.string(),
201
+ /**
202
+ * ISO 8601 timestamp when permissions were granted.
203
+ * Format: "2024-01-15T10:30:00.000Z"
204
+ */
205
+ grantedAt: zod.z.string(),
206
+ /**
207
+ * ISO 8601 timestamp when permissions were revoked, if applicable.
208
+ * Undefined if the collaborator is still active.
209
+ */
210
+ revokedAt: zod.z.string().optional(),
211
+ });
212
+
213
+ exports.ATProtoSDKConfigSchema = ATProtoSDKConfigSchema;
214
+ exports.CollaboratorPermissionsSchema = CollaboratorPermissionsSchema;
215
+ exports.CollaboratorSchema = CollaboratorSchema;
216
+ exports.OAuthConfigSchema = OAuthConfigSchema;
217
+ exports.OrganizationSchema = OrganizationSchema;
218
+ exports.ServerConfigSchema = ServerConfigSchema;
219
+ exports.TimeoutConfigSchema = TimeoutConfigSchema;
220
+ //# sourceMappingURL=types.cjs.map