@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.
- package/.turbo/turbo-build.log +33 -33
- package/dist/binary.cjs +1 -1
- package/dist/binary.js +2 -2
- package/dist/bounds-BQo7rvs9.cjs +1 -0
- package/dist/{bounds-azUOoVVR.js → bounds-Bn5_l4Z3.js} +84 -86
- package/dist/bounds.cjs +1 -1
- package/dist/bounds.js +1 -1
- package/dist/box-0YrQibkB.cjs +1 -0
- package/dist/box-Cc8IzcNo.js +205 -0
- package/dist/box.cjs +1 -1
- package/dist/box.js +1 -1
- package/dist/compare.cjs +1 -1
- package/dist/compare.js +1 -1
- package/dist/deep.cjs +1 -1
- package/dist/deep.js +66 -69
- package/dist/{dimensions-PWy5QZoM.cjs → dimensions-D2QGoNXO.cjs} +1 -1
- package/dist/dimensions.cjs +1 -1
- package/dist/{external-CvWr1nhS.cjs → external-DWQITF5_.cjs} +1 -1
- package/dist/index-BywOGO8U.js +1074 -0
- package/dist/index-CYYjI7Uf.cjs +1 -0
- package/dist/index-C_6NXBlg.cjs +3 -0
- package/dist/{index-BVC_8Cg9.js → index-QGplUHuy.js} +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.js +736 -240
- package/dist/location-BGl5Ddds.cjs +1 -0
- package/dist/{location-BuYbIFHD.js → location-C3aeu046.js} +16 -12
- package/dist/location.cjs +1 -1
- package/dist/location.js +1 -1
- package/dist/{position-DTrNGtrm.cjs → position-Cai5-wi1.cjs} +1 -1
- package/dist/{position-DemzGvAY.js → position-DIglP1l2.js} +2 -2
- package/dist/position.cjs +1 -1
- package/dist/position.js +1 -1
- package/dist/record.js +3 -1
- package/dist/{scale-DpJM6__6.cjs → scale-BtZINJ-A.cjs} +1 -1
- package/dist/{scale-C0EllH-1.js → scale-DfJe9755.js} +4 -4
- package/dist/scale.cjs +1 -1
- package/dist/scale.js +1 -1
- package/dist/{series-CXnO-P0V.js → series-B9JERcqi.js} +541 -474
- package/dist/series-DqJ6f97G.cjs +11 -0
- package/dist/spatial.cjs +1 -1
- package/dist/spatial.js +6 -6
- package/dist/src/binary/{encoder.d.ts → codec.d.ts} +14 -8
- package/dist/src/binary/codec.d.ts.map +1 -0
- package/dist/src/binary/codec.spec.d.ts +2 -0
- package/dist/src/binary/codec.spec.d.ts.map +1 -0
- package/dist/src/binary/index.d.ts +1 -1
- package/dist/src/binary/index.d.ts.map +1 -1
- package/dist/src/breaker/breaker.d.ts +14 -21
- package/dist/src/breaker/breaker.d.ts.map +1 -1
- package/dist/src/change/change.d.ts +5 -18
- package/dist/src/change/change.d.ts.map +1 -1
- package/dist/src/color/color.d.ts +126 -0
- package/dist/src/color/color.d.ts.map +1 -0
- package/dist/src/color/color.spec.d.ts +2 -0
- package/dist/src/color/color.spec.d.ts.map +1 -0
- package/dist/src/color/external.d.ts +5 -0
- package/dist/src/color/external.d.ts.map +1 -0
- package/dist/src/color/gradient.d.ts +18 -0
- package/dist/src/color/gradient.d.ts.map +1 -0
- package/dist/src/color/index.d.ts +2 -0
- package/dist/src/color/index.d.ts.map +1 -0
- package/dist/src/color/palette.d.ts +19 -0
- package/dist/src/color/palette.d.ts.map +1 -0
- package/dist/src/color/transformColorsToHex.d.ts +6 -0
- package/dist/src/color/transformColorsToHex.d.ts.map +1 -0
- package/dist/src/control/control.d.ts +69 -74
- package/dist/src/control/control.d.ts.map +1 -1
- package/dist/src/deep/merge.d.ts +1 -1
- package/dist/src/deep/merge.d.ts.map +1 -1
- package/dist/src/errors/errors.d.ts +127 -7
- package/dist/src/errors/errors.d.ts.map +1 -1
- package/dist/src/errors/errors.spec.d.ts +2 -0
- package/dist/src/errors/errors.spec.d.ts.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/jsonrpc/jsonrpc.d.ts +10 -7
- package/dist/src/jsonrpc/jsonrpc.d.ts.map +1 -1
- package/dist/src/kv/types.d.ts +1 -7
- package/dist/src/kv/types.d.ts.map +1 -1
- package/dist/src/math/external.d.ts +3 -0
- package/dist/src/math/external.d.ts.map +1 -0
- package/dist/src/math/index.d.ts +1 -1
- package/dist/src/math/index.d.ts.map +1 -1
- package/dist/src/math/round.d.ts +40 -0
- package/dist/src/math/round.d.ts.map +1 -0
- package/dist/src/math/round.spec.d.ts +2 -0
- package/dist/src/math/round.spec.d.ts.map +1 -0
- package/dist/src/migrate/migrate.d.ts +1 -1
- package/dist/src/notation/index.d.ts +2 -0
- package/dist/src/notation/index.d.ts.map +1 -0
- package/dist/src/notation/notation.d.ts +37 -0
- package/dist/src/notation/notation.d.ts.map +1 -0
- package/dist/src/notation/notation.spec.d.ts +2 -0
- package/dist/src/notation/notation.spec.d.ts.map +1 -0
- package/dist/src/record.d.ts +2 -1
- package/dist/src/record.d.ts.map +1 -1
- package/dist/src/replace.d.ts +2 -0
- package/dist/src/replace.d.ts.map +1 -0
- package/dist/src/runtime/os.d.ts +9 -1
- package/dist/src/runtime/os.d.ts.map +1 -1
- package/dist/src/singleton/define.d.ts +9 -0
- package/dist/src/singleton/define.d.ts.map +1 -0
- package/dist/src/singleton/define.spec.d.ts +2 -0
- package/dist/src/singleton/define.spec.d.ts.map +1 -0
- package/dist/src/singleton/index.d.ts +2 -0
- package/dist/src/singleton/index.d.ts.map +1 -0
- package/dist/src/spatial/base.d.ts +74 -70
- package/dist/src/spatial/base.d.ts.map +1 -1
- package/dist/src/spatial/box/box.d.ts +22 -76
- package/dist/src/spatial/box/box.d.ts.map +1 -1
- package/dist/src/spatial/dimensions/dimensions.d.ts +5 -29
- package/dist/src/spatial/dimensions/dimensions.d.ts.map +1 -1
- package/dist/src/spatial/direction/direction.d.ts +9 -1
- package/dist/src/spatial/direction/direction.d.ts.map +1 -1
- package/dist/src/spatial/location/location.d.ts +45 -24
- package/dist/src/spatial/location/location.d.ts.map +1 -1
- package/dist/src/spatial/scale/scale.d.ts +12 -120
- package/dist/src/spatial/scale/scale.d.ts.map +1 -1
- package/dist/src/spatial/xy/xy.d.ts +15 -29
- package/dist/src/spatial/xy/xy.d.ts.map +1 -1
- package/dist/src/sync/index.d.ts +2 -0
- package/dist/src/sync/index.d.ts.map +1 -0
- package/dist/src/sync/mutex.d.ts +8 -0
- package/dist/src/sync/mutex.d.ts.map +1 -0
- package/dist/src/telem/gl.d.ts +4 -1
- package/dist/src/telem/gl.d.ts.map +1 -1
- package/dist/src/telem/series.d.ts +46 -125
- package/dist/src/telem/series.d.ts.map +1 -1
- package/dist/src/telem/telem.d.ts +102 -85
- package/dist/src/telem/telem.d.ts.map +1 -1
- package/dist/src/toArray.d.ts +1 -1
- package/dist/src/toArray.d.ts.map +1 -1
- package/dist/src/zod/util.d.ts.map +1 -1
- package/dist/telem.cjs +1 -1
- package/dist/telem.js +1 -1
- package/dist/toArray.cjs +1 -1
- package/dist/toArray.js +1 -1
- package/dist/{xy-DyQSETQZ.cjs → xy-B7065J2S.cjs} +1 -1
- package/dist/{xy-DHBO1dG_.js → xy-D_LqxaGt.js} +8 -4
- package/dist/xy.cjs +1 -1
- package/dist/xy.js +1 -1
- package/dist/zod.cjs +1 -1
- package/package.json +11 -8
- package/src/binary/codec.spec.ts +370 -0
- package/src/binary/{encoder.ts → codec.ts} +55 -11
- package/src/binary/index.ts +1 -1
- package/src/breaker/breaker.spec.ts +16 -25
- package/src/breaker/breaker.ts +36 -19
- package/src/color/color.spec.ts +673 -0
- package/src/color/color.ts +317 -0
- package/src/color/external.ts +13 -0
- package/src/color/gradient.ts +78 -0
- package/src/color/index.ts +10 -0
- package/src/color/palette.ts +28 -0
- package/src/color/transformColorsToHex.ts +30 -0
- package/src/control/control.ts +30 -22
- package/src/deep/merge.ts +2 -8
- package/src/errors/errors.spec.ts +152 -0
- package/src/errors/errors.ts +225 -10
- package/src/index.ts +5 -0
- package/src/jsonrpc/jsonrpc.ts +12 -8
- package/src/math/external.ts +11 -0
- package/src/math/index.ts +1 -1
- package/src/math/round.spec.ts +81 -0
- package/src/math/round.ts +68 -0
- package/src/migrate/migrate.ts +2 -2
- package/src/notation/index.ts +10 -0
- package/src/notation/notation.spec.ts +88 -0
- package/src/notation/notation.ts +61 -0
- package/src/primitive.ts +1 -1
- package/src/record.ts +5 -1
- package/src/replace.ts +1 -0
- package/src/singleton/define.spec.ts +93 -0
- package/src/singleton/define.ts +27 -0
- package/src/singleton/index.ts +10 -0
- package/src/spatial/box/box.spec.ts +3 -3
- package/src/spatial/box/box.ts +8 -0
- package/src/spatial/location/location.ts +4 -4
- package/src/spatial/position/position.spec.ts +10 -10
- package/src/spatial/xy/xy.spec.ts +8 -0
- package/src/spatial/xy/xy.ts +14 -0
- package/src/sync/index.ts +1 -0
- package/src/sync/mutex.ts +16 -0
- package/src/telem/series.spec.ts +71 -0
- package/src/telem/series.ts +69 -46
- package/src/telem/telem.spec.ts +151 -10
- package/src/telem/telem.ts +134 -73
- package/src/toArray.ts +2 -2
- package/src/zod/util.spec.ts +17 -1
- package/src/zod/util.ts +4 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/bounds-Dwq6ZFHm.cjs +0 -1
- package/dist/box-Bzya27QS.cjs +0 -1
- package/dist/box-DrsrRNSe.js +0 -201
- package/dist/index-BG3Scw3G.cjs +0 -1
- package/dist/index-C3QzbIwt.js +0 -101
- package/dist/index-CnclyYpG.cjs +0 -3
- package/dist/location-BgpQ3rN2.cjs +0 -1
- package/dist/series-BgoCtU71.cjs +0 -11
- package/dist/src/binary/encoder.d.ts.map +0 -1
- package/dist/src/binary/encoder.spec.d.ts +0 -2
- package/dist/src/binary/encoder.spec.d.ts.map +0 -1
- 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
|
+
});
|
package/src/errors/errors.ts
CHANGED
|
@@ -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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
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
|
-
*
|
|
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
|
|
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";
|
package/src/jsonrpc/jsonrpc.ts
CHANGED
|
@@ -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
|
-
|
|
13
|
-
jsonrpc: string
|
|
14
|
-
id
|
|
15
|
-
method
|
|
16
|
-
params
|
|
17
|
-
result
|
|
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
|
@@ -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
|
+
};
|
package/src/migrate/migrate.ts
CHANGED
|
@@ -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";
|