@synnaxlabs/x 0.23.0 → 0.24.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 +40 -40
- package/dist/{base-D3FrPu8C.cjs → base-B48bPNx5.cjs} +1 -1
- package/dist/{base-Cz4DfmJf.js → base-DnZzEvvz.js} +11 -10
- package/dist/binary.cjs +3 -1
- package/dist/binary.js +83 -20
- package/dist/{bounds-1ouRnRJS.cjs → bounds-Bh_QBZZK.cjs} +1 -1
- package/dist/{bounds-BYz3mV9U.js → bounds-Dmn-hd_-.js} +1 -1
- package/dist/bounds.cjs +1 -1
- package/dist/bounds.js +1 -1
- package/dist/box-C92jYSNY.js +205 -0
- package/dist/box-m3dlc1kR.cjs +1 -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/deep.cjs +1 -1
- package/dist/deep.js +26 -24
- package/dist/{dimensions-9gLSFjFx.js → dimensions-BAuHfd-b.js} +1 -1
- package/dist/{dimensions-Fe8DhhXU.cjs → dimensions-zMcb9pMk.cjs} +1 -1
- package/dist/dimensions.cjs +1 -1
- package/dist/dimensions.js +1 -1
- package/dist/direction-CScbfCdT.js +16 -0
- package/dist/direction-DZbN47uL.cjs +1 -0
- package/dist/direction.cjs +1 -1
- package/dist/direction.js +1 -1
- package/dist/index-BhEWuRmo.cjs +1 -0
- package/dist/index-BpCPHf78.cjs +1 -0
- package/dist/{index-POWYBOZm.js → index-BwkQTA-j.js} +113 -109
- package/dist/index-IGDDLWef.js +96 -0
- package/dist/index.cjs +3 -1
- package/dist/index.js +167 -122
- package/dist/{location-C4YaXxjI.js → location-CfP9TAnW.js} +5 -5
- package/dist/{location-CZOgJmu5.cjs → location-c3g6WKs-.cjs} +1 -1
- package/dist/location.cjs +1 -1
- package/dist/location.js +1 -1
- package/dist/path-BO4pyGZf.cjs +1 -0
- package/dist/path-nTHmt_4i.js +65 -0
- package/dist/{position-BjhXUSEO.cjs → position-BBnkwGX1.cjs} +1 -1
- package/dist/{position-PSYLZzqw.js → position-B_aphAR0.js} +4 -4
- package/dist/position.cjs +1 -1
- package/dist/position.js +1 -1
- package/dist/{scale-DUBO0Q4s.cjs → scale-C8axypQ2.cjs} +1 -1
- package/dist/{scale-DCbi48di.js → scale-CgtgqlYU.js} +4 -4
- package/dist/scale.cjs +1 -1
- package/dist/scale.js +1 -1
- package/dist/{series-CSutMOtt.cjs → series-Cgjkfl5S.cjs} +1 -1
- package/dist/{series-DCv8f29w.js → series-w7kOJP17.js} +9 -9
- package/dist/{spatial-R5OeJgR6.cjs → spatial-BOhaO8xN.cjs} +1 -1
- package/dist/{spatial-6OGCmDve.js → spatial-DozyssiN.js} +1 -1
- package/dist/spatial.cjs +1 -1
- package/dist/spatial.js +9 -9
- package/dist/src/binary/encoder.d.ts +19 -0
- package/dist/src/binary/encoder.d.ts.map +1 -1
- package/dist/src/caseconv/caseconv.d.ts +2 -0
- package/dist/src/caseconv/caseconv.d.ts.map +1 -1
- package/dist/src/compare/compare.d.ts +1 -0
- package/dist/src/compare/compare.d.ts.map +1 -1
- package/dist/src/compare/compare.spec.d.ts +2 -0
- package/dist/src/compare/compare.spec.d.ts.map +1 -0
- package/dist/src/deep/path.d.ts +13 -4
- package/dist/src/deep/path.d.ts.map +1 -1
- package/dist/src/migrate/migrate.d.ts +45 -10
- package/dist/src/migrate/migrate.d.ts.map +1 -1
- package/dist/src/spatial/box/box.d.ts +4 -2
- package/dist/src/spatial/box/box.d.ts.map +1 -1
- package/dist/src/spatial/direction/direction.d.ts +1 -1
- package/dist/src/spatial/direction/direction.d.ts.map +1 -1
- package/dist/src/spatial/xy/xy.d.ts +1 -1
- package/dist/src/spatial/xy/xy.d.ts.map +1 -1
- package/dist/src/zodutil/zodutil.d.ts.map +1 -1
- package/dist/telem.cjs +1 -1
- package/dist/telem.js +1 -1
- package/dist/{xy-H8aUVW_X.js → xy-D1ZbIqpT.js} +12 -12
- package/dist/xy-cP-FXJtR.cjs +1 -0
- package/dist/xy.cjs +1 -1
- package/dist/xy.js +1 -1
- package/dist/zodutil.cjs +1 -1
- package/dist/zodutil.js +2 -3
- package/package.json +3 -3
- package/src/binary/encoder.spec.ts +121 -3
- package/src/binary/encoder.ts +91 -1
- package/src/caseconv/caseconv.ts +10 -1
- package/src/compare/compare.spec.ts +49 -0
- package/src/compare/compare.ts +32 -2
- package/src/deep/path.spec.ts +3 -2
- package/src/deep/path.ts +27 -21
- package/src/migrate/migrate.spec.ts +62 -54
- package/src/migrate/migrate.ts +131 -19
- package/src/spatial/box/box.spec.ts +70 -22
- package/src/spatial/box/box.ts +15 -11
- package/src/spatial/direction/direction.ts +2 -2
- package/src/spatial/xy/xy.ts +3 -2
- package/src/telem/telem.spec.ts +35 -10
- package/src/zodutil/zodutil.spec.ts +2 -2
- package/src/zodutil/zodutil.ts +2 -3
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/box-CHDDKvN3.cjs +0 -1
- package/dist/box-Q7p9JFnX.js +0 -204
- package/dist/direction-dXjlmg4_.cjs +0 -1
- package/dist/direction-wsPTe9a1.js +0 -15
- package/dist/index-Bcm6lEr8.js +0 -73
- package/dist/index-CnZb0cyW.cjs +0 -1
- package/dist/index-xjptNL6Z.cjs +0 -1
- package/dist/path-BfHj5x9k.js +0 -62
- package/dist/path-By98WcWm.cjs +0 -1
- package/dist/xy-B4SvvNaG.cjs +0 -1
|
@@ -12,7 +12,7 @@ import { z } from "zod";
|
|
|
12
12
|
|
|
13
13
|
import { binary } from "@/binary";
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const sampleSchema = z.object({
|
|
16
16
|
channelKey: z.string(),
|
|
17
17
|
timeStamp: z.number(),
|
|
18
18
|
value: z.unknown(),
|
|
@@ -27,7 +27,7 @@ binary.ENCODERS.forEach((e) => {
|
|
|
27
27
|
value: new Array([1, 2, 3]),
|
|
28
28
|
};
|
|
29
29
|
const encoded = e.encode(sample);
|
|
30
|
-
expect(e.decode(encoded,
|
|
30
|
+
expect(e.decode(encoded, sampleSchema)).toEqual(sample);
|
|
31
31
|
});
|
|
32
32
|
});
|
|
33
33
|
});
|
|
@@ -50,7 +50,125 @@ describe("JSON", () => {
|
|
|
50
50
|
value: new Array([1, 2, 3]),
|
|
51
51
|
};
|
|
52
52
|
const encoded = JSON.stringify(sample);
|
|
53
|
-
const decoded = binary.JSON_ECD.decodeString(encoded,
|
|
53
|
+
const decoded = binary.JSON_ECD.decodeString(encoded, sampleSchema);
|
|
54
54
|
expect(decoded.channelKey).toEqual("test");
|
|
55
55
|
});
|
|
56
|
+
|
|
57
|
+
describe("CSVEncoderDecoder", () => {
|
|
58
|
+
it("should correctly decode CSV data with valid input", () => {
|
|
59
|
+
const sample = `
|
|
60
|
+
channelKey,timeStamp,value
|
|
61
|
+
test,123,5
|
|
62
|
+
test2,124,6
|
|
63
|
+
`;
|
|
64
|
+
const decoded = binary.CSV_ECD.decodeString(sample);
|
|
65
|
+
expect(decoded).toEqual({
|
|
66
|
+
channelKey: ["test", "test2"],
|
|
67
|
+
timeStamp: [123, 124],
|
|
68
|
+
value: [5, 6],
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should handle empty CSV data", () => {
|
|
73
|
+
const sample = `
|
|
74
|
+
`;
|
|
75
|
+
const decoded = binary.CSV_ECD.decodeString(sample);
|
|
76
|
+
expect(decoded).toEqual({});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should handle CSV with only headers", () => {
|
|
80
|
+
const sample = `
|
|
81
|
+
channelKey,timeStamp,value
|
|
82
|
+
`;
|
|
83
|
+
const decoded = binary.CSV_ECD.decodeString(sample);
|
|
84
|
+
expect(decoded).toEqual({
|
|
85
|
+
channelKey: [],
|
|
86
|
+
timeStamp: [],
|
|
87
|
+
value: [],
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should handle CSV with missing values", () => {
|
|
92
|
+
const sample = `
|
|
93
|
+
channelKey,timeStamp,value
|
|
94
|
+
test,123,
|
|
95
|
+
test2,124,6
|
|
96
|
+
`;
|
|
97
|
+
const decoded = binary.CSV_ECD.decodeString(sample);
|
|
98
|
+
expect(decoded).toEqual({
|
|
99
|
+
channelKey: ["test", "test2"],
|
|
100
|
+
timeStamp: [123, 124],
|
|
101
|
+
value: [6],
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should handle CSV with extra values", () => {
|
|
106
|
+
const sample = `
|
|
107
|
+
channelKey,timeStamp,value
|
|
108
|
+
test,123,5,extra
|
|
109
|
+
test2,124,6
|
|
110
|
+
`;
|
|
111
|
+
const decoded = binary.CSV_ECD.decodeString(sample);
|
|
112
|
+
expect(decoded).toEqual({
|
|
113
|
+
channelKey: ["test", "test2"],
|
|
114
|
+
timeStamp: [123, 124],
|
|
115
|
+
value: [5, 6],
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should handle CSV with different types of values", () => {
|
|
120
|
+
const sample = `
|
|
121
|
+
key,number,string
|
|
122
|
+
test,123,"hello"
|
|
123
|
+
test2,456,"world"
|
|
124
|
+
`;
|
|
125
|
+
const decoded = binary.CSV_ECD.decodeString(sample);
|
|
126
|
+
expect(decoded).toEqual({
|
|
127
|
+
key: ["test", "test2"],
|
|
128
|
+
number: [123, 456],
|
|
129
|
+
string: ["hello", "world"],
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should handle CSV with spaces around values", () => {
|
|
134
|
+
const sample = `
|
|
135
|
+
key, number , string
|
|
136
|
+
test , 123 , "hello"
|
|
137
|
+
test2 , 456 , "world"
|
|
138
|
+
`;
|
|
139
|
+
const decoded = binary.CSV_ECD.decodeString(sample);
|
|
140
|
+
expect(decoded).toEqual({
|
|
141
|
+
key: ["test", "test2"],
|
|
142
|
+
number: [123, 456],
|
|
143
|
+
string: ["hello", "world"],
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should handle CSV with empty rows", () => {
|
|
148
|
+
const sample = `
|
|
149
|
+
key,number,string
|
|
150
|
+
test,123,"hello"
|
|
151
|
+
,
|
|
152
|
+
test2,456,"world"
|
|
153
|
+
`;
|
|
154
|
+
const decoded = binary.CSV_ECD.decodeString(sample);
|
|
155
|
+
expect(decoded).toEqual({
|
|
156
|
+
key: ["test", "test2"],
|
|
157
|
+
number: [123, 456],
|
|
158
|
+
string: ["hello", "world"],
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should handle CSV with single column", () => {
|
|
163
|
+
const sample = `
|
|
164
|
+
key
|
|
165
|
+
test
|
|
166
|
+
test2
|
|
167
|
+
`;
|
|
168
|
+
const decoded = binary.CSV_ECD.decodeString(sample);
|
|
169
|
+
expect(decoded).toEqual({
|
|
170
|
+
key: ["test", "test2"],
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
56
174
|
});
|
package/src/binary/encoder.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 { type z,type ZodSchema } from "zod";
|
|
10
|
+
import { type z, type ZodSchema } from "zod";
|
|
11
11
|
|
|
12
12
|
import { caseconv } from "@/caseconv";
|
|
13
13
|
import { isObject } from "@/identity";
|
|
@@ -79,6 +79,96 @@ export class JSONEncoderDecoder implements EncoderDecoder {
|
|
|
79
79
|
static registerCustomType(): void {}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* CSVEncoderDecoder is a CSV implementation of EncoderDecoder.
|
|
84
|
+
*/
|
|
85
|
+
export class CSVEncoderDecoder implements EncoderDecoder {
|
|
86
|
+
contentType = "text/csv";
|
|
87
|
+
|
|
88
|
+
encode(payload: unknown): ArrayBuffer {
|
|
89
|
+
const csvString = this.encodeString(payload);
|
|
90
|
+
return new TextEncoder().encode(csvString).buffer;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
decode<P extends z.ZodTypeAny>(
|
|
94
|
+
data: Uint8Array | ArrayBuffer,
|
|
95
|
+
schema?: P,
|
|
96
|
+
): z.output<P> {
|
|
97
|
+
const csvString = new TextDecoder().decode(data);
|
|
98
|
+
return this.decodeString(csvString, schema);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
encodeString(payload: unknown): string {
|
|
102
|
+
if (!Array.isArray(payload) || payload.length === 0 || !isObject(payload[0])) {
|
|
103
|
+
throw new Error("Payload must be an array of objects");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const keys = Object.keys(payload[0]);
|
|
107
|
+
const csvRows = [keys.join(",")];
|
|
108
|
+
|
|
109
|
+
payload.forEach((item: any) => {
|
|
110
|
+
const values = keys.map((key) => JSON.stringify(item[key] ?? ""));
|
|
111
|
+
csvRows.push(values.join(","));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return csvRows.join("\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
decodeString<P extends z.ZodTypeAny>(data: string, schema?: P): z.output<P> {
|
|
118
|
+
const [headerLine, ...lines] = data
|
|
119
|
+
.trim()
|
|
120
|
+
.split("\n")
|
|
121
|
+
.map((line) => line.trim());
|
|
122
|
+
if (headerLine.length === 0)
|
|
123
|
+
return schema != null ? schema.parse({}) : ({} as z.output<P>);
|
|
124
|
+
const headers = headerLine.split(",").map((header) => header.trim());
|
|
125
|
+
const result: { [key: string]: any[] } = {};
|
|
126
|
+
|
|
127
|
+
headers.forEach((header) => {
|
|
128
|
+
result[header] = [];
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
lines.forEach((line) => {
|
|
132
|
+
const values = line.split(",").map((value) => value.trim());
|
|
133
|
+
headers.forEach((header, index) => {
|
|
134
|
+
const v = this.parseValue(values[index]);
|
|
135
|
+
if (v == null) return;
|
|
136
|
+
result[header].push(v);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return schema != null ? schema.parse(result) : (result as z.output<P>);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private parseValue(value?: string): any {
|
|
144
|
+
if (value == null || value.length === 0) return null;
|
|
145
|
+
const num = Number(value);
|
|
146
|
+
if (!isNaN(num)) return num;
|
|
147
|
+
if (value.startsWith('"') && value.endsWith('"')) return value.slice(1, -1);
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
static registerCustomType(): void {}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export class TextEncoderDecoder implements EncoderDecoder {
|
|
155
|
+
contentType = "text/plain";
|
|
156
|
+
|
|
157
|
+
encode(payload: unknown): ArrayBuffer {
|
|
158
|
+
return new TextEncoder().encode(payload as string).buffer;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
decode<P extends z.ZodTypeAny>(
|
|
162
|
+
data: Uint8Array | ArrayBuffer,
|
|
163
|
+
schema?: P,
|
|
164
|
+
): z.output<P> {
|
|
165
|
+
const text = new TextDecoder().decode(data);
|
|
166
|
+
return schema != null ? schema.parse(text) : (text as z.output<P>);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
82
170
|
export const JSON_ECD = new JSONEncoderDecoder();
|
|
171
|
+
export const CSV_ECD = new CSVEncoderDecoder();
|
|
172
|
+
export const TEXT_ECD = new TextEncoderDecoder();
|
|
83
173
|
|
|
84
174
|
export const ENCODERS: EncoderDecoder[] = [JSON_ECD];
|
package/src/caseconv/caseconv.ts
CHANGED
|
@@ -7,7 +7,12 @@
|
|
|
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 {
|
|
10
|
+
import {
|
|
11
|
+
camelKeys as _camelKeys,
|
|
12
|
+
snakeKeys as _snakeKeys,
|
|
13
|
+
toCamelCase as _toCamelCase,
|
|
14
|
+
toSnakeCase as _toSnakeCase,
|
|
15
|
+
} from "js-convert-case";
|
|
11
16
|
|
|
12
17
|
const options = {
|
|
13
18
|
recursive: true,
|
|
@@ -20,3 +25,7 @@ export const toSnake = <T>(entity: T): T => _snakeKeys(entity, options) as T;
|
|
|
20
25
|
export const toCamel = <T>(entity: T): T => _camelKeys(entity, options) as T;
|
|
21
26
|
|
|
22
27
|
export const capitalize = (str: string): string => str[0].toUpperCase() + str.slice(1);
|
|
28
|
+
|
|
29
|
+
export const stringToSnake = (str: string): string => _toSnakeCase(str);
|
|
30
|
+
|
|
31
|
+
export const stringToCamel = (str: string): string => _toCamelCase(str);
|
|
@@ -0,0 +1,49 @@
|
|
|
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 { compare } from "@/compare";
|
|
13
|
+
|
|
14
|
+
describe("compare", () => {
|
|
15
|
+
describe("numericStrings", () => {
|
|
16
|
+
interface Spec {
|
|
17
|
+
name: string;
|
|
18
|
+
input: string[];
|
|
19
|
+
expected: string[];
|
|
20
|
+
}
|
|
21
|
+
const SPECS: Spec[] = [
|
|
22
|
+
{
|
|
23
|
+
name: "pure numbers",
|
|
24
|
+
input: ["5", "4", "3", "2", "1"],
|
|
25
|
+
expected: ["1", "2", "3", "4", "5"],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "suffixed numbers with equal prefixes",
|
|
29
|
+
input: ["a5", "a4", "a3", "a2", "a1"],
|
|
30
|
+
expected: ["a1", "a2", "a3", "a4", "a5"],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "suffixed numbers with different prefixes",
|
|
34
|
+
input: ["a1", "b1", "a2", "b2", "a3", "b3"],
|
|
35
|
+
expected: ["a1", "a2", "a3", "b1", "b2", "b3"],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "mixed separators",
|
|
39
|
+
input: ["a2", "a.1", "a-3", "a_4"],
|
|
40
|
+
expected: ["a.1", "a2", "a-3", "a_4"],
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
SPECS.forEach((spec) => {
|
|
44
|
+
it(spec.name, () => {
|
|
45
|
+
expect(spec.input.sort(compare.stringsWithNumbers)).toEqual(spec.expected);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
package/src/compare/compare.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 { isStringer,type Primitive } from "@/primitive";
|
|
10
|
+
import { isStringer, type Primitive } from "@/primitive";
|
|
11
11
|
import { type spatial } from "@/spatial";
|
|
12
12
|
|
|
13
13
|
export type CompareF<T> = (a: T, b: T) => number;
|
|
@@ -40,8 +40,11 @@ export const newF = <T>(v: T, reverse: boolean = false): CompareF<T> => {
|
|
|
40
40
|
case "boolean":
|
|
41
41
|
f = (a: T, b: T) => Number(a) - Number(b);
|
|
42
42
|
break;
|
|
43
|
+
case "undefined":
|
|
44
|
+
f = () => 0;
|
|
45
|
+
break;
|
|
43
46
|
default:
|
|
44
|
-
console.warn(
|
|
47
|
+
console.warn(`sortFunc: unknown type ${t}`);
|
|
45
48
|
return () => -1;
|
|
46
49
|
}
|
|
47
50
|
return reverse ? reverseF(f) : f;
|
|
@@ -117,3 +120,30 @@ export const isLessThan = (n: number): boolean => n < EQUAL;
|
|
|
117
120
|
export const isGreaterThan = (n: number): boolean => n > EQUAL;
|
|
118
121
|
|
|
119
122
|
export const isGreaterThanEqual = (n: number): boolean => n >= EQUAL;
|
|
123
|
+
|
|
124
|
+
export const stringsWithNumbers = (a: string, b: string): number => {
|
|
125
|
+
const alphaNumericRegex = /([a-zA-Z]+)|(\d+)/g;
|
|
126
|
+
|
|
127
|
+
// Remove separators and split into parts
|
|
128
|
+
const aParts = a.replace(/[\s_.\-]+/g, "").match(alphaNumericRegex);
|
|
129
|
+
const bParts = b.replace(/[\s_.\-]+/g, "").match(alphaNumericRegex);
|
|
130
|
+
|
|
131
|
+
if (!aParts || !bParts) return 0;
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < Math.min(aParts.length, bParts.length); i++) {
|
|
134
|
+
const aPart = aParts[i];
|
|
135
|
+
const bPart = bParts[i];
|
|
136
|
+
|
|
137
|
+
if (isNaN(Number(aPart)) && isNaN(Number(bPart))) {
|
|
138
|
+
const localeComparison = aPart.localeCompare(bPart);
|
|
139
|
+
if (localeComparison !== 0) return localeComparison;
|
|
140
|
+
} else if (!isNaN(Number(aPart)) && !isNaN(Number(bPart))) {
|
|
141
|
+
const numComparison = Number(aPart) - Number(bPart);
|
|
142
|
+
if (numComparison !== 0) return numComparison;
|
|
143
|
+
} else {
|
|
144
|
+
return isNaN(Number(aPart)) ? -1 : 1;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return aParts.length - bParts.length;
|
|
149
|
+
};
|
package/src/deep/path.spec.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 { describe, expect,it } from "vitest";
|
|
10
|
+
import { describe, expect, it } from "vitest";
|
|
11
11
|
|
|
12
12
|
import { deep } from "@/deep";
|
|
13
13
|
import { UnknownRecord } from "@/record";
|
|
@@ -63,7 +63,8 @@ describe("path", () => {
|
|
|
63
63
|
};
|
|
64
64
|
it("should use the custom getter function", () => {
|
|
65
65
|
expect(
|
|
66
|
-
deep.get(v, "a.value().c",
|
|
66
|
+
deep.get(v, "a.value().c", {
|
|
67
|
+
optional: false,
|
|
67
68
|
getter: (obj, key) => {
|
|
68
69
|
if (key === "value()")
|
|
69
70
|
return (obj as { value: () => { c: number } }).value();
|
package/src/deep/path.ts
CHANGED
|
@@ -7,6 +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 { caseconv } from "@/caseconv";
|
|
10
11
|
import { type Join } from "@/join";
|
|
11
12
|
import { type UnknownRecord } from "@/record";
|
|
12
13
|
|
|
@@ -46,29 +47,28 @@ export type Key<T, D extends number = 5> = [D] extends [never]
|
|
|
46
47
|
}[keyof T]
|
|
47
48
|
: "";
|
|
48
49
|
|
|
49
|
-
export interface GetOptions {
|
|
50
|
-
|
|
50
|
+
export interface GetOptions<O extends boolean | undefined = boolean | undefined> {
|
|
51
|
+
optional: O;
|
|
52
|
+
getter?: (obj: UnknownRecord, key: string) => unknown;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
export
|
|
54
|
-
|
|
55
|
-
path: string,
|
|
56
|
-
allowNull: true,
|
|
57
|
-
options?: GetOptions,
|
|
58
|
-
) => V | null) &
|
|
59
|
-
(<V = unknown, T = UnknownRecord>(
|
|
55
|
+
export interface Get {
|
|
56
|
+
<V = unknown, T = UnknownRecord>(
|
|
60
57
|
obj: T,
|
|
61
58
|
path: string,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
options?: GetOptions<false>,
|
|
60
|
+
): V;
|
|
61
|
+
<V = unknown, T = UnknownRecord>(
|
|
62
|
+
obj: T,
|
|
63
|
+
path: string,
|
|
64
|
+
options?: GetOptions<boolean | undefined>,
|
|
65
|
+
): V | null;
|
|
66
|
+
}
|
|
65
67
|
|
|
66
|
-
export
|
|
67
|
-
obj: T,
|
|
68
|
-
path: string,
|
|
69
|
-
|
|
70
|
-
) => V | null) &
|
|
71
|
-
((obj: T, path: string, allowNull?: boolean) => V);
|
|
68
|
+
export interface TypedGet<V = unknown, T = UnknownRecord> {
|
|
69
|
+
(obj: T, path: string, options?: GetOptions<false>): V;
|
|
70
|
+
(obj: T, path: string, options?: GetOptions<boolean | undefined>): V | null;
|
|
71
|
+
}
|
|
72
72
|
|
|
73
73
|
export const transformPath = (
|
|
74
74
|
path: string,
|
|
@@ -90,19 +90,25 @@ export const transformPath = (
|
|
|
90
90
|
return result.join(".");
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
+
export const pathToSnake = (path: string): string =>
|
|
94
|
+
transformPath(path, caseconv.stringToSnake);
|
|
95
|
+
|
|
96
|
+
export const pathToCamel = (path: string): string =>
|
|
97
|
+
transformPath(path, caseconv.stringToCamel);
|
|
98
|
+
|
|
93
99
|
export const get = (<V = unknown, T = UnknownRecord>(
|
|
94
100
|
obj: T,
|
|
95
101
|
path: string,
|
|
96
|
-
|
|
97
|
-
{ getter = (obj, key) => (obj as UnknownRecord)[key] }: GetOptions = {},
|
|
102
|
+
opts: GetOptions = { optional: false },
|
|
98
103
|
): V | null => {
|
|
104
|
+
const { optional, getter = (obj, key) => (obj as UnknownRecord)[key] } = opts;
|
|
99
105
|
const parts = path.split(".");
|
|
100
106
|
if (parts.length === 1 && parts[0] === "") return obj as unknown as V;
|
|
101
107
|
let result: UnknownRecord = obj as UnknownRecord;
|
|
102
108
|
for (const part of parts) {
|
|
103
109
|
const v = getter(result, part);
|
|
104
110
|
if (v == null) {
|
|
105
|
-
if (
|
|
111
|
+
if (optional) return null;
|
|
106
112
|
throw new Error(`Path ${path} does not exist. ${part} is null`);
|
|
107
113
|
}
|
|
108
114
|
result = v as UnknownRecord;
|
|
@@ -10,80 +10,88 @@
|
|
|
10
10
|
import { describe, expect, it } from "vitest";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
migratable,
|
|
15
|
-
type Migration,
|
|
16
|
-
type Migrations,
|
|
17
|
-
migrator,
|
|
18
|
-
} from "@/migrate/migrate";
|
|
13
|
+
import { migrate } from "@/migrate";
|
|
19
14
|
|
|
20
|
-
const
|
|
15
|
+
const entityV0 = z.object({
|
|
16
|
+
version: z.literal("0.0.0"),
|
|
21
17
|
name: z.string(),
|
|
22
18
|
});
|
|
23
19
|
|
|
24
|
-
type
|
|
20
|
+
type EntityV0 = z.infer<typeof entityV0>;
|
|
25
21
|
|
|
26
|
-
const
|
|
22
|
+
const entityV1 = z.object({
|
|
23
|
+
version: z.literal("1.0.0"),
|
|
27
24
|
title: z.string(),
|
|
28
25
|
});
|
|
29
26
|
|
|
30
|
-
|
|
27
|
+
const migrateV1 = migrate.createMigration<EntityV0, EntityV1>({
|
|
28
|
+
name: "entity",
|
|
29
|
+
inputSchema: entityV0,
|
|
30
|
+
outputSchema: entityV1,
|
|
31
|
+
migrate: (entity) => {
|
|
32
|
+
const { name, ...rest } = entity;
|
|
33
|
+
return { ...rest, version: "1.0.0", title: entity.name };
|
|
34
|
+
},
|
|
35
|
+
});
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
type EntityV1 = z.infer<typeof entityV1>;
|
|
38
|
+
|
|
39
|
+
const entityV2 = z.object({
|
|
40
|
+
version: z.literal("2.0.0"),
|
|
33
41
|
title: z.string(),
|
|
34
42
|
description: z.string(),
|
|
35
43
|
});
|
|
36
44
|
|
|
37
|
-
type
|
|
45
|
+
type EntityV2 = z.infer<typeof entityV2>;
|
|
38
46
|
|
|
39
|
-
const
|
|
40
|
-
|
|
47
|
+
const migrateV2 = migrate.createMigration<EntityV1, EntityV2>({
|
|
48
|
+
name: "entity",
|
|
49
|
+
inputSchema: entityV1,
|
|
50
|
+
outputSchema: entityV2,
|
|
51
|
+
migrate: (entity) => ({ ...entity, version: "2.0.0", description: "" }),
|
|
52
|
+
});
|
|
41
53
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
version: "0.0.1",
|
|
46
|
-
title: entity.name,
|
|
47
|
-
};
|
|
48
|
-
}) as Migration<EntityV0_0_0, EntityV0_0_1>,
|
|
49
|
-
"0.0.1": (entity: EntityV0_0_1): EntityV0_0_2 => {
|
|
50
|
-
return {
|
|
51
|
-
...entity,
|
|
52
|
-
version: "0.0.2",
|
|
53
|
-
description: "",
|
|
54
|
-
};
|
|
55
|
-
},
|
|
54
|
+
const migrations: migrate.Migrations = {
|
|
55
|
+
"0.0.0": migrateV1,
|
|
56
|
+
"1.0.0": migrateV2,
|
|
56
57
|
};
|
|
57
58
|
|
|
59
|
+
describe("compareSemVer", () => {
|
|
60
|
+
it("should return true when the major version is higher", () => {
|
|
61
|
+
expect(migrate.compareSemVer("1.0.0", "0.0.0")).toBeGreaterThan(0);
|
|
62
|
+
expect(migrate.semVerNewer("3.0.0", "0.3.0")).toBeTruthy();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
58
66
|
describe("migrator", () => {
|
|
59
|
-
it("should migrate an entity from v0
|
|
60
|
-
const entity:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
description: "",
|
|
69
|
-
});
|
|
67
|
+
it("should migrate an entity from v0 to v2", () => {
|
|
68
|
+
const entity: EntityV0 = { version: "0.0.0", name: "foo" };
|
|
69
|
+
const DEFAULT: EntityV2 = { version: "2.0.0", title: "", description: "" };
|
|
70
|
+
const migrated = migrate.migrator({
|
|
71
|
+
name: "entity",
|
|
72
|
+
migrations,
|
|
73
|
+
def: DEFAULT,
|
|
74
|
+
})(entity);
|
|
75
|
+
expect(migrated).toEqual({ version: "2.0.0", title: "foo", description: "" });
|
|
70
76
|
});
|
|
71
|
-
it("should
|
|
72
|
-
const entity:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
it("should migrate an entity from v1 to v2", () => {
|
|
78
|
+
const entity: EntityV1 = { version: "1.0.0", title: "foo" };
|
|
79
|
+
const DEFAULT: EntityV2 = { version: "2.0.0", title: "", description: "" };
|
|
80
|
+
const migrated = migrate.migrator({
|
|
81
|
+
name: "entity",
|
|
82
|
+
migrations,
|
|
83
|
+
def: DEFAULT,
|
|
84
|
+
})(entity);
|
|
85
|
+
expect(migrated).toEqual({ version: "2.0.0", title: "foo", description: "" });
|
|
79
86
|
});
|
|
80
|
-
it("should not migrate an entity from
|
|
81
|
-
const entity = {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
it("should not migrate an entity from v2 to v2", () => {
|
|
88
|
+
const entity: EntityV2 = { version: "2.0.0", title: "foo", description: "bar" };
|
|
89
|
+
const DEFAULT: EntityV2 = { version: "2.0.0", title: "", description: "" };
|
|
90
|
+
const migrated = migrate.migrator({
|
|
91
|
+
name: "entity",
|
|
92
|
+
migrations,
|
|
93
|
+
def: DEFAULT,
|
|
94
|
+
})(entity);
|
|
87
95
|
expect(migrated).toEqual(entity);
|
|
88
96
|
});
|
|
89
97
|
});
|