@shivaedev/errors 0.1.1 → 0.1.3

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 ADDED
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ ## 0.1.3 - 2026-10-31
4
+
5
+ ### Fixed
6
+
7
+ - Include `CHANGELOG.md` in published package contents so release notes are available in `node_modules`.
8
+ - Release checks reject published package `files` lists that omit `CHANGELOG.md`, naming the package and how to include it.
9
+
10
+ ## 0.1.2 - 2026-10-30
11
+
12
+ ### Fixed
13
+
14
+ - Report failures and defects masked by `rpcBoundary` through Effect's error reporters at Error severity with diagnostic redaction before returning the rejection.
15
+ - Log boundary-masked RPC failures once at ERROR with their redacted stack; keep declared rejections at INFO.
16
+
17
+ ## 0.1.1 - 2026-10-30
18
+
19
+ ### Changed
20
+
21
+ - Require Effect 4.0.6 and align the Effect companion package peers and generated starter catalog to the same exact release. Install the 4.0.6 Effect stack together.
22
+
23
+ ### Fixed
24
+
25
+ - Register hosted RPC cache-control handling through Effect 4.0.6's request-scoped pre-response API so responses keep `private, no-store`.
26
+ - Accept native RPC's `application/x-ndjson` media type while preserving `application/ndjson` callers at the transport guard.
27
+ - Keep subscriptions open after their HTTP request body completes; closing the socket still cancels them.
28
+
29
+ ## 0.1.0 - 2026-10-27
30
+
31
+ ### Added
32
+
33
+ - First release of `@shivaedev/errors`: the shared expected failures (`taxonomy.ts`), `rejectedField` and the secret-masking policy (`sensitive.ts`, `redact.ts`, `redactCause.ts`) that every Effect boundary uses, and `rpcBoundary` (`boundary.ts`), which keeps declared failures and maps every other failure to one declared fallback. Every module is browser-safe and depends only on `effect`.
package/README.md CHANGED
@@ -41,10 +41,11 @@ A handler fails with `new Conflict({ field: "name", message: "Name is taken" })`
41
41
 
42
42
  ### Mapping unexpected failures at the handler edge
43
43
 
44
- At the handler edge, `rpcBoundary(schema, options)` keeps declared rejections and maps other failures and defects into one declared fallback. It leaves successful values and interruption intact:
44
+ Use `rpcBoundary` only at the RPC handler edge, never inside services. Services keep their typed failures and defects so callers can handle them. At that edge, `rpcBoundary(schema, options)` keeps declared rejections and maps other failures and defects into one declared fallback. It leaves successful values and interruption intact:
45
45
 
46
46
  ```ts
47
47
  import { Schema } from "effect";
48
+ import { Rpc, RpcGroup } from "effect/rpc";
48
49
  import { Conflict, InternalServerError } from "@shivaedev/errors/taxonomy.ts";
49
50
  import { rpcBoundary } from "@shivaedev/errors/boundary.ts";
50
51
 
@@ -53,10 +54,19 @@ const expose = rpcBoundary(Rejection, {
53
54
  unexpected: (message) => new InternalServerError({ message }),
54
55
  });
55
56
 
56
- export const save = storageSave.pipe(expose);
57
+ const Api = RpcGroup.make(
58
+ Rpc.make("Save", {
59
+ error: Rejection,
60
+ success: Schema.String,
61
+ }),
62
+ );
63
+
64
+ export const Handlers = Api.toLayer({
65
+ Save: () => storageSave.pipe(expose),
66
+ });
57
67
  ```
58
68
 
59
- `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. 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.
69
+ `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.
60
70
 
61
71
  ### Masking secrets in failures
62
72
 
@@ -1 +1 @@
1
- {"version":3,"file":"boundary.d.ts","sourceRoot":"","sources":["../src/boundary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE/C,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,CAapF"}
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"}
package/dist/boundary.js CHANGED
@@ -1,5 +1,19 @@
1
- import { Cause, Effect, Schema } from "effect";
1
+ import { Cause, Context, Effect, ErrorReporter, Predicate, Schema } from "effect";
2
+ import { redactDiagnosticCause } from "#redactCause.ts";
2
3
  import { isSensitiveKey, redactText } from "#sensitive.ts";
4
+ function reportable(value) {
5
+ return Object.assign(Predicate.isObject(value) ? value : new Error(String(value)), { [ErrorReporter.severity]: "Error" });
6
+ }
7
+ function reportUnexpected(cause) {
8
+ const diagnostic = redactDiagnosticCause(cause);
9
+ return ErrorReporter.report(Cause.fromReasons(diagnostic.reasons.map((reason) => {
10
+ if (Cause.isInterruptReason(reason)) {
11
+ return reason;
12
+ }
13
+ const reported = Cause.isFailReason(reason) ? Cause.makeFailReason(reportable(reason.error)) : Cause.makeDieReason(reportable(reason.defect));
14
+ return reported.annotate(Context.makeUnsafe(reason.annotations));
15
+ })));
16
+ }
3
17
  export function rpcBoundary(schema, options) {
4
18
  const declared = Schema.is(schema);
5
19
  return (self) => Effect.catchCause(self, (cause) => {
@@ -11,8 +25,11 @@ export function rpcBoundary(schema, options) {
11
25
  return Effect.fail(reason.error);
12
26
  }
13
27
  const rejection = options.unexpected(redactText(options.message ?? "Internal server error", isSensitiveKey));
14
- Object.defineProperty(rejection, "cause", { configurable: true, enumerable: false, value: cause });
15
- return Effect.failCause(Cause.fromReasons([...Cause.fail(rejection).reasons, ...cause.reasons.filter(Cause.isInterruptReason)]));
28
+ Object.defineProperties(rejection, {
29
+ cause: { configurable: true, enumerable: false, value: cause },
30
+ [ErrorReporter.ignore]: { configurable: true, enumerable: false, value: true },
31
+ });
32
+ return reportUnexpected(cause).pipe(Effect.andThen(Effect.failCause(Cause.fromReasons([...Cause.fail(rejection).reasons, ...cause.reasons.filter(Cause.isInterruptReason)]))));
16
33
  });
17
34
  }
18
35
  //# sourceMappingURL=boundary.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"boundary.js","sourceRoot":"","sources":["../src/boundary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAK3D,MAAM,UAAU,WAAW,CAIzB,MAAe,EAAE,OAA4C;IAC9D,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;IACnC,OAAO,CAAU,IAA4B,EAAwC,EAAE,CACtF,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;QACjC,IAAI,KAAK,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;YACpC,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAC3F,CAAC;QACD,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAClG,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;QACD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,IAAI,uBAAuB,EAAE,cAAc,CAAC,CAAC,CAAC;QAC7G,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QACnG,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;IAClI,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"boundary.js","sourceRoot":"","sources":["../src/boundary.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.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Typed expected failures and secret-safe failure reporting for Effect applications",
5
5
  "keywords": [
6
6
  "effect"
@@ -41,11 +41,12 @@
41
41
  "!src/**/*.spec.ts",
42
42
  "!src/**/*.spec.tsx",
43
43
  "!src/**/test-support",
44
+ "CHANGELOG.md",
44
45
  "README.md"
45
46
  ],
46
47
  "devDependencies": {
47
- "@shivaedev/quality": "0.45.0",
48
- "@shivaedev/test": "0.10.1",
48
+ "@shivaedev/quality": "0.48.1",
49
+ "@shivaedev/test": "0.12.0",
49
50
  "@types/node": "24.19.2",
50
51
  "effect": "4.0.6",
51
52
  "typescript": "7.0.2",
package/src/boundary.ts CHANGED
@@ -1,5 +1,24 @@
1
- import { Cause, Effect, Schema } from "effect";
1
+ import { Cause, Context, Effect, ErrorReporter, Predicate, Schema } from "effect";
2
+ import { redactDiagnosticCause } from "#redactCause.ts";
2
3
  import { isSensitiveKey, redactText } from "#sensitive.ts";
4
+
5
+ function reportable(value: unknown) {
6
+ return Object.assign(Predicate.isObject(value) ? value : new Error(String(value)), { [ErrorReporter.severity]: "Error" });
7
+ }
8
+ function reportUnexpected(cause: Cause.Cause<unknown>): Effect.Effect<void> {
9
+ const diagnostic = redactDiagnosticCause(cause);
10
+ return ErrorReporter.report(
11
+ Cause.fromReasons(
12
+ diagnostic.reasons.map((reason) => {
13
+ if (Cause.isInterruptReason(reason)) {
14
+ return reason;
15
+ }
16
+ const reported = Cause.isFailReason(reason) ? Cause.makeFailReason(reportable(reason.error)) : Cause.makeDieReason(reportable(reason.defect));
17
+ return reported.annotate(Context.makeUnsafe(reason.annotations));
18
+ }),
19
+ ),
20
+ );
21
+ }
3
22
  export interface RpcBoundaryOptions<E> {
4
23
  readonly message?: string;
5
24
  readonly unexpected: (message: string) => E;
@@ -20,7 +39,12 @@ export function rpcBoundary<
20
39
  return Effect.fail(reason.error);
21
40
  }
22
41
  const rejection = options.unexpected(redactText(options.message ?? "Internal server error", isSensitiveKey));
23
- Object.defineProperty(rejection, "cause", { configurable: true, enumerable: false, value: cause });
24
- return Effect.failCause(Cause.fromReasons([...Cause.fail(rejection).reasons, ...cause.reasons.filter(Cause.isInterruptReason)]));
42
+ Object.defineProperties(rejection, {
43
+ cause: { configurable: true, enumerable: false, value: cause },
44
+ [ErrorReporter.ignore]: { configurable: true, enumerable: false, value: true },
45
+ });
46
+ return reportUnexpected(cause).pipe(
47
+ Effect.andThen(Effect.failCause(Cause.fromReasons([...Cause.fail(rejection).reasons, ...cause.reasons.filter(Cause.isInterruptReason)]))),
48
+ );
25
49
  });
26
50
  }