@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,267 @@
1
+ /**
2
+ * In-memory implementation of the SessionStore interface.
3
+ *
4
+ * This store keeps OAuth sessions in memory using a Map. It's intended
5
+ * for development, testing, and simple use cases where session persistence
6
+ * across restarts is not required.
7
+ *
8
+ * @remarks
9
+ * **Warning**: This implementation is **not suitable for production** because:
10
+ * - Sessions are lost when the process restarts
11
+ * - Sessions cannot be shared across multiple server instances
12
+ * - No automatic cleanup of expired sessions
13
+ *
14
+ * For production, implement {@link SessionStore} with a persistent backend:
15
+ * - **Redis**: Good for distributed systems, supports TTL
16
+ * - **PostgreSQL/MySQL**: Good for existing database infrastructure
17
+ * - **MongoDB**: Good for document-based storage
18
+ *
19
+ * @example Basic usage
20
+ * ```typescript
21
+ * import { InMemorySessionStore } from "@hypercerts-org/sdk/storage";
22
+ *
23
+ * const sessionStore = new InMemorySessionStore();
24
+ *
25
+ * const sdk = new ATProtoSDK({
26
+ * oauth: { ... },
27
+ * storage: {
28
+ * sessionStore, // Will warn in logs for production
29
+ * },
30
+ * });
31
+ * ```
32
+ *
33
+ * @example Testing usage
34
+ * ```typescript
35
+ * const sessionStore = new InMemorySessionStore();
36
+ *
37
+ * // After tests, clean up
38
+ * sessionStore.clear();
39
+ * ```
40
+ *
41
+ * @see {@link SessionStore} for the interface definition
42
+ * @see {@link InMemoryStateStore} for the corresponding state store
43
+ */
44
+ class InMemorySessionStore {
45
+ constructor() {
46
+ /**
47
+ * Internal storage for sessions, keyed by DID.
48
+ * @internal
49
+ */
50
+ this.sessions = new Map();
51
+ }
52
+ /**
53
+ * Retrieves a session by DID.
54
+ *
55
+ * @param did - The user's Decentralized Identifier
56
+ * @returns Promise resolving to the session, or `undefined` if not found
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const session = await sessionStore.get("did:plc:abc123");
61
+ * if (session) {
62
+ * console.log("Session found");
63
+ * }
64
+ * ```
65
+ */
66
+ async get(did) {
67
+ return this.sessions.get(did);
68
+ }
69
+ /**
70
+ * Stores or updates a session.
71
+ *
72
+ * @param did - The user's DID to use as the key
73
+ * @param session - The session data to store
74
+ *
75
+ * @remarks
76
+ * If a session already exists for the DID, it is overwritten.
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * await sessionStore.set("did:plc:abc123", sessionData);
81
+ * ```
82
+ */
83
+ async set(did, session) {
84
+ this.sessions.set(did, session);
85
+ }
86
+ /**
87
+ * Deletes a session by DID.
88
+ *
89
+ * @param did - The DID of the session to delete
90
+ *
91
+ * @remarks
92
+ * If no session exists for the DID, this is a no-op.
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * await sessionStore.del("did:plc:abc123");
97
+ * ```
98
+ */
99
+ async del(did) {
100
+ this.sessions.delete(did);
101
+ }
102
+ /**
103
+ * Clears all stored sessions.
104
+ *
105
+ * This is primarily useful for testing to ensure a clean state
106
+ * between test runs.
107
+ *
108
+ * @remarks
109
+ * This method is synchronous (not async) for convenience in test cleanup.
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * // In test teardown
114
+ * afterEach(() => {
115
+ * sessionStore.clear();
116
+ * });
117
+ * ```
118
+ */
119
+ clear() {
120
+ this.sessions.clear();
121
+ }
122
+ }
123
+
124
+ /**
125
+ * In-memory implementation of the StateStore interface.
126
+ *
127
+ * This store keeps OAuth state parameters in memory using a Map. State is
128
+ * used during the OAuth authorization flow for CSRF protection and PKCE.
129
+ *
130
+ * @remarks
131
+ * **Warning**: This implementation is **not suitable for production** because:
132
+ * - State is lost when the process restarts (breaking in-progress OAuth flows)
133
+ * - State cannot be shared across multiple server instances
134
+ * - No automatic cleanup of expired state (memory leak potential)
135
+ *
136
+ * For production, implement {@link StateStore} with a persistent backend
137
+ * that supports TTL (time-to-live):
138
+ * - **Redis**: Ideal choice with built-in TTL support
139
+ * - **Database with cleanup job**: PostgreSQL/MySQL with periodic cleanup
140
+ *
141
+ * **State Lifecycle**:
142
+ * 1. Created when user starts OAuth flow (`authorize()`)
143
+ * 2. Retrieved and validated during callback
144
+ * 3. Deleted after successful or failed callback
145
+ * 4. Should expire after ~15 minutes if callback never happens
146
+ *
147
+ * @example Basic usage
148
+ * ```typescript
149
+ * import { InMemoryStateStore } from "@hypercerts-org/sdk/storage";
150
+ *
151
+ * const stateStore = new InMemoryStateStore();
152
+ *
153
+ * const sdk = new ATProtoSDK({
154
+ * oauth: { ... },
155
+ * storage: {
156
+ * stateStore, // Will warn in logs for production
157
+ * },
158
+ * });
159
+ * ```
160
+ *
161
+ * @example Testing usage
162
+ * ```typescript
163
+ * const stateStore = new InMemoryStateStore();
164
+ *
165
+ * // After tests, clean up
166
+ * stateStore.clear();
167
+ * ```
168
+ *
169
+ * @see {@link StateStore} for the interface definition
170
+ * @see {@link InMemorySessionStore} for the corresponding session store
171
+ */
172
+ class InMemoryStateStore {
173
+ constructor() {
174
+ /**
175
+ * Internal storage for OAuth state, keyed by state string.
176
+ * @internal
177
+ */
178
+ this.states = new Map();
179
+ }
180
+ /**
181
+ * Retrieves OAuth state by key.
182
+ *
183
+ * @param key - The state key (random string from authorization URL)
184
+ * @returns Promise resolving to the state, or `undefined` if not found
185
+ *
186
+ * @remarks
187
+ * The key is a cryptographically random string generated during
188
+ * the authorization request. It's included in the callback URL
189
+ * and used to retrieve the associated PKCE verifier and other data.
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * // During OAuth callback
194
+ * const state = await stateStore.get(params.get("state")!);
195
+ * if (!state) {
196
+ * throw new Error("Invalid or expired state");
197
+ * }
198
+ * ```
199
+ */
200
+ async get(key) {
201
+ return this.states.get(key);
202
+ }
203
+ /**
204
+ * Stores OAuth state temporarily.
205
+ *
206
+ * @param key - The state key to use for storage
207
+ * @param state - The OAuth state data (includes PKCE verifier, etc.)
208
+ *
209
+ * @remarks
210
+ * In production implementations, state should be stored with a TTL
211
+ * of approximately 10-15 minutes to prevent stale state accumulation.
212
+ *
213
+ * @example
214
+ * ```typescript
215
+ * // Called internally by OAuthClient during authorize()
216
+ * await stateStore.set(stateKey, {
217
+ * // PKCE code verifier, redirect URI, etc.
218
+ * });
219
+ * ```
220
+ */
221
+ async set(key, state) {
222
+ this.states.set(key, state);
223
+ }
224
+ /**
225
+ * Deletes OAuth state by key.
226
+ *
227
+ * @param key - The state key to delete
228
+ *
229
+ * @remarks
230
+ * Called after the OAuth callback is processed (whether successful or not)
231
+ * to clean up the temporary state.
232
+ *
233
+ * @example
234
+ * ```typescript
235
+ * // After processing callback
236
+ * await stateStore.del(stateKey);
237
+ * ```
238
+ */
239
+ async del(key) {
240
+ this.states.delete(key);
241
+ }
242
+ /**
243
+ * Clears all stored state.
244
+ *
245
+ * This is primarily useful for testing to ensure a clean state
246
+ * between test runs.
247
+ *
248
+ * @remarks
249
+ * This method is synchronous (not async) for convenience in test cleanup.
250
+ * In production, be careful using this as it will invalidate all
251
+ * in-progress OAuth flows.
252
+ *
253
+ * @example
254
+ * ```typescript
255
+ * // In test teardown
256
+ * afterEach(() => {
257
+ * stateStore.clear();
258
+ * });
259
+ * ```
260
+ */
261
+ clear() {
262
+ this.states.clear();
263
+ }
264
+ }
265
+
266
+ export { InMemorySessionStore, InMemoryStateStore };
267
+ //# sourceMappingURL=storage.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.mjs","sources":["../src/storage/InMemorySessionStore.ts","../src/storage/InMemoryStateStore.ts"],"sourcesContent":["import type { SessionStore } from \"../core/interfaces.js\";\nimport type { NodeSavedSession } from \"@atproto/oauth-client-node\";\n\n/**\n * In-memory implementation of the SessionStore interface.\n *\n * This store keeps OAuth sessions in memory using a Map. It's intended\n * for development, testing, and simple use cases where session persistence\n * across restarts is not required.\n *\n * @remarks\n * **Warning**: This implementation is **not suitable for production** because:\n * - Sessions are lost when the process restarts\n * - Sessions cannot be shared across multiple server instances\n * - No automatic cleanup of expired sessions\n *\n * For production, implement {@link SessionStore} with a persistent backend:\n * - **Redis**: Good for distributed systems, supports TTL\n * - **PostgreSQL/MySQL**: Good for existing database infrastructure\n * - **MongoDB**: Good for document-based storage\n *\n * @example Basic usage\n * ```typescript\n * import { InMemorySessionStore } from \"@hypercerts-org/sdk/storage\";\n *\n * const sessionStore = new InMemorySessionStore();\n *\n * const sdk = new ATProtoSDK({\n * oauth: { ... },\n * storage: {\n * sessionStore, // Will warn in logs for production\n * },\n * });\n * ```\n *\n * @example Testing usage\n * ```typescript\n * const sessionStore = new InMemorySessionStore();\n *\n * // After tests, clean up\n * sessionStore.clear();\n * ```\n *\n * @see {@link SessionStore} for the interface definition\n * @see {@link InMemoryStateStore} for the corresponding state store\n */\nexport class InMemorySessionStore implements SessionStore {\n /**\n * Internal storage for sessions, keyed by DID.\n * @internal\n */\n private sessions = new Map<string, NodeSavedSession>();\n\n /**\n * Retrieves a session by DID.\n *\n * @param did - The user's Decentralized Identifier\n * @returns Promise resolving to the session, or `undefined` if not found\n *\n * @example\n * ```typescript\n * const session = await sessionStore.get(\"did:plc:abc123\");\n * if (session) {\n * console.log(\"Session found\");\n * }\n * ```\n */\n async get(did: string): Promise<NodeSavedSession | undefined> {\n return this.sessions.get(did);\n }\n\n /**\n * Stores or updates a session.\n *\n * @param did - The user's DID to use as the key\n * @param session - The session data to store\n *\n * @remarks\n * If a session already exists for the DID, it is overwritten.\n *\n * @example\n * ```typescript\n * await sessionStore.set(\"did:plc:abc123\", sessionData);\n * ```\n */\n async set(did: string, session: NodeSavedSession): Promise<void> {\n this.sessions.set(did, session);\n }\n\n /**\n * Deletes a session by DID.\n *\n * @param did - The DID of the session to delete\n *\n * @remarks\n * If no session exists for the DID, this is a no-op.\n *\n * @example\n * ```typescript\n * await sessionStore.del(\"did:plc:abc123\");\n * ```\n */\n async del(did: string): Promise<void> {\n this.sessions.delete(did);\n }\n\n /**\n * Clears all stored sessions.\n *\n * This is primarily useful for testing to ensure a clean state\n * between test runs.\n *\n * @remarks\n * This method is synchronous (not async) for convenience in test cleanup.\n *\n * @example\n * ```typescript\n * // In test teardown\n * afterEach(() => {\n * sessionStore.clear();\n * });\n * ```\n */\n clear(): void {\n this.sessions.clear();\n }\n}\n","import type { StateStore } from \"../core/interfaces.js\";\nimport type { NodeSavedState } from \"@atproto/oauth-client-node\";\n\n/**\n * In-memory implementation of the StateStore interface.\n *\n * This store keeps OAuth state parameters in memory using a Map. State is\n * used during the OAuth authorization flow for CSRF protection and PKCE.\n *\n * @remarks\n * **Warning**: This implementation is **not suitable for production** because:\n * - State is lost when the process restarts (breaking in-progress OAuth flows)\n * - State cannot be shared across multiple server instances\n * - No automatic cleanup of expired state (memory leak potential)\n *\n * For production, implement {@link StateStore} with a persistent backend\n * that supports TTL (time-to-live):\n * - **Redis**: Ideal choice with built-in TTL support\n * - **Database with cleanup job**: PostgreSQL/MySQL with periodic cleanup\n *\n * **State Lifecycle**:\n * 1. Created when user starts OAuth flow (`authorize()`)\n * 2. Retrieved and validated during callback\n * 3. Deleted after successful or failed callback\n * 4. Should expire after ~15 minutes if callback never happens\n *\n * @example Basic usage\n * ```typescript\n * import { InMemoryStateStore } from \"@hypercerts-org/sdk/storage\";\n *\n * const stateStore = new InMemoryStateStore();\n *\n * const sdk = new ATProtoSDK({\n * oauth: { ... },\n * storage: {\n * stateStore, // Will warn in logs for production\n * },\n * });\n * ```\n *\n * @example Testing usage\n * ```typescript\n * const stateStore = new InMemoryStateStore();\n *\n * // After tests, clean up\n * stateStore.clear();\n * ```\n *\n * @see {@link StateStore} for the interface definition\n * @see {@link InMemorySessionStore} for the corresponding session store\n */\nexport class InMemoryStateStore implements StateStore {\n /**\n * Internal storage for OAuth state, keyed by state string.\n * @internal\n */\n private states = new Map<string, NodeSavedState>();\n\n /**\n * Retrieves OAuth state by key.\n *\n * @param key - The state key (random string from authorization URL)\n * @returns Promise resolving to the state, or `undefined` if not found\n *\n * @remarks\n * The key is a cryptographically random string generated during\n * the authorization request. It's included in the callback URL\n * and used to retrieve the associated PKCE verifier and other data.\n *\n * @example\n * ```typescript\n * // During OAuth callback\n * const state = await stateStore.get(params.get(\"state\")!);\n * if (!state) {\n * throw new Error(\"Invalid or expired state\");\n * }\n * ```\n */\n async get(key: string): Promise<NodeSavedState | undefined> {\n return this.states.get(key);\n }\n\n /**\n * Stores OAuth state temporarily.\n *\n * @param key - The state key to use for storage\n * @param state - The OAuth state data (includes PKCE verifier, etc.)\n *\n * @remarks\n * In production implementations, state should be stored with a TTL\n * of approximately 10-15 minutes to prevent stale state accumulation.\n *\n * @example\n * ```typescript\n * // Called internally by OAuthClient during authorize()\n * await stateStore.set(stateKey, {\n * // PKCE code verifier, redirect URI, etc.\n * });\n * ```\n */\n async set(key: string, state: NodeSavedState): Promise<void> {\n this.states.set(key, state);\n }\n\n /**\n * Deletes OAuth state by key.\n *\n * @param key - The state key to delete\n *\n * @remarks\n * Called after the OAuth callback is processed (whether successful or not)\n * to clean up the temporary state.\n *\n * @example\n * ```typescript\n * // After processing callback\n * await stateStore.del(stateKey);\n * ```\n */\n async del(key: string): Promise<void> {\n this.states.delete(key);\n }\n\n /**\n * Clears all stored state.\n *\n * This is primarily useful for testing to ensure a clean state\n * between test runs.\n *\n * @remarks\n * This method is synchronous (not async) for convenience in test cleanup.\n * In production, be careful using this as it will invalidate all\n * in-progress OAuth flows.\n *\n * @example\n * ```typescript\n * // In test teardown\n * afterEach(() => {\n * stateStore.clear();\n * });\n * ```\n */\n clear(): void {\n this.states.clear();\n }\n}\n"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CG;MACU,oBAAoB,CAAA;AAAjC,IAAA,WAAA,GAAA;AACE;;;AAGG;AACK,QAAA,IAAA,CAAA,QAAQ,GAAG,IAAI,GAAG,EAA4B;IA2ExD;AAzEE;;;;;;;;;;;;;AAaG;IACH,MAAM,GAAG,CAAC,GAAW,EAAA;QACnB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;IAC/B;AAEA;;;;;;;;;;;;;AAaG;AACH,IAAA,MAAM,GAAG,CAAC,GAAW,EAAE,OAAyB,EAAA;QAC9C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC;IACjC;AAEA;;;;;;;;;;;;AAYG;IACH,MAAM,GAAG,CAAC,GAAW,EAAA;AACnB,QAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC;IAC3B;AAEA;;;;;;;;;;;;;;;;AAgBG;IACH,KAAK,GAAA;AACH,QAAA,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE;IACvB;AACD;;AC3HD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CG;MACU,kBAAkB,CAAA;AAA/B,IAAA,WAAA,GAAA;AACE;;;AAGG;AACK,QAAA,IAAA,CAAA,MAAM,GAAG,IAAI,GAAG,EAA0B;IAyFpD;AAvFE;;;;;;;;;;;;;;;;;;;AAmBG;IACH,MAAM,GAAG,CAAC,GAAW,EAAA;QACnB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;IAC7B;AAEA;;;;;;;;;;;;;;;;;AAiBG;AACH,IAAA,MAAM,GAAG,CAAC,GAAW,EAAE,KAAqB,EAAA;QAC1C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC;IAC7B;AAEA;;;;;;;;;;;;;;AAcG;IACH,MAAM,GAAG,CAAC,GAAW,EAAA;AACnB,QAAA,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;IACzB;AAEA;;;;;;;;;;;;;;;;;;AAkBG;IACH,KAAK,GAAA;AACH,QAAA,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;IACrB;AACD;;;;"}
@@ -0,0 +1,415 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Mock factories for testing.
5
+ *
6
+ * This module provides factory functions to create mock objects
7
+ * for testing SDK functionality without real AT Protocol connections.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ /**
12
+ * Creates a mock OAuth session for testing.
13
+ *
14
+ * The mock session includes all required properties and a mock
15
+ * `fetchHandler` that returns empty successful responses by default.
16
+ *
17
+ * @param overrides - Partial session object to override default values
18
+ * @returns A mock Session object suitable for testing
19
+ *
20
+ * @remarks
21
+ * The mock session is cast to `Session` type for compatibility.
22
+ * In real usage, sessions come from the OAuth flow and contain
23
+ * actual tokens and a real fetch handler.
24
+ *
25
+ * **Default Values**:
26
+ * - `did`: `"did:plc:test123"`
27
+ * - `handle`: `"test.bsky.social"`
28
+ * - `fetchHandler`: Returns `Response` with `{}` body
29
+ *
30
+ * @example Basic mock session
31
+ * ```typescript
32
+ * import { createMockSession } from "@hypercerts-org/sdk/testing";
33
+ *
34
+ * const session = createMockSession();
35
+ * const repo = sdk.repository(session);
36
+ * ```
37
+ *
38
+ * @example With custom DID
39
+ * ```typescript
40
+ * const session = createMockSession({
41
+ * did: "did:plc:custom-test-user",
42
+ * handle: "custom.bsky.social",
43
+ * });
44
+ * ```
45
+ *
46
+ * @example With custom fetch handler
47
+ * ```typescript
48
+ * const session = createMockSession({
49
+ * fetchHandler: async (url, init) => {
50
+ * // Custom response logic
51
+ * return new Response(JSON.stringify({ success: true }));
52
+ * },
53
+ * });
54
+ * ```
55
+ */
56
+ function createMockSession(overrides = {}) {
57
+ const mockSession = {
58
+ did: "did:plc:test123",
59
+ sub: "did:plc:test123",
60
+ handle: "test.bsky.social",
61
+ accessJwt: "mock-access-jwt",
62
+ refreshJwt: "mock-refresh-jwt",
63
+ active: true,
64
+ fetchHandler: async (_input, _init) => {
65
+ return new Response(JSON.stringify({}), {
66
+ status: 200,
67
+ headers: { "Content-Type": "application/json" },
68
+ });
69
+ },
70
+ ...overrides,
71
+ };
72
+ return mockSession;
73
+ }
74
+ /**
75
+ * Creates a mock SDK configuration for testing.
76
+ *
77
+ * The configuration includes all required OAuth settings with
78
+ * placeholder values suitable for testing (not real credentials).
79
+ *
80
+ * @param overrides - Partial configuration to override default values
81
+ * @returns A complete ATProtoSDKConfig suitable for testing
82
+ *
83
+ * @remarks
84
+ * The default configuration uses example.com domains and a minimal
85
+ * JWK structure. This is sufficient for unit tests but won't work
86
+ * for integration tests that require real OAuth flows.
87
+ *
88
+ * **Default Values**:
89
+ * - `clientId`: `"https://test.example.com/client-metadata.json"`
90
+ * - `pds`: `"https://bsky.social"`
91
+ * - `sds`: `"https://sds.example.com"`
92
+ *
93
+ * @example Basic test config
94
+ * ```typescript
95
+ * import { createTestConfig } from "@hypercerts-org/sdk/testing";
96
+ *
97
+ * const config = createTestConfig();
98
+ * const sdk = new ATProtoSDK(config);
99
+ * ```
100
+ *
101
+ * @example With custom PDS
102
+ * ```typescript
103
+ * const config = createTestConfig({
104
+ * servers: {
105
+ * pds: "https://custom-pds.example.com",
106
+ * },
107
+ * });
108
+ * ```
109
+ *
110
+ * @example With logger for debugging tests
111
+ * ```typescript
112
+ * const config = createTestConfig({
113
+ * logger: console,
114
+ * });
115
+ * ```
116
+ */
117
+ function createTestConfig(overrides = {}) {
118
+ return {
119
+ oauth: {
120
+ clientId: "https://test.example.com/client-metadata.json",
121
+ redirectUri: "https://test.example.com/callback",
122
+ scope: "atproto transition:generic",
123
+ jwksUri: "https://test.example.com/jwks.json",
124
+ jwkPrivate: JSON.stringify({
125
+ kty: "EC",
126
+ crv: "P-256",
127
+ x: "test",
128
+ y: "test",
129
+ d: "test",
130
+ }),
131
+ },
132
+ servers: {
133
+ pds: "https://bsky.social",
134
+ sds: "https://sds.example.com",
135
+ },
136
+ ...overrides,
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Mock storage implementations for testing.
142
+ *
143
+ * This module provides mock implementations of SessionStore and StateStore
144
+ * that track all operations for verification in tests.
145
+ *
146
+ * @packageDocumentation
147
+ */
148
+ /**
149
+ * Mock session store that tracks all operations.
150
+ *
151
+ * This implementation stores sessions in memory and records all
152
+ * method calls for verification in tests.
153
+ *
154
+ * @remarks
155
+ * Use this in tests to:
156
+ * - Verify that sessions are being stored correctly
157
+ * - Check what DIDs have been accessed
158
+ * - Assert on the number and order of operations
159
+ * - Pre-populate sessions for testing restore flows
160
+ *
161
+ * @example Basic usage
162
+ * ```typescript
163
+ * import { MockSessionStore } from "@hypercerts-org/sdk/testing";
164
+ *
165
+ * const sessionStore = new MockSessionStore();
166
+ * const sdk = new ATProtoSDK({
167
+ * ...config,
168
+ * storage: { sessionStore },
169
+ * });
170
+ *
171
+ * // After some operations...
172
+ * expect(sessionStore.setCalls).toHaveLength(1);
173
+ * expect(sessionStore.getCalls).toContain("did:plc:test123");
174
+ * ```
175
+ *
176
+ * @example Pre-populating for tests
177
+ * ```typescript
178
+ * const sessionStore = new MockSessionStore();
179
+ *
180
+ * // Pre-populate a session
181
+ * await sessionStore.set("did:plc:existing", mockSessionData);
182
+ *
183
+ * // Reset tracking (keeps the data)
184
+ * sessionStore.getCalls = [];
185
+ * sessionStore.setCalls = [];
186
+ *
187
+ * // Now test restore behavior
188
+ * const session = await sdk.restoreSession("did:plc:existing");
189
+ * expect(sessionStore.getCalls).toContain("did:plc:existing");
190
+ * ```
191
+ *
192
+ * @example Asserting on operations
193
+ * ```typescript
194
+ * const sessionStore = new MockSessionStore();
195
+ *
196
+ * // ... perform operations ...
197
+ *
198
+ * // Verify session was stored for correct DID
199
+ * expect(sessionStore.setCalls[0].did).toBe("did:plc:expected");
200
+ *
201
+ * // Verify session was deleted on logout
202
+ * expect(sessionStore.delCalls).toContain("did:plc:logged-out");
203
+ * ```
204
+ */
205
+ class MockSessionStore {
206
+ constructor() {
207
+ /**
208
+ * Internal storage for sessions.
209
+ * @internal
210
+ */
211
+ this.store = new Map();
212
+ /**
213
+ * Record of all `get()` calls made to this store.
214
+ *
215
+ * Each entry is the DID that was requested.
216
+ */
217
+ this.getCalls = [];
218
+ /**
219
+ * Record of all `set()` calls made to this store.
220
+ *
221
+ * Each entry contains the DID and session that was stored.
222
+ */
223
+ this.setCalls = [];
224
+ /**
225
+ * Record of all `del()` calls made to this store.
226
+ *
227
+ * Each entry is the DID that was deleted.
228
+ */
229
+ this.delCalls = [];
230
+ }
231
+ /**
232
+ * Retrieves a session by DID.
233
+ *
234
+ * Records the call in `getCalls`.
235
+ *
236
+ * @param did - The DID to look up
237
+ * @returns The stored session or undefined
238
+ */
239
+ async get(did) {
240
+ this.getCalls.push(did);
241
+ return this.store.get(did);
242
+ }
243
+ /**
244
+ * Stores a session.
245
+ *
246
+ * Records the call in `setCalls`.
247
+ *
248
+ * @param did - The DID to store under
249
+ * @param session - The session data to store
250
+ */
251
+ async set(did, session) {
252
+ this.setCalls.push({ did, session });
253
+ this.store.set(did, session);
254
+ }
255
+ /**
256
+ * Deletes a session.
257
+ *
258
+ * Records the call in `delCalls`.
259
+ *
260
+ * @param did - The DID to delete
261
+ */
262
+ async del(did) {
263
+ this.delCalls.push(did);
264
+ this.store.delete(did);
265
+ }
266
+ /**
267
+ * Resets the store to initial state.
268
+ *
269
+ * Clears all stored sessions and all recorded calls.
270
+ * Call this in `beforeEach` or `afterEach` to ensure test isolation.
271
+ *
272
+ * @example
273
+ * ```typescript
274
+ * beforeEach(() => {
275
+ * sessionStore.reset();
276
+ * });
277
+ * ```
278
+ */
279
+ reset() {
280
+ this.store.clear();
281
+ this.getCalls = [];
282
+ this.setCalls = [];
283
+ this.delCalls = [];
284
+ }
285
+ }
286
+ /**
287
+ * Mock state store that tracks all operations.
288
+ *
289
+ * This implementation stores OAuth state in memory and records all
290
+ * method calls for verification in tests.
291
+ *
292
+ * @remarks
293
+ * Use this in tests to:
294
+ * - Verify OAuth state is being created during authorization
295
+ * - Check that state is retrieved during callback
296
+ * - Assert that state is cleaned up after use
297
+ * - Test error handling for missing/invalid state
298
+ *
299
+ * @example Basic usage
300
+ * ```typescript
301
+ * import { MockStateStore } from "@hypercerts-org/sdk/testing";
302
+ *
303
+ * const stateStore = new MockStateStore();
304
+ * const sdk = new ATProtoSDK({
305
+ * ...config,
306
+ * storage: { stateStore },
307
+ * });
308
+ *
309
+ * // After authorize()
310
+ * expect(stateStore.setCalls).toHaveLength(1);
311
+ *
312
+ * // After callback()
313
+ * expect(stateStore.getCalls).toHaveLength(1);
314
+ * expect(stateStore.delCalls).toHaveLength(1);
315
+ * ```
316
+ *
317
+ * @example Testing invalid state
318
+ * ```typescript
319
+ * const stateStore = new MockStateStore();
320
+ * // Don't pre-populate - state will be missing
321
+ *
322
+ * // This should fail because state doesn't exist
323
+ * await expect(sdk.callback(params)).rejects.toThrow();
324
+ *
325
+ * // Verify the lookup was attempted
326
+ * expect(stateStore.getCalls).toContain(stateKey);
327
+ * ```
328
+ */
329
+ class MockStateStore {
330
+ constructor() {
331
+ /**
332
+ * Internal storage for OAuth state.
333
+ * @internal
334
+ */
335
+ this.store = new Map();
336
+ /**
337
+ * Record of all `get()` calls made to this store.
338
+ *
339
+ * Each entry is the state key that was requested.
340
+ */
341
+ this.getCalls = [];
342
+ /**
343
+ * Record of all `set()` calls made to this store.
344
+ *
345
+ * Each entry contains the key and state that was stored.
346
+ */
347
+ this.setCalls = [];
348
+ /**
349
+ * Record of all `del()` calls made to this store.
350
+ *
351
+ * Each entry is the state key that was deleted.
352
+ */
353
+ this.delCalls = [];
354
+ }
355
+ /**
356
+ * Retrieves OAuth state by key.
357
+ *
358
+ * Records the call in `getCalls`.
359
+ *
360
+ * @param key - The state key to look up
361
+ * @returns The stored state or undefined
362
+ */
363
+ async get(key) {
364
+ this.getCalls.push(key);
365
+ return this.store.get(key);
366
+ }
367
+ /**
368
+ * Stores OAuth state.
369
+ *
370
+ * Records the call in `setCalls`.
371
+ *
372
+ * @param key - The state key to store under
373
+ * @param state - The OAuth state data to store
374
+ */
375
+ async set(key, state) {
376
+ this.setCalls.push({ key, state });
377
+ this.store.set(key, state);
378
+ }
379
+ /**
380
+ * Deletes OAuth state.
381
+ *
382
+ * Records the call in `delCalls`.
383
+ *
384
+ * @param key - The state key to delete
385
+ */
386
+ async del(key) {
387
+ this.delCalls.push(key);
388
+ this.store.delete(key);
389
+ }
390
+ /**
391
+ * Resets the store to initial state.
392
+ *
393
+ * Clears all stored state and all recorded calls.
394
+ * Call this in `beforeEach` or `afterEach` to ensure test isolation.
395
+ *
396
+ * @example
397
+ * ```typescript
398
+ * beforeEach(() => {
399
+ * stateStore.reset();
400
+ * });
401
+ * ```
402
+ */
403
+ reset() {
404
+ this.store.clear();
405
+ this.getCalls = [];
406
+ this.setCalls = [];
407
+ this.delCalls = [];
408
+ }
409
+ }
410
+
411
+ exports.MockSessionStore = MockSessionStore;
412
+ exports.MockStateStore = MockStateStore;
413
+ exports.createMockSession = createMockSession;
414
+ exports.createTestConfig = createTestConfig;
415
+ //# sourceMappingURL=testing.cjs.map