1/**
2 * Wraps sensitive values so normal output does not reveal them.
3 *
4 * A `Redacted<A>` shows a redacted placeholder in string, JSON, and inspection
5 * output, while still storing the original value for trusted code that needs to
6 * recover it. This helps reduce accidental leaks in logs and diagnostics. This
7 * module includes constructors, runtime checks, value recovery, wiping of stored
8 * values, and comparison helpers that avoid exposing the wrapped value at the
9 * call site.
10 *
11 * @since 3.3.0
12 */
13import * as Equal from "./Equal.ts"
14import * as Equivalence from "./Equivalence.ts"
15import * as Hash from "./Hash.ts"
16import { PipeInspectableProto } from "./internal/core.ts"
17import * as redacted from "./internal/redacted.ts"
18import type { Pipeable } from "./Pipeable.ts"
19import { hasProperty, isString } from "./Predicate.ts"
20import type { Covariant } from "./Types.ts"
21
22const TypeId = "~effect/Redacted"
23
24/**
25 * A wrapper for sensitive values whose string, JSON, and inspection output is
26 * redacted.
27 *
28 * **When to use**
29 *
30 * Use to carry sensitive values while reducing accidental exposure in string,
31 * JSON, and inspection output.
32 *
33 * **Gotchas**
34 *
35 * The underlying value is still stored in memory and can be recovered with
36 * `Redacted.value` until the wrapper is wiped or becomes unreachable. Use
37 * `Redacted` to reduce accidental disclosure in logs and diagnostics, not as a
38 * cryptographic protection mechanism.
39 *
40 * **Example** (Creating redacted values)
41 *
42 * ```ts import.meta.vitest
43 * import { Redacted } from "effect"
44 *
45 * // Create a redacted value to protect sensitive information
46 * const apiKey = Redacted.make("secret-key")
47 * const userPassword = Redacted.make("user-password")
48 *
49 * // TypeScript will infer the types as Redacted<string>
50 * Array.of(String(apiKey), String(userPassword)) // => ["<redacted>", "<redacted>"]
51 * ```
52 *
53 * @category models
54 * @since 3.3.0
55 */
56export interface Redacted<out A = string> extends Redacted.Variance<A>, Equal.Equal, Pipeable {
57 readonly label: string | undefined
58}
59
60/**
61 * Namespace containing type-level members associated with `Redacted` values.
62 *
63 * **When to use**
64 *
65 * Use to access type-level helpers associated with `Redacted`.
66 *
67 * **Example** (Using namespace utilities)
68 *
69 * ```ts import.meta.vitest
70 * import { Redacted } from "effect"
71 *
72 * // Use the Redacted namespace for type-level operations
73 * const secret = Redacted.make("my-secret")
74 *
75 * // The namespace contains utilities for working with Redacted values
76 * Redacted.isRedacted(secret) // => true
77 * ```
78 *
79 * @since 3.3.0
80 */
81export declare namespace Redacted {
82 /**
83 * Type-level variance marker for `Redacted`.
84 *
85 * **When to use**
86 *
87 * Use when defining internals that need to preserve the covariant value type
88 * carried by `Redacted`.
89 *
90 * **Details**
91 *
92 * This interface records the covariant value type carried by a `Redacted`
93 * value and is not normally referenced directly by users.
94 *
95 * @category models
96 * @since 3.3.0
97 */
98 export interface Variance<out A> {
99 readonly [TypeId]: {
100 readonly _A: Covariant<A>
101 }
102 }
103
104 /**
105 * Extracts the underlying value type from a `Redacted` type.
106 *
107 * **When to use**
108 *
109 * Use to infer the sensitive value type from an existing `Redacted` type.
110 *
111 * **Example** (Extracting the redacted value type)
112 *
113 * ```ts import.meta.vitest
114 * import { Redacted } from "effect"
115 *
116 * type ApiKey = Redacted.Redacted<{ readonly token: string }>
117 * type ApiKeyValue = Redacted.Redacted.Value<ApiKey>
118 *
119 * const rotate = (value: ApiKeyValue): ApiKeyValue => ({
120 * token: `${value.token}:rotated`
121 * })
122 *
123 * rotate({ token: "secret" }) // => { token: "secret:rotated" }
124 * ```
125 *
126 * @category utility types
127 * @since 3.3.0
128 */
129 export type Value<T extends Redacted<any>> = [T] extends [Redacted<infer _A>] ? _A : never
130}
131
132/**
133 * Returns `true` if a value is a `Redacted` wrapper.
134 *
135 * **When to use**
136 *
137 * Use to validate unknown input and narrow it to `Redacted`.
138 *
139 * **Details**
140 *
141 * When this function returns `true`, TypeScript narrows the value to
142 * `Redacted<unknown>`.
143 *
144 * **Example** (Checking for redacted values)
145 *
146 * ```ts import.meta.vitest
147 * import { Redacted } from "effect"
148 *
149 * const secret = Redacted.make("my-secret")
150 * const plainString = "not-secret"
151 *
152 * Redacted.isRedacted(secret) // => true
153 * Redacted.isRedacted(plainString) // => false
154 * ```
155 *
156 * @category guards
157 * @since 3.3.0
158 */
159export const isRedacted = (u: unknown): u is Redacted<unknown> => hasProperty(u, TypeId)
160
161/**
162 * Creates a `Redacted` wrapper for a sensitive value.
163 *
164 * **When to use**
165 *
166 * Use to wrap a sensitive value so normal string, JSON, and inspection output
167 * is redacted.
168 *
169 * **Details**
170 *
171 * The wrapper redacts string, JSON, and inspection output to reduce accidental
172 * disclosure. The original value remains retrievable with `Redacted.value`
173 * until the wrapper is wiped or becomes unreachable.
174 *
175 * **Example** (Creating a redacted value)
176 *
177 * ```ts import.meta.vitest
178 * import { Redacted } from "effect"
179 *
180 * const API_KEY = Redacted.make("1234567890")
181 * String(API_KEY) // => "<redacted>"
182 * ```
183 *
184 * @category constructors
185 * @since 3.3.0
186 */
187export const make = <T>(value: T, options?: {
188 readonly label?: string | undefined
189}): Redacted<T> => {
190 const self = Object.create(Proto)
191 if (options?.label) {
192 self.label = options.label
193 }
194 redacted.redactedRegistry.set(self, value)
195 return self
196}
197
198const Proto = {
199 [TypeId]: {
200 _A: (_: never) => _
201 },
202 label: undefined,
203 ...PipeInspectableProto,
204 toJSON() {
205 return this.toString()
206 },
207 toString() {
208 return `<redacted${isString(this.label) ? ":" + this.label : ""}>`
209 },
210 [Hash.symbol]<T>(this: Redacted<T>): number {
211 return Hash.hash(redacted.redactedRegistry.get(this))
212 },
213 [Equal.symbol]<T>(this: Redacted<T>, that: unknown): boolean {
214 return (
215 isRedacted(that) &&
216 Equal.equals(
217 redacted.redactedRegistry.get(this),
218 redacted.redactedRegistry.get(that)
219 )
220 )
221 }
222}
223
224/**
225 * Retrieves the original value from a `Redacted` instance. Use this function
226 * with caution, as it exposes the sensitive data.
227 *
228 * **When to use**
229 *
230 * Use when you need the underlying sensitive value at a trusted boundary.
231 *
232 * **Example** (Retrieving a redacted value)
233 *
234 * ```ts import.meta.vitest
235 * import { Redacted } from "effect"
236 *
237 * const API_KEY = Redacted.make("1234567890")
238 *
239 * Redacted.value(API_KEY) // => "1234567890"
240 * ```
241 *
242 * @category getters
243 * @since 3.3.0
244 */
245export const value: <T>(self: Redacted<T>) => T = redacted.value
246
247/**
248 * Deletes the stored value for a `Redacted` wrapper, making future
249 * `Redacted.value` calls on that wrapper fail.
250 *
251 * **When to use**
252 *
253 * Use when a `Redacted` wrapper should no longer be able to reveal its stored
254 * value.
255 *
256 * **Gotchas**
257 *
258 * This unsafe operation does not zero memory and does not affect other
259 * references to the original value. It only removes the value from the
260 * internal redacted registry.
261 *
262 * **Example** (Wiping a redacted value)
263 *
264 * ```ts import.meta.vitest
265 * import { Redacted, Result } from "effect"
266 *
267 * const API_KEY = Redacted.make("1234567890")
268 *
269 * Redacted.value(API_KEY) // => "1234567890"
270 *
271 * Redacted.wipeUnsafe(API_KEY)
272 *
273 * const failure = Result.try({
274 * try: () => Redacted.value(API_KEY),
275 * catch: (error) => (error as Error).message
276 * })
277 * failure // => Result.fail("Unable to get redacted value")
278 * ```
279 *
280 * @category unsafe
281 * @since 4.0.0
282 */
283export const wipeUnsafe = <T>(self: Redacted<T>): boolean => redacted.redactedRegistry.delete(self)
284
285/**
286 * Generates an equivalence relation for `Redacted<A>` values based on an
287 * equivalence relation for the underlying values `A`. This function is useful
288 * for comparing `Redacted` instances without exposing their contents.
289 *
290 * **When to use**
291 *
292 * Use when you need to compare wrapped secrets through an approved equality
293 * rule without exposing the underlying values at each comparison site.
294 *
295 * **Example** (Comparing redacted values)
296 *
297 * ```ts import.meta.vitest
298 * import { Equivalence, Redacted } from "effect"
299 *
300 * const API_KEY1 = Redacted.make("1234567890")
301 * const API_KEY2 = Redacted.make("1-34567890")
302 * const API_KEY3 = Redacted.make("1234567890")
303 *
304 * const equivalence = Redacted.makeEquivalence(Equivalence.strictEqual<string>())
305 *
306 * equivalence(API_KEY1, API_KEY2) // => false
307 * equivalence(API_KEY1, API_KEY3) // => true
308 * ```
309 *
310 * @category instances
311 * @since 4.0.0
312 */
313export const makeEquivalence = <A>(isEquivalent: Equivalence.Equivalence<A>): Equivalence.Equivalence<Redacted<A>> =>
314 Equivalence.make((x, y) => isEquivalent(value(x), value(y)))