@shivaedev/errors 0.4.1 → 0.6.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.
- package/CHANGELOG.md +33 -0
- package/README.md +17 -2
- package/dist/boundary.d.ts.map +1 -1
- package/dist/boundary.js +18 -3
- package/dist/boundary.js.map +1 -1
- package/dist/redactBrowserText.d.ts +3 -0
- package/dist/redactBrowserText.d.ts.map +1 -0
- package/dist/redactBrowserText.js +6 -0
- package/dist/redactBrowserText.js.map +1 -0
- package/dist/rpcBoundaryHandlers.d.ts +1 -1
- package/dist/rpcBoundaryHandlers.d.ts.map +1 -1
- package/dist/rpcBoundaryHandlers.js +12 -4
- package/dist/rpcBoundaryHandlers.js.map +1 -1
- package/dist/schemaFieldScope.d.ts.map +1 -1
- package/dist/schemaFieldScope.js +24 -13
- package/dist/schemaFieldScope.js.map +1 -1
- package/dist/schemaSensitiveAst.d.ts +1 -0
- package/dist/schemaSensitiveAst.d.ts.map +1 -1
- package/dist/schemaSensitiveAst.js +13 -0
- package/dist/schemaSensitiveAst.js.map +1 -1
- package/dist/schemaSensitiveKeys.d.ts +2 -1
- package/dist/schemaSensitiveKeys.d.ts.map +1 -1
- package/dist/schemaSensitiveKeys.js +16 -3
- package/dist/schemaSensitiveKeys.js.map +1 -1
- package/dist/sensitive.d.ts.map +1 -1
- package/dist/sensitive.js +4 -2
- package/dist/sensitive.js.map +1 -1
- package/dist/taxonomy.d.ts +3 -1
- package/dist/taxonomy.d.ts.map +1 -1
- package/dist/taxonomy.js +5 -1
- package/dist/taxonomy.js.map +1 -1
- package/dist/wireMask.d.ts +11 -0
- package/dist/wireMask.d.ts.map +1 -0
- package/dist/wireMask.js +88 -0
- package/dist/wireMask.js.map +1 -0
- package/dist/wireRedactor.d.ts +6 -0
- package/dist/wireRedactor.d.ts.map +1 -0
- package/dist/wireRedactor.js +105 -0
- package/dist/wireRedactor.js.map +1 -0
- package/package.json +3 -4
- package/src/boundary.ts +16 -3
- package/src/redactBrowserText.ts +6 -0
- package/src/rpcBoundaryHandlers.ts +16 -9
- package/src/schemaFieldScope.ts +29 -13
- package/src/schemaSensitiveAst.ts +13 -0
- package/src/schemaSensitiveKeys.ts +16 -3
- package/src/sensitive.ts +5 -2
- package/src/taxonomy.ts +5 -1
- package/src/wireMask.ts +94 -0
- package/src/wireRedactor.ts +131 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Effect, Predicate, Schema, type SchemaAST } from "effect";
|
|
2
|
+
import { redactWire } from "#redact.ts";
|
|
3
|
+
import { collect, maskedValue, sensitiveEncoding } from "#schemaSensitiveAst.ts";
|
|
4
|
+
import { REDACTED, type SensitiveKey } from "#sensitive.ts";
|
|
5
|
+
import { wireMask } from "#wireMask.ts";
|
|
6
|
+
|
|
7
|
+
type Redactor = (value: unknown) => unknown;
|
|
8
|
+
type MaskError = Effect.Error<ReturnType<typeof wireMask>>;
|
|
9
|
+
interface IndexRedactor {
|
|
10
|
+
readonly accepts: (key: unknown) => boolean;
|
|
11
|
+
readonly apply: Redactor;
|
|
12
|
+
readonly mask: Redactor;
|
|
13
|
+
}
|
|
14
|
+
function objectRedactor(fields: ReadonlyMap<PropertyKey, Redactor>, indexes: readonly IndexRedactor[], sensitive: SensitiveKey): Redactor {
|
|
15
|
+
function field(key: string): Redactor {
|
|
16
|
+
const property = fields.get(key);
|
|
17
|
+
if (property !== undefined) {
|
|
18
|
+
return property;
|
|
19
|
+
}
|
|
20
|
+
const index = indexes.find((entry) => entry.accepts(key));
|
|
21
|
+
if (index !== undefined) {
|
|
22
|
+
return sensitive(key) ? index.mask : index.apply;
|
|
23
|
+
}
|
|
24
|
+
return (value) => (sensitive(key) ? REDACTED : redactWire(value, sensitive));
|
|
25
|
+
}
|
|
26
|
+
return (value) =>
|
|
27
|
+
Predicate.isObjectOrArray(value)
|
|
28
|
+
? Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, field(key)(nested)]))
|
|
29
|
+
: redactWire(value, sensitive);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const wireRedactor = Effect.fnUntraced(function* (ast: SchemaAST.AST, field: string, sensitive: SensitiveKey, keys: ReadonlySet<string>) {
|
|
33
|
+
const cache = new Map<SchemaAST.AST, Redactor>();
|
|
34
|
+
function constrainedContainer(node: SchemaAST.AST): boolean {
|
|
35
|
+
if (node.checks === undefined) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const nodes = new Set<SchemaAST.AST>();
|
|
39
|
+
collect(node, nodes);
|
|
40
|
+
return [...nodes].some(
|
|
41
|
+
(child) =>
|
|
42
|
+
child._tag === "Objects" && child.propertySignatures.some((property) => typeof property.name === "string" && sensitive(property.name)),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function encoded(node: SchemaAST.AST): SchemaAST.AST {
|
|
46
|
+
if (node.encoding !== undefined) {
|
|
47
|
+
return node.encoding.at(-1)?.to ?? node;
|
|
48
|
+
}
|
|
49
|
+
return node._tag === "Declaration" ? Schema.toEncoded(Schema.toCodecJson(Schema.make(node))).ast : node;
|
|
50
|
+
}
|
|
51
|
+
const compileObject = Effect.fnUntraced(function* (node: SchemaAST.Objects, path: string): Effect.fn.Return<Redactor, MaskError> {
|
|
52
|
+
const fields = new Map<PropertyKey, Redactor>();
|
|
53
|
+
for (const property of node.propertySignatures) {
|
|
54
|
+
const stackHeader = property.name === "stack" && (sensitive("message") || sensitive("name"));
|
|
55
|
+
fields.set(
|
|
56
|
+
property.name,
|
|
57
|
+
yield* compile(
|
|
58
|
+
property.type,
|
|
59
|
+
`${path}.${String(property.name)}`,
|
|
60
|
+
stackHeader || (typeof property.name === "string" && sensitive(property.name)),
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const indexes = yield* Effect.forEach(node.indexSignatures, (index) =>
|
|
65
|
+
Effect.gen(function* () {
|
|
66
|
+
const apply = yield* compile(index.type, `${path}[value]`);
|
|
67
|
+
const mask = yield* compile(index.type, `${path}[value]`, true);
|
|
68
|
+
return { accepts: Schema.is(Schema.make(index.parameter)), apply, mask };
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
return objectRedactor(fields, indexes, sensitive);
|
|
72
|
+
});
|
|
73
|
+
const compileArray = Effect.fnUntraced(function* (node: SchemaAST.Arrays, path: string): Effect.fn.Return<Redactor, MaskError> {
|
|
74
|
+
const elements = yield* Effect.forEach(node.elements, (element, index) => compile(element, `${path}[${index}]`));
|
|
75
|
+
const rest = yield* Effect.forEach(node.rest, (element) => compile(element, `${path}[]`));
|
|
76
|
+
return (value) =>
|
|
77
|
+
Array.isArray(value)
|
|
78
|
+
? value.map((item, index) => (elements[index] ?? rest[0] ?? ((entry) => redactWire(entry, sensitive)))(item))
|
|
79
|
+
: redactWire(value, sensitive);
|
|
80
|
+
});
|
|
81
|
+
const compileUnion = Effect.fnUntraced(function* (node: SchemaAST.Union, path: string): Effect.fn.Return<Redactor, MaskError> {
|
|
82
|
+
const members = yield* Effect.forEach(node.types, (member, index) =>
|
|
83
|
+
Effect.map(compile(member, `${path}[${index}]`), (mask) => ({
|
|
84
|
+
accepts: Schema.is(Schema.toEncoded(Schema.toCodecJson(Schema.make(member)))),
|
|
85
|
+
mask,
|
|
86
|
+
})),
|
|
87
|
+
);
|
|
88
|
+
return (value) => {
|
|
89
|
+
const member = members.find(({ accepts }) => accepts(value));
|
|
90
|
+
return member === undefined ? REDACTED : member.mask(value);
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
function contents(node: SchemaAST.AST, path: string): Effect.Effect<Redactor, MaskError> {
|
|
94
|
+
switch (node._tag) {
|
|
95
|
+
case "Objects":
|
|
96
|
+
return compileObject(node, path);
|
|
97
|
+
case "Arrays":
|
|
98
|
+
return compileArray(node, path);
|
|
99
|
+
case "Union":
|
|
100
|
+
return compileUnion(node, path);
|
|
101
|
+
case "Suspend":
|
|
102
|
+
return compile(node.thunk(), path);
|
|
103
|
+
default:
|
|
104
|
+
return Effect.succeed((value) => redactWire(value, sensitive));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function compile(node: SchemaAST.AST, path: string, hidden = false): Effect.Effect<Redactor, MaskError> {
|
|
108
|
+
return Effect.gen(function* () {
|
|
109
|
+
if (
|
|
110
|
+
hidden
|
|
111
|
+
|| constrainedContainer(node)
|
|
112
|
+
|| sensitiveEncoding(node, keys)
|
|
113
|
+
|| (node._tag !== "Union" && maskedValue(node, keys))
|
|
114
|
+
|| Schema.resolveAnnotations(Schema.make(node))?.sensitive === true
|
|
115
|
+
) {
|
|
116
|
+
const mask = yield* wireMask(node, path);
|
|
117
|
+
return () => mask;
|
|
118
|
+
}
|
|
119
|
+
const cached = cache.get(node);
|
|
120
|
+
if (cached !== undefined) {
|
|
121
|
+
return cached;
|
|
122
|
+
}
|
|
123
|
+
let apply: Redactor = () => REDACTED;
|
|
124
|
+
const redactor: Redactor = (value) => apply(value);
|
|
125
|
+
cache.set(node, redactor);
|
|
126
|
+
apply = yield* contents(encoded(node), path);
|
|
127
|
+
return redactor;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return yield* compile(ast, field);
|
|
131
|
+
});
|