@synnaxlabs/freighter 0.41.0 → 0.42.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/dist/http.d.ts CHANGED
@@ -16,6 +16,6 @@ export declare class HTTPClient extends MiddlewareCollector implements UnaryClie
16
16
  fetch: typeof fetch;
17
17
  constructor(endpoint: URL, encoder: binary.Codec, secure?: boolean);
18
18
  get headers(): Record<string, string>;
19
- send<RQ extends z.ZodTypeAny, RS extends z.ZodTypeAny = RQ>(target: string, req: z.input<RQ> | z.output<RQ>, reqSchema: RQ, resSchema: RS): Promise<[z.output<RS> | null, Error | null]>;
19
+ send<RQ extends z.ZodTypeAny, RS extends z.ZodTypeAny = RQ>(target: string, req: z.input<RQ> | z.output<RQ>, reqSchema: RQ, resSchema: RS): Promise<[z.output<RS>, null] | [null, Error]>;
20
20
  }
21
21
  //# sourceMappingURL=http.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,MAAM,EAAW,KAAK,GAAG,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,OAAO,EAAgB,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,eAAO,MAAM,uBAAuB,iBAAiB,CAAC;AActD;;;;;;GAMG;AACH,qBAAa,UAAW,SAAQ,mBAAoB,YAAW,WAAW;IACxE,QAAQ,EAAE,GAAG,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC;IACtB,KAAK,EAAE,OAAO,KAAK,CAAC;gBAER,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,GAAE,OAAe;IAczE,IAAI,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIpC;IAEK,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC,UAAU,EAAE,EAAE,SAAS,CAAC,CAAC,UAAU,GAAG,EAAE,EAC9D,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAC/B,SAAS,EAAE,EAAE,EACb,SAAS,EAAE,EAAE,GACZ,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC;CAuDhD"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,MAAM,EAAmB,KAAK,GAAG,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,OAAO,EAAgB,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,eAAO,MAAM,uBAAuB,iBAAiB,CAAC;AAoBtD;;;;;;GAMG;AACH,qBAAa,UAAW,SAAQ,mBAAoB,YAAW,WAAW;IACxE,QAAQ,EAAE,GAAG,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC;IACtB,KAAK,EAAE,OAAO,KAAK,CAAC;gBAER,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,GAAE,OAAe;IAczE,IAAI,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIpC;IAEK,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC,UAAU,EAAE,EAAE,SAAS,CAAC,CAAC,UAAU,GAAG,EAAE,EAC9D,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAC/B,SAAS,EAAE,EAAE,EACb,SAAS,EAAE,EAAE,GACZ,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;CAwDjD"}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- export { BaseTypedError, decodeError, encodeError, EOF, errorMatcher, type ErrorPayload, errorZ, type MatchableErrorType, registerError, StreamClosed, type TypedError, Unreachable, } from './errors';
1
+ export { EOF, StreamClosed, Unreachable } from './errors';
2
2
  export { HTTPClient } from './http';
3
3
  export { type Context, type Middleware, type Next } from './middleware';
4
4
  export { type Stream, type StreamClient } from './stream';
5
5
  export { sendRequired, type UnaryClient, unaryWithBreaker } from './unary';
6
6
  export { WebSocketClient } from './websocket';
7
+ export { type WebsocketMessage } from './websocket';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EACL,cAAc,EACd,WAAW,EACX,WAAW,EACX,GAAG,EACH,YAAY,EACZ,KAAK,YAAY,EACjB,MAAM,EACN,KAAK,kBAAkB,EACvB,aAAa,EACb,YAAY,EACZ,KAAK,UAAU,EACf,WAAW,GACZ,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,IAAI,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,IAAI,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
package/dist/unary.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { breaker } from '@synnaxlabs/x';
1
+ import { breaker, errors } from '@synnaxlabs/x';
2
2
  import { z } from 'zod';
3
3
  import { Transport } from './transport';
4
4
  /**
@@ -12,7 +12,7 @@ export interface UnaryClient extends Transport {
12
12
  * @param req - The request to send.
13
13
  * @param resSchema - The schema to validate the response against.
14
14
  */
15
- send: <RQ extends z.ZodTypeAny, RS extends z.ZodTypeAny = RQ>(target: string, req: z.input<RQ> | z.output<RQ>, reqSchema: RQ, resSchema: RS) => Promise<[z.output<RS>, null] | [null, Error]>;
15
+ send: <RQ extends z.ZodTypeAny, RS extends z.ZodTypeAny = RQ>(target: string, req: z.input<RQ> | z.output<RQ>, reqSchema: RQ, resSchema: RS) => Promise<errors.Return<z.output<RS>>>;
16
16
  }
17
17
  export declare const unaryWithBreaker: (base: UnaryClient, cfg: breaker.Config) => UnaryClient;
18
18
  export declare const sendRequired: <RQ extends z.ZodTypeAny, RS extends z.ZodTypeAny = RQ>(client: UnaryClient, target: string, req: z.input<RQ> | z.output<RQ>, reqSchema: RQ, resSchema: RS) => Promise<z.output<RS>>;
@@ -1 +1 @@
1
- {"version":3,"file":"unary.d.ts","sourceRoot":"","sources":["../src/unary.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,CAAC;AAI7B,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;GAGG;AACH,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C;;;;;OAKG;IACH,IAAI,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,UAAU,EAAE,EAAE,SAAS,CAAC,CAAC,UAAU,GAAG,EAAE,EAC1D,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAC/B,SAAS,EAAE,EAAE,EACb,SAAS,EAAE,EAAE,KACV,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;CACpD;AAED,eAAO,MAAM,gBAAgB,GAC3B,MAAM,WAAW,EACjB,KAAK,OAAO,CAAC,MAAM,KAClB,WA2BF,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,EAAE,SAAS,CAAC,CAAC,UAAU,EACvB,EAAE,SAAS,CAAC,CAAC,UAAU,GAAG,EAAE,EAE5B,QAAQ,WAAW,EACnB,QAAQ,MAAM,EACd,KAAK,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAC/B,WAAW,EAAE,EACb,WAAW,EAAE,KACZ,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAItB,CAAC"}
1
+ {"version":3,"file":"unary.d.ts","sourceRoot":"","sources":["../src/unary.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,CAAC;AAI7B,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;GAGG;AACH,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C;;;;;OAKG;IACH,IAAI,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,UAAU,EAAE,EAAE,SAAS,CAAC,CAAC,UAAU,GAAG,EAAE,EAC1D,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAC/B,SAAS,EAAE,EAAE,EACb,SAAS,EAAE,EAAE,KACV,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;CAC3C;AAED,eAAO,MAAM,gBAAgB,GAC3B,MAAM,WAAW,EACjB,KAAK,OAAO,CAAC,MAAM,KAClB,WA4BF,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,EAAE,SAAS,CAAC,CAAC,UAAU,EACvB,EAAE,SAAS,CAAC,CAAC,UAAU,GAAG,EAAE,EAE5B,QAAQ,WAAW,EACnB,QAAQ,MAAM,EACd,KAAK,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAC/B,WAAW,EAAE,EACb,WAAW,EAAE,KACZ,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAItB,CAAC"}
@@ -1,7 +1,12 @@
1
- import { binary, URL } from '@synnaxlabs/x';
1
+ import { binary, errors, URL } from '@synnaxlabs/x';
2
2
  import { z } from 'zod';
3
3
  import { MiddlewareCollector } from './middleware';
4
4
  import { Stream, StreamClient } from './stream';
5
+ export type WebsocketMessage<P = unknown> = {
6
+ type: "data" | "close" | "open";
7
+ error?: errors.Payload;
8
+ payload?: P;
9
+ };
5
10
  export declare const FREIGHTER_METADATA_PREFIX = "freighterctx";
6
11
  /**
7
12
  * WebSocketClient is an implementation of StreamClient that is backed by
@@ -10,6 +15,7 @@ export declare const FREIGHTER_METADATA_PREFIX = "freighterctx";
10
15
  export declare class WebSocketClient extends MiddlewareCollector implements StreamClient {
11
16
  baseUrl: URL;
12
17
  encoder: binary.Codec;
18
+ secure: boolean;
13
19
  static readonly MESSAGE_TYPE = "arraybuffer";
14
20
  /**
15
21
  * @param encoder - The encoder to use for encoding messages and decoding
@@ -17,6 +23,7 @@ export declare class WebSocketClient extends MiddlewareCollector implements Stre
17
23
  * @param baseEndpoint - A base url to use as a prefix for all requests.
18
24
  */
19
25
  constructor(baseEndpoint: URL, encoder: binary.Codec, secure?: boolean);
26
+ withCodec(codec: binary.Codec): WebSocketClient;
20
27
  /** Implements the StreamClient interface. */
21
28
  stream<RQ extends z.ZodTypeAny, RS extends z.ZodTypeAny = RQ>(target: string, reqSchema: RQ, resSchema: RS): Promise<Stream<RQ, RS>>;
22
29
  private buildURL;
@@ -1 +1 @@
1
- {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,MAAM,EAA6B,KAAK,GAAG,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAgB,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAoH1D,eAAO,MAAM,yBAAyB,iBAAiB,CAAC;AAQxD;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,mBAAoB,YAAW,YAAY;IAC9E,OAAO,EAAE,GAAG,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC;IAEtB,MAAM,CAAC,QAAQ,CAAC,YAAY,iBAAiB;IAE7C;;;;OAIG;gBACS,YAAY,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,UAAQ;IAMpE,6CAA6C;IACvC,MAAM,CAAC,EAAE,SAAS,CAAC,CAAC,UAAU,EAAE,EAAE,SAAS,CAAC,CAAC,UAAU,GAAG,EAAE,EAChE,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,EAAE,EACb,SAAS,EAAE,EAAE,GACZ,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAmB1B,OAAO,CAAC,QAAQ;YAWF,UAAU;CAsBzB"}
1
+ {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"AASA,OAAO,EACL,KAAK,MAAM,EAEX,MAAM,EAEN,KAAK,GAAG,EACT,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAgB,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAc1D,MAAM,MAAM,gBAAgB,CAAC,CAAC,GAAG,OAAO,IAAI;IAC1C,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,CAAC;CACb,CAAC;AA8GF,eAAO,MAAM,yBAAyB,iBAAiB,CAAC;AAIxD;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,mBAAoB,YAAW,YAAY;IAC9E,OAAO,EAAE,GAAG,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAEhB,MAAM,CAAC,QAAQ,CAAC,YAAY,iBAAiB;IAE7C;;;;OAIG;gBACS,YAAY,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,UAAQ;IAOpE,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,GAAG,eAAe;IAM/C,6CAA6C;IACvC,MAAM,CAAC,EAAE,SAAS,CAAC,CAAC,UAAU,EAAE,EAAE,SAAS,CAAC,CAAC,UAAU,GAAG,EAAE,EAChE,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,EAAE,EACb,SAAS,EAAE,EAAE,GACZ,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAmB1B,OAAO,CAAC,QAAQ;YAWF,UAAU;CAsBzB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synnaxlabs/freighter",
3
- "version": "0.41.0",
3
+ "version": "0.42.0",
4
4
  "type": "module",
5
5
  "description": "a modular transport abstraction",
6
6
  "repository": "https://github.com/synnaxlabs/synnax/tree/main/freighter/ts",
@@ -16,19 +16,20 @@
16
16
  "dependencies": {
17
17
  "node-fetch": "^2.7.0",
18
18
  "ws": "^8.18.1",
19
- "zod": "^3.24.2",
20
- "@synnaxlabs/alamos": "0.41.0",
21
- "@synnaxlabs/x": "0.41.0"
19
+ "zod": "^4.0.0-beta",
20
+ "@synnaxlabs/alamos": "0.42.0",
21
+ "@synnaxlabs/x": "0.42.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/node": "^22.13.10",
25
+ "@vitest/coverage-v8": "^3.1.3",
25
26
  "eslint": "^9.24.0",
26
27
  "madge": "^8.0.0",
27
28
  "typescript": "^5.8.3",
28
29
  "vite": "^6.2.5",
29
30
  "vitest": "^3.1.1",
30
- "@synnaxlabs/vite-plugin": "0.0.1",
31
31
  "@synnaxlabs/tsconfig": "0.0.2",
32
+ "@synnaxlabs/vite-plugin": "0.0.1",
32
33
  "eslint-config-synnaxlabs": "0.0.1"
33
34
  },
34
35
  "main": "dist/freighter.cjs",
package/src/alamos.ts CHANGED
@@ -9,13 +9,14 @@
9
9
 
10
10
  import { type Instrumentation } from "@synnaxlabs/alamos";
11
11
 
12
- import { type Context,type Middleware } from "@/middleware";
12
+ import { type Context, type Middleware } from "@/middleware";
13
13
 
14
14
  export const middleware =
15
15
  (instrumentation: Instrumentation): Middleware =>
16
16
  async (context, next) => {
17
17
  if (context.role === "client") instrumentation.T.propagate(context.params);
18
- const [res, exc] = await instrumentation.T.trace(
18
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
19
+ const [res, exc] = (await instrumentation.T.trace(
19
20
  context.target,
20
21
  "debug",
21
22
  async (span): Promise<[Context, Error | null]> => {
@@ -23,7 +24,7 @@ export const middleware =
23
24
  if (err != null) span.recordError(err);
24
25
  return [ctx, err];
25
26
  },
26
- );
27
+ )) as [Context, Error | null];
27
28
  log(context, instrumentation, exc);
28
29
  return [res, exc];
29
30
  };
@@ -7,86 +7,19 @@
7
7
  // License, use of this software will be governed by the Apache License, Version 2.0,
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
+ import { errors } from "@synnaxlabs/x";
10
11
  import { describe, expect, test } from "vitest";
11
12
 
12
- import {
13
- assertErrorType,
14
- BaseTypedError,
15
- decodeError,
16
- encodeError,
17
- EOF,
18
- type ErrorPayload,
19
- FREIGHTER,
20
- isTypedError,
21
- NONE,
22
- registerError,
23
- StreamClosed,
24
- type TypedError,
25
- UNKNOWN,
26
- UnknownError,
27
- Unreachable,
28
- } from "@/errors";
29
-
30
- class MyCustomError extends BaseTypedError {
31
- type = "MyCustomError";
32
- }
33
-
34
- const myCustomErrorEncoder = (error: MyCustomError): ErrorPayload | null => {
35
- if (error.type !== "MyCustomError") return null;
36
- return { type: "MyCustomError", data: error.message };
37
- };
38
-
39
- const myCustomErrorDecoder = (encoded: ErrorPayload): TypedError =>
40
- new MyCustomError(encoded.data);
13
+ import { EOF, FreighterError, StreamClosed, Unreachable } from "@/errors";
41
14
 
42
15
  describe("errors", () => {
43
- test("isTypedError", () => {
44
- const error = new MyCustomError("test");
45
- const fError = isTypedError(error);
46
- expect(fError).toBeTruthy();
47
- expect(error.type).toEqual("MyCustomError");
48
- });
49
-
50
- test("encoding and decoding a custom error through registry", () => {
51
- registerError({
52
- encode: myCustomErrorEncoder,
53
- decode: myCustomErrorDecoder,
54
- });
55
- const error = new MyCustomError("test");
56
- const encoded = encodeError(error);
57
- expect(encoded.type).toEqual("MyCustomError");
58
- expect(encoded.data).toEqual("test");
59
- const decoded = assertErrorType<MyCustomError>(
60
- "MyCustomError",
61
- decodeError(encoded),
62
- );
63
- expect(decoded.message).toEqual("test");
64
- });
65
-
66
- test("encoding and decoding a null error", () => {
67
- const encoded = encodeError(null);
68
- expect(encoded.type).toEqual(NONE);
69
- expect(encoded.data).toEqual("");
70
- const decoded = decodeError(encoded);
71
- expect(decoded).toBeNull();
72
- });
73
-
74
- test("encoding and decoding an unrecognized error", () => {
75
- const error = new Error("test");
76
- const encoded = encodeError(error);
77
- expect(encoded.type).toEqual(UNKNOWN);
78
- expect(encoded.data).toEqual("{}");
79
- const decoded = decodeError(encoded);
80
- expect(decoded).toEqual(new UnknownError("{}"));
81
- });
82
-
83
16
  test("encoding and decoding freighter errors", () => {
84
17
  [new EOF(), new StreamClosed(), new Unreachable()].forEach((error) => {
85
- const encoded = encodeError(error);
86
- expect(encoded.type.startsWith(FREIGHTER)).toBeTruthy();
18
+ const encoded = errors.encode(error);
19
+ expect(encoded.type.startsWith(FreighterError.TYPE)).toBeTruthy();
87
20
  expect(encoded.data).toEqual(error.message);
88
- const decoded = decodeError(encoded);
89
- expect(decoded).toEqual(error);
21
+ const decoded = errors.decode(encoded);
22
+ expect(error.matches(decoded)).toBeTruthy();
90
23
  });
91
24
  });
92
25
  });
package/src/errors.ts CHANGED
@@ -7,189 +7,41 @@
7
7
  // License, use of this software will be governed by the Apache License, Version 2.0,
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
- import { URL } from "@synnaxlabs/x";
11
- import { z } from "zod";
12
-
13
- /** Basic interface for an error or error type that can be matched against a candidate error */
14
- export interface MatchableErrorType {
15
- /**
16
- * @returns a function that matches errors of the given type. Returns true if
17
- * the provided instance of Error or a string message contains the provided error type.
18
- */
19
- matches: (e: string | Error | unknown) => boolean;
20
- }
21
-
22
- export interface TypedError extends Error {
23
- discriminator: "FreighterError";
24
- /**
25
- * @description Returns a unique type identifier for the error. Freighter uses this to
26
- * determine the correct decoder to use on the other end of the freighter.
27
- */
28
- type: string;
29
- }
10
+ import { errors, URL } from "@synnaxlabs/x";
30
11
 
31
12
  /**
32
- * @param type - the error type to match
33
- * @returns a function that matches errors of the given type. Returns true if
34
- * the provided instance of Error or a string message contains the provided error type.
13
+ * @description Base class for all freighter-specific errors
35
14
  */
36
- export const errorMatcher =
37
- (type: string) =>
38
- (e: string | Error | unknown): boolean => {
39
- if (e != null && typeof e === "object" && "type" in e && typeof e.type === "string")
40
- return e.type.includes(type);
41
- if (e instanceof Error) return e.message.includes(type);
42
- if (typeof e !== "string") return false;
43
- return e.includes(type);
44
- };
45
-
46
- export class BaseTypedError extends Error implements TypedError {
47
- readonly discriminator = "FreighterError";
48
- type: string = "";
49
- }
50
-
51
- type ErrorDecoder = (encoded: ErrorPayload) => Error | null;
52
- type ErrorEncoder = (error: TypedError) => ErrorPayload | null;
53
-
54
- export const isTypedError = (error: unknown): error is TypedError => {
55
- if (error == null || typeof error !== "object") return false;
56
- const typedError = error as TypedError;
57
- if (typedError.discriminator !== "FreighterError") return false;
58
- if (!("type" in typedError))
59
- throw new Error(
60
- `Freighter error is missing its type property: ${JSON.stringify(typedError)}`,
61
- );
62
- return true;
63
- };
64
-
65
- export const assertErrorType = <T>(type: string, error?: Error | null): T => {
66
- if (error == null)
67
- throw new Error(`Expected error of type ${type} but got nothing instead`);
68
- if (!isTypedError(error))
69
- throw new Error(`Expected a typed error, got: ${error.message}`);
70
- if (error.type !== type)
71
- throw new Error(
72
- `Expected error of type ${type}, got ${error.type}: ${error.message}`,
73
- );
74
- return error as T;
75
- };
76
-
77
- export const UNKNOWN = "unknown";
78
- export const NONE = "nil";
79
- export const FREIGHTER = "freighter";
80
-
81
- export const errorZ = z.object({ type: z.string(), data: z.string() });
82
-
83
- export type ErrorPayload = z.infer<typeof errorZ>;
84
-
85
- interface errorProvider {
86
- encode: ErrorEncoder;
87
- decode: ErrorDecoder;
88
- }
89
-
90
- class Registry {
91
- private readonly providers: errorProvider[] = [];
92
-
93
- register(provider: errorProvider): void {
94
- this.providers.push(provider);
95
- }
96
-
97
- encode(error: unknown): ErrorPayload {
98
- if (error == null) return { type: NONE, data: "" };
99
- if (isTypedError(error))
100
- for (const provider of this.providers) {
101
- const payload = provider.encode(error);
102
- if (payload != null) return payload;
103
- }
104
- return { type: UNKNOWN, data: JSON.stringify(error) };
105
- }
106
-
107
- decode(payload?: ErrorPayload | null): Error | null {
108
- if (payload == null || payload.type === NONE) return null;
109
- if (payload.type === UNKNOWN) return new UnknownError(payload.data);
110
- for (const provider of this.providers) {
111
- const error = provider.decode(payload);
112
- if (error != null) return error;
113
- }
114
- return new UnknownError(payload.data);
115
- }
116
- }
117
-
118
- const REGISTRY = new Registry();
119
-
120
- /**
121
- * Registers a custom error type with the error registry, which allows it to be
122
- * encoded/decoded and sent over the network.
123
- *
124
- * @param type - A unique string identifier for the error type.
125
- * @param encode - A function that encodes the error into a string.
126
- * @param decode - A function that decodes the error from a string.
127
- */
128
- export const registerError = ({
129
- encode,
130
- decode,
131
- }: {
132
- encode: ErrorEncoder;
133
- decode: ErrorDecoder;
134
- }): void => REGISTRY.register({ encode, decode });
135
-
136
- /**
137
- * Encodes an error into a payload that can be sent between a freighter server
138
- * and client.
139
- * @param error - The error to encode.
140
- * @returns The encoded error.
141
- */
142
- export const encodeError = (error: unknown): ErrorPayload => REGISTRY.encode(error);
15
+ export class FreighterError extends errors.createTyped("freighter") {}
143
16
 
144
17
  /**
145
- * Decodes an error payload into an exception. If a custom decoder can be found
146
- * for the error type, it will be used. Otherwise, a generic Error containing
147
- * the error data is returned.
148
- *
149
- * @param payload - The encoded error payload.
150
- * @returns The decoded error.
18
+ * @description Error thrown when reaching the end of a file or stream
151
19
  */
152
- export const decodeError = (payload: ErrorPayload): Error | null =>
153
- REGISTRY.decode(payload);
154
-
155
- export class UnknownError extends BaseTypedError implements TypedError {
156
- type = "unknown";
157
- }
158
-
159
- const FREIGHTER_ERROR_TYPE = "freighter.";
160
-
161
- /** Thrown/returned when a stream closed normally. */
162
- export class EOF extends BaseTypedError implements TypedError {
163
- static readonly TYPE = `${FREIGHTER_ERROR_TYPE}eof`;
164
- type = EOF.TYPE;
165
- static readonly matches = errorMatcher(EOF.TYPE);
166
-
20
+ export class EOF extends FreighterError.sub("eof") {
167
21
  constructor() {
168
22
  super("EOF");
169
23
  }
170
24
  }
171
25
 
172
- /** Thrown/returned when a stream is closed abnormally. */
173
- export class StreamClosed extends BaseTypedError implements TypedError {
174
- static readonly TYPE = `${FREIGHTER_ERROR_TYPE}stream_closed`;
175
- static readonly matches = errorMatcher(StreamClosed.TYPE);
176
- type = StreamClosed.TYPE;
177
-
26
+ /**
27
+ * @description Error thrown when attempting to operate on a closed stream
28
+ */
29
+ export class StreamClosed extends FreighterError.sub("stream_closed") {
178
30
  constructor() {
179
31
  super("StreamClosed");
180
32
  }
181
33
  }
182
34
 
35
+ /**
36
+ * @description Arguments for constructing an Unreachable error
37
+ */
183
38
  export interface UnreachableArgs {
184
39
  message?: string;
185
40
  url?: URL;
186
41
  }
187
42
 
188
- /** Thrown when a target is unreachable. */
189
- export class Unreachable extends BaseTypedError implements TypedError {
190
- static readonly TYPE = `${FREIGHTER_ERROR_TYPE}unreachable`;
191
- type = Unreachable.TYPE;
192
- static readonly matches = errorMatcher(Unreachable.TYPE);
43
+ /** @description Thrown when a network target is unreachable. */
44
+ export class Unreachable extends FreighterError.sub("unreachable") {
193
45
  url: URL;
194
46
 
195
47
  constructor(args: UnreachableArgs = {}) {
@@ -199,8 +51,8 @@ export class Unreachable extends BaseTypedError implements TypedError {
199
51
  }
200
52
  }
201
53
 
202
- const freighterErrorEncoder: ErrorEncoder = (error: TypedError) => {
203
- if (!error.type.startsWith(FREIGHTER)) return null;
54
+ const freighterErrorEncoder: errors.Encoder = (error: errors.Typed) => {
55
+ if (!error.type.startsWith(FreighterError.TYPE)) return null;
204
56
  if (EOF.matches(error)) return { type: EOF.TYPE, data: "EOF" };
205
57
  if (StreamClosed.matches(error))
206
58
  return { type: StreamClosed.TYPE, data: "StreamClosed" };
@@ -209,8 +61,8 @@ const freighterErrorEncoder: ErrorEncoder = (error: TypedError) => {
209
61
  throw new Error(`Unknown error type: ${error.type}: ${error.message}`);
210
62
  };
211
63
 
212
- const freighterErrorDecoder: ErrorDecoder = (encoded: ErrorPayload) => {
213
- if (!encoded.type.startsWith(FREIGHTER_ERROR_TYPE)) return null;
64
+ const freighterErrorDecoder: errors.Decoder = (encoded: errors.Payload) => {
65
+ if (!encoded.type.startsWith(FreighterError.TYPE)) return null;
214
66
  switch (encoded.type) {
215
67
  case EOF.TYPE:
216
68
  return new EOF();
@@ -219,11 +71,11 @@ const freighterErrorDecoder: ErrorDecoder = (encoded: ErrorPayload) => {
219
71
  case Unreachable.TYPE:
220
72
  return new Unreachable();
221
73
  default:
222
- throw new Error(`Unknown error type: ${encoded.data}`);
74
+ throw new errors.Unknown(`Unknown error type: ${encoded.data}`);
223
75
  }
224
76
  };
225
77
 
226
- registerError({
78
+ errors.register({
227
79
  encode: freighterErrorEncoder,
228
80
  decode: freighterErrorDecoder,
229
81
  });
package/src/http.ts CHANGED
@@ -7,10 +7,10 @@
7
7
  // License, use of this software will be governed by the Apache License, Version 2.0,
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
- import { type binary, runtime, type URL } from "@synnaxlabs/x";
10
+ import { type binary, errors, runtime, type URL } from "@synnaxlabs/x";
11
11
  import { type z } from "zod";
12
12
 
13
- import { decodeError, errorZ, Unreachable } from "@/errors";
13
+ import { Unreachable } from "@/errors";
14
14
  import { type Context, MiddlewareCollector } from "@/middleware";
15
15
  import { type UnaryClient } from "@/unary";
16
16
 
@@ -28,6 +28,12 @@ const resolveFetchAPI = (protocol: "http" | "https"): typeof fetch => {
28
28
  return async (info, init) => await _fetch(info, { ...init, agent });
29
29
  };
30
30
 
31
+ const shouldCastToUnreachable = (err: Error): boolean =>
32
+ ("code" in err && err.code === "ECONNREFUSED") ||
33
+ err.message.toLowerCase().includes("load failed");
34
+
35
+ const HTTP_STATUS_BAD_REQUEST = 400;
36
+
31
37
  /**
32
38
  * HTTPClientFactory provides a POST and GET implementation of the Unary
33
39
  * protocol.
@@ -65,14 +71,13 @@ export class HTTPClient extends MiddlewareCollector implements UnaryClient {
65
71
  req: z.input<RQ> | z.output<RQ>,
66
72
  reqSchema: RQ,
67
73
  resSchema: RS,
68
- ): Promise<[z.output<RS> | null, Error | null]> {
74
+ ): Promise<[z.output<RS>, null] | [null, Error]> {
69
75
  req = reqSchema?.parse(req);
70
- let res: RS | null = null;
76
+ let res: z.output<RS> | null = null;
71
77
  const url = this.endpoint.child(target);
72
78
  const request: RequestInit = {};
73
79
  request.method = "POST";
74
80
  request.body = this.encoder.encode(req ?? {});
75
-
76
81
  const [, err] = await this.executeMiddleware(
77
82
  {
78
83
  target: url.toString(),
@@ -92,18 +97,19 @@ export class HTTPClient extends MiddlewareCollector implements UnaryClient {
92
97
  httpRes = await f(ctx.target, request);
93
98
  } catch (err_) {
94
99
  let err = err_ as Error;
95
- if (err.message === "Load failed") err = new Unreachable({ url });
100
+ if (shouldCastToUnreachable(err)) err = new Unreachable({ url });
96
101
  return [outCtx, err];
97
102
  }
98
- const data = await httpRes.arrayBuffer();
103
+ const data = new Uint8Array(await (await httpRes.blob()).arrayBuffer());
99
104
  if (httpRes?.ok) {
100
- if (resSchema != null) res = this.encoder.decode(data, resSchema);
105
+ if (resSchema != null) res = this.encoder.decode<RS>(data, resSchema);
101
106
  return [outCtx, null];
102
107
  }
103
108
  try {
104
- if (httpRes.status !== 400) return [outCtx, new Error(httpRes.statusText)];
105
- const err = this.encoder.decode(data, errorZ);
106
- const decoded = decodeError(err);
109
+ if (httpRes.status !== HTTP_STATUS_BAD_REQUEST)
110
+ return [outCtx, new Error(httpRes.statusText)];
111
+ const err = this.encoder.decode(data, errors.payloadZ);
112
+ const decoded = errors.decode(err);
107
113
  return [outCtx, decoded];
108
114
  } catch (e) {
109
115
  return [
@@ -118,6 +124,7 @@ export class HTTPClient extends MiddlewareCollector implements UnaryClient {
118
124
  },
119
125
  );
120
126
 
121
- return [res, err];
127
+ if (err != null) return [null, err];
128
+ return [res, null];
122
129
  }
123
130
  }
package/src/index.ts CHANGED
@@ -7,22 +7,10 @@
7
7
  // License, use of this software will be governed by the Apache License, Version 2.0,
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
- export {
11
- BaseTypedError,
12
- decodeError,
13
- encodeError,
14
- EOF,
15
- errorMatcher,
16
- type ErrorPayload,
17
- errorZ,
18
- type MatchableErrorType,
19
- registerError,
20
- StreamClosed,
21
- type TypedError,
22
- Unreachable,
23
- } from "@/errors";
10
+ export { EOF, StreamClosed, Unreachable } from "@/errors";
24
11
  export { HTTPClient } from "@/http";
25
12
  export { type Context, type Middleware, type Next } from "@/middleware";
26
13
  export { type Stream, type StreamClient } from "@/stream";
27
14
  export { sendRequired, type UnaryClient, unaryWithBreaker } from "@/unary";
28
15
  export { WebSocketClient } from "@/websocket";
16
+ export { type WebsocketMessage } from "@/websocket";
package/src/unary.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  // License, use of this software will be governed by the Apache License, Version 2.0,
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
- import { breaker } from "@synnaxlabs/x";
10
+ import { breaker, type errors } from "@synnaxlabs/x";
11
11
  import { type z } from "zod";
12
12
 
13
13
  import { Unreachable } from "@/errors";
@@ -30,7 +30,7 @@ export interface UnaryClient extends Transport {
30
30
  req: z.input<RQ> | z.output<RQ>,
31
31
  reqSchema: RQ,
32
32
  resSchema: RS,
33
- ) => Promise<[z.output<RS>, null] | [null, Error]>;
33
+ ) => Promise<errors.Return<z.output<RS>>>;
34
34
  }
35
35
 
36
36
  export const unaryWithBreaker = (
@@ -53,12 +53,13 @@ export const unaryWithBreaker = (
53
53
  req: z.input<RQ> | z.output<RQ>,
54
54
  reqSchema: RQ,
55
55
  resSchema: RS,
56
- ): Promise<[z.output<RS>, null] | [null, Error]> {
57
- const brk = breaker.create(cfg);
56
+ ): Promise<errors.Return<z.output<RS>>> {
57
+ const brk = new breaker.Breaker(cfg);
58
58
  do {
59
59
  const [res, err] = await this.wrapped.send(target, req, reqSchema, resSchema);
60
- if (err == null || !Unreachable.matches(err)) return [res, err];
61
- if (!(await brk())) return [res, err];
60
+ if (err == null) return [res, null];
61
+ if (!Unreachable.matches(err)) return [null, err];
62
+ if (!(await brk.wait())) return [res, err];
62
63
  } while (true);
63
64
  }
64
65
  }