@synnaxlabs/x 0.36.0 → 0.38.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 +41 -37
- package/dist/binary.cjs +1 -1
- package/dist/binary.js +1 -1
- package/dist/box-BZ6d2d5A.cjs +1 -0
- package/dist/box-Cto-5Uxu.js +201 -0
- package/dist/box.cjs +1 -1
- package/dist/box.js +1 -1
- package/dist/caseconv.cjs +1 -1
- package/dist/caseconv.js +1 -1
- package/dist/compare.cjs +1 -1
- package/dist/compare.js +1 -1
- package/dist/external-C-dNgNQw.cjs +1 -0
- package/dist/external-Cax-LfQW.cjs +1 -0
- package/dist/external-DqPrWKvU.js +47 -0
- package/dist/external-vFGUdZf6.js +30 -0
- package/dist/index-BG3Scw3G.cjs +1 -0
- package/dist/{index-HQonyH7n.js → index-BVC_8Cg9.js} +1 -1
- package/dist/index-BfDeGfej.js +41 -0
- package/dist/{index-CYxQwEdX.cjs → index-CnclyYpG.cjs} +1 -1
- package/dist/index-D4NCYiQB.js +19 -0
- package/dist/index-Dd8DLyMx.cjs +1 -0
- package/dist/{index-BBa2mWG1.js → index-OHIRoCei.js} +1 -1
- package/dist/index-udOjA9d-.cjs +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.js +126 -124
- package/dist/kv.cjs +1 -1
- package/dist/kv.js +2 -2
- package/dist/link.cjs +1 -0
- package/dist/link.js +10 -0
- package/dist/{location-DLP2ZS0o.cjs → location-BIet4Mig.cjs} +1 -1
- package/dist/{location-Cn1ByVTZ.js → location-C5Ot4MVG.js} +1 -1
- package/dist/location.cjs +1 -1
- package/dist/location.js +1 -1
- package/dist/position-CvSNZkSD.cjs +1 -0
- package/dist/position-GeF1oEYk.js +85 -0
- package/dist/position.cjs +1 -1
- package/dist/position.js +1 -1
- package/dist/runtime.cjs +1 -1
- package/dist/runtime.js +1 -1
- package/dist/{scale-BI4wJF3b.cjs → scale-BTgf0Mr-.cjs} +1 -1
- package/dist/{scale-rZ1YKDFy.js → scale-Dh1UNRoC.js} +3 -3
- package/dist/scale.cjs +1 -1
- package/dist/scale.js +1 -1
- package/dist/series-BMhEEJZL.cjs +11 -0
- package/dist/{series-CnEQe1dh.js → series-DxDIugLj.js} +296 -286
- package/dist/spatial.cjs +1 -1
- package/dist/spatial.js +5 -5
- package/dist/src/breaker/breaker.d.ts +1 -1
- package/dist/src/breaker/breaker.d.ts.map +1 -1
- package/dist/src/caseconv/caseconv.d.ts +12 -6
- package/dist/src/caseconv/caseconv.d.ts.map +1 -1
- package/dist/src/deep/path.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/kv/external.d.ts +3 -0
- package/dist/src/kv/external.d.ts.map +1 -0
- package/dist/src/kv/index.d.ts +1 -1
- package/dist/src/kv/index.d.ts.map +1 -1
- package/dist/src/kv/mock.d.ts +24 -0
- package/dist/src/kv/mock.d.ts.map +1 -0
- package/dist/src/kv/mock.spec.d.ts +2 -0
- package/dist/src/kv/mock.spec.d.ts.map +1 -0
- package/dist/src/kv/types.d.ts +18 -18
- package/dist/src/kv/types.d.ts.map +1 -1
- package/dist/src/link/index.d.ts +2 -0
- package/dist/src/link/index.d.ts.map +1 -0
- package/dist/src/link/link.d.ts +2 -0
- package/dist/src/link/link.d.ts.map +1 -0
- package/dist/src/link/link.spec.d.ts +2 -0
- package/dist/src/link/link.spec.d.ts.map +1 -0
- package/dist/src/runtime/os.d.ts +1 -1
- package/dist/src/runtime/os.d.ts.map +1 -1
- package/dist/src/sleep/sleep.d.ts.map +1 -1
- package/dist/src/telem/index.d.ts +1 -1
- package/dist/src/telem/index.d.ts.map +1 -1
- package/dist/src/telem/series.d.ts +1 -0
- package/dist/src/telem/series.d.ts.map +1 -1
- package/dist/src/telem/telem.d.ts +2 -0
- package/dist/src/telem/telem.d.ts.map +1 -1
- package/dist/src/unique/index.d.ts +2 -0
- package/dist/src/unique/index.d.ts.map +1 -0
- package/dist/src/unique/unique.d.ts +42 -0
- package/dist/src/unique/unique.d.ts.map +1 -0
- package/dist/src/unique/unique.spec.d.ts +2 -0
- package/dist/src/unique/unique.spec.d.ts.map +1 -0
- package/dist/src/zodutil/index.d.ts.map +1 -1
- package/dist/src/zodutil/zodutil.d.ts +1 -16
- package/dist/src/zodutil/zodutil.d.ts.map +1 -1
- package/dist/telem.cjs +1 -1
- package/dist/telem.js +1 -1
- package/dist/unique.cjs +1 -1
- package/dist/unique.js +2 -2
- package/dist/url.cjs +1 -1
- package/dist/url.js +5 -5
- package/dist/{xy-LADI2wVU.cjs → xy-Budz-qz-.cjs} +1 -1
- package/dist/{xy-DQdccWlc.js → xy-DxjPL2DZ.js} +4 -4
- package/dist/xy.cjs +1 -1
- package/dist/xy.js +1 -1
- package/dist/zodutil-BfrF8jE3.js +23 -0
- package/dist/zodutil-DFJyyQd2.cjs +1 -0
- package/dist/zodutil.cjs +1 -1
- package/dist/zodutil.js +1 -1
- package/package.json +8 -10
- package/src/breaker/breaker.spec.ts +3 -4
- package/src/breaker/breaker.ts +3 -2
- package/src/caseconv/caseconv.spec.ts +20 -20
- package/src/caseconv/caseconv.ts +34 -5
- package/src/compare/compare.ts +2 -2
- package/src/deep/path.ts +1 -1
- package/src/index.ts +2 -1
- package/src/{unique.ts → kv/external.ts} +2 -1
- package/src/kv/index.ts +1 -1
- package/src/kv/mock.spec.ts +101 -0
- package/src/kv/mock.ts +58 -0
- package/src/kv/types.ts +22 -28
- package/src/link/index.ts +10 -0
- package/src/link/link.spec.ts +68 -0
- package/src/link/link.ts +21 -0
- package/src/runtime/os.ts +18 -2
- package/src/sleep/sleep.ts +1 -1
- package/src/telem/index.ts +1 -1
- package/src/telem/series.spec.ts +21 -0
- package/src/telem/series.ts +15 -9
- package/src/telem/telem.ts +34 -10
- package/src/unique/index.ts +10 -0
- package/src/unique/unique.spec.ts +192 -0
- package/src/unique/unique.ts +67 -0
- package/src/zodutil/index.ts +1 -1
- package/src/zodutil/zodutil.ts +1 -29
- package/tsconfig.tsbuildinfo +1 -1
- package/vite.config.ts +6 -8
- package/dist/box-BpSX4si6.cjs +0 -1
- package/dist/box-CYXc9-qp.js +0 -201
- package/dist/external-B3XSLDq5.cjs +0 -1
- package/dist/external-sVtvYJS6.js +0 -23
- package/dist/index-YsO0EMN8.cjs +0 -1
- package/dist/index-eue4dSQX.js +0 -45
- package/dist/index-h-QAL9T1.cjs +0 -1
- package/dist/position-DJXB-pDS.js +0 -85
- package/dist/position-JCN6-sJC.cjs +0 -1
- package/dist/series-BN9CILsQ.cjs +0 -11
- package/dist/src/unique.d.ts +0 -2
- package/dist/src/unique.d.ts.map +0 -1
- package/dist/types-BpAJW2TM.js +0 -11
- package/dist/types-zRwnQ1hc.cjs +0 -1
- package/dist/zodutil-BRjUdYAv.cjs +0 -1
- package/dist/zodutil-DI4gVZkT.js +0 -27
package/src/link/link.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Copyright 2024 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 urlRegex = new RegExp(
|
|
11
|
+
"^(https?:\\/\\/)?" + // http:// or https:// (optional)
|
|
12
|
+
"((([a-zA-Z0-9][a-zA-Z0-9-]*\\.)+[a-zA-Z]{2,})|" + // domain name and extension
|
|
13
|
+
"localhost|" + // localhost
|
|
14
|
+
"(\\d{1,3}\\.){3}\\d{1,3})" + // or IP address
|
|
15
|
+
"(\\:\\d+)?" + // port (optional)
|
|
16
|
+
"(\\/[-a-zA-Z0-9@:%._\\+~#=]*)*" + // path (optional)
|
|
17
|
+
"(\\?[;&a-zA-Z0-9%_.,~+=-]*)?" + // query string (optional)
|
|
18
|
+
"(#[-a-zA-Z0-9_]*)?$", // fragment identifier (optional)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export const is = (string: string): boolean => urlRegex.test(string);
|
package/src/runtime/os.ts
CHANGED
|
@@ -10,7 +10,23 @@
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
12
|
export const OPERATING_SYSTEMS = ["MacOS", "Windows", "Linux", "Docker"] as const;
|
|
13
|
-
|
|
13
|
+
const LOWERCASE_OPERATING_SYSTEMS = ["macos", "windows", "linux", "docker"] as const;
|
|
14
|
+
const LOWER_TO_UPPER_OPERATING_SYSTEMS: Record<
|
|
15
|
+
(typeof LOWERCASE_OPERATING_SYSTEMS)[number],
|
|
16
|
+
(typeof OPERATING_SYSTEMS)[number]
|
|
17
|
+
> = {
|
|
18
|
+
macos: "MacOS",
|
|
19
|
+
windows: "Windows",
|
|
20
|
+
linux: "Linux",
|
|
21
|
+
docker: "Docker",
|
|
22
|
+
};
|
|
23
|
+
export const osZ = z
|
|
24
|
+
.enum(OPERATING_SYSTEMS)
|
|
25
|
+
.or(
|
|
26
|
+
z
|
|
27
|
+
.enum(LOWERCASE_OPERATING_SYSTEMS)
|
|
28
|
+
.transform((s) => LOWER_TO_UPPER_OPERATING_SYSTEMS[s]),
|
|
29
|
+
);
|
|
14
30
|
export type OS = (typeof OPERATING_SYSTEMS)[number];
|
|
15
31
|
|
|
16
32
|
export type RequiredGetOSProps = {
|
|
@@ -47,4 +63,4 @@ export const getOS = ((props: GetOSProps = {}): OS | undefined => {
|
|
|
47
63
|
if (os != null) return os;
|
|
48
64
|
os = evalOS();
|
|
49
65
|
return os ?? default_;
|
|
50
|
-
}) as
|
|
66
|
+
}) as GetOS;
|
package/src/sleep/sleep.ts
CHANGED
package/src/telem/index.ts
CHANGED
|
@@ -7,6 +7,6 @@
|
|
|
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 type
|
|
10
|
+
export { type GLBufferController } from "@/telem/gl";
|
|
11
11
|
export * from "@/telem/series";
|
|
12
12
|
export * from "@/telem/telem";
|
package/src/telem/series.spec.ts
CHANGED
|
@@ -933,6 +933,27 @@ describe("MultiSeries", () => {
|
|
|
933
933
|
});
|
|
934
934
|
});
|
|
935
935
|
|
|
936
|
+
describe("parseJSON", () => {
|
|
937
|
+
it("should correctly parse a multi-series of JSON", () => {
|
|
938
|
+
const a = new Series([
|
|
939
|
+
{ a: 1, b: "apple" },
|
|
940
|
+
{ a: 2, b: "banana" },
|
|
941
|
+
]);
|
|
942
|
+
const b = new Series([
|
|
943
|
+
{ a: 3, b: "carrot" },
|
|
944
|
+
{ a: 4, b: "dog" },
|
|
945
|
+
]);
|
|
946
|
+
const multi = new MultiSeries([a, b]);
|
|
947
|
+
const arr = multi.parseJSON(z.object({ a: z.number(), b: z.string() }));
|
|
948
|
+
expect(arr).toEqual([
|
|
949
|
+
{ a: 1, b: "apple" },
|
|
950
|
+
{ a: 2, b: "banana" },
|
|
951
|
+
{ a: 3, b: "carrot" },
|
|
952
|
+
{ a: 4, b: "dog" },
|
|
953
|
+
]);
|
|
954
|
+
});
|
|
955
|
+
});
|
|
956
|
+
|
|
936
957
|
describe("array construction", () => {
|
|
937
958
|
it("should correctly construct a JS array from a multi-series", () => {
|
|
938
959
|
const a = new Series(new Float32Array([1, 2, 3]));
|
package/src/telem/series.ts
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
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 { isTypedArray } from "util/types";
|
|
11
10
|
import { z } from "zod";
|
|
12
11
|
|
|
13
12
|
import { binary } from "@/binary";
|
|
@@ -390,7 +389,7 @@ export class Series<T extends TelemValue = TelemValue> {
|
|
|
390
389
|
/** @returns a native typed array with the proper data type. */
|
|
391
390
|
get data(): TypedArray {
|
|
392
391
|
if (this.writePos === FULL_BUFFER) return this.underlyingData;
|
|
393
|
-
// @ts-expect-error -
|
|
392
|
+
// @ts-expect-error - issues with union types in array constructors.
|
|
394
393
|
return new this.dataType.Array(this._data, 0, this.writePos);
|
|
395
394
|
}
|
|
396
395
|
|
|
@@ -420,7 +419,7 @@ export class Series<T extends TelemValue = TelemValue> {
|
|
|
420
419
|
|
|
421
420
|
parseJSON<Z extends z.ZodTypeAny>(schema: Z): Array<z.output<Z>> {
|
|
422
421
|
if (!this.dataType.equals(DataType.JSON))
|
|
423
|
-
throw new Error("cannot
|
|
422
|
+
throw new Error("cannot parse non-JSON series as JSON");
|
|
424
423
|
return new TextDecoder()
|
|
425
424
|
.decode(this.underlyingData)
|
|
426
425
|
.split("\n")
|
|
@@ -623,10 +622,8 @@ export class Series<T extends TelemValue = TelemValue> {
|
|
|
623
622
|
}
|
|
624
623
|
const slice = this.data.slice(start, end);
|
|
625
624
|
if (this.dataType.equals(DataType.STRING))
|
|
626
|
-
return new TextDecoder().decode(slice) as
|
|
627
|
-
return caseconv.snakeToCamel(
|
|
628
|
-
JSON.parse(new TextDecoder().decode(slice)),
|
|
629
|
-
) as unknown as T;
|
|
625
|
+
return new TextDecoder().decode(slice) as T;
|
|
626
|
+
return caseconv.snakeToCamel(JSON.parse(new TextDecoder().decode(slice))) as T;
|
|
630
627
|
}
|
|
631
628
|
|
|
632
629
|
/**
|
|
@@ -650,8 +647,11 @@ export class Series<T extends TelemValue = TelemValue> {
|
|
|
650
647
|
|
|
651
648
|
updateGLBuffer(gl: GLBufferController): void {
|
|
652
649
|
this.gl.control = gl;
|
|
653
|
-
if (
|
|
654
|
-
|
|
650
|
+
if (
|
|
651
|
+
!this.dataType.equals(DataType.FLOAT32) &&
|
|
652
|
+
!this.dataType.equals(DataType.UINT8)
|
|
653
|
+
)
|
|
654
|
+
throw new Error("Only FLOAT32 and UINT8 arrays can be used in WebGL");
|
|
655
655
|
const { buffer, bufferUsage, prevBuffer } = this.gl;
|
|
656
656
|
|
|
657
657
|
// If no buffer has been created yet, create one.
|
|
@@ -1092,6 +1092,12 @@ export class MultiSeries<T extends TelemValue = TelemValue> implements Iterable<
|
|
|
1092
1092
|
return bounds.distance(b, start, end);
|
|
1093
1093
|
}
|
|
1094
1094
|
|
|
1095
|
+
parseJSON<Z extends z.ZodTypeAny>(schema: Z): Array<z.output<Z>> {
|
|
1096
|
+
if (!this.dataType.equals(DataType.JSON))
|
|
1097
|
+
throw new Error("cannot parse non-JSON series as JSON");
|
|
1098
|
+
return this.series.flatMap((s) => s.parseJSON(schema));
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1095
1101
|
[Symbol.iterator](): Iterator<T> {
|
|
1096
1102
|
if (this.series.length === 0)
|
|
1097
1103
|
return {
|
package/src/telem/telem.ts
CHANGED
|
@@ -1231,7 +1231,8 @@ export class DataType extends String implements Stringer {
|
|
|
1231
1231
|
}
|
|
1232
1232
|
|
|
1233
1233
|
get isInteger(): boolean {
|
|
1234
|
-
|
|
1234
|
+
const str = this.toString();
|
|
1235
|
+
return str.startsWith("int") || str.startsWith("uint");
|
|
1235
1236
|
}
|
|
1236
1237
|
|
|
1237
1238
|
get isFloat(): boolean {
|
|
@@ -1244,18 +1245,41 @@ export class DataType extends String implements Stringer {
|
|
|
1244
1245
|
return v;
|
|
1245
1246
|
}
|
|
1246
1247
|
|
|
1248
|
+
get isUnsigned(): boolean {
|
|
1249
|
+
return (
|
|
1250
|
+
this.equals(DataType.UINT8) ||
|
|
1251
|
+
this.equals(DataType.UINT16) ||
|
|
1252
|
+
this.equals(DataType.UINT32) ||
|
|
1253
|
+
this.equals(DataType.UINT64)
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
get isSigned(): boolean {
|
|
1258
|
+
return (
|
|
1259
|
+
this.equals(DataType.INT8) ||
|
|
1260
|
+
this.equals(DataType.INT16) ||
|
|
1261
|
+
this.equals(DataType.INT32) ||
|
|
1262
|
+
this.equals(DataType.INT64)
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1247
1266
|
/** @returns true if the data type can be cast to the other data type without loss of precision. */
|
|
1248
1267
|
canSafelyCastTo(other: DataType): boolean {
|
|
1249
1268
|
if (this.equals(other)) return true;
|
|
1250
|
-
if (
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
if (
|
|
1258
|
-
|
|
1269
|
+
if (!this.isNumeric || !other.isNumeric) return false;
|
|
1270
|
+
if (this.isVariable || other.isVariable) return false;
|
|
1271
|
+
if (this.isUnsigned && other.isSigned) return false;
|
|
1272
|
+
|
|
1273
|
+
if (this.isFloat)
|
|
1274
|
+
return other.isFloat && this.density.valueOf() <= other.density.valueOf();
|
|
1275
|
+
if (this.equals(DataType.INT32) && other.equals(DataType.FLOAT64)) return true;
|
|
1276
|
+
if (this.equals(DataType.INT8) && other.equals(DataType.FLOAT32)) return true;
|
|
1277
|
+
if (this.isInteger && other.isInteger)
|
|
1278
|
+
return (
|
|
1279
|
+
this.density.valueOf() <= other.density.valueOf() &&
|
|
1280
|
+
this.isUnsigned === other.isUnsigned
|
|
1281
|
+
);
|
|
1282
|
+
|
|
1259
1283
|
return false;
|
|
1260
1284
|
}
|
|
1261
1285
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright 2024 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 unique from "@/unique/unique";
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// Copyright 2024 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 { unique } from "@/unique";
|
|
13
|
+
|
|
14
|
+
describe("unique", () => {
|
|
15
|
+
it("removes duplicate primitive values", () => {
|
|
16
|
+
const result = unique.unique([1, 2, 2, 3, 4, 4, 5]);
|
|
17
|
+
expect(result).toEqual([1, 2, 3, 4, 5]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("works with strings", () => {
|
|
21
|
+
const result = unique.unique(["a", "b", "a", "c", "b"]);
|
|
22
|
+
expect(result).toEqual(["a", "b", "c"]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("works with mixed types", () => {
|
|
26
|
+
const result = unique.unique([1, "1", 2, "2", 1, "1"]);
|
|
27
|
+
expect(result).toEqual([1, "1", 2, "2"]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("handles an empty array", () => {
|
|
31
|
+
const result = unique.unique([]);
|
|
32
|
+
expect(result).toEqual([]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("works with readonly arrays", () => {
|
|
36
|
+
const values: readonly number[] = [1, 1, 2, 3];
|
|
37
|
+
const result = unique.unique(values);
|
|
38
|
+
expect(result).toEqual([1, 2, 3]);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("by", () => {
|
|
43
|
+
interface IDTestCase {
|
|
44
|
+
id: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
it("removes duplicates based on a key function and keeps the first instance by default", () => {
|
|
48
|
+
const result = unique.by(
|
|
49
|
+
[
|
|
50
|
+
{ id: 1, name: "A" },
|
|
51
|
+
{ id: 2, name: "B" },
|
|
52
|
+
{ id: 1, name: "C" },
|
|
53
|
+
],
|
|
54
|
+
(value: IDTestCase) => value.id,
|
|
55
|
+
);
|
|
56
|
+
expect(result).toEqual([
|
|
57
|
+
{ id: 1, name: "A" },
|
|
58
|
+
{ id: 2, name: "B" },
|
|
59
|
+
]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("removes duplicates based on a key function and keeps the first instance when keepFirst is true", () => {
|
|
63
|
+
const result = unique.by(
|
|
64
|
+
[
|
|
65
|
+
{ id: 1, name: "A" },
|
|
66
|
+
{ id: 2, name: "B" },
|
|
67
|
+
{ id: 1, name: "C" },
|
|
68
|
+
],
|
|
69
|
+
(value: IDTestCase) => value.id,
|
|
70
|
+
true,
|
|
71
|
+
);
|
|
72
|
+
expect(result).toEqual([
|
|
73
|
+
{ id: 1, name: "A" },
|
|
74
|
+
{ id: 2, name: "B" },
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("removes duplicates based on a key function and keeps the last instance when keepFirst is false", () => {
|
|
79
|
+
const result = unique.by(
|
|
80
|
+
[
|
|
81
|
+
{ id: 1, name: "A" },
|
|
82
|
+
{ id: 2, name: "B" },
|
|
83
|
+
{ id: 1, name: "C" },
|
|
84
|
+
],
|
|
85
|
+
(value: IDTestCase) => value.id,
|
|
86
|
+
false,
|
|
87
|
+
);
|
|
88
|
+
expect(result).toEqual([
|
|
89
|
+
{ id: 2, name: "B" },
|
|
90
|
+
{ id: 1, name: "C" },
|
|
91
|
+
]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
interface ValueTestCase {
|
|
95
|
+
value: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
it("works with a custom key function", () => {
|
|
99
|
+
const result = unique.by(
|
|
100
|
+
[{ value: "apple" }, { value: "banana" }, { value: "apple" }],
|
|
101
|
+
(v: ValueTestCase) => v.value,
|
|
102
|
+
);
|
|
103
|
+
expect(result).toEqual([{ value: "apple" }, { value: "banana" }]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("handles an empty array", () => {
|
|
107
|
+
const result = unique.by([], (v: unknown) => v);
|
|
108
|
+
expect(result).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("works with readonly arrays and keeps the first instance by default", () => {
|
|
112
|
+
const values: readonly { id: number; name: string }[] = [
|
|
113
|
+
{ id: 1, name: "A" },
|
|
114
|
+
{ id: 1, name: "B" },
|
|
115
|
+
];
|
|
116
|
+
const result = unique.by(values, (v: IDTestCase) => v.id);
|
|
117
|
+
expect(result).toEqual([{ id: 1, name: "A" }]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("works with readonly arrays and keeps the last instance when keepFirst is false", () => {
|
|
121
|
+
const values: readonly { id: number; name: string }[] = [
|
|
122
|
+
{ id: 1, name: "A" },
|
|
123
|
+
{ id: 1, name: "B" },
|
|
124
|
+
];
|
|
125
|
+
const result = unique.by(values, (v: IDTestCase) => v.id, false);
|
|
126
|
+
expect(result).toEqual([{ id: 1, name: "B" }]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
interface ComplexTestCase {
|
|
130
|
+
id: number;
|
|
131
|
+
nested: { value: string };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
it("works with complex keys and keeps the first instance by default", () => {
|
|
135
|
+
const result = unique.by(
|
|
136
|
+
[
|
|
137
|
+
{ id: 1, nested: { value: "A" } },
|
|
138
|
+
{ id: 1, nested: { value: "B" } },
|
|
139
|
+
{ id: 1, nested: { value: "A", otherKey: "4" } },
|
|
140
|
+
],
|
|
141
|
+
(v: ComplexTestCase) => `${v.id}-${v.nested.value}`,
|
|
142
|
+
);
|
|
143
|
+
expect(result).toEqual([
|
|
144
|
+
{ id: 1, nested: { value: "A" } },
|
|
145
|
+
{ id: 1, nested: { value: "B" } },
|
|
146
|
+
]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("works with complex keys and keeps the last instance when keepFirst is false", () => {
|
|
150
|
+
const result = unique.by(
|
|
151
|
+
[
|
|
152
|
+
{ id: 1, nested: { value: "A" } },
|
|
153
|
+
{ id: 1, nested: { value: "B" } },
|
|
154
|
+
{ id: 1, nested: { value: "A", otherKey: "4" } },
|
|
155
|
+
],
|
|
156
|
+
(v: ComplexTestCase) => `${v.id}-${v.nested.value}`,
|
|
157
|
+
false,
|
|
158
|
+
);
|
|
159
|
+
expect(result).toEqual([
|
|
160
|
+
{ id: 1, nested: { value: "B" } },
|
|
161
|
+
{ id: 1, nested: { value: "A", otherKey: "4" } },
|
|
162
|
+
]);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("handles cases where all keys are unique", () => {
|
|
166
|
+
const result = unique.by(
|
|
167
|
+
[
|
|
168
|
+
{ id: 1, name: "A" },
|
|
169
|
+
{ id: 2, name: "B" },
|
|
170
|
+
{ id: 3, name: "C" },
|
|
171
|
+
],
|
|
172
|
+
(v: IDTestCase) => v.id,
|
|
173
|
+
);
|
|
174
|
+
expect(result).toEqual([
|
|
175
|
+
{ id: 1, name: "A" },
|
|
176
|
+
{ id: 2, name: "B" },
|
|
177
|
+
{ id: 3, name: "C" },
|
|
178
|
+
]);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("handles cases where all values are identical", () => {
|
|
182
|
+
const result = unique.by(
|
|
183
|
+
[
|
|
184
|
+
{ id: 1, name: "A" },
|
|
185
|
+
{ id: 1, name: "A" },
|
|
186
|
+
{ id: 1, name: "A" },
|
|
187
|
+
],
|
|
188
|
+
(v: IDTestCase) => v.id,
|
|
189
|
+
);
|
|
190
|
+
expect(result).toEqual([{ id: 1, name: "A" }]);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Copyright 2024 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
|
+
/**
|
|
11
|
+
* Removes duplicate values from an array, preserving the order of the first occurrence
|
|
12
|
+
* of each unique value.
|
|
13
|
+
*
|
|
14
|
+
* @param values - An array or readonly array of values to deduplicate.
|
|
15
|
+
* @returns A new array containing only unique values.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* unique([1, 2, 2, 3, 4, 4, 5]); // [1, 2, 3, 4, 5]
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export const unique = <V>(values: V[] | readonly V[]): V[] => [...new Set(values)];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Removes duplicate values from an array based on a key function, preserving either
|
|
26
|
+
* the first or last occurrence of each unique key. If
|
|
27
|
+
*
|
|
28
|
+
* @param values - An array or readonly array of values to deduplicate.
|
|
29
|
+
* @param key - A function that generates a unique key for each value.
|
|
30
|
+
* @param keepFirst - An optional boolean indicating whether to keep the first instance
|
|
31
|
+
* (`true`, default) or the last instance (`false`) of each unique key.
|
|
32
|
+
* @returns A new array containing only unique values based on the generated keys.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // Default behavior (keep first instance):
|
|
36
|
+
* by(
|
|
37
|
+
* [{ id: 1, name: "A" }, { id: 2, name: "B" }, { id: 1, name: "C" }],
|
|
38
|
+
* (value) => value.id
|
|
39
|
+
* );
|
|
40
|
+
* // Result: [{ id: 1, name: "A" }, { id: 2, name: "B" }]
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // Keep last instance:
|
|
44
|
+
* by(
|
|
45
|
+
* [{ id: 1, name: "A" }, { id: 2, name: "B" }, { id: 1, name: "C" }],
|
|
46
|
+
* (value) => value.id,
|
|
47
|
+
* false
|
|
48
|
+
* );
|
|
49
|
+
* // Result: [{ id: 2, name: "B" }, { id: 1, name: "C" }]
|
|
50
|
+
*/
|
|
51
|
+
export const by = <V>(
|
|
52
|
+
values: V[] | readonly V[],
|
|
53
|
+
key: (value: V) => unknown,
|
|
54
|
+
keepFirst: boolean = true,
|
|
55
|
+
): V[] => {
|
|
56
|
+
const map = new Map<unknown, V>();
|
|
57
|
+
values.forEach((v) => {
|
|
58
|
+
const k = key(v);
|
|
59
|
+
if (map.has(k)) {
|
|
60
|
+
if (keepFirst) return;
|
|
61
|
+
map.delete(k);
|
|
62
|
+
}
|
|
63
|
+
// different delete and set operations for keepLast so order is preserved
|
|
64
|
+
map.set(k, v);
|
|
65
|
+
});
|
|
66
|
+
return Array.from(map.values());
|
|
67
|
+
};
|
package/src/zodutil/index.ts
CHANGED
package/src/zodutil/zodutil.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
|
-
import { z
|
|
10
|
+
import { z } from "zod";
|
|
11
11
|
|
|
12
12
|
import { deep } from "@/deep";
|
|
13
13
|
import { type UnknownRecord } from "@/record";
|
|
@@ -43,32 +43,4 @@ export const getFieldSchema: deep.TypedGet<z.ZodTypeAny, z.ZodTypeAny> = ((
|
|
|
43
43
|
{ ...options, getter: sourceTypeGetter } as deep.GetOptions<boolean | undefined>,
|
|
44
44
|
) as z.ZodTypeAny | null) as deep.TypedGet<z.ZodTypeAny, z.ZodTypeAny>;
|
|
45
45
|
|
|
46
|
-
/**
|
|
47
|
-
* Creates a transformer function that validates and transforms input values based on
|
|
48
|
-
* provided schemas. The first schema to successfully validate the input value is used
|
|
49
|
-
* in the transformation. If no schema is found that validates the input, the
|
|
50
|
-
* transformer function returns null.
|
|
51
|
-
*
|
|
52
|
-
* @template Input - The type of the input value.
|
|
53
|
-
* @template Output - The type of the output value.
|
|
54
|
-
* @param transform - The function to transform the input value to the output value.
|
|
55
|
-
* @param schemas - An array of Zod schemas to validate the input value against.
|
|
56
|
-
* @returns A function that takes an unknown value, validates it against the schemas,
|
|
57
|
-
* and uses the first valid schema to transform the input type. If no schema can
|
|
58
|
-
* validate the input, the function returns null.
|
|
59
|
-
*/
|
|
60
|
-
export const transformer =
|
|
61
|
-
<Input, Output>(
|
|
62
|
-
transform: (input: Input) => Output,
|
|
63
|
-
schemas: ZodSchema<Input>[],
|
|
64
|
-
): ((value: unknown) => Output | null) =>
|
|
65
|
-
(value) => {
|
|
66
|
-
const matchingSchema = schemas.find((schema) => {
|
|
67
|
-
const res = schema.safeParse(value);
|
|
68
|
-
return res.success;
|
|
69
|
-
});
|
|
70
|
-
if (matchingSchema == null) return null;
|
|
71
|
-
return transform(matchingSchema.parse(value));
|
|
72
|
-
};
|
|
73
|
-
|
|
74
46
|
export const bigInt = z.bigint().or(z.string().transform(BigInt));
|