Skip to content

Commit 984d6b6

Browse files
committed
hooks: add HookMutationResult + result helpers; refactor hooks and option sanitizers; export helpers
Introduce HookSuccessResult, HookFailureResult, HookMutationResult and factory helpers (createHookSuccessResult / createHookFailureResult). Refactor useSecret and useSecureStorage to return the unified HookMutationResult and use the new factories. Simplify option sanitization by stripping hook-only flags with destructuring. Export the new helpers from the package entrypoint.
1 parent e9a9567 commit 984d6b6

5 files changed

Lines changed: 98 additions & 35 deletions

File tree

src/hooks/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ export {
33
type HookErrorOptions,
44
type AsyncState,
55
type VoidAsyncState,
6+
type HookMutationResult,
7+
type HookSuccessResult,
8+
type HookFailureResult,
9+
createHookSuccessResult,
10+
createHookFailureResult,
611
} from './types'
712
export {
813
useSecretItem,

src/hooks/types.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,27 @@ export interface VoidAsyncState {
4646
readonly isPending: boolean
4747
}
4848

49+
/**
50+
* Successful outcome produced by hook mutation helpers.
51+
*/
52+
export interface HookSuccessResult {
53+
readonly success: true
54+
readonly error?: undefined
55+
}
56+
57+
/**
58+
* Failure outcome produced by hook mutation helpers.
59+
*/
60+
export interface HookFailureResult {
61+
readonly success: false
62+
readonly error: HookError
63+
}
64+
65+
/**
66+
* Combined type returned by hook mutation helpers (`saveSecret`, `clearAll`, ...).
67+
*/
68+
export type HookMutationResult = HookSuccessResult | HookFailureResult
69+
4970
/**
5071
* Factory used to initialise {@link AsyncState} values.
5172
*/
@@ -68,3 +89,17 @@ export function createInitialVoidState(): VoidAsyncState {
6889
isPending: false,
6990
}
7091
}
92+
93+
/**
94+
* Helper used to return a canonical success result from mutation helpers.
95+
*/
96+
export function createHookSuccessResult(): HookSuccessResult {
97+
return { success: true }
98+
}
99+
100+
/**
101+
* Helper used to return a canonical failure result from mutation helpers.
102+
*/
103+
export function createHookFailureResult(error: HookError): HookFailureResult {
104+
return { success: false, error }
105+
}

src/hooks/useSecret.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ import type {
44
SensitiveInfoOptions,
55
} from '../sensitive-info.nitro'
66
import { deleteItem, setItem } from '../core/storage'
7-
import { HookError } from './types'
8-
import type { AsyncState } from './types'
7+
import {
8+
createHookFailureResult,
9+
createHookSuccessResult,
10+
type HookMutationResult,
11+
type AsyncState,
12+
} from './types'
913
import { useSecretItem, type UseSecretItemOptions } from './useSecretItem'
1014
import createHookError from './error-utils'
1115

@@ -19,27 +23,21 @@ export type UseSecretOptions = UseSecretItemOptions
1923
* Result bag returned by {@link useSecret}.
2024
*/
2125
export interface UseSecretResult extends AsyncState<SensitiveInfoItem> {
22-
readonly saveSecret: (
23-
value: string
24-
) => Promise<{ success: boolean; error?: HookError }>
25-
readonly deleteSecret: () => Promise<{ success: boolean; error?: HookError }>
26+
readonly saveSecret: (value: string) => Promise<HookMutationResult>
27+
readonly deleteSecret: () => Promise<HookMutationResult>
2628
readonly refetch: () => Promise<void>
2729
}
2830

2931
/**
3032
* Removes hook-specific flags before delegating to the storage module.
3133
*/
32-
const stripHookFlags = (options: UseSecretOptions): SensitiveInfoOptions => {
33-
const sanitized = { ...options } as Record<string, unknown>
34-
delete sanitized.skip
35-
delete sanitized.includeValue
36-
return sanitized as SensitiveInfoOptions
37-
}
38-
3934
const normalizeMutationOptions = (
4035
options?: UseSecretOptions
41-
): SensitiveInfoOptions | undefined =>
42-
options ? stripHookFlags(options) : undefined
36+
): SensitiveInfoOptions | undefined => {
37+
if (!options) return undefined
38+
const { skip: _skip, includeValue: _includeValue, ...core } = options
39+
return core as SensitiveInfoOptions
40+
}
4341

4442
/**
4543
* Maintains a secure item while exposing imperative helpers to mutate or refresh it.
@@ -63,14 +61,14 @@ export function useSecret(
6361
try {
6462
await setItem(key, value, normalizeMutationOptions(options))
6563
await refetch()
66-
return { success: true } as const
64+
return createHookSuccessResult()
6765
} catch (errorLike) {
6866
const hookError = createHookError(
6967
'useSecret.saveSecret',
7068
errorLike,
7169
'Check the access control requirements for this key.'
7270
)
73-
return { success: false, error: hookError } as const
71+
return createHookFailureResult(hookError)
7472
}
7573
},
7674
[key, options, refetch]
@@ -80,14 +78,14 @@ export function useSecret(
8078
try {
8179
await deleteItem(key, normalizeMutationOptions(options))
8280
await refetch()
83-
return { success: true } as const
81+
return createHookSuccessResult()
8482
} catch (errorLike) {
8583
const hookError = createHookError(
8684
'useSecret.deleteSecret',
8785
errorLike,
8886
'Ensure the user completed biometric prompts or that the key is spelled correctly.'
8987
)
90-
return { success: false, error: hookError } as const
88+
return createHookFailureResult(hookError)
9189
}
9290
}, [key, options, refetch])
9391

src/hooks/useSecureStorage.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import type {
44
SensitiveInfoOptions,
55
} from '../sensitive-info.nitro'
66
import { clearService, deleteItem, getAllItems, setItem } from '../core/storage'
7-
import { HookError } from './types'
7+
import {
8+
HookError,
9+
createHookFailureResult,
10+
createHookSuccessResult,
11+
type HookMutationResult,
12+
} from './types'
813
import useAsyncLifecycle from './useAsyncLifecycle'
914
import useStableOptions from './useStableOptions'
1015
import createHookError from './error-utils'
@@ -32,10 +37,8 @@ const DEFAULTS: Required<
3237
const extractCoreOptions = (
3338
options: UseSecureStorageOptions
3439
): SensitiveInfoOptions => {
35-
const sanitized = { ...options } as Record<string, unknown>
36-
delete sanitized.skip
37-
delete sanitized.includeValues
38-
return sanitized as SensitiveInfoOptions
40+
const { skip: _skip, includeValues: _includeValues, ...core } = options
41+
return core as SensitiveInfoOptions
3942
}
4043

4144
/**
@@ -48,11 +51,9 @@ export interface UseSecureStorageResult {
4851
readonly saveSecret: (
4952
key: string,
5053
value: string
51-
) => Promise<{ success: boolean; error?: HookError }>
52-
readonly removeSecret: (
53-
key: string
54-
) => Promise<{ success: boolean; error?: HookError }>
55-
readonly clearAll: () => Promise<{ success: boolean; error?: HookError }>
54+
) => Promise<HookMutationResult>
55+
readonly removeSecret: (key: string) => Promise<HookMutationResult>
56+
readonly clearAll: () => Promise<HookMutationResult>
5657
readonly refreshItems: () => Promise<void>
5758
}
5859

@@ -134,7 +135,7 @@ export function useSecureStorage(
134135
if (mountedRef.current) {
135136
await fetchItems()
136137
}
137-
return { success: true } as const
138+
return createHookSuccessResult()
138139
} catch (errorLike) {
139140
const hookError = createHookError(
140141
'useSecureStorage.saveSecret',
@@ -144,7 +145,7 @@ export function useSecureStorage(
144145
if (mountedRef.current) {
145146
setError(hookError)
146147
}
147-
return { success: false, error: hookError } as const
148+
return createHookFailureResult(hookError)
148149
}
149150
},
150151
[fetchItems, mountedRef, stableOptions]
@@ -157,7 +158,7 @@ export function useSecureStorage(
157158
if (mountedRef.current) {
158159
setItems((prev) => prev.filter((item) => item.key !== key))
159160
}
160-
return { success: true } as const
161+
return createHookSuccessResult()
161162
} catch (errorLike) {
162163
const hookError = createHookError(
163164
'useSecureStorage.removeSecret',
@@ -167,7 +168,7 @@ export function useSecureStorage(
167168
if (mountedRef.current) {
168169
setError(hookError)
169170
}
170-
return { success: false, error: hookError } as const
171+
return createHookFailureResult(hookError)
171172
}
172173
},
173174
[mountedRef, stableOptions]
@@ -180,7 +181,7 @@ export function useSecureStorage(
180181
setItems([])
181182
setError(null)
182183
}
183-
return { success: true } as const
184+
return createHookSuccessResult()
184185
} catch (errorLike) {
185186
const hookError = createHookError(
186187
'useSecureStorage.clearAll',
@@ -190,7 +191,7 @@ export function useSecureStorage(
190191
if (mountedRef.current) {
191192
setError(hookError)
192193
}
193-
return { success: false, error: hookError } as const
194+
return createHookFailureResult(hookError)
194195
}
195196
}, [mountedRef, stableOptions])
196197

src/index.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
/* eslint-disable no-restricted-exports -- Preserve the default export for backwards compatibility. */
22

3+
export type {
4+
AccessControl,
5+
AuthenticationPrompt,
6+
MutationResult,
7+
SecurityAvailability,
8+
SecurityLevel,
9+
SensitiveInfo as SensitiveInfoModule,
10+
SensitiveInfoSpec,
11+
SensitiveInfoDeleteRequest,
12+
SensitiveInfoEnumerateRequest,
13+
SensitiveInfoGetRequest,
14+
SensitiveInfoHasRequest,
15+
SensitiveInfoItem,
16+
SensitiveInfoOptions,
17+
SensitiveInfoSetRequest,
18+
StorageBackend,
19+
StorageMetadata,
20+
} from './sensitive-info.nitro'
21+
322
/**
423
* Core storage helpers that mirror the native Nitro surface.
524
*/
@@ -22,12 +41,17 @@ export { default } from './core/storage'
2241
*/
2342
export {
2443
HookError,
44+
createHookFailureResult,
45+
createHookSuccessResult,
2546
useHasSecret,
2647
useSecret,
2748
useSecretItem,
2849
useSecureOperation,
2950
useSecureStorage,
3051
useSecurityAvailability,
52+
type HookFailureResult,
53+
type HookMutationResult,
54+
type HookSuccessResult,
3155
type UseHasSecretOptions,
3256
type UseHasSecretResult,
3357
type UseSecretItemOptions,

0 commit comments

Comments
 (0)