@evolu/common 7.3.0 → 7.4.1
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.
- package/dist/src/Buffer.d.ts +2 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +1 -0
- package/dist/src/Callbacks.d.ts +1 -1
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +1 -1
- package/dist/src/Console.d.ts +1 -9
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +1 -41
- package/dist/src/Instances.d.ts.map +1 -1
- package/dist/src/Redacted.d.ts +89 -0
- package/dist/src/Redacted.d.ts.map +1 -0
- package/dist/src/Redacted.js +48 -0
- package/dist/src/Type.d.ts +5 -9
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +0 -202
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Owner.d.ts +4 -0
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Public.d.ts +6 -1
- package/dist/src/local-first/Public.d.ts.map +1 -1
- package/dist/src/local-first/Public.js +6 -1
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Sync.d.ts.map +1 -1
- package/dist/src/local-first/Sync.js +10 -13
- package/dist/src/local-first/index.d.ts +9 -1
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +9 -1
- package/package.json +3 -4
- package/src/Buffer.ts +2 -1
- package/src/Callbacks.ts +1 -1
- package/src/Console.ts +1 -10
- package/src/Instances.ts +4 -2
- package/src/Redacted.ts +118 -0
- package/src/Type.ts +229 -221
- package/src/index.ts +1 -0
- package/src/local-first/Evolu.ts +3 -2
- package/src/local-first/Owner.ts +4 -0
- package/src/local-first/Public.ts +6 -1
- package/src/local-first/Storage.ts +10 -12
- package/src/local-first/Sync.ts +10 -21
- package/src/local-first/index.ts +9 -1
package/src/Redacted.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { assert } from "./Assert.js";
|
|
2
|
+
import type { Brand } from "./Brand.js";
|
|
3
|
+
import type { Eq } from "./Eq.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A wrapper type that prevents sensitive values from being accidentally exposed
|
|
7
|
+
* through logging, serialization, or inspection.
|
|
8
|
+
*
|
|
9
|
+
* The wrapped value is hidden and can only be accessed explicitly via
|
|
10
|
+
* {@link revealRedacted}. All standard methods (`toString`, `toJSON`, and
|
|
11
|
+
* Node.js inspect) return `<redacted>`.
|
|
12
|
+
*
|
|
13
|
+
* For type-level distinction between different secrets, use branded types.
|
|
14
|
+
*
|
|
15
|
+
* The actual value lives in a `WeakMap`, so it never appears as a property and
|
|
16
|
+
* is automatically garbage collected when the wrapper is dropped. This is
|
|
17
|
+
* better than a class with a private field because private fields are still
|
|
18
|
+
* visible in devtools. Symbols can't be used because they don't support custom
|
|
19
|
+
* `toString`.
|
|
20
|
+
*
|
|
21
|
+
* Implements `Disposable` for automatic cleanup via the `using` syntax.
|
|
22
|
+
*
|
|
23
|
+
* ### Example
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* // Define branded types for your secrets
|
|
27
|
+
* type ApiKey = string & Brand<"ApiKey">;
|
|
28
|
+
* type DbPassword = string & Brand<"DbPassword">;
|
|
29
|
+
*
|
|
30
|
+
* // Wrap them with Redacted for safe passing
|
|
31
|
+
* type RedactedApiKey = Redacted<ApiKey>;
|
|
32
|
+
* type RedactedDbPassword = Redacted<DbPassword>;
|
|
33
|
+
*
|
|
34
|
+
* // Create a redacted secret
|
|
35
|
+
* const apiKey: ApiKey = "secret-123" as ApiKey;
|
|
36
|
+
* const redactedKey: RedactedApiKey = createRedacted(apiKey);
|
|
37
|
+
*
|
|
38
|
+
* console.log(redactedKey); // <redacted>
|
|
39
|
+
* console.log(revealRedacted(redactedKey)); // secret-123
|
|
40
|
+
*
|
|
41
|
+
* // Type safety: RedactedApiKey ≠ RedactedDbPassword
|
|
42
|
+
* const fetchUser = (key: RedactedApiKey) => {
|
|
43
|
+
* const value: ApiKey = revealRedacted(key);
|
|
44
|
+
* // use value...
|
|
45
|
+
* };
|
|
46
|
+
*
|
|
47
|
+
* fetchUser(redactedKey); // ✅
|
|
48
|
+
* // fetchUser(createRedacted("x" as DbPassword)); // ❌ type error
|
|
49
|
+
*
|
|
50
|
+
* // Automatic cleanup with `using`
|
|
51
|
+
* {
|
|
52
|
+
* using secret = createRedacted("sensitive" as ApiKey);
|
|
53
|
+
* // ... use secret ...
|
|
54
|
+
* } // automatically wiped from memory
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @experimental
|
|
58
|
+
*/
|
|
59
|
+
export interface Redacted<A> extends Brand<"Redacted">, Disposable {
|
|
60
|
+
/** The inner type. Useful for inference via `typeof redacted.Type`. */
|
|
61
|
+
readonly Type: A;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Creates a {@link Redacted} wrapper for a sensitive value. */
|
|
65
|
+
export const createRedacted = <A>(value: A): Redacted<A> => {
|
|
66
|
+
const redacted = Object.create(proto) as Redacted<A>;
|
|
67
|
+
registry.set(redacted, value);
|
|
68
|
+
return redacted;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const proto = {
|
|
72
|
+
toString: () => redactedString,
|
|
73
|
+
toJSON: () => redactedString,
|
|
74
|
+
[Symbol.for("nodejs.util.inspect.custom")]: () => redactedString,
|
|
75
|
+
[Symbol.dispose](this: Redacted<unknown>) {
|
|
76
|
+
registry.delete(this);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
const redactedString = "<redacted>";
|
|
80
|
+
const registry = new WeakMap<Redacted<unknown>, unknown>();
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Reveals the original value from a {@link Redacted} wrapper.
|
|
84
|
+
*
|
|
85
|
+
* This is a separate function rather than a method on {@link Redacted} to make
|
|
86
|
+
* access visually explicit and easy to grep in code reviews. Accessing
|
|
87
|
+
* sensitive values should feel intentional, not convenient.
|
|
88
|
+
*/
|
|
89
|
+
export const revealRedacted = <A>(redacted: Redacted<A>): A => {
|
|
90
|
+
assert(registry.has(redacted), "Redacted value was not in registry");
|
|
91
|
+
return registry.get(redacted) as A;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** Checks if a value is a {@link Redacted} wrapper. */
|
|
95
|
+
export const isRedacted = (value: unknown): value is Redacted<unknown> =>
|
|
96
|
+
typeof value === "object" &&
|
|
97
|
+
value !== null &&
|
|
98
|
+
Object.getPrototypeOf(value) === proto;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Creates an {@link Eq} for {@link Redacted} values based on an equality function
|
|
102
|
+
* for the underlying type.
|
|
103
|
+
*
|
|
104
|
+
* ### Example
|
|
105
|
+
*
|
|
106
|
+
* ```ts
|
|
107
|
+
* type ApiKey = string & Brand<"ApiKey">;
|
|
108
|
+
* const eqRedactedApiKey = createEqRedacted<ApiKey>(eqString);
|
|
109
|
+
*
|
|
110
|
+
* const a = createRedacted("x" as ApiKey);
|
|
111
|
+
* const b = createRedacted("x" as ApiKey);
|
|
112
|
+
* eqRedactedApiKey(a, b); // true
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export const createEqRedacted =
|
|
116
|
+
<A>(eq: Eq<A>): Eq<Redacted<A>> =>
|
|
117
|
+
(x, y) =>
|
|
118
|
+
eq(revealRedacted(x), revealRedacted(y));
|