@synnaxlabs/x 0.40.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.
Files changed (203) hide show
  1. package/.turbo/turbo-build.log +33 -33
  2. package/dist/binary.cjs +1 -1
  3. package/dist/binary.js +2 -2
  4. package/dist/bounds-BQo7rvs9.cjs +1 -0
  5. package/dist/{bounds-azUOoVVR.js → bounds-Bn5_l4Z3.js} +84 -86
  6. package/dist/bounds.cjs +1 -1
  7. package/dist/bounds.js +1 -1
  8. package/dist/box-0YrQibkB.cjs +1 -0
  9. package/dist/box-Cc8IzcNo.js +205 -0
  10. package/dist/box.cjs +1 -1
  11. package/dist/box.js +1 -1
  12. package/dist/compare.cjs +1 -1
  13. package/dist/compare.js +1 -1
  14. package/dist/deep.cjs +1 -1
  15. package/dist/deep.js +66 -69
  16. package/dist/{dimensions-PWy5QZoM.cjs → dimensions-D2QGoNXO.cjs} +1 -1
  17. package/dist/dimensions.cjs +1 -1
  18. package/dist/{external-CvWr1nhS.cjs → external-DWQITF5_.cjs} +1 -1
  19. package/dist/index-BywOGO8U.js +1074 -0
  20. package/dist/index-CYYjI7Uf.cjs +1 -0
  21. package/dist/index-C_6NXBlg.cjs +3 -0
  22. package/dist/{index-BVC_8Cg9.js → index-QGplUHuy.js} +1 -1
  23. package/dist/index.cjs +3 -3
  24. package/dist/index.js +736 -240
  25. package/dist/location-BGl5Ddds.cjs +1 -0
  26. package/dist/{location-BuYbIFHD.js → location-C3aeu046.js} +16 -12
  27. package/dist/location.cjs +1 -1
  28. package/dist/location.js +1 -1
  29. package/dist/{position-DTrNGtrm.cjs → position-Cai5-wi1.cjs} +1 -1
  30. package/dist/{position-DemzGvAY.js → position-DIglP1l2.js} +2 -2
  31. package/dist/position.cjs +1 -1
  32. package/dist/position.js +1 -1
  33. package/dist/record.js +3 -1
  34. package/dist/{scale-DpJM6__6.cjs → scale-BtZINJ-A.cjs} +1 -1
  35. package/dist/{scale-C0EllH-1.js → scale-DfJe9755.js} +4 -4
  36. package/dist/scale.cjs +1 -1
  37. package/dist/scale.js +1 -1
  38. package/dist/{series-CXnO-P0V.js → series-B9JERcqi.js} +541 -474
  39. package/dist/series-DqJ6f97G.cjs +11 -0
  40. package/dist/spatial.cjs +1 -1
  41. package/dist/spatial.js +6 -6
  42. package/dist/src/binary/{encoder.d.ts → codec.d.ts} +14 -8
  43. package/dist/src/binary/codec.d.ts.map +1 -0
  44. package/dist/src/binary/codec.spec.d.ts +2 -0
  45. package/dist/src/binary/codec.spec.d.ts.map +1 -0
  46. package/dist/src/binary/index.d.ts +1 -1
  47. package/dist/src/binary/index.d.ts.map +1 -1
  48. package/dist/src/breaker/breaker.d.ts +14 -21
  49. package/dist/src/breaker/breaker.d.ts.map +1 -1
  50. package/dist/src/change/change.d.ts +5 -18
  51. package/dist/src/change/change.d.ts.map +1 -1
  52. package/dist/src/color/color.d.ts +126 -0
  53. package/dist/src/color/color.d.ts.map +1 -0
  54. package/dist/src/color/color.spec.d.ts +2 -0
  55. package/dist/src/color/color.spec.d.ts.map +1 -0
  56. package/dist/src/color/external.d.ts +5 -0
  57. package/dist/src/color/external.d.ts.map +1 -0
  58. package/dist/src/color/gradient.d.ts +18 -0
  59. package/dist/src/color/gradient.d.ts.map +1 -0
  60. package/dist/src/color/index.d.ts +2 -0
  61. package/dist/src/color/index.d.ts.map +1 -0
  62. package/dist/src/color/palette.d.ts +19 -0
  63. package/dist/src/color/palette.d.ts.map +1 -0
  64. package/dist/src/color/transformColorsToHex.d.ts +6 -0
  65. package/dist/src/color/transformColorsToHex.d.ts.map +1 -0
  66. package/dist/src/control/control.d.ts +69 -74
  67. package/dist/src/control/control.d.ts.map +1 -1
  68. package/dist/src/deep/merge.d.ts +1 -1
  69. package/dist/src/deep/merge.d.ts.map +1 -1
  70. package/dist/src/errors/errors.d.ts +127 -7
  71. package/dist/src/errors/errors.d.ts.map +1 -1
  72. package/dist/src/errors/errors.spec.d.ts +2 -0
  73. package/dist/src/errors/errors.spec.d.ts.map +1 -0
  74. package/dist/src/index.d.ts +5 -0
  75. package/dist/src/index.d.ts.map +1 -1
  76. package/dist/src/jsonrpc/jsonrpc.d.ts +10 -7
  77. package/dist/src/jsonrpc/jsonrpc.d.ts.map +1 -1
  78. package/dist/src/kv/types.d.ts +1 -7
  79. package/dist/src/kv/types.d.ts.map +1 -1
  80. package/dist/src/math/external.d.ts +3 -0
  81. package/dist/src/math/external.d.ts.map +1 -0
  82. package/dist/src/math/index.d.ts +1 -1
  83. package/dist/src/math/index.d.ts.map +1 -1
  84. package/dist/src/math/round.d.ts +40 -0
  85. package/dist/src/math/round.d.ts.map +1 -0
  86. package/dist/src/math/round.spec.d.ts +2 -0
  87. package/dist/src/math/round.spec.d.ts.map +1 -0
  88. package/dist/src/migrate/migrate.d.ts +1 -1
  89. package/dist/src/notation/index.d.ts +2 -0
  90. package/dist/src/notation/index.d.ts.map +1 -0
  91. package/dist/src/notation/notation.d.ts +37 -0
  92. package/dist/src/notation/notation.d.ts.map +1 -0
  93. package/dist/src/notation/notation.spec.d.ts +2 -0
  94. package/dist/src/notation/notation.spec.d.ts.map +1 -0
  95. package/dist/src/record.d.ts +2 -1
  96. package/dist/src/record.d.ts.map +1 -1
  97. package/dist/src/replace.d.ts +2 -0
  98. package/dist/src/replace.d.ts.map +1 -0
  99. package/dist/src/runtime/os.d.ts +9 -1
  100. package/dist/src/runtime/os.d.ts.map +1 -1
  101. package/dist/src/singleton/define.d.ts +9 -0
  102. package/dist/src/singleton/define.d.ts.map +1 -0
  103. package/dist/src/singleton/define.spec.d.ts +2 -0
  104. package/dist/src/singleton/define.spec.d.ts.map +1 -0
  105. package/dist/src/singleton/index.d.ts +2 -0
  106. package/dist/src/singleton/index.d.ts.map +1 -0
  107. package/dist/src/spatial/base.d.ts +74 -70
  108. package/dist/src/spatial/base.d.ts.map +1 -1
  109. package/dist/src/spatial/box/box.d.ts +22 -76
  110. package/dist/src/spatial/box/box.d.ts.map +1 -1
  111. package/dist/src/spatial/dimensions/dimensions.d.ts +5 -29
  112. package/dist/src/spatial/dimensions/dimensions.d.ts.map +1 -1
  113. package/dist/src/spatial/direction/direction.d.ts +9 -1
  114. package/dist/src/spatial/direction/direction.d.ts.map +1 -1
  115. package/dist/src/spatial/location/location.d.ts +45 -24
  116. package/dist/src/spatial/location/location.d.ts.map +1 -1
  117. package/dist/src/spatial/scale/scale.d.ts +12 -120
  118. package/dist/src/spatial/scale/scale.d.ts.map +1 -1
  119. package/dist/src/spatial/xy/xy.d.ts +15 -29
  120. package/dist/src/spatial/xy/xy.d.ts.map +1 -1
  121. package/dist/src/sync/index.d.ts +2 -0
  122. package/dist/src/sync/index.d.ts.map +1 -0
  123. package/dist/src/sync/mutex.d.ts +8 -0
  124. package/dist/src/sync/mutex.d.ts.map +1 -0
  125. package/dist/src/telem/gl.d.ts +4 -1
  126. package/dist/src/telem/gl.d.ts.map +1 -1
  127. package/dist/src/telem/series.d.ts +46 -125
  128. package/dist/src/telem/series.d.ts.map +1 -1
  129. package/dist/src/telem/telem.d.ts +102 -85
  130. package/dist/src/telem/telem.d.ts.map +1 -1
  131. package/dist/src/toArray.d.ts +1 -1
  132. package/dist/src/toArray.d.ts.map +1 -1
  133. package/dist/src/zod/util.d.ts.map +1 -1
  134. package/dist/telem.cjs +1 -1
  135. package/dist/telem.js +1 -1
  136. package/dist/toArray.cjs +1 -1
  137. package/dist/toArray.js +1 -1
  138. package/dist/{xy-DyQSETQZ.cjs → xy-B7065J2S.cjs} +1 -1
  139. package/dist/{xy-DHBO1dG_.js → xy-D_LqxaGt.js} +8 -4
  140. package/dist/xy.cjs +1 -1
  141. package/dist/xy.js +1 -1
  142. package/dist/zod.cjs +1 -1
  143. package/package.json +11 -8
  144. package/src/binary/codec.spec.ts +370 -0
  145. package/src/binary/{encoder.ts → codec.ts} +55 -11
  146. package/src/binary/index.ts +1 -1
  147. package/src/breaker/breaker.spec.ts +16 -25
  148. package/src/breaker/breaker.ts +36 -19
  149. package/src/color/color.spec.ts +673 -0
  150. package/src/color/color.ts +317 -0
  151. package/src/color/external.ts +13 -0
  152. package/src/color/gradient.ts +78 -0
  153. package/src/color/index.ts +10 -0
  154. package/src/color/palette.ts +28 -0
  155. package/src/color/transformColorsToHex.ts +30 -0
  156. package/src/control/control.ts +30 -22
  157. package/src/deep/merge.ts +2 -8
  158. package/src/errors/errors.spec.ts +152 -0
  159. package/src/errors/errors.ts +225 -10
  160. package/src/index.ts +5 -0
  161. package/src/jsonrpc/jsonrpc.ts +12 -8
  162. package/src/math/external.ts +11 -0
  163. package/src/math/index.ts +1 -1
  164. package/src/math/round.spec.ts +81 -0
  165. package/src/math/round.ts +68 -0
  166. package/src/migrate/migrate.ts +2 -2
  167. package/src/notation/index.ts +10 -0
  168. package/src/notation/notation.spec.ts +88 -0
  169. package/src/notation/notation.ts +61 -0
  170. package/src/primitive.ts +1 -1
  171. package/src/record.ts +5 -1
  172. package/src/replace.ts +1 -0
  173. package/src/singleton/define.spec.ts +93 -0
  174. package/src/singleton/define.ts +27 -0
  175. package/src/singleton/index.ts +10 -0
  176. package/src/spatial/box/box.spec.ts +3 -3
  177. package/src/spatial/box/box.ts +8 -0
  178. package/src/spatial/location/location.ts +4 -4
  179. package/src/spatial/position/position.spec.ts +10 -10
  180. package/src/spatial/xy/xy.spec.ts +8 -0
  181. package/src/spatial/xy/xy.ts +14 -0
  182. package/src/sync/index.ts +1 -0
  183. package/src/sync/mutex.ts +16 -0
  184. package/src/telem/series.spec.ts +71 -0
  185. package/src/telem/series.ts +69 -46
  186. package/src/telem/telem.spec.ts +151 -10
  187. package/src/telem/telem.ts +134 -73
  188. package/src/toArray.ts +2 -2
  189. package/src/zod/util.spec.ts +17 -1
  190. package/src/zod/util.ts +4 -2
  191. package/tsconfig.tsbuildinfo +1 -1
  192. package/dist/bounds-Dwq6ZFHm.cjs +0 -1
  193. package/dist/box-Bzya27QS.cjs +0 -1
  194. package/dist/box-DrsrRNSe.js +0 -201
  195. package/dist/index-BG3Scw3G.cjs +0 -1
  196. package/dist/index-C3QzbIwt.js +0 -101
  197. package/dist/index-CnclyYpG.cjs +0 -3
  198. package/dist/location-BgpQ3rN2.cjs +0 -1
  199. package/dist/series-BgoCtU71.cjs +0 -11
  200. package/dist/src/binary/encoder.d.ts.map +0 -1
  201. package/dist/src/binary/encoder.spec.d.ts +0 -2
  202. package/dist/src/binary/encoder.spec.d.ts.map +0 -1
  203. package/src/binary/encoder.spec.ts +0 -174
@@ -0,0 +1,152 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { describe, expect, it, test } from "vitest";
11
+
12
+ import { errors } from "@/errors";
13
+
14
+ class ErrorOne extends errors.createTyped("one") {}
15
+
16
+ class ErrorTwo extends errors.createTyped("two") {}
17
+
18
+ class SubError extends ErrorOne.sub("child") {}
19
+
20
+ const myCustomErrorEncoder = (error: errors.Typed): errors.Payload | null => {
21
+ if (error.type !== "one") return null;
22
+ return { type: "one", data: error.message };
23
+ };
24
+
25
+ const myCustomErrorDecoder = (encoded: errors.Payload): errors.Typed =>
26
+ new ErrorOne(encoded.data);
27
+
28
+ describe("errors", () => {
29
+ describe("isTypedError", () => {
30
+ it("should return true if the error implements the TypedError interface", () => {
31
+ const error = new ErrorOne("test");
32
+ const fError = errors.isTyped(error);
33
+ expect(fError).toBeTruthy();
34
+ expect(error.type).toEqual("one");
35
+ });
36
+
37
+ it("should return false if the error does not implement the TypedError interface", () => {
38
+ const error = new Error("rando");
39
+ const fError = errors.isTyped(error);
40
+ expect(fError).toBeFalsy();
41
+ });
42
+ });
43
+
44
+ describe("encoding/decoding", () => {
45
+ it("should encode and decode a custom error through the registry", () => {
46
+ errors.register({
47
+ encode: myCustomErrorEncoder,
48
+ decode: myCustomErrorDecoder,
49
+ });
50
+ const error = new ErrorOne("test");
51
+ const encoded = errors.encode(error);
52
+ const decoded = errors.decode(encoded);
53
+ expect(ErrorOne.matches(decoded)).toBeTruthy();
54
+ });
55
+
56
+ test("should correctly encode/decode a null error", () => {
57
+ const encoded = errors.encode(null);
58
+ expect(encoded.type).toEqual(errors.NONE);
59
+ expect(encoded.data).toEqual("");
60
+ const decoded = errors.decode(encoded);
61
+ expect(decoded).toBeNull();
62
+ });
63
+
64
+ it("should correctly encode/decode an undefined error", () => {
65
+ const encoded = errors.encode(undefined);
66
+ expect(encoded.type).toEqual(errors.NONE);
67
+ expect(encoded.data).toEqual("");
68
+ const decoded = errors.decode(encoded);
69
+ expect(decoded).toBeNull();
70
+ });
71
+
72
+ it("should correctly encode/decode a generic error", () => {
73
+ const error = new Error("test");
74
+ const encoded = errors.encode(error);
75
+ expect(encoded.type).toEqual(errors.UNKNOWN);
76
+ expect(encoded.data).toEqual("test");
77
+ const decoded = errors.decode(encoded);
78
+ expect(decoded).toBeInstanceOf(Error);
79
+ expect((decoded as Error).message).toEqual(error.message);
80
+ });
81
+
82
+ it("should correctly encode/decode a string that is not an error", () => {
83
+ const error = "test";
84
+ const encoded = errors.encode(error);
85
+ expect(encoded.type).toEqual(errors.UNKNOWN);
86
+ expect(encoded.data).toEqual("test");
87
+ const decoded = errors.decode(encoded);
88
+ expect(errors.Unknown.matches(decoded)).toBeTruthy();
89
+ });
90
+
91
+ it("should correctly encode/decode a random object", () => {
92
+ const error = { foo: "bar" };
93
+ const encoded = errors.encode(error);
94
+ expect(encoded.type).toEqual(errors.UNKNOWN);
95
+ expect(encoded.data).toEqual(JSON.stringify(error));
96
+ const decoded = errors.decode(encoded);
97
+ expect(errors.Unknown.matches(decoded)).toBeTruthy();
98
+ });
99
+ });
100
+
101
+ describe("matches", () => {
102
+ it("should return true if the errors are exactly the same", () => {
103
+ const v = new ErrorOne("test");
104
+ expect(ErrorOne.matches(v)).toBeTruthy();
105
+ const v2 = new ErrorOne("test");
106
+ expect(v2.matches(v)).toBeTruthy();
107
+ });
108
+
109
+ it("should return false if the errors are typed and clearly different", () => {
110
+ const e1 = new ErrorOne("test");
111
+ const e2 = new ErrorTwo("test");
112
+ expect(e1.matches(e2)).toBeFalsy();
113
+ expect(ErrorOne.matches(e2)).toBeFalsy();
114
+ expect(ErrorTwo.matches(e1)).toBeFalsy();
115
+ });
116
+
117
+ it("should return false if the error is not typed", () => {
118
+ const e1 = new ErrorOne("test");
119
+ const e2 = new Error("rando");
120
+ expect(e1.matches(e2)).toBeFalsy();
121
+ expect(ErrorOne.matches(e2)).toBeFalsy();
122
+ });
123
+
124
+ it("should return false if the error is not actually an error", () => {
125
+ const e1 = new ErrorOne("test");
126
+ const e2 = "rando";
127
+ expect(e1.matches(e2)).toBeFalsy();
128
+ expect(ErrorOne.matches(e2)).toBeFalsy();
129
+ });
130
+
131
+ it("should return false if hte error is undefined", () => {
132
+ const e1 = new ErrorOne("test");
133
+ const e2 = undefined;
134
+ expect(e1.matches(e2)).toBeFalsy();
135
+ expect(ErrorOne.matches(e2)).toBeFalsy();
136
+ });
137
+
138
+ it("should return true if the the matching error is a parent", () => {
139
+ const e1 = new ErrorOne("rando");
140
+ const e2 = new SubError("rando");
141
+ expect(e1.matches(e2)).toBeTruthy();
142
+ expect(ErrorOne.matches(e1)).toBeTruthy();
143
+ });
144
+
145
+ it("should return false if the matching error is a sub-error", () => {
146
+ const e1 = new ErrorOne("random");
147
+ const e2 = new SubError("random");
148
+ expect(e2.matches(e1)).toBeFalsy();
149
+ expect(SubError.matches(e1)).toBeFalsy();
150
+ });
151
+ });
152
+ });
@@ -7,20 +7,235 @@
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
- class Canceled extends Error {
11
- static readonly MESSAGE = "canceled";
12
- constructor() {
13
- super(Canceled.MESSAGE);
10
+ import { z } from "zod";
11
+
12
+ import { singleton } from "@/singleton";
13
+
14
+ /**
15
+ * @returns general function that returns true if an error matches a set of
16
+ * abstracted criteria
17
+ */
18
+ export type Matcher = (e: string | Error | unknown) => boolean;
19
+
20
+ /** @description an error type that can match against other errors. */
21
+ export interface Matchable {
22
+ /**
23
+ * @returns a function that matches errors of the given type. Returns true if
24
+ * the provided instance of Error or a string message contains the provided error type.
25
+ */
26
+ matches: Matcher;
27
+ }
28
+
29
+ /**
30
+ * @description an error that has a network-portable type, allowing it to be encoded/
31
+ * decoded by freighter. Also allows for simpler matching using @method matches instead
32
+ * of using instanceof, which has a number of caveats.
33
+ */
34
+ export interface Typed extends Error, Matchable {
35
+ discriminator: "FreighterError";
36
+ /**
37
+ * @description Returns a unique type identifier for the error. Freighter uses this to
38
+ * determine the correct decoder to use on the other end of the freighter.
39
+ */
40
+ type: string;
41
+ }
42
+
43
+ /**
44
+ * @description a class that, when constructed, implements the TypedError interface.
45
+ * Also provides utilities for matching and creating subclasses.
46
+ */
47
+ export interface TypedClass extends Matchable {
48
+ /**
49
+ * @description constructs a new TypedError. Identical to the Error constructor.
50
+ * @param message - the error message.
51
+ * @param options - the error options.
52
+ * @returns a new TypedError.
53
+ */
54
+ new (message?: string, options?: ErrorOptions): Typed;
55
+ /**
56
+ * @description the type of the error.
57
+ */
58
+ TYPE: string;
59
+ /**
60
+ * @description creates a new subclass of the error that extends its type. So if
61
+ * the type of this class is `dog` and subType is `labrador`, the type of the new
62
+ * class will be `dog.labrador`.
63
+ * @param subType - the type of the new error.
64
+ * @returns a new TypedErrorClass.
65
+ */
66
+ sub: (subType: string) => TypedClass;
67
+ }
68
+
69
+ /**
70
+ * @param type - the error type to match
71
+ * @returns a function that matches errors of the given type. Returns true if
72
+ * the provided instance of Error or a string message contains the provided error type.
73
+ */
74
+ const createTypeMatcher =
75
+ (type: string): Matcher =>
76
+ (e) => {
77
+ if (e != null && typeof e === "object" && "type" in e && typeof e.type === "string")
78
+ return e.type.startsWith(type);
79
+ if (e instanceof Error) return e.message.startsWith(type);
80
+ if (typeof e !== "string") return false;
81
+ return e.startsWith(type);
82
+ };
83
+
84
+ /**
85
+ * Creates a new class definition that implements the TypedErrorClass interface.
86
+ * @param type - the type of the error.
87
+ * @returns a new TypedErrorClass.
88
+ * @example
89
+ * ```ts
90
+ * class MyError extends createTypedError("my_error") {}
91
+ * ```
92
+ */
93
+ export const createTyped = (type: string): TypedClass =>
94
+ class Internal extends Error implements Typed {
95
+ static readonly discriminator = "FreighterError";
96
+ readonly discriminator = Internal.discriminator;
97
+
98
+ static readonly TYPE = type;
99
+ readonly type: string = Internal.TYPE;
100
+
101
+ static readonly matches = createTypeMatcher(type);
102
+ readonly matches: Matcher = Internal.matches;
103
+
104
+ constructor(message?: string, options?: ErrorOptions) {
105
+ super(message, options);
106
+ this.name = Internal.TYPE;
107
+ }
108
+ static sub(subType: string): TypedClass {
109
+ return createTyped(`${type}.${subType}`);
110
+ }
111
+ };
112
+
113
+ /**
114
+ * @description Function that decodes an encoded error payload back into an error object
115
+ * @param encoded - The encoded error payload to decode
116
+ * @returns The decoded error object or null if the decoder cannot handle this error type
117
+ */
118
+ export type Decoder = (encoded: Payload) => Error | null;
119
+
120
+ /**
121
+ * @description Function that encodes a typed error into a network-portable payload
122
+ * @param error - The typed error to encode
123
+ * @returns The encoded error payload or null if the encoder cannot handle this error type
124
+ */
125
+ export type Encoder = (error: Typed) => Payload | null;
126
+
127
+ /**
128
+ * @description Checks if an unknown value is a TypedError
129
+ * @param error - The value to check
130
+ * @returns True if the value is a TypedError, false otherwise
131
+ */
132
+ export const isTyped = (error: unknown): error is Typed => {
133
+ if (error == null || typeof error !== "object") return false;
134
+ const typedError = error as Typed;
135
+ if (typedError.discriminator !== "FreighterError") return false;
136
+ if (!("type" in typedError))
137
+ throw new Error(
138
+ `Freighter error is missing its type property: ${JSON.stringify(typedError)}`,
139
+ );
140
+ return true;
141
+ };
142
+
143
+ /** @description Constant representing an unknown error type */
144
+ export const UNKNOWN = "unknown";
145
+
146
+ /** @description Constant representing no error (null) */
147
+ export const NONE = "nil";
148
+
149
+ interface provider {
150
+ encode: Encoder;
151
+ decode: Decoder;
152
+ }
153
+
154
+ class Registry {
155
+ private readonly providers: provider[] = [];
156
+
157
+ register(provider: provider): void {
158
+ this.providers.push(provider);
14
159
  }
15
160
 
16
- /** Returns true if the error or message is a cancellation error" */
17
- matches(e: Error | string): boolean {
18
- if (typeof e === "string") return e.includes(Canceled.MESSAGE);
19
- return e instanceof Canceled || e.message.includes(Canceled.MESSAGE);
161
+ encode(error: unknown): Payload {
162
+ if (error == null) return { type: NONE, data: "" };
163
+ if (isTyped(error))
164
+ for (const provider of this.providers) {
165
+ const payload = provider.encode(error);
166
+ if (payload != null) return payload;
167
+ }
168
+ if (error instanceof Error) return { type: UNKNOWN, data: error.message };
169
+ if (typeof error === "string") return { type: UNKNOWN, data: error };
170
+ try {
171
+ return { type: UNKNOWN, data: JSON.stringify(error) };
172
+ } catch {
173
+ return { type: UNKNOWN, data: "unable to encode error information" };
174
+ }
175
+ }
176
+
177
+ decode(payload?: Payload | null): Error | null {
178
+ if (payload == null || payload.type === NONE) return null;
179
+ if (payload.type === UNKNOWN) return new Unknown(payload.data);
180
+ for (const provider of this.providers) {
181
+ const error = provider.decode(payload);
182
+ if (error != null) return error;
183
+ }
184
+ return new Unknown(payload.data);
20
185
  }
21
186
  }
22
187
 
188
+ const getRegistry = singleton.define("synnax-error-registry", () => new Registry());
189
+
23
190
  /**
24
- * CANCELED should be thrown to indicate the cancellation of an operation.
191
+ * Registers a custom error type with the error registry, which allows it to be
192
+ * encoded/decoded and sent over the network.
193
+ *
194
+ * @param type - A unique string identifier for the error type.
195
+ * @param encode - A function that encodes the error into a string.
196
+ * @param decode - A function that decodes the error from a string.
25
197
  */
26
- export const CANCELED = new Canceled();
198
+ export const register = ({
199
+ encode,
200
+ decode,
201
+ }: {
202
+ encode: Encoder;
203
+ decode: Decoder;
204
+ }): void => getRegistry().register({ encode, decode });
205
+
206
+ /**
207
+ * Encodes an error into a payload that can be sent between a freighter server
208
+ * and client.
209
+ * @param error - The error to encode.
210
+ * @returns The encoded error.
211
+ */
212
+ export const encode = (error: unknown): Payload => getRegistry().encode(error);
213
+
214
+ /**
215
+ * Decodes an error payload into an exception. If a custom decoder can be found
216
+ * for the error type, it will be used. Otherwise, a generic Error containing
217
+ * the error data is returned.
218
+ *
219
+ * @param payload - The encoded error payload.
220
+ * @returns The decoded error.
221
+ */
222
+ export const decode = (payload?: Payload | null): Error | null => {
223
+ if (payload == null) return null;
224
+ return getRegistry().decode(payload);
225
+ };
226
+
227
+ /**
228
+ * @description Generic error for representing unknown errors
229
+ */
230
+ export class Unknown extends createTyped("unknown") {}
231
+
232
+ /** @description Zod schema for validating error payloads */
233
+ export const payloadZ = z.object({ type: z.string(), data: z.string() });
234
+
235
+ /** @description Network-portable representation of an error */
236
+ export type Payload = z.infer<typeof payloadZ>;
237
+
238
+ /** @description Error for representing the cancellation of an operation */
239
+ export class Canceled extends createTyped("canceled") {}
240
+
241
+ export type Return<T> = [T, null] | [null, Error];
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ export * from "@/breaker";
12
12
  export * from "@/caseconv";
13
13
  export * from "@/change";
14
14
  export * from "@/clamp/clamp";
15
+ export * from "@/color";
15
16
  export * from "@/compare";
16
17
  export * from "@/control";
17
18
  export * from "@/debounce/debounce";
@@ -26,17 +27,21 @@ export * from "@/kv";
26
27
  export * from "@/link";
27
28
  export * from "@/math";
28
29
  export * from "@/migrate";
30
+ export * from "@/notation";
29
31
  export * from "@/observe";
30
32
  export * from "@/optional";
31
33
  export * from "@/primitive";
32
34
  export * from "@/record";
33
35
  export * from "@/renderable";
36
+ export * from "@/replace";
34
37
  export * from "@/runtime";
35
38
  export * from "@/runtime";
36
39
  export * from "@/search";
37
40
  export * from "@/shallowCopy";
41
+ export * from "@/sleep";
38
42
  export * from "@/spatial";
39
43
  export * from "@/strings";
44
+ export * from "@/sync";
40
45
  export * from "@/telem";
41
46
  export * from "@/toArray";
42
47
  export * from "@/transform";
@@ -7,15 +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 z from "zod";
11
+
10
12
  import { binary } from "@/binary";
11
13
 
12
- export interface Message {
13
- jsonrpc: string;
14
- id?: number;
15
- method?: string;
16
- params?: unknown;
17
- result?: unknown;
18
- }
14
+ const messageZ = z.object({
15
+ jsonrpc: z.string(),
16
+ id: z.number().optional(),
17
+ method: z.string().optional(),
18
+ params: z.unknown().optional(),
19
+ result: z.unknown().optional(),
20
+ });
21
+
22
+ export type Message = z.infer<typeof messageZ>;
19
23
 
20
24
  export interface ChunkParser {
21
25
  (chunk: Uint8Array | ArrayBuffer | string): void;
@@ -77,7 +81,7 @@ export const streamDecodeChunks = (
77
81
  buffer = buffer.slice(expectedLength);
78
82
  expectedLength = null;
79
83
  const messageStr = decoder.decode(messageBytes);
80
- const parsed = binary.JSON_CODEC.decodeString(messageStr);
84
+ const parsed = binary.JSON_CODEC.decodeString(messageStr, messageZ);
81
85
  onMessage(parsed);
82
86
  } else break;
83
87
  }
@@ -0,0 +1,11 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ export * from "@/math/math";
11
+ export * from "@/math/round";
package/src/math/index.ts CHANGED
@@ -7,4 +7,4 @@
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 * as math from "@/math/math";
10
+ export * as math from "@/math/external";
@@ -0,0 +1,81 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { describe, expect, it } from "vitest";
11
+
12
+ import { math } from "@/math";
13
+ import { type bounds } from "@/spatial";
14
+
15
+ interface TestCase {
16
+ name: string;
17
+ value: number;
18
+ bounds: bounds.Bounds<number>;
19
+ expected: number;
20
+ }
21
+
22
+ const TEST_CASES: TestCase[] = [
23
+ {
24
+ name: "should handle large spans (>= 1000)",
25
+ value: 1234.5678,
26
+ bounds: { lower: 0, upper: 2000 },
27
+ expected: 1234.57,
28
+ },
29
+ {
30
+ name: "should handle medium spans (>= 1)",
31
+ value: 1.23456,
32
+ bounds: { lower: 0, upper: 2 },
33
+ expected: 1.235,
34
+ },
35
+ {
36
+ name: "should handle small spans (< 1)",
37
+ value: 0.123456,
38
+ bounds: { lower: 0, upper: 0.2 },
39
+ expected: 0.123,
40
+ },
41
+ {
42
+ name: "should handle very small spans",
43
+ value: 0.0001234,
44
+ bounds: { lower: 0, upper: 0.0002 },
45
+ expected: 0.000123,
46
+ },
47
+ {
48
+ name: "should handle negative values",
49
+ value: -1.23456,
50
+ bounds: { lower: -2, upper: 0 },
51
+ expected: -1.235,
52
+ },
53
+ {
54
+ name: "should handle NaN",
55
+ value: NaN,
56
+ bounds: { lower: 0, upper: 1 },
57
+ expected: NaN,
58
+ },
59
+ {
60
+ name: "should handle Infinity",
61
+ value: Infinity,
62
+ bounds: { lower: 0, upper: 1 },
63
+ expected: Infinity,
64
+ },
65
+ {
66
+ name: "should handle zero span bounds",
67
+ value: 1.23456,
68
+ bounds: { lower: 1, upper: 1 },
69
+ expected: 1.23456,
70
+ },
71
+ ];
72
+
73
+ describe("roundBySpan", () => {
74
+ TEST_CASES.forEach(({ name, value, bounds, expected }) => {
75
+ it(name, () => {
76
+ const result = math.roundBySpan(value, bounds);
77
+ if (Number.isNaN(expected)) expect(Number.isNaN(result)).toBe(true);
78
+ else expect(result).toBe(expected);
79
+ });
80
+ });
81
+ });
@@ -0,0 +1,68 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { bounds } from "@/spatial/bounds";
11
+
12
+ const LARGE_SPAN_DECIMAL_PLACES = 2;
13
+ const MEDIUM_SPAN_DECIMAL_PLACES = 3;
14
+
15
+ // The number of additional decimal places to show past the precision of the span.
16
+ const EXTRA_DECIMAL_PLACES = 2;
17
+
18
+ /**
19
+ * Rounds a number based on the span of the provided bounds. The function adjusts the
20
+ * number of decimal places based on the magnitude of the bounds.
21
+ *
22
+ * @param value - The number to be rounded.
23
+ * @param bounds - The bounds object containing the min and max values that provide
24
+ * context for rounding.
25
+ * @returns The rounded number.
26
+ *
27
+ * Rules for decimal places:
28
+ * - For spans >= 1000: 2 decimal places
29
+ * - For spans >= 1: 3 decimal places
30
+ * - For spans < 1: 2 decimal places + 2 decimal places past the precision of the span
31
+ *
32
+ * Edge cases:
33
+ * - If the value is `NaN`, returns `NaN`
34
+ * - If the value is `Infinity` or `-Infinity`, returns the original value
35
+ * - If the bounds span is 0, returns the original value
36
+ *
37
+ * Examples:
38
+ * ```typescript
39
+ * // Large spans (>= 1000) use 2 decimal places
40
+ * roundBySpan(1234.5678, { start: 0, end: 2000 }); // 1200
41
+ *
42
+ * // Medium spans (>= 1) use 3 decimal places
43
+ * roundBySpan(1.23456, { start: 0, end: 2 }); // 1.235
44
+ *
45
+ * // Small spans (< 1) adapt based on the span
46
+ * roundBySpan(0.123456, { start: 0, end: 0.2 }); // 0.123 = 1 + 2 decimal places
47
+ * roundBySpan(0.0001234, { start: 0, end: 0.001 }); // 0.00012 = 3 + 2 decimal places
48
+ *
49
+ * // Edge cases
50
+ * roundBySpan(NaN, { start: 0, end: 1 }); // NaN
51
+ * roundBySpan(Infinity, { start: 0, end: 1 }); // Infinity
52
+ * roundBySpan(123, { start: 1, end: 1 }); // 123 (span is 0)
53
+ * ```
54
+ */
55
+ export const roundBySpan = (value: number, b: bounds.Bounds<number>): number => {
56
+ if (Number.isNaN(value) || !Number.isFinite(value)) return value;
57
+ const span = bounds.span(b);
58
+ if (span == 0) return value;
59
+ let decimalPlaces: number;
60
+ if (span >= 1000) decimalPlaces = LARGE_SPAN_DECIMAL_PLACES;
61
+ else if (span >= 1) decimalPlaces = MEDIUM_SPAN_DECIMAL_PLACES;
62
+ else {
63
+ const decimalPlacesInSpan = Math.ceil(-Math.log10(span));
64
+ decimalPlaces = decimalPlacesInSpan + EXTRA_DECIMAL_PLACES;
65
+ }
66
+ const multiplier = 10 ** decimalPlaces;
67
+ return Math.round(value * multiplier) / multiplier;
68
+ };
@@ -189,8 +189,8 @@ export const migrator = <
189
189
  return def;
190
190
  }
191
191
  try {
192
- if (targetSchema != null) return targetSchema.parse(v);
193
- return v;
192
+ if (targetSchema != null) return targetSchema.parse(v) as O;
193
+ return v as unknown as O;
194
194
  } catch (e) {
195
195
  console.log(`${name} failed to parse default. Exiting with default`);
196
196
  console.error(e);
@@ -0,0 +1,10 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ export * as notation from "@/notation/notation";