@shivaedev/errors 0.5.0 → 0.7.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 CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0 - 2026-11-03
4
+
5
+ ### Breaking
6
+
7
+ - `rpcBoundaryHandlers(group, options, handlers)` derives each operation's rejection schema independently. Replace the former `rpcBoundary(schema, options)` second argument with `options`; every operation must declare the fallback returned by `unexpected`. Effect and Stream handlers retain their own rejection types and required services.
8
+
9
+ ## 0.6.0 - 2026-11-03
10
+
11
+ ### Added
12
+
13
+ - Sensitive RPC failure schemas accept a constant encoded `redactionMask` annotation when a generic mask cannot decode. Policy definition returns a typed `InvalidRedactionMask` naming the field when its mask is incompatible.
14
+
15
+ ### Fixed
16
+
17
+ - Declared failures alongside interruptions retain their classification and interruption. Native RPC exit schemas supply the complete failure schema, including stream rejections.
18
+
19
+ ### Breaking
20
+
21
+ - Constrained sensitive failure schemas whose generic masks cannot decode must supply a non-secret encoded `redactionMask`; invalid definitions fail before the host listens.
22
+
3
23
  ## 0.5.0 - 2026-11-02
4
24
 
5
25
  ### Added
package/README.md CHANGED
@@ -70,19 +70,23 @@ export const Handlers = Api.toLayer({
70
70
 
71
71
  `storageSave` is the application's Effect. `unexpected` constructs a member of the declared schema from the public message. The default message is `Internal server error`; `message` supplies application text, with credential-shaped strings redacted. Before masking an unexpected failure, the boundary reports its diagnostic-redacted cause through Effect's registered `ErrorReporter`s at `Error` severity. Request tracing logs that masked failure at ERROR with its redacted stack, while declared rejections stay at INFO. The original Effect cause is kept on the server rejection's non-enumerable `cause`, so a server span can trace it. Keep `cause` out of the wire schema, as the taxonomy classes do. `ServiceUnavailable` describes a declared availability failure without implying a session-provider outage.
72
72
 
73
- Apply the same boundary to an entire handler registration with `rpcBoundaryHandlers(group, boundary, handlers)`:
73
+ Apply boundary options to an entire handler registration with `rpcBoundaryHandlers(group, options, handlers)`:
74
74
 
75
75
  ```ts
76
76
  import { rpcBoundaryHandlers } from "@shivaedev/errors/rpcBoundaryHandlers.ts";
77
77
 
78
78
  export const Handlers = Api.toLayer(
79
- rpcBoundaryHandlers(Api, expose, {
80
- Save: () => storageSave,
81
- }),
79
+ rpcBoundaryHandlers(
80
+ Api,
81
+ { unexpected: (message) => new InternalServerError({ message }) },
82
+ {
83
+ Save: () => storageSave,
84
+ },
85
+ ),
82
86
  );
83
87
  ```
84
88
 
85
- The group supplies payload and result types. Each RPC must declare the boundary's rejection schema. Effect and Stream handlers share the mapping, including failures after a stream has delivered values; native `Rpc.Wrapper` options remain available. Handler service requirements pass through to the native handler Layer. Use this map inside an Effect passed to `toLayer` when handler construction needs services. Queue-backed streams and Deferred results use native handlers with an explicit boundary at their producer instead.
89
+ The group supplies payload, result and rejection types for each operation. Each handler keeps only its own declared rejections; a rejection declared by another operation is unexpected here. Every operation must declare the fallback returned by `unexpected`. Effect and Stream handlers share the mapping, including failures after a stream has delivered values; native `Rpc.Wrapper` options remain available. Handler service requirements pass through to the native handler Layer. Use this map inside an Effect passed to `toLayer` when handler construction needs services. Queue-backed streams and Deferred results use native handlers with an explicit boundary at their producer instead.
86
90
 
87
91
  ### Masking secrets in failures
88
92
 
@@ -96,10 +100,23 @@ Schema diagnostics receive additional masking: `redactText` masks rejected value
96
100
 
97
101
  `redactCause.ts` applies it to Effect causes. `redactCause(cause, sensitive?)` rebuilds a cause with masked defects and keeps its typed failures, interruptions and annotations as they are, so the failure channel keeps its type. `redactDiagnosticCause(cause, sensitive?)` masks failures, defects and annotations for diagnostics, `redactDefect(defect, sensitive?)` masks one defect, and `redactingErrorReporter(reporter, sensitive?)` wraps an Effect error reporter so it only ever receives diagnostic-masked causes.
98
102
 
99
- `schemaSensitiveKeys(group, extraKeys?)` from `schemaSensitiveKeys.ts` derives the key predicate from native RPC payload, success, error, defect and middleware error schemas. It includes nested sensitive annotations, Redacted schemas, encoded field names and additional application keys. The predicate’s `forRpc(tag)` method selects the schema-derived policy for one RPC; additional application keys apply to every RPC. The predicate’s `maskFailure(tag, reason)` method identifies whole error or defect roots that need masking. The host applies this predicate to logs, error reports, RPC failure data and exported traces.
103
+ `schemaSensitiveKeys(group, extraKeys?)` from `schemaSensitiveKeys.ts` derives the key predicate from native RPC payload, success, error, stream error, defect and middleware error schemas. It includes nested sensitive annotations, Redacted schemas, encoded field names and additional application keys. The predicate’s `forRpc(tag)` method selects the schema-derived policy for one RPC; additional application keys apply to every RPC. The predicate’s `maskFailure(tag, reason)` method identifies whole error or defect roots that need masking. Its `redactFailure(tag, reason, value)` method masks encoded failure values within their schemas. The host applies this predicate to logs, error reports, RPC failure data and exported traces.
100
104
 
101
105
  Use `yield* schemaSensitiveKeys(group, extraKeys)` to obtain the predicate. Each schema derives its encoded sensitive names independently. Masking a containing field does not independently mark its child names sensitive; native `OptionFromNullOr` encoding preserves those field scopes. Different RPC methods can use the same field name with different sensitivity. Within one RPC, diagnostics carry plain values without schema identity, so a sensitive name cannot also name a public field in its payload, success, failure or middleware schemas, including nested fields. The Effect fails with `SensitiveFieldConflict` from `schemaFieldScope.ts`, naming both schema paths. Rename one field or mark both sensitive. Explicit application keys and built-in credential rules apply everywhere and do not create mixed visibility.
102
106
 
107
+ RPC failure masks preserve declared wire shapes: strings use `<redacted>`, numbers use `0`, booleans use `false`, arrays use empty arrays or masked required tuple elements, and objects retain required fields with masked values. Records use empty objects, optional fields may be omitted, schema literals remain the constants declared in the contract, and whole-value union masks choose a compatible constant alternative. Native tagged-error classes retain their declared classification, including when a failure accompanies an interruption.
108
+
109
+ When the generic mask does not decode through the sensitive schema, supply a public, constant `redactionMask` annotation. Its value is the encoded wire value, including for a transformed schema:
110
+
111
+ ```ts
112
+ const PrivateAmount = Schema.Number.check(Schema.isGreaterThan(0)).annotate({
113
+ sensitive: true,
114
+ redactionMask: 1,
115
+ });
116
+ ```
117
+
118
+ Policy definition with `yield* schemaSensitiveKeys(group)` validates masks. An incompatible generic or explicit value fails with `InvalidRedactionMask` from `wireMask.ts`; its `field` and `message` name the schema path and tell the caller to supply a mask. `appHost` performs this definition before listening. The constant replaces the sensitive input without inspecting it; callers must choose a non-secret value. Diagnostic masks still use `<redacted>` independently of wire shape.
119
+
103
120
  The policy matches names and credential or URL shapes, not arbitrary values: it does not scan prose for personal data, so keep secrets out of free text and span names.
104
121
 
105
122
  ### Install and limits
@@ -3,7 +3,5 @@ export interface RpcBoundaryOptions<E> {
3
3
  readonly message?: string;
4
4
  readonly unexpected: (message: string) => E;
5
5
  }
6
- export declare function rpcBoundary<TSchema extends Schema.ConstraintDecoder<{
7
- readonly message: string;
8
- }>>(schema: TSchema, options: RpcBoundaryOptions<TSchema["Type"]>): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, TSchema["Type"], R>;
6
+ export declare function rpcBoundary<TSchema extends Schema.Constraint>(schema: TSchema, options: RpcBoundaryOptions<TSchema["Type"]>): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, TSchema["Type"], R>;
9
7
  //# sourceMappingURL=boundary.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"boundary.d.ts","sourceRoot":"","sources":["../src/boundary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,MAAM,EAA4B,MAAM,EAAE,MAAM,QAAQ,CAAC;AAqBlF,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,CAAC,CAAC;CAC5C;AACD,wBAAgB,WAAW,CAC1B,OAAO,SAAS,MAAM,CAAC,iBAAiB,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB,CAAC,EACD,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,kBAAkB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAEtD,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAkBpF"}
1
+ {"version":3,"file":"boundary.d.ts","sourceRoot":"","sources":["../src/boundary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,MAAM,EAA4B,MAAM,EAAE,MAAM,QAAQ,CAAC;AAkClF,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,CAAC,CAAC;CAC5C;AACD,wBAAgB,WAAW,CAAC,OAAO,SAAS,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,kBAAkB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAEnH,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAkBpF"}
package/dist/boundary.js CHANGED
@@ -14,15 +14,30 @@ function reportUnexpected(cause) {
14
14
  return reported.annotate(Context.makeUnsafe(reason.annotations));
15
15
  })));
16
16
  }
17
+ function declaredCause(cause, declared) {
18
+ const reasons = [];
19
+ for (const reason of cause.reasons) {
20
+ if (Cause.isInterruptReason(reason)) {
21
+ reasons.push(reason);
22
+ }
23
+ else if (Cause.isFailReason(reason) && declared(reason.error)) {
24
+ reasons.push(Cause.makeFailReason(reason.error).annotate(Context.makeUnsafe(reason.annotations)));
25
+ }
26
+ else {
27
+ return undefined;
28
+ }
29
+ }
30
+ return Cause.fromReasons(reasons);
31
+ }
17
32
  export function rpcBoundary(schema, options) {
18
33
  const declared = Schema.is(schema);
19
34
  return (self) => Effect.catchCause(self, (cause) => {
20
35
  if (Cause.hasInterruptsOnly(cause)) {
21
36
  return Effect.failCause(Cause.fromReasons(cause.reasons.filter(Cause.isInterruptReason)));
22
37
  }
23
- const reason = cause.reasons[0];
24
- if (cause.reasons.length === 1 && reason && Cause.isFailReason(reason) && declared(reason.error)) {
25
- return Effect.fail(reason.error);
38
+ const expected = declaredCause(cause, declared);
39
+ if (expected !== undefined) {
40
+ return Effect.failCause(expected);
26
41
  }
27
42
  const rejection = options.unexpected(redactText(options.message ?? "Internal server error", isSensitiveKey));
28
43
  Object.defineProperties(rejection, {
@@ -1 +1 @@
1
- {"version":3,"file":"boundary.js","sourceRoot":"","sources":["../src/boundary.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"boundary.js","sourceRoot":"","sources":["../src/boundary.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,19 @@
1
- import { Effect, Stream } from "effect";
1
+ import { Effect, Stream, type Types } from "effect";
2
2
  import { Rpc, type RpcGroup, RpcSchema } from "effect/rpc";
3
+ import { type RpcBoundaryOptions } from "#boundary.ts";
3
4
  type Handlers<R extends Rpc.Any> = {
4
5
  readonly [TRpc in R as TRpc["_tag"]]: (...args: Parameters<Rpc.ToHandlerFn<TRpc>>) => Rpc.WrapperOr<TRpc extends {
5
6
  readonly successSchema: RpcSchema.Stream<infer TSuccess, infer _E>;
6
7
  } ? Stream.Stream<TSuccess["Type"], unknown, unknown> : Effect.Effect<Rpc.Success<TRpc>, unknown, unknown>>;
7
8
  };
8
9
  type Bounded<T, E> = T extends Effect.Effect<infer A, infer _E, infer R> ? Effect.Effect<A, E, R> : T extends Stream.Stream<infer A, infer _E, infer R> ? Stream.Stream<A, E, R> : T extends Rpc.Wrapper<infer TValue> ? Rpc.Wrapper<Bounded<TValue, E>> : never;
9
- type BoundedHandlers<THandlers, E> = {
10
- readonly [TKey in keyof THandlers]: THandlers[TKey] extends (...args: infer TArgs) => infer TValue ? (...args: TArgs) => Bounded<TValue, E> : never;
10
+ type Rejection<TRpc extends Rpc.Any> = TRpc extends Rpc.AnyWithProps ? TRpc["errorSchema"]["Type"] | (TRpc["successSchema"] extends RpcSchema.Stream<infer _A, infer E> ? E["Type"] : never) : never;
11
+ type BoundedHandlers<TRpcs extends Rpc.Any, THandlers> = {
12
+ readonly [TKey in keyof THandlers]: THandlers[TKey] extends (...args: infer TArgs) => infer TValue ? (...args: TArgs) => Bounded<TValue, Rejection<Extract<TRpcs, {
13
+ readonly _tag: TKey;
14
+ }>>> : never;
11
15
  };
12
- export declare function rpcBoundaryHandlers<TRpcs extends Rpc.Any, E, THandlers extends Handlers<TRpcs>>(group: RpcGroup.RpcGroup<TRpcs>, boundary: <A, TError, R>(effect: Effect.Effect<A, TError, R>) => Effect.Effect<A, E, R>, handlers: THandlers): BoundedHandlers<THandlers, E>;
16
+ type GroupOptions<TRpcs extends Rpc.Any> = Types.UnionToIntersection<TRpcs extends unknown ? RpcBoundaryOptions<Rejection<TRpcs>> : never>;
17
+ export declare function rpcBoundaryHandlers<TRpcs extends Rpc.Any, THandlers extends Handlers<TRpcs>>(group: RpcGroup.RpcGroup<TRpcs>, options: NoInfer<GroupOptions<TRpcs>>, handlers: THandlers): BoundedHandlers<TRpcs, THandlers>;
13
18
  export {};
14
19
  //# sourceMappingURL=rpcBoundaryHandlers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rpcBoundaryHandlers.d.ts","sourceRoot":"","sources":["../src/rpcBoundaryHandlers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAE,GAAG,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG3D,KAAK,QAAQ,CAAC,CAAC,SAAS,GAAG,CAAC,GAAG,IAAI;IAClC,QAAQ,EAAE,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CACrC,GAAG,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,KACtC,GAAG,CAAC,SAAS,CACjB,IAAI,SAAS;QAAE,QAAQ,CAAC,aAAa,EAAE,SAAS,CAAC,MAAM,CAAC,MAAM,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;KAAE,GAChF,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,GACjD,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CACrD;CACD,CAAC;AAEF,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,IAChB,CAAC,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,GAChD,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GACtB,CAAC,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,GAClD,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GACtB,CAAC,SAAS,GAAG,CAAC,OAAO,CAAC,MAAM,MAAM,CAAC,GAClC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAC/B,KAAK,CAAC;AAEZ,KAAK,eAAe,CAAC,SAAS,EAAE,CAAC,IAAI;IACpC,QAAQ,EAAE,IAAI,IAAI,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,KAAK,KAAK,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,KAAK;CACnJ,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,SAAS,SAAS,QAAQ,CAAC,KAAK,CAAC,EAC9F,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAC/B,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACvF,QAAQ,EAAE,SAAS,GACjB,eAAe,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"rpcBoundaryHandlers.d.ts","sourceRoot":"","sources":["../src/rpcBoundaryHandlers.ts"],"names":[],"mappings":"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"}
@@ -1,17 +1,24 @@
1
- import { Effect, Result, Stream } from "effect";
1
+ import { Effect, Result, Schema, Stream } from "effect";
2
2
  import { Rpc, RpcSchema } from "effect/rpc";
3
- export function rpcBoundaryHandlers(group, boundary, handlers) {
4
- return Object.fromEntries(Object.entries(handlers).map(([tag, handler]) => [
5
- tag,
6
- (...args) => {
7
- const constructed = Result.try(() => handler(...args));
8
- if (Result.isFailure(constructed)) {
9
- const failure = boundary(Effect.die(constructed.failure));
10
- const rpc = group.requests.get(tag);
11
- return Rpc.isRpc(rpc) && RpcSchema.isStreamSchema(rpc.successSchema) ? Stream.fromEffect(failure) : failure;
12
- }
13
- return Rpc.wrapMap(constructed.success, (result) => Effect.isEffect(result) ? boundary(result) : Stream.catchCause(result, (cause) => Stream.fromEffect(boundary(Effect.failCause(cause)))));
14
- },
15
- ]));
3
+ import { rpcBoundary } from "#boundary.ts";
4
+ export function rpcBoundaryHandlers(group, options, handlers) {
5
+ return Object.fromEntries(Array.from(group.requests.values(), (rpc) => {
6
+ const tag = rpc._tag;
7
+ const handler = handlers[tag];
8
+ const streaming = RpcSchema.isStreamSchema(rpc.successSchema);
9
+ const schema = RpcSchema.isStreamSchema(rpc.successSchema) ? Schema.Union([rpc.errorSchema, rpc.successSchema.error]) : rpc.errorSchema;
10
+ const boundary = rpcBoundary(schema, options);
11
+ return [
12
+ tag,
13
+ (...args) => {
14
+ const constructed = Result.try(() => handler(...args));
15
+ if (Result.isFailure(constructed)) {
16
+ const failure = boundary(Effect.die(constructed.failure));
17
+ return streaming ? Stream.fromEffect(failure) : failure;
18
+ }
19
+ return Rpc.wrapMap(constructed.success, (result) => Effect.isEffect(result) ? boundary(result) : Stream.catchCause(result, (cause) => Stream.fromEffect(boundary(Effect.failCause(cause)))));
20
+ },
21
+ ];
22
+ }));
16
23
  }
17
24
  //# sourceMappingURL=rpcBoundaryHandlers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rpcBoundaryHandlers.js","sourceRoot":"","sources":["../src/rpcBoundaryHandlers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAE,GAAG,EAAiB,SAAS,EAAE,MAAM,YAAY,CAAC;AA+B3D,MAAM,UAAU,mBAAmB,CAClC,KAAiC,EACjC,QAA6F,EAC7F,QAAsF;IAEtF,OAAO,MAAM,CAAC,WAAW,CACxB,MAAM,CAAC,OAAO,CAAqD,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC;QACpG,GAAG;QACH,CAAC,GAAG,IAAa,EAAE,EAAE;YACpB,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;YACvD,IAAI,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC;gBACnC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;gBAC1D,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACpC,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,cAAc,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YAC7G,CAAC;YACD,OAAO,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAClD,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CACvI,CAAC;QACH,CAAC;KACD,CAAC,CACF,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"rpcBoundaryHandlers.js","sourceRoot":"","sources":["../src/rpcBoundaryHandlers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAc,MAAM,QAAQ,CAAC;AACpE,OAAO,EAAE,GAAG,EAAiB,SAAS,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAA2B,WAAW,EAAE,MAAM,cAAc,CAAC;AAuCpE,MAAM,UAAU,mBAAmB,CAClC,KAAoE,EACpE,OAAoC,EACpC,QAAoF;IAEpF,OAAO,MAAM,CAAC,WAAW,CACxB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE;QAC3C,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC;QACrB,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,SAAS,GAAG,SAAS,CAAC,cAAc,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QAC9D,MAAM,MAAM,GAAG,SAAS,CAAC,cAAc,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC;QACxI,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC9C,OAAO;YACN,GAAG;YACH,CAAC,GAAG,IAAa,EAAE,EAAE;gBACpB,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;gBACvD,IAAI,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC;oBACnC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;oBAC1D,OAAO,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBACzD,CAAC;gBACD,OAAO,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAClD,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CACvI,CAAC;YACH,CAAC;SACD,CAAC;IACH,CAAC,CAAC,CACF,CAAC;AACH,CAAC"}
@@ -2,6 +2,7 @@ import { type SchemaAST } from "effect";
2
2
  export declare function children(ast: SchemaAST.AST): readonly SchemaAST.AST[];
3
3
  export declare function marked(ast: SchemaAST.AST, seen?: Set<SchemaAST.AST>): boolean;
4
4
  export declare function collectEncodedKeys(ast: SchemaAST.AST, keys: Set<string>): void;
5
+ export declare function sensitiveEncoding(ast: SchemaAST.AST, keys: ReadonlySet<string>): boolean;
5
6
  export declare function maskedValue(ast: SchemaAST.AST, keys: ReadonlySet<string>, seen?: Set<SchemaAST.AST>): boolean;
6
7
  export declare function collect(ast: SchemaAST.AST, nodes: Set<SchemaAST.AST>, encodings?: boolean): void;
7
8
  export declare function collectFieldKeys(properties: SchemaAST.Objects["propertySignatures"], keys: Set<string>, sensitive: (ast: SchemaAST.AST) => boolean): void;
@@ -1 +1 @@
1
- {"version":3,"file":"schemaSensitiveAst.d.ts","sourceRoot":"","sources":["../src/schemaSensitiveAst.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGnD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,GAAG,SAAS,SAAS,CAAC,GAAG,EAAE,CAerE;AAID,wBAAgB,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,IAAI,qBAA2B,GAAG,OAAO,CAenF;AAgBD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,QAkBvE;AAOD,wBAAgB,WAAW,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,qBAA2B,GAAG,OAAO,CAuBnH;AACD,wBAAgB,OAAO,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,SAAS,UAAO,QAQtF;AAMD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,SAAS,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,KAAK,OAAO,QAMlJ"}
1
+ {"version":3,"file":"schemaSensitiveAst.d.ts","sourceRoot":"","sources":["../src/schemaSensitiveAst.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGnD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,GAAG,SAAS,SAAS,CAAC,GAAG,EAAE,CAerE;AAID,wBAAgB,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,IAAI,qBAA2B,GAAG,OAAO,CAenF;AAmBD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,QAkBvE;AACD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CASxF;AAOD,wBAAgB,WAAW,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,qBAA2B,GAAG,OAAO,CAuBnH;AACD,wBAAgB,OAAO,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,SAAS,UAAO,QAQtF;AAMD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,SAAS,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,KAAK,OAAO,QAMlJ"}
@@ -38,6 +38,9 @@ function containsSensitive(ast, keys) {
38
38
  || propertiesOf(ast).some((property) => typeof property.name === "string" && (keys.has(property.name) || isSensitiveKey(property.name))));
39
39
  }
40
40
  function preservesFields(source, target) {
41
+ if (source._tag === "Declaration" && source.annotations?.["~constructor"] !== undefined && source.typeParameters[0] === target) {
42
+ return true;
43
+ }
41
44
  return (source._tag === "Declaration"
42
45
  && Predicate.hasProperty(source.annotations?.representation, "id")
43
46
  && source.annotations.representation.id === "effect/schema/Option"
@@ -63,6 +66,16 @@ export function collectEncodedKeys(ast, keys) {
63
66
  source = link.to;
64
67
  }
65
68
  }
69
+ export function sensitiveEncoding(ast, keys) {
70
+ let source = ast;
71
+ for (const link of ast.encoding ?? []) {
72
+ if (!preservesFields(source, link.to) && (containsSensitive(source, keys) || containsSensitive(link.to, keys))) {
73
+ return true;
74
+ }
75
+ source = link.to;
76
+ }
77
+ return false;
78
+ }
66
79
  function fixedOutput(ast) {
67
80
  if (ast._tag === "Union") {
68
81
  return ast.types.every(fixedOutput);
@@ -1 +1 @@
1
- {"version":3,"file":"schemaSensitiveAst.js","sourceRoot":"","sources":["../src/schemaSensitiveAst.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schemaSensitiveAst.js","sourceRoot":"","sources":["../src/schemaSensitiveAst.ts"],"names":[],"mappings":"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"}
@@ -3,5 +3,6 @@ import { Rpc, type RpcGroup } from "effect/rpc";
3
3
  export declare const schemaSensitiveKeys: <R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>, extra?: readonly string[] | undefined) => Effect.Effect<((key: string) => boolean) & {
4
4
  forRpc: (tag: string | undefined) => (key: string) => boolean;
5
5
  maskFailure: (tag: string | undefined, reason: "Fail" | "Die") => boolean;
6
- }, import("#schemaFieldScope.ts").SensitiveFieldConflict, never>;
6
+ redactFailure: (tag: string | undefined, reason: "Fail" | "Die", value: unknown) => unknown;
7
+ }, import("./wireMask.ts").InvalidRedactionMask | import("#schemaFieldScope.ts").SensitiveFieldConflict, never>;
7
8
  //# sourceMappingURL=schemaSensitiveKeys.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schemaSensitiveKeys.d.ts","sourceRoot":"","sources":["../src/schemaSensitiveKeys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,GAAG,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAoBhD,eAAO,MAAM,mBAAmB,GAAgC,CAAC,SAAS,GAAG,CAAC,GAAG,8FAOxD,MAAM;kBAIf,MAAM,GAAG,SAAS,WAJT,MAAM;uBAKV,MAAM,GAAG,SAAS,UAAU,MAAM,GAAG,KAAK;gEAG7D,CAAC"}
1
+ {"version":3,"file":"schemaSensitiveKeys.d.ts","sourceRoot":"","sources":["../src/schemaSensitiveKeys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,GAAG,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AA6BhD,eAAO,MAAM,mBAAmB,GAAgC,CAAC,SAAS,GAAG,CAAC,GAAG,8FAOxD,MAAM;kBAIf,MAAM,GAAG,SAAS,WAJT,MAAM;uBAKV,MAAM,GAAG,SAAS,UAAU,MAAM,GAAG,KAAK;yBAExC,MAAM,GAAG,SAAS,UAAU,MAAM,GAAG,KAAK,SAAS,OAAO;+GAK/E,CAAC"}
@@ -2,20 +2,29 @@ import { Effect } from "effect";
2
2
  import { Rpc } from "effect/rpc";
3
3
  import { schemaFieldKeys, validateFieldScope } from "#schemaFieldScope.ts";
4
4
  import { maskedValue } from "#schemaSensitiveAst.ts";
5
- import { isSensitiveKey } from "#sensitive.ts";
5
+ import { isSensitiveKey, REDACTED } from "#sensitive.ts";
6
+ import { wireRedactor } from "#wireRedactor.ts";
6
7
  const rpcSensitiveKeys = Effect.fnUntraced(function* (rpc, extra) {
8
+ const Errors = Rpc.exitSchema(rpc).error;
7
9
  const schemas = [
8
10
  { ast: rpc.payloadSchema.ast, name: `${rpc._tag}.payload` },
9
11
  { ast: rpc.successSchema.ast, name: `${rpc._tag}.success` },
10
12
  { ast: rpc.errorSchema.ast, name: `${rpc._tag}.error` },
11
13
  { ast: rpc.defectSchema.ast, name: `${rpc._tag}.defect` },
12
14
  ...[...rpc.middlewares].map((middleware, index) => ({ ast: middleware.error.ast, name: `${rpc._tag}.middleware[${index}].error` })),
15
+ { ast: Errors.ast, name: `${rpc._tag}.exit.error` },
13
16
  ];
14
17
  yield* validateFieldScope(schemas, extra);
15
18
  const keys = new Set(schemas.flatMap(({ ast }) => [...schemaFieldKeys(ast, extra)]));
16
- return Object.assign((key) => keys.has(key) || isSensitiveKey(key), {
19
+ function sensitive(key) {
20
+ return keys.has(key) || isSensitiveKey(key);
21
+ }
22
+ const fail = yield* wireRedactor(Errors.ast, `${rpc._tag}.error`, sensitive, keys);
23
+ const die = yield* wireRedactor(rpc.defectSchema.ast, `${rpc._tag}.defect`, sensitive, keys);
24
+ return Object.assign(sensitive, {
17
25
  die: maskedValue(rpc.defectSchema.ast, keys),
18
- fail: [rpc.errorSchema, ...[...rpc.middlewares].map((m) => m.error)].some((schema) => maskedValue(schema.ast, keys)),
26
+ fail: maskedValue(Errors.ast, keys),
27
+ redactFailure: (reason, value) => (reason === "Fail" ? fail(value) : die(value)),
19
28
  });
20
29
  });
21
30
  export const schemaSensitiveKeys = Effect.fnUntraced(function* (group, extra = []) {
@@ -31,6 +40,10 @@ export const schemaSensitiveKeys = Effect.fnUntraced(function* (group, extra = [
31
40
  return Object.assign(sensitive, {
32
41
  forRpc: (tag) => (tag === undefined ? sensitive : (policies.get(tag) ?? sensitive)),
33
42
  maskFailure: (tag, reason) => [...policies].some(([method, policy]) => (tag === undefined || tag === method) && (reason === "Fail" ? policy.fail : policy.die)),
43
+ redactFailure: (tag, reason, value) => {
44
+ const policy = policies.get(tag ?? "");
45
+ return policy === undefined ? REDACTED : policy.redactFailure(reason, value);
46
+ },
34
47
  });
35
48
  });
36
49
  //# sourceMappingURL=schemaSensitiveKeys.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schemaSensitiveKeys.js","sourceRoot":"","sources":["../src/schemaSensitiveKeys.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schemaSensitiveKeys.js","sourceRoot":"","sources":["../src/schemaSensitiveKeys.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { Effect, Schema, type SchemaAST } from "effect";
2
+ declare const InvalidRedactionMask_base: Schema.Class<InvalidRedactionMask, Schema.TaggedStruct<"InvalidRedactionMask", {
3
+ readonly field: Schema.String;
4
+ readonly message: Schema.String;
5
+ }>, import("effect/Cause").YieldableError>;
6
+ export declare class InvalidRedactionMask extends InvalidRedactionMask_base {
7
+ }
8
+ type Ancestors = ReadonlySet<SchemaAST.AST>;
9
+ export declare const wireMask: (ast: SchemaAST.AST, field: string, ancestors?: Ancestors | undefined) => Effect.Effect<unknown, InvalidRedactionMask, never>;
10
+ export {};
11
+ //# sourceMappingURL=wireMask.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wireMask.d.ts","sourceRoot":"","sources":["../src/wireMask.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;;;;;AAGzE,qBAAa,oBAAqB,SAAQ,yBAGxC;CAAG;AAEL,KAAK,SAAS,GAAG,WAAW,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;AA2D5C,eAAO,MAAM,QAAQ,+HA0BnB,CAAC"}
@@ -0,0 +1,88 @@
1
+ import { Effect, Exit, Predicate, Schema } from "effect";
2
+ import { REDACTED } from "#sensitive.ts";
3
+ export class InvalidRedactionMask extends Schema.TaggedError()("InvalidRedactionMask", {
4
+ field: Schema.String,
5
+ message: Schema.String,
6
+ }) {
7
+ }
8
+ const objectMask = Effect.fnUntraced(function* (ast, field, seen) {
9
+ const entries = [];
10
+ for (const property of ast.propertySignatures) {
11
+ if (!property.type.context?.isOptional) {
12
+ entries.push([property.name, yield* wireMask(property.type, `${field}.${String(property.name)}`, seen)]);
13
+ }
14
+ }
15
+ return Object.fromEntries(entries);
16
+ });
17
+ const arrayMask = Effect.fnUntraced(function* (ast, field, seen) {
18
+ const items = [];
19
+ for (const [index, element] of ast.elements.entries()) {
20
+ if (!element.context?.isOptional) {
21
+ items.push(yield* wireMask(element, `${field}[${index}]`, seen));
22
+ }
23
+ }
24
+ return items;
25
+ });
26
+ const unionMask = Effect.fnUntraced(function* (ast, field, seen) {
27
+ for (const member of ast.types) {
28
+ const candidate = yield* Effect.result(wireMask(member, field, seen));
29
+ if (candidate._tag === "Success") {
30
+ return candidate.success;
31
+ }
32
+ }
33
+ return undefined;
34
+ });
35
+ function genericMask(ast, field, seen) {
36
+ switch (ast._tag) {
37
+ case "String":
38
+ case "Unknown":
39
+ case "Any":
40
+ return Effect.succeed(REDACTED);
41
+ case "Number":
42
+ return Effect.succeed(0);
43
+ case "Boolean":
44
+ return Effect.succeed(false);
45
+ case "Null":
46
+ return Effect.succeed(null);
47
+ case "Undefined":
48
+ case "Void":
49
+ return Effect.succeed(undefined);
50
+ case "Literal":
51
+ return Effect.succeed(ast.literal);
52
+ case "Objects":
53
+ return objectMask(ast, field, seen);
54
+ case "Arrays":
55
+ return arrayMask(ast, field, seen);
56
+ case "Union":
57
+ return unionMask(ast, field, seen);
58
+ case "Suspend":
59
+ return wireMask(ast.thunk(), field, seen);
60
+ default:
61
+ return Effect.succeed(REDACTED);
62
+ }
63
+ }
64
+ export const wireMask = Effect.fnUntraced(function* (ast, field, ancestors = new Set()) {
65
+ function invalid() {
66
+ return new InvalidRedactionMask({
67
+ field,
68
+ message: `Sensitive field "${field}" needs a schema-compatible mask; supply a redactionMask annotation on its wire schema.`,
69
+ });
70
+ }
71
+ if (ancestors.has(ast)) {
72
+ return yield* invalid();
73
+ }
74
+ const seen = new Set(ancestors).add(ast);
75
+ const Annotations = Schema.resolveAnnotations(Schema.make(ast));
76
+ let value;
77
+ if (Predicate.hasProperty(Annotations, "redactionMask")) {
78
+ value = Annotations.redactionMask;
79
+ }
80
+ else {
81
+ value = ast.encoding === undefined ? yield* genericMask(ast, field, seen) : yield* wireMask(ast.encoding.at(-1)?.to ?? ast, field, seen);
82
+ }
83
+ if (Exit.isFailure(Schema.decodeUnknownExit(Schema.toCodecJson(Schema.make(ast)))(value))) {
84
+ return yield* invalid();
85
+ }
86
+ return value;
87
+ });
88
+ //# sourceMappingURL=wireMask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wireMask.js","sourceRoot":"","sources":["../src/wireMask.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { Effect, type SchemaAST } from "effect";
2
+ import { type SensitiveKey } from "#sensitive.ts";
3
+ type Redactor = (value: unknown) => unknown;
4
+ export declare const wireRedactor: (ast: SchemaAST.AST, field: string, sensitive: SensitiveKey, keys: ReadonlySet<string>) => Effect.Effect<Redactor, import("#wireMask.ts").InvalidRedactionMask, never>;
5
+ export {};
6
+ //# sourceMappingURL=wireRedactor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wireRedactor.d.ts","sourceRoot":"","sources":["../src/wireRedactor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGnE,OAAO,EAAY,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAG5D,KAAK,QAAQ,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;AAyB5C,eAAO,MAAM,YAAY,wKAmGvB,CAAC"}
@@ -0,0 +1,105 @@
1
+ import { Effect, Predicate, Schema } from "effect";
2
+ import { redactWire } from "#redact.ts";
3
+ import { collect, maskedValue, sensitiveEncoding } from "#schemaSensitiveAst.ts";
4
+ import { REDACTED } from "#sensitive.ts";
5
+ import { wireMask } from "#wireMask.ts";
6
+ function objectRedactor(fields, indexes, sensitive) {
7
+ function field(key) {
8
+ const property = fields.get(key);
9
+ if (property !== undefined) {
10
+ return property;
11
+ }
12
+ const index = indexes.find((entry) => entry.accepts(key));
13
+ if (index !== undefined) {
14
+ return sensitive(key) ? index.mask : index.apply;
15
+ }
16
+ return (value) => (sensitive(key) ? REDACTED : redactWire(value, sensitive));
17
+ }
18
+ return (value) => Predicate.isObjectOrArray(value)
19
+ ? Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, field(key)(nested)]))
20
+ : redactWire(value, sensitive);
21
+ }
22
+ export const wireRedactor = Effect.fnUntraced(function* (ast, field, sensitive, keys) {
23
+ const cache = new Map();
24
+ function constrainedContainer(node) {
25
+ if (node.checks === undefined) {
26
+ return false;
27
+ }
28
+ const nodes = new Set();
29
+ collect(node, nodes);
30
+ return [...nodes].some((child) => child._tag === "Objects" && child.propertySignatures.some((property) => typeof property.name === "string" && sensitive(property.name)));
31
+ }
32
+ function encoded(node) {
33
+ if (node.encoding !== undefined) {
34
+ return node.encoding.at(-1)?.to ?? node;
35
+ }
36
+ return node._tag === "Declaration" ? Schema.toEncoded(Schema.toCodecJson(Schema.make(node))).ast : node;
37
+ }
38
+ const compileObject = Effect.fnUntraced(function* (node, path) {
39
+ const fields = new Map();
40
+ for (const property of node.propertySignatures) {
41
+ const stackHeader = property.name === "stack" && (sensitive("message") || sensitive("name"));
42
+ fields.set(property.name, yield* compile(property.type, `${path}.${String(property.name)}`, stackHeader || (typeof property.name === "string" && sensitive(property.name))));
43
+ }
44
+ const indexes = yield* Effect.forEach(node.indexSignatures, (index) => Effect.gen(function* () {
45
+ const apply = yield* compile(index.type, `${path}[value]`);
46
+ const mask = yield* compile(index.type, `${path}[value]`, true);
47
+ return { accepts: Schema.is(Schema.make(index.parameter)), apply, mask };
48
+ }));
49
+ return objectRedactor(fields, indexes, sensitive);
50
+ });
51
+ const compileArray = Effect.fnUntraced(function* (node, path) {
52
+ const elements = yield* Effect.forEach(node.elements, (element, index) => compile(element, `${path}[${index}]`));
53
+ const rest = yield* Effect.forEach(node.rest, (element) => compile(element, `${path}[]`));
54
+ return (value) => Array.isArray(value)
55
+ ? value.map((item, index) => (elements[index] ?? rest[0] ?? ((entry) => redactWire(entry, sensitive)))(item))
56
+ : redactWire(value, sensitive);
57
+ });
58
+ const compileUnion = Effect.fnUntraced(function* (node, path) {
59
+ const members = yield* Effect.forEach(node.types, (member, index) => Effect.map(compile(member, `${path}[${index}]`), (mask) => ({
60
+ accepts: Schema.is(Schema.toEncoded(Schema.toCodecJson(Schema.make(member)))),
61
+ mask,
62
+ })));
63
+ return (value) => {
64
+ const member = members.find(({ accepts }) => accepts(value));
65
+ return member === undefined ? REDACTED : member.mask(value);
66
+ };
67
+ });
68
+ function contents(node, path) {
69
+ switch (node._tag) {
70
+ case "Objects":
71
+ return compileObject(node, path);
72
+ case "Arrays":
73
+ return compileArray(node, path);
74
+ case "Union":
75
+ return compileUnion(node, path);
76
+ case "Suspend":
77
+ return compile(node.thunk(), path);
78
+ default:
79
+ return Effect.succeed((value) => redactWire(value, sensitive));
80
+ }
81
+ }
82
+ function compile(node, path, hidden = false) {
83
+ return Effect.gen(function* () {
84
+ if (hidden
85
+ || constrainedContainer(node)
86
+ || sensitiveEncoding(node, keys)
87
+ || (node._tag !== "Union" && maskedValue(node, keys))
88
+ || Schema.resolveAnnotations(Schema.make(node))?.sensitive === true) {
89
+ const mask = yield* wireMask(node, path);
90
+ return () => mask;
91
+ }
92
+ const cached = cache.get(node);
93
+ if (cached !== undefined) {
94
+ return cached;
95
+ }
96
+ let apply = () => REDACTED;
97
+ const redactor = (value) => apply(value);
98
+ cache.set(node, redactor);
99
+ apply = yield* contents(encoded(node), path);
100
+ return redactor;
101
+ });
102
+ }
103
+ return yield* compile(ast, field);
104
+ });
105
+ //# sourceMappingURL=wireRedactor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wireRedactor.js","sourceRoot":"","sources":["../src/wireRedactor.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/errors",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Typed expected failures and secret-safe failure reporting for Effect applications",
5
5
  "keywords": [
6
6
  "effect"
@@ -45,8 +45,8 @@
45
45
  "README.md"
46
46
  ],
47
47
  "devDependencies": {
48
- "@shivaedev/quality": "0.55.0",
49
- "@shivaedev/test-story": "0.33.0",
48
+ "@shivaedev/quality": "0.57.1",
49
+ "@shivaedev/test-story": "0.36.0",
50
50
  "@types/node": "24.19.2",
51
51
  "effect": "4.0.6",
52
52
  "typescript": "7.0.2",
package/src/boundary.ts CHANGED
@@ -19,24 +19,33 @@ function reportUnexpected(cause: Cause.Cause<unknown>): Effect.Effect<void> {
19
19
  ),
20
20
  );
21
21
  }
22
+ function declaredCause<E>(cause: Cause.Cause<unknown>, declared: (value: unknown) => value is E): Cause.Cause<E> | undefined {
23
+ const reasons: Cause.Reason<E>[] = [];
24
+ for (const reason of cause.reasons) {
25
+ if (Cause.isInterruptReason(reason)) {
26
+ reasons.push(reason);
27
+ } else if (Cause.isFailReason(reason) && declared(reason.error)) {
28
+ reasons.push(Cause.makeFailReason(reason.error).annotate(Context.makeUnsafe(reason.annotations)));
29
+ } else {
30
+ return undefined;
31
+ }
32
+ }
33
+ return Cause.fromReasons(reasons);
34
+ }
22
35
  export interface RpcBoundaryOptions<E> {
23
36
  readonly message?: string;
24
37
  readonly unexpected: (message: string) => E;
25
38
  }
26
- export function rpcBoundary<
27
- TSchema extends Schema.ConstraintDecoder<{
28
- readonly message: string;
29
- }>,
30
- >(schema: TSchema, options: RpcBoundaryOptions<TSchema["Type"]>) {
39
+ export function rpcBoundary<TSchema extends Schema.Constraint>(schema: TSchema, options: RpcBoundaryOptions<TSchema["Type"]>) {
31
40
  const declared = Schema.is(schema);
32
41
  return <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, TSchema["Type"], R> =>
33
42
  Effect.catchCause(self, (cause) => {
34
43
  if (Cause.hasInterruptsOnly(cause)) {
35
44
  return Effect.failCause(Cause.fromReasons(cause.reasons.filter(Cause.isInterruptReason)));
36
45
  }
37
- const reason = cause.reasons[0];
38
- if (cause.reasons.length === 1 && reason && Cause.isFailReason(reason) && declared(reason.error)) {
39
- return Effect.fail(reason.error);
46
+ const expected = declaredCause(cause, declared);
47
+ if (expected !== undefined) {
48
+ return Effect.failCause(expected);
40
49
  }
41
50
  const rejection = options.unexpected(redactText(options.message ?? "Internal server error", isSensitiveKey));
42
51
  Object.defineProperties(rejection, {
@@ -1,5 +1,6 @@
1
- import { Effect, Result, Stream } from "effect";
1
+ import { Effect, Result, Schema, Stream, type Types } from "effect";
2
2
  import { Rpc, type RpcGroup, RpcSchema } from "effect/rpc";
3
+ import { type RpcBoundaryOptions, rpcBoundary } from "#boundary.ts";
3
4
 
4
5
  type HandlerResult = Effect.Effect<unknown, unknown, unknown> | Stream.Stream<unknown, unknown, unknown>;
5
6
  type Handlers<R extends Rpc.Any> = {
@@ -21,34 +22,48 @@ type Bounded<T, E> =
21
22
  ? Rpc.Wrapper<Bounded<TValue, E>>
22
23
  : never;
23
24
 
24
- type BoundedHandlers<THandlers, E> = {
25
- readonly [TKey in keyof THandlers]: THandlers[TKey] extends (...args: infer TArgs) => infer TValue ? (...args: TArgs) => Bounded<TValue, E> : never;
25
+ type Rejection<TRpc extends Rpc.Any> = TRpc extends Rpc.AnyWithProps
26
+ ? TRpc["errorSchema"]["Type"] | (TRpc["successSchema"] extends RpcSchema.Stream<infer _A, infer E> ? E["Type"] : never)
27
+ : never;
28
+
29
+ type BoundedHandlers<TRpcs extends Rpc.Any, THandlers> = {
30
+ readonly [TKey in keyof THandlers]: THandlers[TKey] extends (...args: infer TArgs) => infer TValue
31
+ ? (...args: TArgs) => Bounded<TValue, Rejection<Extract<TRpcs, { readonly _tag: TKey }>>>
32
+ : never;
26
33
  };
27
34
 
28
- export function rpcBoundaryHandlers<TRpcs extends Rpc.Any, E, THandlers extends Handlers<TRpcs>>(
35
+ type GroupOptions<TRpcs extends Rpc.Any> = Types.UnionToIntersection<TRpcs extends unknown ? RpcBoundaryOptions<Rejection<TRpcs>> : never>;
36
+
37
+ export function rpcBoundaryHandlers<TRpcs extends Rpc.Any, THandlers extends Handlers<TRpcs>>(
29
38
  group: RpcGroup.RpcGroup<TRpcs>,
30
- boundary: <A, TError, R>(effect: Effect.Effect<A, TError, R>) => Effect.Effect<A, E, R>,
39
+ options: NoInfer<GroupOptions<TRpcs>>,
31
40
  handlers: THandlers,
32
- ): BoundedHandlers<THandlers, E>;
33
- export function rpcBoundaryHandlers(
34
- group: RpcGroup.RpcGroup<Rpc.Any>,
35
- boundary: <A, TError, R>(effect: Effect.Effect<A, TError, R>) => Effect.Effect<A, unknown, R>,
36
- handlers: Readonly<Record<string, (...args: never[]) => Rpc.WrapperOr<HandlerResult>>>,
41
+ ): BoundedHandlers<TRpcs, THandlers>;
42
+ export function rpcBoundaryHandlers<TTag extends string>(
43
+ group: RpcGroup.RpcGroup<Rpc.AnyWithProps & { readonly _tag: TTag }>,
44
+ options: RpcBoundaryOptions<unknown>,
45
+ handlers: Readonly<Record<TTag, (...args: never[]) => Rpc.WrapperOr<HandlerResult>>>,
37
46
  ) {
38
47
  return Object.fromEntries(
39
- Object.entries<(...args: never[]) => Rpc.WrapperOr<HandlerResult>>(handlers).map(([tag, handler]) => [
40
- tag,
41
- (...args: never[]) => {
42
- const constructed = Result.try(() => handler(...args));
43
- if (Result.isFailure(constructed)) {
44
- const failure = boundary(Effect.die(constructed.failure));
45
- const rpc = group.requests.get(tag);
46
- return Rpc.isRpc(rpc) && RpcSchema.isStreamSchema(rpc.successSchema) ? Stream.fromEffect(failure) : failure;
47
- }
48
- return Rpc.wrapMap(constructed.success, (result) =>
49
- Effect.isEffect(result) ? boundary(result) : Stream.catchCause(result, (cause) => Stream.fromEffect(boundary(Effect.failCause(cause)))),
50
- );
51
- },
52
- ]),
48
+ Array.from(group.requests.values(), (rpc) => {
49
+ const tag = rpc._tag;
50
+ const handler = handlers[tag];
51
+ const streaming = RpcSchema.isStreamSchema(rpc.successSchema);
52
+ const schema = RpcSchema.isStreamSchema(rpc.successSchema) ? Schema.Union([rpc.errorSchema, rpc.successSchema.error]) : rpc.errorSchema;
53
+ const boundary = rpcBoundary(schema, options);
54
+ return [
55
+ tag,
56
+ (...args: never[]) => {
57
+ const constructed = Result.try(() => handler(...args));
58
+ if (Result.isFailure(constructed)) {
59
+ const failure = boundary(Effect.die(constructed.failure));
60
+ return streaming ? Stream.fromEffect(failure) : failure;
61
+ }
62
+ return Rpc.wrapMap(constructed.success, (result) =>
63
+ Effect.isEffect(result) ? boundary(result) : Stream.catchCause(result, (cause) => Stream.fromEffect(boundary(Effect.failCause(cause)))),
64
+ );
65
+ },
66
+ ];
67
+ }),
53
68
  );
54
69
  }
@@ -43,6 +43,9 @@ function containsSensitive(ast: SchemaAST.AST, keys: ReadonlySet<string>): boole
43
43
  );
44
44
  }
45
45
  function preservesFields(source: SchemaAST.AST, target: SchemaAST.AST): boolean {
46
+ if (source._tag === "Declaration" && source.annotations?.["~constructor"] !== undefined && source.typeParameters[0] === target) {
47
+ return true;
48
+ }
46
49
  return (
47
50
  source._tag === "Declaration"
48
51
  && Predicate.hasProperty(source.annotations?.representation, "id")
@@ -70,6 +73,16 @@ export function collectEncodedKeys(ast: SchemaAST.AST, keys: Set<string>) {
70
73
  source = link.to;
71
74
  }
72
75
  }
76
+ export function sensitiveEncoding(ast: SchemaAST.AST, keys: ReadonlySet<string>): boolean {
77
+ let source = ast;
78
+ for (const link of ast.encoding ?? []) {
79
+ if (!preservesFields(source, link.to) && (containsSensitive(source, keys) || containsSensitive(link.to, keys))) {
80
+ return true;
81
+ }
82
+ source = link.to;
83
+ }
84
+ return false;
85
+ }
73
86
  function fixedOutput(ast: SchemaAST.AST): boolean {
74
87
  if (ast._tag === "Union") {
75
88
  return ast.types.every(fixedOutput);
@@ -2,21 +2,30 @@ import { Effect } from "effect";
2
2
  import { Rpc, type RpcGroup } from "effect/rpc";
3
3
  import { schemaFieldKeys, validateFieldScope } from "#schemaFieldScope.ts";
4
4
  import { maskedValue } from "#schemaSensitiveAst.ts";
5
- import { isSensitiveKey } from "#sensitive.ts";
5
+ import { isSensitiveKey, REDACTED } from "#sensitive.ts";
6
+ import { wireRedactor } from "#wireRedactor.ts";
6
7
 
7
8
  const rpcSensitiveKeys = Effect.fnUntraced(function* (rpc: Rpc.AnyWithProps, extra: readonly string[]) {
9
+ const Errors = Rpc.exitSchema(rpc).error;
8
10
  const schemas = [
9
11
  { ast: rpc.payloadSchema.ast, name: `${rpc._tag}.payload` },
10
12
  { ast: rpc.successSchema.ast, name: `${rpc._tag}.success` },
11
13
  { ast: rpc.errorSchema.ast, name: `${rpc._tag}.error` },
12
14
  { ast: rpc.defectSchema.ast, name: `${rpc._tag}.defect` },
13
15
  ...[...rpc.middlewares].map((middleware, index) => ({ ast: middleware.error.ast, name: `${rpc._tag}.middleware[${index}].error` })),
16
+ { ast: Errors.ast, name: `${rpc._tag}.exit.error` },
14
17
  ];
15
18
  yield* validateFieldScope(schemas, extra);
16
19
  const keys = new Set(schemas.flatMap(({ ast }) => [...schemaFieldKeys(ast, extra)]));
17
- return Object.assign((key: string) => keys.has(key) || isSensitiveKey(key), {
20
+ function sensitive(key: string) {
21
+ return keys.has(key) || isSensitiveKey(key);
22
+ }
23
+ const fail = yield* wireRedactor(Errors.ast, `${rpc._tag}.error`, sensitive, keys);
24
+ const die = yield* wireRedactor(rpc.defectSchema.ast, `${rpc._tag}.defect`, sensitive, keys);
25
+ return Object.assign(sensitive, {
18
26
  die: maskedValue(rpc.defectSchema.ast, keys),
19
- fail: [rpc.errorSchema, ...[...rpc.middlewares].map((m) => m.error)].some((schema) => maskedValue(schema.ast, keys)),
27
+ fail: maskedValue(Errors.ast, keys),
28
+ redactFailure: (reason: "Fail" | "Die", value: unknown) => (reason === "Fail" ? fail(value) : die(value)),
20
29
  });
21
30
  });
22
31
  export const schemaSensitiveKeys = Effect.fnUntraced(function* <R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>, extra: readonly string[] = []) {
@@ -33,5 +42,9 @@ export const schemaSensitiveKeys = Effect.fnUntraced(function* <R extends Rpc.An
33
42
  forRpc: (tag: string | undefined) => (tag === undefined ? sensitive : (policies.get(tag) ?? sensitive)),
34
43
  maskFailure: (tag: string | undefined, reason: "Fail" | "Die") =>
35
44
  [...policies].some(([method, policy]) => (tag === undefined || tag === method) && (reason === "Fail" ? policy.fail : policy.die)),
45
+ redactFailure: (tag: string | undefined, reason: "Fail" | "Die", value: unknown) => {
46
+ const policy = policies.get(tag ?? "");
47
+ return policy === undefined ? REDACTED : policy.redactFailure(reason, value);
48
+ },
36
49
  });
37
50
  });
@@ -0,0 +1,94 @@
1
+ import { Effect, Exit, Predicate, Schema, type SchemaAST } from "effect";
2
+ import { REDACTED } from "#sensitive.ts";
3
+
4
+ export class InvalidRedactionMask extends Schema.TaggedError<InvalidRedactionMask>()("InvalidRedactionMask", {
5
+ field: Schema.String,
6
+ message: Schema.String,
7
+ }) {}
8
+
9
+ type Ancestors = ReadonlySet<SchemaAST.AST>;
10
+
11
+ const objectMask = Effect.fnUntraced(function* (ast: SchemaAST.Objects, field: string, seen: Ancestors) {
12
+ const entries: [PropertyKey, unknown][] = [];
13
+ for (const property of ast.propertySignatures) {
14
+ if (!property.type.context?.isOptional) {
15
+ entries.push([property.name, yield* wireMask(property.type, `${field}.${String(property.name)}`, seen)]);
16
+ }
17
+ }
18
+ return Object.fromEntries(entries);
19
+ });
20
+ const arrayMask = Effect.fnUntraced(function* (ast: SchemaAST.Arrays, field: string, seen: Ancestors) {
21
+ const items: unknown[] = [];
22
+ for (const [index, element] of ast.elements.entries()) {
23
+ if (!element.context?.isOptional) {
24
+ items.push(yield* wireMask(element, `${field}[${index}]`, seen));
25
+ }
26
+ }
27
+ return items;
28
+ });
29
+ const unionMask = Effect.fnUntraced(function* (ast: SchemaAST.Union, field: string, seen: Ancestors) {
30
+ for (const member of ast.types) {
31
+ const candidate = yield* Effect.result(wireMask(member, field, seen));
32
+ if (candidate._tag === "Success") {
33
+ return candidate.success;
34
+ }
35
+ }
36
+ return undefined;
37
+ });
38
+ function genericMask(ast: SchemaAST.AST, field: string, seen: Ancestors): Effect.Effect<unknown, InvalidRedactionMask> {
39
+ switch (ast._tag) {
40
+ case "String":
41
+ case "Unknown":
42
+ case "Any":
43
+ return Effect.succeed(REDACTED);
44
+ case "Number":
45
+ return Effect.succeed(0);
46
+ case "Boolean":
47
+ return Effect.succeed(false);
48
+ case "Null":
49
+ return Effect.succeed(null);
50
+ case "Undefined":
51
+ case "Void":
52
+ return Effect.succeed(undefined);
53
+ case "Literal":
54
+ return Effect.succeed(ast.literal);
55
+ case "Objects":
56
+ return objectMask(ast, field, seen);
57
+ case "Arrays":
58
+ return arrayMask(ast, field, seen);
59
+ case "Union":
60
+ return unionMask(ast, field, seen);
61
+ case "Suspend":
62
+ return wireMask(ast.thunk(), field, seen);
63
+ default:
64
+ return Effect.succeed(REDACTED);
65
+ }
66
+ }
67
+
68
+ export const wireMask = Effect.fnUntraced(function* (
69
+ ast: SchemaAST.AST,
70
+ field: string,
71
+ ancestors: Ancestors = new Set(),
72
+ ): Effect.fn.Return<unknown, InvalidRedactionMask> {
73
+ function invalid() {
74
+ return new InvalidRedactionMask({
75
+ field,
76
+ message: `Sensitive field "${field}" needs a schema-compatible mask; supply a redactionMask annotation on its wire schema.`,
77
+ });
78
+ }
79
+ if (ancestors.has(ast)) {
80
+ return yield* invalid();
81
+ }
82
+ const seen = new Set(ancestors).add(ast);
83
+ const Annotations = Schema.resolveAnnotations(Schema.make(ast));
84
+ let value: unknown;
85
+ if (Predicate.hasProperty(Annotations, "redactionMask")) {
86
+ value = Annotations.redactionMask;
87
+ } else {
88
+ value = ast.encoding === undefined ? yield* genericMask(ast, field, seen) : yield* wireMask(ast.encoding.at(-1)?.to ?? ast, field, seen);
89
+ }
90
+ if (Exit.isFailure(Schema.decodeUnknownExit(Schema.toCodecJson(Schema.make<Schema.Codec<unknown>>(ast)))(value))) {
91
+ return yield* invalid();
92
+ }
93
+ return value;
94
+ });
@@ -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
+ });