@cofhe/sdk 0.0.0-beta-20251027110729

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 (110) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/adapters/ethers5.test.ts +174 -0
  3. package/adapters/ethers5.ts +36 -0
  4. package/adapters/ethers6.test.ts +169 -0
  5. package/adapters/ethers6.ts +36 -0
  6. package/adapters/hardhat-node.ts +167 -0
  7. package/adapters/hardhat.hh2.test.ts +159 -0
  8. package/adapters/hardhat.ts +37 -0
  9. package/adapters/index.test.ts +25 -0
  10. package/adapters/index.ts +5 -0
  11. package/adapters/smartWallet.ts +91 -0
  12. package/adapters/test-utils.ts +53 -0
  13. package/adapters/types.ts +6 -0
  14. package/adapters/wagmi.test.ts +156 -0
  15. package/adapters/wagmi.ts +17 -0
  16. package/chains/chains/arbSepolia.ts +14 -0
  17. package/chains/chains/baseSepolia.ts +14 -0
  18. package/chains/chains/hardhat.ts +15 -0
  19. package/chains/chains/sepolia.ts +14 -0
  20. package/chains/chains.test.ts +49 -0
  21. package/chains/defineChain.ts +18 -0
  22. package/chains/index.ts +33 -0
  23. package/chains/types.ts +32 -0
  24. package/core/baseBuilder.ts +138 -0
  25. package/core/client.test.ts +298 -0
  26. package/core/client.ts +308 -0
  27. package/core/config.test.ts +224 -0
  28. package/core/config.ts +213 -0
  29. package/core/decrypt/MockQueryDecrypterAbi.ts +129 -0
  30. package/core/decrypt/cofheMocksSealOutput.ts +57 -0
  31. package/core/decrypt/decryptHandleBuilder.ts +281 -0
  32. package/core/decrypt/decryptUtils.ts +28 -0
  33. package/core/decrypt/tnSealOutput.ts +59 -0
  34. package/core/encrypt/MockZkVerifierAbi.ts +106 -0
  35. package/core/encrypt/cofheMocksZkVerifySign.ts +278 -0
  36. package/core/encrypt/encryptInputsBuilder.test.ts +735 -0
  37. package/core/encrypt/encryptInputsBuilder.ts +512 -0
  38. package/core/encrypt/encryptUtils.ts +64 -0
  39. package/core/encrypt/zkPackProveVerify.ts +273 -0
  40. package/core/error.ts +170 -0
  41. package/core/fetchKeys.test.ts +212 -0
  42. package/core/fetchKeys.ts +170 -0
  43. package/core/index.ts +77 -0
  44. package/core/keyStore.test.ts +226 -0
  45. package/core/keyStore.ts +127 -0
  46. package/core/permits.test.ts +242 -0
  47. package/core/permits.ts +136 -0
  48. package/core/result.test.ts +180 -0
  49. package/core/result.ts +67 -0
  50. package/core/test-utils.ts +45 -0
  51. package/core/types.ts +352 -0
  52. package/core/utils.ts +88 -0
  53. package/dist/adapters.cjs +88 -0
  54. package/dist/adapters.d.cts +14558 -0
  55. package/dist/adapters.d.ts +14558 -0
  56. package/dist/adapters.js +83 -0
  57. package/dist/chains.cjs +101 -0
  58. package/dist/chains.d.cts +99 -0
  59. package/dist/chains.d.ts +99 -0
  60. package/dist/chains.js +1 -0
  61. package/dist/chunk-GZCQQYVI.js +93 -0
  62. package/dist/chunk-KFGPTJ6X.js +2295 -0
  63. package/dist/chunk-LU7BMUUT.js +804 -0
  64. package/dist/core.cjs +3174 -0
  65. package/dist/core.d.cts +16 -0
  66. package/dist/core.d.ts +16 -0
  67. package/dist/core.js +3 -0
  68. package/dist/node.cjs +3090 -0
  69. package/dist/node.d.cts +22 -0
  70. package/dist/node.d.ts +22 -0
  71. package/dist/node.js +90 -0
  72. package/dist/permit-S9CnI6MF.d.cts +333 -0
  73. package/dist/permit-S9CnI6MF.d.ts +333 -0
  74. package/dist/permits.cjs +856 -0
  75. package/dist/permits.d.cts +1056 -0
  76. package/dist/permits.d.ts +1056 -0
  77. package/dist/permits.js +1 -0
  78. package/dist/types-KImPrEIe.d.cts +48 -0
  79. package/dist/types-KImPrEIe.d.ts +48 -0
  80. package/dist/types-PhwGgQvs.d.ts +953 -0
  81. package/dist/types-bB7wLj0q.d.cts +953 -0
  82. package/dist/web.cjs +3067 -0
  83. package/dist/web.d.cts +22 -0
  84. package/dist/web.d.ts +22 -0
  85. package/dist/web.js +64 -0
  86. package/node/client.test.ts +152 -0
  87. package/node/config.test.ts +68 -0
  88. package/node/encryptInputs.test.ts +175 -0
  89. package/node/index.ts +96 -0
  90. package/node/storage.ts +51 -0
  91. package/package.json +120 -0
  92. package/permits/index.ts +67 -0
  93. package/permits/localstorage.test.ts +118 -0
  94. package/permits/permit.test.ts +474 -0
  95. package/permits/permit.ts +396 -0
  96. package/permits/sealing.test.ts +84 -0
  97. package/permits/sealing.ts +131 -0
  98. package/permits/signature.ts +79 -0
  99. package/permits/store.test.ts +128 -0
  100. package/permits/store.ts +168 -0
  101. package/permits/test-utils.ts +20 -0
  102. package/permits/types.ts +174 -0
  103. package/permits/utils.ts +63 -0
  104. package/permits/validation.test.ts +288 -0
  105. package/permits/validation.ts +349 -0
  106. package/web/client.web.test.ts +152 -0
  107. package/web/config.web.test.ts +71 -0
  108. package/web/encryptInputs.web.test.ts +195 -0
  109. package/web/index.ts +97 -0
  110. package/web/storage.ts +20 -0
@@ -0,0 +1,242 @@
1
+ /**
2
+ * @vitest-environment happy-dom
3
+ */
4
+ /* eslint-disable no-unused-vars */
5
+ import { permitStore } from '@/permits';
6
+
7
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
8
+ import { createPublicClient, createWalletClient, http, type PublicClient, type WalletClient } from 'viem';
9
+ import { arbitrumSepolia } from 'viem/chains';
10
+ import { privateKeyToAccount } from 'viem/accounts';
11
+ import { permits } from './permits.js';
12
+
13
+ // Type declarations for happy-dom environment
14
+ declare const localStorage: {
15
+ clear: () => void;
16
+ getItem: (name: string) => string | null;
17
+ setItem: (name: string, value: string) => void;
18
+ };
19
+
20
+ // Test private keys (well-known test keys from Anvil/Hardhat)
21
+ const BOB_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; // Bob - always issuer
22
+ const ALICE_PRIVATE_KEY = '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'; // Alice - always recipient
23
+
24
+ // Create real viem clients for Arbitrum Sepolia
25
+ const publicClient: PublicClient = createPublicClient({
26
+ chain: arbitrumSepolia,
27
+ transport: http(),
28
+ });
29
+
30
+ const bobWalletClient: WalletClient = createWalletClient({
31
+ chain: arbitrumSepolia,
32
+ transport: http(),
33
+ account: privateKeyToAccount(BOB_PRIVATE_KEY),
34
+ });
35
+
36
+ const aliceWalletClient: WalletClient = createWalletClient({
37
+ chain: arbitrumSepolia,
38
+ transport: http(),
39
+ account: privateKeyToAccount(ALICE_PRIVATE_KEY),
40
+ });
41
+
42
+ // Helper to get the wallet addresses
43
+ const bobAddress = bobWalletClient.account!.address;
44
+ const aliceAddress = aliceWalletClient.account!.address;
45
+ const chainId = 421614; // Arbitrum Sepolia
46
+
47
+ describe('Core Permits Tests', () => {
48
+ beforeEach(() => {
49
+ // Clear localStorage and reset stores
50
+ localStorage.clear();
51
+ permitStore.store.setState({ permits: {}, activePermitHash: {} });
52
+ });
53
+
54
+ afterEach(() => {
55
+ localStorage.clear();
56
+ permitStore.store.setState({ permits: {}, activePermitHash: {} });
57
+ });
58
+
59
+ describe('Permit Creation', () => {
60
+ it('should create and store self permit', async () => {
61
+ const permit = await permits.createSelf(
62
+ { name: 'Test Self Permit', issuer: bobAddress },
63
+ publicClient,
64
+ bobWalletClient
65
+ );
66
+
67
+ expect(permit).toBeDefined();
68
+ expect(permit.name).toBe('Test Self Permit');
69
+ expect(permit.type).toBe('self');
70
+ expect(permit.issuer).toBe(bobAddress);
71
+ expect(permit.issuerSignature).toBeDefined();
72
+ expect(permit.issuerSignature).not.toBe('0x');
73
+
74
+ // Verify localStorage
75
+ const storedData = localStorage.getItem('cofhesdk-permits');
76
+ expect(storedData).toBeDefined();
77
+ const parsedData = JSON.parse(storedData!);
78
+ expect(parsedData.state.permits[chainId][bobAddress]).toBeDefined();
79
+ expect(parsedData.state.activePermitHash[chainId][bobAddress]).toBeDefined();
80
+ });
81
+
82
+ it('should create and store sharing permit', async () => {
83
+ const permit = await permits.createSharing(
84
+ {
85
+ name: 'Test Sharing Permit',
86
+ issuer: bobAddress,
87
+ recipient: aliceAddress,
88
+ },
89
+ publicClient,
90
+ bobWalletClient
91
+ );
92
+
93
+ expect(permit.name).toBe('Test Sharing Permit');
94
+ expect(permit.type).toBe('sharing');
95
+ expect(permit.issuer).toBe(bobAddress);
96
+ expect(permit.recipient).toBe(aliceAddress);
97
+ expect(permit.issuerSignature).toBeDefined();
98
+ expect(permit.issuerSignature).not.toBe('0x');
99
+
100
+ // Verify localStorage
101
+ const storedData = localStorage.getItem('cofhesdk-permits');
102
+ expect(storedData).toBeDefined();
103
+ const parsedData = JSON.parse(storedData!);
104
+ expect(parsedData.state.permits[chainId][bobAddress]).toBeDefined();
105
+ expect(parsedData.state.activePermitHash[chainId][bobAddress]).toBeDefined();
106
+ });
107
+
108
+ it('should import shared permit from JSON string', async () => {
109
+ // First create a sharing permit to import
110
+ const sharingPermit = await permits.createSharing(
111
+ {
112
+ name: 'Original Sharing Permit',
113
+ issuer: bobAddress,
114
+ recipient: aliceAddress,
115
+ },
116
+ publicClient,
117
+ bobWalletClient
118
+ );
119
+
120
+ // Export the permit as JSON string
121
+ const permitJson = JSON.stringify({
122
+ name: sharingPermit.name,
123
+ type: sharingPermit.type,
124
+ issuer: sharingPermit.issuer,
125
+ expiration: sharingPermit.expiration,
126
+ recipient: sharingPermit.recipient,
127
+ validatorId: sharingPermit.validatorId,
128
+ validatorContract: sharingPermit.validatorContract,
129
+ issuerSignature: sharingPermit.issuerSignature,
130
+ });
131
+
132
+ // Import the permit as Alice (recipient)
133
+ const permit = await permits.importShared(permitJson, publicClient, aliceWalletClient);
134
+
135
+ expect(permit.name).toBe('Original Sharing Permit');
136
+ expect(permit.type).toBe('recipient');
137
+ expect(permit.issuer).toBe(bobAddress);
138
+ expect(permit.recipient).toBe(aliceAddress);
139
+ expect(permit.recipientSignature).toBeDefined();
140
+ expect(permit.recipientSignature).not.toBe('0x');
141
+ });
142
+ });
143
+
144
+ describe('Permit Retrieval', () => {
145
+ let createdPermit: any;
146
+ let permitHash: string;
147
+
148
+ beforeEach(async () => {
149
+ // Create a real permit for testing
150
+ createdPermit = await permits.createSelf(
151
+ { name: 'Test Permit', issuer: bobAddress },
152
+ publicClient,
153
+ bobWalletClient
154
+ );
155
+ permitHash = permits.getHash(createdPermit);
156
+ });
157
+
158
+ it('should get permit by hash', async () => {
159
+ const permit = await permits.getPermit(chainId, bobAddress, permitHash);
160
+ expect(permit?.name).toBe('Test Permit');
161
+ expect(permit?.type).toBe('self');
162
+ });
163
+
164
+ it('should get all permits', async () => {
165
+ const allPermits = await permits.getPermits(chainId, bobAddress);
166
+ expect(Object.keys(allPermits).length).toBeGreaterThan(0);
167
+ });
168
+
169
+ it('should get active permit', async () => {
170
+ const permit = await permits.getActivePermit(chainId, bobAddress);
171
+ expect(permit?.name).toBe('Test Permit');
172
+ });
173
+
174
+ it('should get active permit hash', async () => {
175
+ const hash = await permits.getActivePermitHash(chainId, bobAddress);
176
+ expect(typeof hash).toBe('string');
177
+ });
178
+ });
179
+
180
+ describe('localStorage Integration', () => {
181
+ it('should persist permits to localStorage', async () => {
182
+ const createdPermit = await permits.createSelf(
183
+ { name: 'Test Permit', issuer: bobAddress },
184
+ publicClient,
185
+ bobWalletClient
186
+ );
187
+
188
+ const storedData = localStorage.getItem('cofhesdk-permits');
189
+ expect(storedData).toBeDefined();
190
+
191
+ const parsedData = JSON.parse(storedData!);
192
+ expect(parsedData.state.permits[chainId][bobAddress]).toBeDefined();
193
+ expect(parsedData.state.activePermitHash[chainId][bobAddress]).toBeDefined();
194
+
195
+ // Verify the permit data structure
196
+ const permitKeys = Object.keys(parsedData.state.permits[chainId][bobAddress]);
197
+ expect(permitKeys.length).toBeGreaterThan(0);
198
+
199
+ const permitHash = permits.getHash(createdPermit);
200
+ const serializedPermit = permits.serialize(createdPermit);
201
+ expect(parsedData.state.permits[chainId][bobAddress][permitHash]).toEqual(serializedPermit);
202
+ });
203
+ });
204
+
205
+ describe('Real Network Integration', () => {
206
+ it('should create permit with real EIP712 domain from Arbitrum Sepolia', async () => {
207
+ const permit = await permits.createSelf(
208
+ { name: 'Real Network Permit', issuer: bobAddress },
209
+ publicClient,
210
+ bobWalletClient
211
+ );
212
+
213
+ expect(permit._signedDomain).toBeDefined();
214
+ expect(permit._signedDomain?.chainId).toBe(chainId);
215
+ expect(permit._signedDomain?.name).toBeDefined();
216
+ expect(permit._signedDomain?.version).toBeDefined();
217
+ expect(permit._signedDomain?.verifyingContract).toBeDefined();
218
+ });
219
+
220
+ it('should handle multiple permits on real network', async () => {
221
+ // Create multiple permits
222
+ await permits.createSelf({ name: 'Permit 1', issuer: bobAddress }, publicClient, bobWalletClient);
223
+ await permits.createSharing(
224
+ {
225
+ name: 'Permit 2',
226
+ issuer: bobAddress,
227
+ recipient: aliceAddress,
228
+ },
229
+ publicClient,
230
+ bobWalletClient
231
+ );
232
+
233
+ // Verify both permits exist
234
+ const allPermits = await permits.getPermits(chainId, bobAddress);
235
+ expect(Object.keys(allPermits).length).toBeGreaterThanOrEqual(2);
236
+
237
+ // Verify active permit is the last created one
238
+ const activePermit = await permits.getActivePermit(chainId, bobAddress);
239
+ expect(activePermit?.name).toBe('Permit 2');
240
+ });
241
+ });
242
+ });
@@ -0,0 +1,136 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import {
3
+ type ImportSharedPermitOptions,
4
+ PermitUtils,
5
+ type CreateSelfPermitOptions,
6
+ type CreateSharingPermitOptions,
7
+ type Permit,
8
+ permitStore,
9
+ type SerializedPermit,
10
+ } from '@/permits';
11
+
12
+ import { type PublicClient, type WalletClient } from 'viem';
13
+
14
+ // HELPERS
15
+
16
+ // Helper function to store permit as active permit
17
+ const storeActivePermit = async (permit: Permit, publicClient: any, walletClient: any) => {
18
+ const chainId = await publicClient.getChainId();
19
+ const account = walletClient.account!.address;
20
+
21
+ permitStore.setPermit(chainId, account, permit);
22
+ permitStore.setActivePermitHash(chainId, account, PermitUtils.getHash(permit));
23
+ };
24
+
25
+ // Generic function to handle permit creation with error handling
26
+ const createPermitWithSign = async <T>(
27
+ options: T,
28
+ publicClient: PublicClient,
29
+ walletClient: WalletClient,
30
+ permitMethod: (options: T, publicClient: PublicClient, walletClient: WalletClient) => Promise<Permit>
31
+ ): Promise<Permit> => {
32
+ const permit = await permitMethod(options, publicClient, walletClient);
33
+ await storeActivePermit(permit, publicClient, walletClient);
34
+ return permit;
35
+ };
36
+
37
+ // CREATE
38
+
39
+ /**
40
+ * Create a permit usable by the connected user
41
+ * Stores the permit and selects it as the active permit
42
+ * @param options - The options for creating a self permit
43
+ * @returns The created permit or error
44
+ */
45
+ const createSelf = async (
46
+ options: CreateSelfPermitOptions,
47
+ publicClient: PublicClient,
48
+ walletClient: WalletClient
49
+ ): Promise<Permit> => {
50
+ return createPermitWithSign(options, publicClient, walletClient, PermitUtils.createSelfAndSign);
51
+ };
52
+
53
+ const createSharing = async (
54
+ options: CreateSharingPermitOptions,
55
+ publicClient: PublicClient,
56
+ walletClient: WalletClient
57
+ ): Promise<Permit> => {
58
+ return createPermitWithSign(options, publicClient, walletClient, PermitUtils.createSharingAndSign);
59
+ };
60
+
61
+ const importShared = async (
62
+ options: ImportSharedPermitOptions | any | string,
63
+ publicClient: PublicClient,
64
+ walletClient: WalletClient
65
+ ): Promise<Permit> => {
66
+ return createPermitWithSign(options, publicClient, walletClient, PermitUtils.importSharedAndSign);
67
+ };
68
+
69
+ // PERMIT UTILS
70
+
71
+ const getHash = (permit: Permit) => {
72
+ return PermitUtils.getHash(permit);
73
+ };
74
+
75
+ const serialize = (permit: Permit) => {
76
+ return PermitUtils.serialize(permit);
77
+ };
78
+
79
+ const deserialize = (serialized: SerializedPermit) => {
80
+ return PermitUtils.deserialize(serialized);
81
+ };
82
+
83
+ // GET
84
+
85
+ const getPermit = async (chainId: number, account: string, hash: string): Promise<Permit | undefined> => {
86
+ return permitStore.getPermit(chainId, account, hash);
87
+ };
88
+
89
+ const getPermits = async (chainId: number, account: string): Promise<Record<string, Permit>> => {
90
+ return permitStore.getPermits(chainId, account);
91
+ };
92
+
93
+ const getActivePermit = async (chainId: number, account: string): Promise<Permit | undefined> => {
94
+ return permitStore.getActivePermit(chainId, account);
95
+ };
96
+
97
+ const getActivePermitHash = async (chainId: number, account: string): Promise<string | undefined> => {
98
+ return permitStore.getActivePermitHash(chainId, account);
99
+ };
100
+
101
+ const selectActivePermit = async (chainId: number, account: string, hash: string): Promise<void> => {
102
+ await permitStore.setActivePermitHash(chainId, account, hash);
103
+ };
104
+
105
+ // REMOVE
106
+
107
+ const removePermit = async (chainId: number, account: string, hash: string): Promise<void> => {
108
+ await permitStore.removePermit(chainId, account, hash);
109
+ };
110
+
111
+ const removeActivePermit = async (chainId: number, account: string): Promise<void> => {
112
+ await permitStore.removeActivePermitHash(chainId, account);
113
+ };
114
+
115
+ // EXPORT
116
+
117
+ export const permits = {
118
+ getSnapshot: permitStore.store.getState,
119
+ subscribe: permitStore.store.subscribe,
120
+
121
+ createSelf,
122
+ createSharing,
123
+ importShared,
124
+
125
+ getHash,
126
+ serialize,
127
+ deserialize,
128
+
129
+ getPermit,
130
+ getPermits,
131
+ getActivePermit,
132
+ getActivePermitHash,
133
+ removePermit,
134
+ selectActivePermit,
135
+ removeActivePermit,
136
+ };
@@ -0,0 +1,180 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ResultOk, ResultErr, ResultErrOrInternal, resultWrapper, resultWrapperSync, type Result } from './result.js';
3
+ import { CofhesdkError, CofhesdkErrorCode } from './error.js';
4
+
5
+ export const expectResultError = <T>(result: Result<T>, errorPartial: string) => {
6
+ expect(result.success).to.eq(false, 'Result should be an error');
7
+ expect(result.error!.message).to.include(errorPartial, `Error should contain error partial: ${errorPartial}`);
8
+ };
9
+
10
+ export const expectResultSuccess = <T>(result: Result<T>): T => {
11
+ expect(result.success).to.eq(true, 'Result should be a success');
12
+ return result.data!;
13
+ };
14
+
15
+ export const expectResultValue = <T>(result: Result<T>, value: T): T => {
16
+ expect(result.success).to.eq(true, 'Result should be a success');
17
+ expect(result.data).to.eq(value, `Result should have the expected value ${value}`);
18
+ return result.data!;
19
+ };
20
+
21
+ describe('Result Type', () => {
22
+ describe('ResultOk', () => {
23
+ it('should create a successful result', () => {
24
+ const result = ResultOk('test data');
25
+ expect(result.success).toBe(true);
26
+ expect(result.data).toBe('test data');
27
+ expect(result.error).toBe(null);
28
+ });
29
+
30
+ it('should handle different data types', () => {
31
+ const stringResult = ResultOk('string');
32
+ const numberResult = ResultOk(42);
33
+ const objectResult = ResultOk({ key: 'value' });
34
+ const nullResult = ResultOk(null);
35
+
36
+ expect(stringResult.data).toBe('string');
37
+ expect(numberResult.data).toBe(42);
38
+ expect(objectResult.data).toEqual({ key: 'value' });
39
+ expect(nullResult.data).toBe(null);
40
+ });
41
+ });
42
+
43
+ describe('ResultErr', () => {
44
+ it('should create an error result', () => {
45
+ const error = new CofhesdkError({
46
+ code: CofhesdkErrorCode.InternalError,
47
+ message: 'Test error',
48
+ });
49
+ const result = ResultErr(error);
50
+
51
+ expect(result.success).toBe(false);
52
+ expect(result.data).toBe(null);
53
+ expect(result.error).toBe(error);
54
+ });
55
+ });
56
+ });
57
+
58
+ describe('Error Utilities', () => {
59
+ describe('ResultErrOrInternal', () => {
60
+ it('should wrap unknown errors as internal errors', () => {
61
+ const result = ResultErrOrInternal(new Error('string error'));
62
+ expect(result.success).toBe(false);
63
+ expect(result.error!).toBeInstanceOf(CofhesdkError);
64
+ expect(result.error!.code).toBe(CofhesdkErrorCode.InternalError);
65
+ expect(result.error!.message).toBe('An internal error occurred | Caused by: string error');
66
+ });
67
+
68
+ it('should preserve CofhesdkError instances', () => {
69
+ const originalError = new CofhesdkError({
70
+ code: CofhesdkErrorCode.MissingPublicClient,
71
+ message: 'Public client missing',
72
+ });
73
+ const result = ResultErrOrInternal(originalError);
74
+ expect(result.success).toBe(false);
75
+ expect(result.error!).toBe(originalError);
76
+ });
77
+
78
+ it('should handle Error instances', () => {
79
+ const error = new Error('Standard error');
80
+ const result = ResultErrOrInternal(error);
81
+ expect(result.success).toBe(false);
82
+ expect(result.error!).toBeInstanceOf(CofhesdkError);
83
+ expect(result.error!.cause).toBe(error);
84
+ });
85
+ });
86
+ });
87
+
88
+ describe('Result Wrappers', () => {
89
+ describe('resultWrapperSync', () => {
90
+ it('should wrap successful sync function', () => {
91
+ const result = resultWrapperSync(() => 'success');
92
+ expect(result.success).toBe(true);
93
+ expect(result.data).toBe('success');
94
+ });
95
+
96
+ it('should wrap failing sync function', () => {
97
+ const result = resultWrapperSync(() => {
98
+ throw new Error('Sync error');
99
+ });
100
+ expect(result.success).toBe(false);
101
+ expect(result.error).toBeInstanceOf(CofhesdkError);
102
+ });
103
+ });
104
+
105
+ describe('resultWrapper', () => {
106
+ it('should wrap successful async function', async () => {
107
+ const result = await resultWrapper(async () => 'async success');
108
+ expect(result.success).toBe(true);
109
+ expect(result.data).toBe('async success');
110
+ });
111
+
112
+ it('should wrap failing async function', async () => {
113
+ const result = await resultWrapper(async () => {
114
+ throw new Error('Async error');
115
+ });
116
+ expect(result.success).toBe(false);
117
+ expect(result.error).toBeInstanceOf(CofhesdkError);
118
+ });
119
+
120
+ it('should handle async operations', async () => {
121
+ const result = await resultWrapper(async () => {
122
+ await new Promise((resolve) => setTimeout(resolve, 10));
123
+ return 'delayed result';
124
+ });
125
+ expect(result.success).toBe(true);
126
+ expect(result.data).toBe('delayed result');
127
+ });
128
+
129
+ it('should handle Promise rejections', async () => {
130
+ const result = await resultWrapper(async () => {
131
+ return Promise.reject(new Error('Promise rejection'));
132
+ });
133
+ expect(result.success).toBe(false);
134
+ expect(result.error).toBeInstanceOf(CofhesdkError);
135
+ });
136
+ });
137
+ });
138
+
139
+ describe('Result Type Guards', () => {
140
+ it('should correctly identify success results', () => {
141
+ const successResult = ResultOk('data');
142
+ expect(successResult.success).toBe(true);
143
+ expect(successResult.data).toBe('data');
144
+ expect(successResult.error).toBe(null);
145
+ });
146
+
147
+ it('should correctly identify error results', () => {
148
+ const errorResult = ResultErr(
149
+ new CofhesdkError({
150
+ code: CofhesdkErrorCode.InternalError,
151
+ message: 'Test error',
152
+ })
153
+ );
154
+ expect(errorResult.success).toBe(false);
155
+ expect(errorResult.data).toBe(null);
156
+ expect(errorResult.error).toBeInstanceOf(CofhesdkError);
157
+ });
158
+ });
159
+
160
+ describe('Result Test Utilities', () => {
161
+ it('expectResultSuccess should return data for success', () => {
162
+ const result = ResultOk('data');
163
+ expect(expectResultSuccess(result)).toBe('data');
164
+ });
165
+
166
+ it('expectResultValue should validate and return matching data', () => {
167
+ const result = ResultOk('expected');
168
+ expect(expectResultValue(result, 'expected')).toBe('expected');
169
+ });
170
+
171
+ it('expectResultError should validate error messages', () => {
172
+ const result = ResultErr(
173
+ new CofhesdkError({
174
+ code: CofhesdkErrorCode.InternalError,
175
+ message: 'Test error message',
176
+ })
177
+ );
178
+ expectResultError(result, 'Test error');
179
+ });
180
+ });
package/core/result.ts ADDED
@@ -0,0 +1,67 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import { CofhesdkError, CofhesdkErrorCode } from './error.js';
3
+
4
+ export type Result<T> = { success: true; data: T; error: null } | { success: false; data: null; error: CofhesdkError };
5
+
6
+ export const ResultErr = <T>(error: CofhesdkError): Result<T> => ({
7
+ success: false,
8
+ data: null,
9
+ error,
10
+ });
11
+
12
+ export const ResultOk = <T>(data: T): Result<T> => ({
13
+ success: true,
14
+ data,
15
+ error: null,
16
+ });
17
+
18
+ export const ResultErrOrInternal = <T>(error: unknown): Result<T> => {
19
+ return ResultErr(CofhesdkError.fromError(error));
20
+ };
21
+
22
+ export const ResultHttpError = (error: unknown, url: string, status?: number): CofhesdkError => {
23
+ if (error instanceof CofhesdkError) return error;
24
+
25
+ const message = status ? `HTTP error ${status} from ${url}` : `HTTP request failed for ${url}`;
26
+
27
+ return new CofhesdkError({
28
+ code: CofhesdkErrorCode.InternalError,
29
+ message,
30
+ cause: error instanceof Error ? error : undefined,
31
+ });
32
+ };
33
+
34
+ export const ResultValidationError = (message: string): CofhesdkError => {
35
+ return new CofhesdkError({
36
+ code: CofhesdkErrorCode.InvalidPermitData,
37
+ message,
38
+ });
39
+ };
40
+
41
+ // Async resultWrapper
42
+ export const resultWrapper = async <T>(
43
+ tryFn: () => Promise<T>,
44
+ catchFn?: (error: CofhesdkError) => void,
45
+ finallyFn?: () => void
46
+ ): Promise<Result<T>> => {
47
+ try {
48
+ const result = await tryFn();
49
+ return ResultOk(result);
50
+ } catch (error) {
51
+ const result = ResultErrOrInternal(error);
52
+ catchFn?.(result.error!);
53
+ return result as Result<T>;
54
+ } finally {
55
+ finallyFn?.();
56
+ }
57
+ };
58
+
59
+ // Sync resultWrapper
60
+ export const resultWrapperSync = <T>(fn: () => T): Result<T> => {
61
+ try {
62
+ const result = fn();
63
+ return ResultOk(result);
64
+ } catch (error) {
65
+ return ResultErrOrInternal(error);
66
+ }
67
+ };
@@ -0,0 +1,45 @@
1
+ import { expect } from 'vitest';
2
+ import { type Result } from './result.js';
3
+ import { CofhesdkError, CofhesdkErrorCode } from './error.js';
4
+
5
+ /**
6
+ * Assert that a Result is successful and return its data
7
+ * Useful for type narrowing in tests
8
+ */
9
+ export const expectResultSuccess = <T>(result: Result<T>): NonNullable<T> => {
10
+ expect(result.error, `Result error: ${result.error?.toString()}`).toBe(null);
11
+ expect(result.success, `Result: ${bigintSafeJsonStringify(result)}`).toBe(true);
12
+ expect(result.data, `Result: ${bigintSafeJsonStringify(result)}`).not.toBe(null);
13
+ return result.data!;
14
+ };
15
+
16
+ /**
17
+ * Assert that a Result is an error and optionally verify the error details
18
+ */
19
+ export const expectResultError = <T>(
20
+ result: Result<T>,
21
+ errorCode?: CofhesdkErrorCode,
22
+ errorMessage?: string
23
+ ): CofhesdkError => {
24
+ expect(result.success, `Result: ${bigintSafeJsonStringify(result)}`).toBe(false);
25
+ expect(result.data, `Result: ${bigintSafeJsonStringify(result)}`).toBe(null);
26
+ expect(result.error, `Result: ${bigintSafeJsonStringify(result)}`).not.toBe(null);
27
+ const error = result.error as CofhesdkError;
28
+ expect(error, `Result error: ${result.error?.toString()}`).toBeInstanceOf(CofhesdkError);
29
+ if (errorCode) {
30
+ expect(error.code, `Result error: ${result.error?.toString()}`).toBe(errorCode);
31
+ }
32
+ if (errorMessage) {
33
+ expect(error.message, `Result error: ${result.error?.toString()}`).toBe(errorMessage);
34
+ }
35
+ return error;
36
+ };
37
+
38
+ const bigintSafeJsonStringify = (value: unknown): string => {
39
+ return JSON.stringify(value, (key, value) => {
40
+ if (typeof value === 'bigint') {
41
+ return `${value}n`;
42
+ }
43
+ return value;
44
+ });
45
+ };