@synnaxlabs/x 0.52.5 → 0.53.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.
- package/.turbo/turbo-build.log +6 -6
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/json/index.d.ts +2 -0
- package/dist/src/json/index.d.ts.map +1 -0
- package/dist/src/json/json.d.ts +21 -0
- package/dist/src/json/json.d.ts.map +1 -0
- package/dist/src/json/json.spec.d.ts +2 -0
- package/dist/src/json/json.spec.d.ts.map +1 -0
- package/dist/src/telem/telem.d.ts +3 -2
- package/dist/src/telem/telem.d.ts.map +1 -1
- package/dist/x.cjs +7 -7
- package/dist/x.js +419 -399
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/json/index.ts +10 -0
- package/src/json/json.spec.ts +96 -0
- package/src/json/json.ts +32 -0
- package/src/telem/series.ts +1 -1
- package/src/telem/telem.spec.ts +28 -9
- package/src/telem/telem.ts +26 -14
- package/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synnaxlabs/x",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Common Utilities for Synnax Labs",
|
|
6
6
|
"repository": "https://github.com/synnaxlabs/synnax/tree/main/x/ts",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"vite": "^7.3.1",
|
|
28
28
|
"vitest": "^3.2.4",
|
|
29
29
|
"@synnaxlabs/eslint-config": "^0.0.0",
|
|
30
|
-
"@synnaxlabs/
|
|
31
|
-
"@synnaxlabs/
|
|
30
|
+
"@synnaxlabs/vite-plugin": "^0.0.0",
|
|
31
|
+
"@synnaxlabs/tsconfig": "^0.0.0"
|
|
32
32
|
},
|
|
33
33
|
"main": "dist/x.cjs",
|
|
34
34
|
"module": "dist/x.js",
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright 2026 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 json from "@/json/json";
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Copyright 2026 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 { json } from "@/json";
|
|
13
|
+
|
|
14
|
+
describe("json", () => {
|
|
15
|
+
describe("pointerZ", () => {
|
|
16
|
+
it("should accept an empty string", () => {
|
|
17
|
+
expect(json.pointerZ.parse("")).toBe("");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should accept a simple pointer", () => {
|
|
21
|
+
expect(json.pointerZ.parse("/foo")).toBe("/foo");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should accept a nested pointer", () => {
|
|
25
|
+
expect(json.pointerZ.parse("/foo/bar/baz")).toBe("/foo/bar/baz");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should accept a pointer with array indices", () => {
|
|
29
|
+
expect(json.pointerZ.parse("/items/0/name")).toBe("/items/0/name");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should accept a pointer with escaped tilde", () => {
|
|
33
|
+
expect(json.pointerZ.parse("/a~0b")).toBe("/a~0b");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should accept a pointer with escaped slash", () => {
|
|
37
|
+
expect(json.pointerZ.parse("/a~1b")).toBe("/a~1b");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should reject a pointer that does not start with /", () => {
|
|
41
|
+
expect(json.pointerZ.safeParse("foo").success).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should reject a pointer with a bare tilde", () => {
|
|
45
|
+
expect(json.pointerZ.safeParse("/a~b").success).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("primitiveZ", () => {
|
|
50
|
+
it("should accept a string", () => {
|
|
51
|
+
expect(json.primitiveZ.parse("hello")).toBe("hello");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should accept a number", () => {
|
|
55
|
+
expect(json.primitiveZ.parse(42)).toBe(42);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should accept a boolean", () => {
|
|
59
|
+
expect(json.primitiveZ.parse(true)).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should accept null", () => {
|
|
63
|
+
expect(json.primitiveZ.parse(null)).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should reject an object", () => {
|
|
67
|
+
expect(json.primitiveZ.safeParse({}).success).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should reject an array", () => {
|
|
71
|
+
expect(json.primitiveZ.safeParse([]).success).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("primitiveTypeZ", () => {
|
|
76
|
+
it("should accept 'string'", () => {
|
|
77
|
+
expect(json.primitiveTypeZ.parse("string")).toBe("string");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should accept 'number'", () => {
|
|
81
|
+
expect(json.primitiveTypeZ.parse("number")).toBe("number");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should accept 'boolean'", () => {
|
|
85
|
+
expect(json.primitiveTypeZ.parse("boolean")).toBe("boolean");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should accept 'null'", () => {
|
|
89
|
+
expect(json.primitiveTypeZ.parse("null")).toBe("null");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should reject an invalid type name", () => {
|
|
93
|
+
expect(json.primitiveTypeZ.safeParse("object").success).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
package/src/json/json.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Copyright 2026 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 { z } from "zod";
|
|
11
|
+
|
|
12
|
+
/** Validates an RFC 6901 JSON Pointer string (e.g. "/foo/bar/0"). */
|
|
13
|
+
export const pointerZ = z
|
|
14
|
+
.string()
|
|
15
|
+
.regex(/^(?:$|(?:\/(?:[^~/]|~0|~1)*)+)$/, "must be a valid JSON pointer (RFC 6901)");
|
|
16
|
+
|
|
17
|
+
/** A JSON primitive value: string, number, boolean, or null. */
|
|
18
|
+
export const primitiveZ = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
|
19
|
+
|
|
20
|
+
export type Primitive = z.infer<typeof primitiveZ>;
|
|
21
|
+
|
|
22
|
+
/** The type name of a JSON primitive: "string", "number", "boolean", or "null". */
|
|
23
|
+
export const primitiveTypeZ = z.enum(["string", "number", "boolean", "null"]);
|
|
24
|
+
|
|
25
|
+
export type PrimitiveType = z.infer<typeof primitiveTypeZ>;
|
|
26
|
+
|
|
27
|
+
export const ZERO_PRIMITIVES = {
|
|
28
|
+
string: "",
|
|
29
|
+
number: 0,
|
|
30
|
+
boolean: false,
|
|
31
|
+
null: null,
|
|
32
|
+
} as const satisfies Record<PrimitiveType, Primitive>;
|
package/src/telem/series.ts
CHANGED
|
@@ -330,7 +330,7 @@ export class Series<T extends TelemValue = TelemValue>
|
|
|
330
330
|
if (typeof first === "string") this.dataType = DataType.STRING;
|
|
331
331
|
else if (typeof first === "number") this.dataType = DataType.FLOAT64;
|
|
332
332
|
else if (typeof first === "bigint") this.dataType = DataType.INT64;
|
|
333
|
-
else if (typeof first === "boolean") this.dataType = DataType.
|
|
333
|
+
else if (typeof first === "boolean") this.dataType = DataType.UINT8;
|
|
334
334
|
else if (
|
|
335
335
|
first instanceof TimeStamp ||
|
|
336
336
|
first instanceof Date ||
|
package/src/telem/telem.spec.ts
CHANGED
|
@@ -144,6 +144,26 @@ describe("TimeStamp", () => {
|
|
|
144
144
|
|
|
145
145
|
expect(ts1.valueOf()).toEqual(ts2.valueOf());
|
|
146
146
|
});
|
|
147
|
+
|
|
148
|
+
test("should round-trip across DST boundaries", () => {
|
|
149
|
+
// 2025-03-09 is US DST spring-forward, 2025-11-02 is US DST fall-back. These
|
|
150
|
+
// dates may have a different UTC offset than the current date, so we verify the
|
|
151
|
+
// round-trip works regardless of DST transitions.
|
|
152
|
+
const dstDates = [
|
|
153
|
+
"2025-03-09T12:00:00.000",
|
|
154
|
+
"2025-03-10T12:00:00.000",
|
|
155
|
+
"2025-11-02T12:00:00.000",
|
|
156
|
+
"2025-11-03T12:00:00.000",
|
|
157
|
+
"2025-06-15T12:00:00.000",
|
|
158
|
+
"2025-01-15T12:00:00.000",
|
|
159
|
+
];
|
|
160
|
+
for (const input of dstDates) {
|
|
161
|
+
const ts1 = new TimeStamp(input, "local");
|
|
162
|
+
const output = ts1.toString("ISO", "local").slice(0, -1);
|
|
163
|
+
const ts2 = new TimeStamp(output, "local");
|
|
164
|
+
expect(ts1.valueOf()).toEqual(ts2.valueOf());
|
|
165
|
+
}
|
|
166
|
+
});
|
|
147
167
|
});
|
|
148
168
|
|
|
149
169
|
test("construct from date", () => {
|
|
@@ -1832,6 +1852,9 @@ describe("DataType", () => {
|
|
|
1832
1852
|
it("should return false if the data type does not have a variable length", () => {
|
|
1833
1853
|
expect(DataType.STRING.isVariable).toBe(true);
|
|
1834
1854
|
});
|
|
1855
|
+
it("should return true for BYTES", () => {
|
|
1856
|
+
expect(DataType.BYTES.isVariable).toBe(true);
|
|
1857
|
+
});
|
|
1835
1858
|
});
|
|
1836
1859
|
|
|
1837
1860
|
describe("construct", () => {
|
|
@@ -1877,7 +1900,6 @@ describe("DataType", () => {
|
|
|
1877
1900
|
[DataType.INT32, DataType.FLOAT32, false],
|
|
1878
1901
|
[DataType.INT32, DataType.FLOAT64, true],
|
|
1879
1902
|
[DataType.INT32, DataType.STRING, false],
|
|
1880
|
-
[DataType.INT32, DataType.BOOLEAN, false],
|
|
1881
1903
|
[DataType.INT32, DataType.INT8, false],
|
|
1882
1904
|
[DataType.INT64, DataType.INT32, false],
|
|
1883
1905
|
[DataType.INT64, DataType.INT64, true],
|
|
@@ -1887,22 +1909,19 @@ describe("DataType", () => {
|
|
|
1887
1909
|
[DataType.FLOAT64, DataType.FLOAT32, false],
|
|
1888
1910
|
[DataType.FLOAT64, DataType.FLOAT64, true],
|
|
1889
1911
|
[DataType.FLOAT64, DataType.STRING, false],
|
|
1890
|
-
[DataType.FLOAT64, DataType.BOOLEAN, false],
|
|
1891
1912
|
[DataType.FLOAT32, DataType.FLOAT64, true],
|
|
1892
1913
|
[DataType.FLOAT32, DataType.FLOAT32, true],
|
|
1893
1914
|
[DataType.FLOAT32, DataType.STRING, false],
|
|
1894
|
-
[DataType.FLOAT32, DataType.BOOLEAN, false],
|
|
1895
1915
|
[DataType.STRING, DataType.STRING, true],
|
|
1896
1916
|
[DataType.STRING, DataType.INT32, false],
|
|
1897
1917
|
[DataType.STRING, DataType.INT64, false],
|
|
1898
1918
|
[DataType.STRING, DataType.FLOAT32, false],
|
|
1899
1919
|
[DataType.STRING, DataType.FLOAT64, false],
|
|
1900
|
-
[DataType.STRING, DataType.BOOLEAN, false],
|
|
1901
1920
|
[DataType.STRING, DataType.INT8, false],
|
|
1902
|
-
[DataType.BOOLEAN, DataType.BOOLEAN, true],
|
|
1903
|
-
[DataType.BOOLEAN, DataType.INT32, false],
|
|
1904
|
-
[DataType.BOOLEAN, DataType.INT64, false],
|
|
1905
1921
|
[DataType.INT8, DataType.FLOAT32, true],
|
|
1922
|
+
[DataType.BYTES, DataType.BYTES, true],
|
|
1923
|
+
[DataType.BYTES, DataType.INT32, false],
|
|
1924
|
+
[DataType.BYTES, DataType.STRING, false],
|
|
1906
1925
|
];
|
|
1907
1926
|
TESTS.forEach(([from, to, expected]) =>
|
|
1908
1927
|
it(`should return ${expected} when casting from ${from.toString()} to ${to.toString()}`, () => {
|
|
@@ -1923,7 +1942,7 @@ describe("DataType", () => {
|
|
|
1923
1942
|
for (const to of numericTypes) expect(from.canCastTo(to)).toBe(true);
|
|
1924
1943
|
});
|
|
1925
1944
|
it("should return true for non-numeric data types ONLY if they are equal", () => {
|
|
1926
|
-
const nonNumericTypes = [DataType.STRING, DataType.
|
|
1945
|
+
const nonNumericTypes = [DataType.STRING, DataType.BYTES];
|
|
1927
1946
|
for (const from of nonNumericTypes)
|
|
1928
1947
|
for (const to of nonNumericTypes) expect(from.canCastTo(to)).toBe(from === to);
|
|
1929
1948
|
});
|
|
@@ -1969,10 +1988,10 @@ describe("DataType", () => {
|
|
|
1969
1988
|
{ input: "float32", expected: "float32", short: "f32" },
|
|
1970
1989
|
{ input: "float64", expected: "float64", short: "f64" },
|
|
1971
1990
|
{ input: "string", expected: "string", short: "str" },
|
|
1972
|
-
{ input: "boolean", expected: "boolean", short: "bool" },
|
|
1973
1991
|
{ input: "timestamp", expected: "timestamp", short: "ts" },
|
|
1974
1992
|
{ input: "uuid", expected: "uuid", short: "uuid" },
|
|
1975
1993
|
{ input: "json", expected: "json", short: "json" },
|
|
1994
|
+
{ input: "bytes", expected: "bytes", short: "bytes" },
|
|
1976
1995
|
];
|
|
1977
1996
|
|
|
1978
1997
|
testCases.forEach(({ input, expected, short }) => {
|
package/src/telem/telem.ts
CHANGED
|
@@ -192,7 +192,11 @@ export class TimeStamp
|
|
|
192
192
|
|
|
193
193
|
private toISOString(tzInfo: TZInfo = "UTC"): string {
|
|
194
194
|
if (tzInfo === "UTC") return this.date().toISOString();
|
|
195
|
-
|
|
195
|
+
const d = this.date();
|
|
196
|
+
const offset = new TimeSpan(
|
|
197
|
+
BigInt(d.getTimezoneOffset()) * TimeStamp.MINUTE.valueOf(),
|
|
198
|
+
);
|
|
199
|
+
return this.sub(offset).date().toISOString();
|
|
196
200
|
}
|
|
197
201
|
|
|
198
202
|
private timeString(milliseconds: boolean = false, tzInfo: TZInfo = "UTC"): string {
|
|
@@ -1852,7 +1856,11 @@ export class DataType
|
|
|
1852
1856
|
* @example DataType.INT32.isVariable // false
|
|
1853
1857
|
*/
|
|
1854
1858
|
get isVariable(): boolean {
|
|
1855
|
-
return
|
|
1859
|
+
return (
|
|
1860
|
+
this.equals(DataType.JSON) ||
|
|
1861
|
+
this.equals(DataType.STRING) ||
|
|
1862
|
+
this.equals(DataType.BYTES)
|
|
1863
|
+
);
|
|
1856
1864
|
}
|
|
1857
1865
|
|
|
1858
1866
|
/**
|
|
@@ -1985,8 +1993,6 @@ export class DataType
|
|
|
1985
1993
|
static readonly UINT16 = new DataType("uint16");
|
|
1986
1994
|
/** Represents a 8-bit unsigned integer value. */
|
|
1987
1995
|
static readonly UINT8 = new DataType("uint8");
|
|
1988
|
-
/** Represents a boolean value. Stored as a 8-bit unsigned integer. */
|
|
1989
|
-
static readonly BOOLEAN = new DataType("boolean");
|
|
1990
1996
|
/** Represents a 64-bit unix epoch. */
|
|
1991
1997
|
static readonly TIMESTAMP = new DataType("timestamp");
|
|
1992
1998
|
/** Represents a UUID data type. */
|
|
@@ -1997,6 +2003,9 @@ export class DataType
|
|
|
1997
2003
|
/** Represents a JSON data type. JSON has an unknown density, and is separated by a
|
|
1998
2004
|
* newline character. */
|
|
1999
2005
|
static readonly JSON = new DataType("json");
|
|
2006
|
+
/** Represents a bytes data type for arbitrary byte arrays. Bytes have an unknown
|
|
2007
|
+
* density, and are separated by a newline character. */
|
|
2008
|
+
static readonly BYTES = new DataType("bytes");
|
|
2000
2009
|
|
|
2001
2010
|
private static readonly ARRAY_CONSTRUCTORS: Map<string, TypedArrayConstructor> =
|
|
2002
2011
|
new Map<string, TypedArrayConstructor>([
|
|
@@ -2014,6 +2023,7 @@ export class DataType
|
|
|
2014
2023
|
[DataType.STRING.toString(), Uint8Array],
|
|
2015
2024
|
[DataType.JSON.toString(), Uint8Array],
|
|
2016
2025
|
[DataType.UUID.toString(), Uint8Array],
|
|
2026
|
+
[DataType.BYTES.toString(), Uint8Array],
|
|
2017
2027
|
]);
|
|
2018
2028
|
|
|
2019
2029
|
private static readonly ARRAY_CONSTRUCTOR_DATA_TYPES: Map<string, DataType> = new Map<
|
|
@@ -2047,25 +2057,27 @@ export class DataType
|
|
|
2047
2057
|
[DataType.STRING.toString(), Density.UNKNOWN],
|
|
2048
2058
|
[DataType.JSON.toString(), Density.UNKNOWN],
|
|
2049
2059
|
[DataType.UUID.toString(), Density.BIT128],
|
|
2060
|
+
[DataType.BYTES.toString(), Density.UNKNOWN],
|
|
2050
2061
|
]);
|
|
2051
2062
|
|
|
2052
2063
|
/** All the data types. */
|
|
2053
2064
|
static readonly ALL = [
|
|
2054
2065
|
DataType.UNKNOWN,
|
|
2055
|
-
DataType.FLOAT64,
|
|
2056
|
-
DataType.FLOAT32,
|
|
2057
|
-
DataType.INT64,
|
|
2058
|
-
DataType.INT32,
|
|
2059
|
-
DataType.INT16,
|
|
2060
|
-
DataType.INT8,
|
|
2061
|
-
DataType.UINT64,
|
|
2062
|
-
DataType.UINT32,
|
|
2063
|
-
DataType.UINT16,
|
|
2064
2066
|
DataType.UINT8,
|
|
2067
|
+
DataType.UINT16,
|
|
2068
|
+
DataType.UINT32,
|
|
2069
|
+
DataType.UINT64,
|
|
2070
|
+
DataType.INT8,
|
|
2071
|
+
DataType.INT16,
|
|
2072
|
+
DataType.INT32,
|
|
2073
|
+
DataType.INT64,
|
|
2074
|
+
DataType.FLOAT32,
|
|
2075
|
+
DataType.FLOAT64,
|
|
2065
2076
|
DataType.TIMESTAMP,
|
|
2066
2077
|
DataType.UUID,
|
|
2067
2078
|
DataType.STRING,
|
|
2068
2079
|
DataType.JSON,
|
|
2080
|
+
DataType.BYTES,
|
|
2069
2081
|
];
|
|
2070
2082
|
|
|
2071
2083
|
private static readonly SHORT_STRINGS = new Map<string, string>([
|
|
@@ -2079,11 +2091,11 @@ export class DataType
|
|
|
2079
2091
|
[DataType.INT64.toString(), "i64"],
|
|
2080
2092
|
[DataType.FLOAT32.toString(), "f32"],
|
|
2081
2093
|
[DataType.FLOAT64.toString(), "f64"],
|
|
2082
|
-
[DataType.BOOLEAN.toString(), "bool"],
|
|
2083
2094
|
[DataType.TIMESTAMP.toString(), "ts"],
|
|
2084
2095
|
[DataType.UUID.toString(), "uuid"],
|
|
2085
2096
|
[DataType.STRING.toString(), "str"],
|
|
2086
2097
|
[DataType.JSON.toString(), "json"],
|
|
2098
|
+
[DataType.BYTES.toString(), "bytes"],
|
|
2087
2099
|
]);
|
|
2088
2100
|
|
|
2089
2101
|
static readonly BIG_INT_TYPES = [DataType.INT64, DataType.UINT64, DataType.TIMESTAMP];
|