@synnaxlabs/x 0.41.0 → 0.42.1

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 (159) hide show
  1. package/.turbo/turbo-build.log +23 -23
  2. package/dist/binary.cjs +1 -1
  3. package/dist/binary.js +2 -2
  4. package/dist/{bounds-M-SZ3X1Z.cjs → bounds-BQo7rvs9.cjs} +1 -1
  5. package/dist/{bounds-DQrjn60Q.js → bounds-Bn5_l4Z3.js} +10 -9
  6. package/dist/bounds.cjs +1 -1
  7. package/dist/bounds.js +1 -1
  8. package/dist/compare.cjs +1 -1
  9. package/dist/compare.js +1 -1
  10. package/dist/deep.cjs +1 -1
  11. package/dist/deep.js +84 -77
  12. package/dist/{dimensions-PWy5QZoM.cjs → dimensions-D2QGoNXO.cjs} +1 -1
  13. package/dist/dimensions.cjs +1 -1
  14. package/dist/{external-CvWr1nhS.cjs → external-DWQITF5_.cjs} +1 -1
  15. package/dist/index-BywOGO8U.js +1074 -0
  16. package/dist/index-CYYjI7Uf.cjs +1 -0
  17. package/dist/index-C_6NXBlg.cjs +3 -0
  18. package/dist/{index-BVC_8Cg9.js → index-QGplUHuy.js} +1 -1
  19. package/dist/index.cjs +3 -3
  20. package/dist/index.js +702 -243
  21. package/dist/record.js +3 -1
  22. package/dist/{scale-DL9VFGhL.cjs → scale-BtZINJ-A.cjs} +1 -1
  23. package/dist/{scale-DQwBWnwc.js → scale-DfJe9755.js} +1 -1
  24. package/dist/scale.cjs +1 -1
  25. package/dist/scale.js +1 -1
  26. package/dist/{series-D0zxMWxP.js → series-B9JERcqi.js} +571 -492
  27. package/dist/series-DqJ6f97G.cjs +11 -0
  28. package/dist/spatial.cjs +1 -1
  29. package/dist/spatial.js +2 -2
  30. package/dist/src/binary/{encoder.d.ts → codec.d.ts} +14 -8
  31. package/dist/src/binary/codec.d.ts.map +1 -0
  32. package/dist/src/binary/codec.spec.d.ts +2 -0
  33. package/dist/src/binary/codec.spec.d.ts.map +1 -0
  34. package/dist/src/binary/index.d.ts +1 -1
  35. package/dist/src/binary/index.d.ts.map +1 -1
  36. package/dist/src/breaker/breaker.d.ts +14 -21
  37. package/dist/src/breaker/breaker.d.ts.map +1 -1
  38. package/dist/src/change/change.d.ts +5 -18
  39. package/dist/src/change/change.d.ts.map +1 -1
  40. package/dist/src/color/color.d.ts +126 -0
  41. package/dist/src/color/color.d.ts.map +1 -0
  42. package/dist/src/color/color.spec.d.ts +2 -0
  43. package/dist/src/color/color.spec.d.ts.map +1 -0
  44. package/dist/src/color/external.d.ts +5 -0
  45. package/dist/src/color/external.d.ts.map +1 -0
  46. package/dist/src/color/gradient.d.ts +18 -0
  47. package/dist/src/color/gradient.d.ts.map +1 -0
  48. package/dist/src/color/index.d.ts +2 -0
  49. package/dist/src/color/index.d.ts.map +1 -0
  50. package/dist/src/color/palette.d.ts +19 -0
  51. package/dist/src/color/palette.d.ts.map +1 -0
  52. package/dist/src/color/transformColorsToHex.d.ts +6 -0
  53. package/dist/src/color/transformColorsToHex.d.ts.map +1 -0
  54. package/dist/src/control/control.d.ts +69 -74
  55. package/dist/src/control/control.d.ts.map +1 -1
  56. package/dist/src/deep/merge.d.ts +1 -1
  57. package/dist/src/deep/merge.d.ts.map +1 -1
  58. package/dist/src/errors/errors.d.ts +127 -7
  59. package/dist/src/errors/errors.d.ts.map +1 -1
  60. package/dist/src/errors/errors.spec.d.ts +2 -0
  61. package/dist/src/errors/errors.spec.d.ts.map +1 -0
  62. package/dist/src/index.d.ts +4 -0
  63. package/dist/src/index.d.ts.map +1 -1
  64. package/dist/src/jsonrpc/jsonrpc.d.ts +10 -7
  65. package/dist/src/jsonrpc/jsonrpc.d.ts.map +1 -1
  66. package/dist/src/kv/types.d.ts +1 -7
  67. package/dist/src/kv/types.d.ts.map +1 -1
  68. package/dist/src/migrate/migrate.d.ts +1 -1
  69. package/dist/src/notation/notation.d.ts +5 -1
  70. package/dist/src/notation/notation.d.ts.map +1 -1
  71. package/dist/src/record.d.ts +2 -1
  72. package/dist/src/record.d.ts.map +1 -1
  73. package/dist/src/replace.d.ts +2 -0
  74. package/dist/src/replace.d.ts.map +1 -0
  75. package/dist/src/runtime/os.d.ts +9 -1
  76. package/dist/src/runtime/os.d.ts.map +1 -1
  77. package/dist/src/singleton/define.d.ts +9 -0
  78. package/dist/src/singleton/define.d.ts.map +1 -0
  79. package/dist/src/singleton/define.spec.d.ts +2 -0
  80. package/dist/src/singleton/define.spec.d.ts.map +1 -0
  81. package/dist/src/singleton/index.d.ts +2 -0
  82. package/dist/src/singleton/index.d.ts.map +1 -0
  83. package/dist/src/spatial/base.d.ts +74 -70
  84. package/dist/src/spatial/base.d.ts.map +1 -1
  85. package/dist/src/spatial/box/box.d.ts +18 -76
  86. package/dist/src/spatial/box/box.d.ts.map +1 -1
  87. package/dist/src/spatial/dimensions/dimensions.d.ts +5 -29
  88. package/dist/src/spatial/dimensions/dimensions.d.ts.map +1 -1
  89. package/dist/src/spatial/direction/direction.d.ts +9 -1
  90. package/dist/src/spatial/direction/direction.d.ts.map +1 -1
  91. package/dist/src/spatial/location/location.d.ts +43 -22
  92. package/dist/src/spatial/location/location.d.ts.map +1 -1
  93. package/dist/src/spatial/scale/scale.d.ts +12 -120
  94. package/dist/src/spatial/scale/scale.d.ts.map +1 -1
  95. package/dist/src/spatial/xy/xy.d.ts +5 -29
  96. package/dist/src/spatial/xy/xy.d.ts.map +1 -1
  97. package/dist/src/sync/index.d.ts +2 -0
  98. package/dist/src/sync/index.d.ts.map +1 -0
  99. package/dist/src/sync/mutex.d.ts +8 -0
  100. package/dist/src/sync/mutex.d.ts.map +1 -0
  101. package/dist/src/telem/gl.d.ts +4 -1
  102. package/dist/src/telem/gl.d.ts.map +1 -1
  103. package/dist/src/telem/series.d.ts +46 -125
  104. package/dist/src/telem/series.d.ts.map +1 -1
  105. package/dist/src/telem/telem.d.ts +101 -86
  106. package/dist/src/telem/telem.d.ts.map +1 -1
  107. package/dist/src/toArray.d.ts +1 -1
  108. package/dist/src/toArray.d.ts.map +1 -1
  109. package/dist/src/zod/util.d.ts.map +1 -1
  110. package/dist/telem.cjs +1 -1
  111. package/dist/telem.js +1 -1
  112. package/dist/toArray.cjs +1 -1
  113. package/dist/toArray.js +1 -1
  114. package/dist/zod.cjs +1 -1
  115. package/package.json +5 -2
  116. package/src/binary/codec.spec.ts +370 -0
  117. package/src/binary/{encoder.ts → codec.ts} +55 -11
  118. package/src/binary/index.ts +1 -1
  119. package/src/breaker/breaker.spec.ts +16 -25
  120. package/src/breaker/breaker.ts +36 -19
  121. package/src/color/color.spec.ts +673 -0
  122. package/src/color/color.ts +317 -0
  123. package/src/color/external.ts +13 -0
  124. package/src/color/gradient.ts +78 -0
  125. package/src/color/index.ts +10 -0
  126. package/src/color/palette.ts +28 -0
  127. package/src/color/transformColorsToHex.ts +30 -0
  128. package/src/control/control.ts +30 -22
  129. package/src/deep/merge.spec.ts +60 -0
  130. package/src/deep/merge.ts +13 -8
  131. package/src/errors/errors.spec.ts +152 -0
  132. package/src/errors/errors.ts +225 -10
  133. package/src/index.ts +4 -0
  134. package/src/jsonrpc/jsonrpc.ts +12 -8
  135. package/src/migrate/migrate.ts +2 -2
  136. package/src/primitive.ts +1 -1
  137. package/src/record.ts +5 -1
  138. package/src/replace.ts +1 -0
  139. package/src/singleton/define.spec.ts +93 -0
  140. package/src/singleton/define.ts +27 -0
  141. package/src/singleton/index.ts +10 -0
  142. package/src/sync/index.ts +1 -0
  143. package/src/sync/mutex.ts +16 -0
  144. package/src/telem/series.spec.ts +32 -0
  145. package/src/telem/series.ts +54 -19
  146. package/src/telem/telem.spec.ts +151 -10
  147. package/src/telem/telem.ts +126 -73
  148. package/src/toArray.ts +2 -2
  149. package/src/zod/util.spec.ts +17 -1
  150. package/src/zod/util.ts +4 -2
  151. package/tsconfig.tsbuildinfo +1 -1
  152. package/dist/index-BG3Scw3G.cjs +0 -1
  153. package/dist/index-C3QzbIwt.js +0 -101
  154. package/dist/index-CnclyYpG.cjs +0 -3
  155. package/dist/series-BMma2b5q.cjs +0 -11
  156. package/dist/src/binary/encoder.d.ts.map +0 -1
  157. package/dist/src/binary/encoder.spec.d.ts +0 -2
  158. package/dist/src/binary/encoder.spec.d.ts.map +0 -1
  159. 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";
@@ -32,12 +33,15 @@ export * from "@/optional";
32
33
  export * from "@/primitive";
33
34
  export * from "@/record";
34
35
  export * from "@/renderable";
36
+ export * from "@/replace";
35
37
  export * from "@/runtime";
36
38
  export * from "@/runtime";
37
39
  export * from "@/search";
38
40
  export * from "@/shallowCopy";
41
+ export * from "@/sleep";
39
42
  export * from "@/spatial";
40
43
  export * from "@/strings";
44
+ export * from "@/sync";
41
45
  export * from "@/telem";
42
46
  export * from "@/toArray";
43
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
  }
@@ -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);
package/src/primitive.ts CHANGED
@@ -41,6 +41,6 @@ export const primitiveIsZero = <V extends Primitive>(value: V): boolean => {
41
41
  case "undefined":
42
42
  return true;
43
43
  case "object":
44
- return value === null;
44
+ return true;
45
45
  }
46
46
  };
package/src/record.ts CHANGED
@@ -13,6 +13,8 @@ export type Key = string | number;
13
13
 
14
14
  export type UnknownRecord = Record<Key, unknown>;
15
15
 
16
+ export type UnknownStringRecord = Record<string, unknown>;
17
+
16
18
  export interface Keyed<K extends Key> {
17
19
  key: K;
18
20
  }
@@ -40,4 +42,6 @@ export const mapValues = <T extends Record<Key, unknown>, U>(
40
42
  obj: T,
41
43
  fn: (value: T[keyof T], key: Key) => U,
42
44
  ): Record<Key, U> =>
43
- Object.fromEntries(getEntries(obj).map(([key, value]) => [key, fn(value, key as Key)]));
45
+ Object.fromEntries(
46
+ getEntries(obj).map(([key, value]) => [key, fn(value, key as Key)]),
47
+ );
package/src/replace.ts ADDED
@@ -0,0 +1 @@
1
+ export type Replace<T, R> = Omit<T, keyof R> & R;
@@ -0,0 +1,93 @@
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 { define } from "@/singleton/define";
13
+
14
+ describe("define", () => {
15
+ it("should create and return a singleton instance", () => {
16
+ const key = "test-singleton-1";
17
+ let constructorCallCount = 0;
18
+
19
+ const createInstance = () => {
20
+ constructorCallCount++;
21
+ return { value: "singleton-value" };
22
+ };
23
+
24
+ const getInstance = define(key, createInstance);
25
+ const instance1 = getInstance();
26
+
27
+ expect(constructorCallCount).toBe(1);
28
+ expect(instance1).toEqual({ value: "singleton-value" });
29
+
30
+ const instance2 = getInstance();
31
+ expect(constructorCallCount).toBe(1);
32
+ expect(instance2).toBe(instance1); // Same instance reference
33
+ });
34
+
35
+ it("should maintain separate singletons for different keys", () => {
36
+ const key1 = "test-singleton-2";
37
+ const key2 = "test-singleton-3";
38
+
39
+ const getInstance1 = define(key1, () => ({ id: 1 }));
40
+ const getInstance2 = define(key2, () => ({ id: 2 }));
41
+
42
+ const instance1 = getInstance1();
43
+ const instance2 = getInstance2();
44
+
45
+ expect(instance1).toEqual({ id: 1 });
46
+ expect(instance2).toEqual({ id: 2 });
47
+ expect(instance1).not.toBe(instance2);
48
+ });
49
+
50
+ it("should retrieve the same singleton when defining with the same key multiple times", () => {
51
+ const key = "test-singleton-4";
52
+ let constructorCallCount = 0;
53
+
54
+ const createInstance1 = () => {
55
+ constructorCallCount++;
56
+ return { id: constructorCallCount };
57
+ };
58
+
59
+ const getInstance1 = define(key, createInstance1);
60
+ const firstInstance = getInstance1();
61
+ expect(firstInstance).toEqual({ id: 1 });
62
+ expect(constructorCallCount).toBe(1);
63
+
64
+ // Second definition with the same key but different factory
65
+ const createInstance2 = () => {
66
+ constructorCallCount++;
67
+ return { id: 999 }; // Different value
68
+ };
69
+
70
+ const getInstance2 = define(key, createInstance2);
71
+ const secondInstance = getInstance2();
72
+
73
+ expect(secondInstance).toBe(firstInstance);
74
+ expect(secondInstance).toEqual({ id: 1 });
75
+ expect(constructorCallCount).toBe(1); // Still 1, not 2
76
+ });
77
+
78
+ it("should handle object instances as singleton values", () => {
79
+ class TestClass {
80
+ public value: number;
81
+ constructor(value: number) {
82
+ this.value = value;
83
+ }
84
+ }
85
+
86
+ const key = "test-singleton-5";
87
+ const getInstance = define(key, () => new TestClass(42));
88
+
89
+ const instance = getInstance();
90
+ expect(instance).toBeInstanceOf(TestClass);
91
+ expect(instance.value).toBe(42);
92
+ });
93
+ });
@@ -0,0 +1,27 @@
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
+ const isDefined = (key: symbol): boolean =>
11
+ Object.getOwnPropertySymbols(globalThis).includes(key);
12
+
13
+ /**
14
+ * Defines a new global singleton instance of a value.
15
+ *
16
+ * @param key - The unique identifier for the singleton.
17
+ * @param value - A function that returns the singleton instance.
18
+ * @returns A function that returns the singleton instance.
19
+ */
20
+ export const define = <T>(key: string, value: () => T): (() => T) => {
21
+ const symbol = Symbol.for(key);
22
+ if (!isDefined(symbol)) {
23
+ const singleton = value();
24
+ Object.defineProperty(globalThis, symbol, { value: singleton });
25
+ }
26
+ return () => (globalThis as any)[symbol] as T;
27
+ };
@@ -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 singleton from "@/singleton/define";
@@ -0,0 +1 @@
1
+ export * as sync from "@/sync/mutex";
@@ -0,0 +1,16 @@
1
+ import { Mutex as Core } from "async-mutex";
2
+
3
+ export type Mutex<G> = G & Core;
4
+
5
+ export class mutex<G> extends Core {
6
+ static new<G>(guard: G): Mutex<G> & G {
7
+ return new mutex(guard) as Mutex<G> & G;
8
+ }
9
+
10
+ constructor(guard: G) {
11
+ super();
12
+ Object.assign(this, guard);
13
+ }
14
+ }
15
+
16
+ export const newMutex = <G>(guard: G): Mutex<G> => new mutex(guard) as Mutex<G>;