@synnaxlabs/x 0.53.0 → 0.54.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 +14 -8
- package/dist/src/array/nullable.d.ts +10 -1
- package/dist/src/array/nullable.d.ts.map +1 -1
- package/dist/src/binary/codec.d.ts +5 -5
- package/dist/src/binary/codec.d.ts.map +1 -1
- package/dist/src/caseconv/caseconv.bench.d.ts +2 -0
- package/dist/src/caseconv/caseconv.bench.d.ts.map +1 -0
- package/dist/src/caseconv/caseconv.d.ts +19 -1
- package/dist/src/caseconv/caseconv.d.ts.map +1 -1
- package/dist/src/color/color.d.ts +22 -24
- package/dist/src/color/color.d.ts.map +1 -1
- package/dist/src/color/gradient.d.ts +10 -10
- package/dist/src/color/palette.d.ts +16 -16
- package/dist/src/control/control.d.ts +21 -37
- package/dist/src/control/control.d.ts.map +1 -1
- package/dist/src/control/external.d.ts +3 -0
- package/dist/src/control/external.d.ts.map +1 -0
- package/dist/src/control/index.d.ts +1 -1
- package/dist/src/control/index.d.ts.map +1 -1
- package/dist/src/control/types.gen.d.ts +34 -0
- package/dist/src/control/types.gen.d.ts.map +1 -0
- package/dist/src/deep/merge.d.ts.map +1 -1
- package/dist/src/label/index.d.ts +1 -1
- package/dist/src/label/index.d.ts.map +1 -1
- package/dist/src/label/types.gen.d.ts +51 -0
- package/dist/src/label/types.gen.d.ts.map +1 -0
- package/dist/src/math/constants.d.ts +1 -0
- package/dist/src/math/constants.d.ts.map +1 -1
- package/dist/src/record/record.d.ts +12 -12
- package/dist/src/record/record.d.ts.map +1 -1
- package/dist/src/spatial/base.d.ts +40 -43
- package/dist/src/spatial/base.d.ts.map +1 -1
- package/dist/src/spatial/bounds/bounds.d.ts +2 -2
- package/dist/src/spatial/bounds/bounds.d.ts.map +1 -1
- package/dist/src/spatial/box/box.d.ts +4 -16
- package/dist/src/spatial/box/box.d.ts.map +1 -1
- package/dist/src/spatial/dimensions/dimensions.d.ts +3 -7
- package/dist/src/spatial/dimensions/dimensions.d.ts.map +1 -1
- package/dist/src/spatial/direction/direction.d.ts +3 -3
- package/dist/src/spatial/direction/direction.d.ts.map +1 -1
- package/dist/src/spatial/external.d.ts +1 -0
- package/dist/src/spatial/external.d.ts.map +1 -1
- package/dist/src/spatial/location/location.d.ts +9 -3
- package/dist/src/spatial/location/location.d.ts.map +1 -1
- package/dist/src/spatial/spatial.d.ts +2 -1
- package/dist/src/spatial/spatial.d.ts.map +1 -1
- package/dist/src/spatial/types.gen.d.ts +20 -0
- package/dist/src/spatial/types.gen.d.ts.map +1 -0
- package/dist/src/spatial/xy/xy.d.ts +3 -2
- package/dist/src/spatial/xy/xy.d.ts.map +1 -1
- package/dist/src/status/external.d.ts +3 -0
- package/dist/src/status/external.d.ts.map +1 -0
- package/dist/src/status/index.d.ts +1 -1
- package/dist/src/status/index.d.ts.map +1 -1
- package/dist/src/status/status.d.ts +5 -35
- package/dist/src/status/status.d.ts.map +1 -1
- package/dist/src/status/types.gen.d.ts +74 -0
- package/dist/src/status/types.gen.d.ts.map +1 -0
- package/dist/src/telem/external.d.ts +4 -0
- package/dist/src/telem/external.d.ts.map +1 -0
- package/dist/src/telem/index.d.ts +2 -3
- package/dist/src/telem/index.d.ts.map +1 -1
- package/dist/src/telem/telem.d.ts +62 -0
- package/dist/src/telem/telem.d.ts.map +1 -1
- package/dist/src/zod/nullToUndefined.d.ts +1 -1
- package/dist/src/zod/nullToUndefined.d.ts.map +1 -1
- package/dist/src/zod/schemas.d.ts +6 -0
- package/dist/src/zod/schemas.d.ts.map +1 -1
- package/dist/x.cjs +8 -13
- package/dist/x.js +4393 -5591
- package/package.json +8 -8
- package/src/array/nullable.ts +10 -1
- package/src/binary/codec.spec.ts +31 -0
- package/src/binary/codec.ts +9 -8
- package/src/caseconv/caseconv.bench.ts +270 -0
- package/src/caseconv/caseconv.spec.ts +534 -0
- package/src/caseconv/caseconv.ts +186 -41
- package/src/color/color.spec.ts +51 -36
- package/src/color/color.ts +7 -8
- package/src/control/control.ts +7 -32
- package/src/{label/label.ts → control/external.ts} +2 -13
- package/src/control/index.ts +1 -1
- package/src/control/types.gen.ts +52 -0
- package/src/deep/merge.ts +2 -1
- package/src/deep/path.ts +1 -1
- package/src/deep/remove.ts +1 -1
- package/src/label/index.ts +1 -1
- package/src/label/types.gen.ts +35 -0
- package/src/math/constants.ts +1 -0
- package/src/migrate/migrate.ts +2 -2
- package/src/record/record.spec.ts +31 -7
- package/src/record/record.ts +17 -17
- package/src/spatial/base.ts +63 -39
- package/src/spatial/bounds/bounds.ts +2 -2
- package/src/spatial/box/box.ts +2 -2
- package/src/spatial/dimensions/dimensions.ts +11 -5
- package/src/spatial/direction/direction.ts +2 -2
- package/src/spatial/external.ts +1 -0
- package/src/spatial/location/location.ts +15 -13
- package/src/spatial/spatial.ts +29 -2
- package/src/spatial/sticky/sticky.ts +1 -1
- package/src/spatial/types.gen.ts +28 -0
- package/src/spatial/xy/xy.ts +9 -10
- package/src/status/external.ts +11 -0
- package/src/status/index.ts +1 -1
- package/src/status/status.ts +11 -59
- package/src/status/types.gen.ts +118 -0
- package/src/strings/deduplicateFileName.ts +3 -3
- package/src/telem/external.ts +12 -0
- package/src/telem/index.ts +2 -3
- package/src/telem/telem.ts +30 -5
- package/src/zod/nullToUndefined.ts +4 -4
- package/src/zod/schemas.ts +9 -2
- package/src/zod/util.ts +2 -2
- package/tsconfig.json +3 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/vite.config.ts +8 -1
- package/dist/src/label/label.d.ts +0 -25
- package/dist/src/label/label.d.ts.map +0 -1
package/src/caseconv/caseconv.ts
CHANGED
|
@@ -7,56 +7,212 @@
|
|
|
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 } from "zod";
|
|
11
|
+
|
|
10
12
|
import { type record } from "@/record";
|
|
11
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Global symbol used to mark Zod schemas that should not have their keys converted.
|
|
16
|
+
* Uses Symbol.for() to ensure the same symbol is used across different module instances.
|
|
17
|
+
*/
|
|
18
|
+
const PRESERVE_CASE_SYMBOL = "synnax.caseconv.preserveCase";
|
|
19
|
+
|
|
20
|
+
interface ZodDef extends z.core.$ZodTypeDef {
|
|
21
|
+
innerType?: z.core.SomeType;
|
|
22
|
+
options?: readonly z.core.SomeType[];
|
|
23
|
+
in?: z.core.SomeType;
|
|
24
|
+
out?: z.core.SomeType;
|
|
25
|
+
element?: z.core.SomeType;
|
|
26
|
+
shape?: Record<string, z.ZodType>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ZodInternals extends z.core.$ZodTypeInternals {
|
|
30
|
+
def: ZodDef;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ZodSchema extends z.core.$ZodType {
|
|
34
|
+
[PRESERVE_CASE_SYMBOL]?: boolean;
|
|
35
|
+
_zod: ZodInternals;
|
|
36
|
+
shape?: Record<string, z.ZodType>;
|
|
37
|
+
sourceType?: () => { shape?: Record<string, z.ZodType> } | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Marks a Zod schema to prevent case conversion of its keys and nested content.
|
|
42
|
+
* Use this for schemas where keys are semantic values (like OPC UA NodeIds or Modbus channel keys)
|
|
43
|
+
* rather than property names.
|
|
44
|
+
*
|
|
45
|
+
* @param schema - The Zod schema to mark
|
|
46
|
+
* @returns The same schema with a preserve case marker
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* const propertiesZ = z.object({
|
|
50
|
+
* read: z.object({
|
|
51
|
+
* channels: preserveCase(z.record(z.string(), z.number()))
|
|
52
|
+
* })
|
|
53
|
+
* });
|
|
54
|
+
*/
|
|
55
|
+
export const preserveCase = <T extends z.ZodType>(schema: T): T => {
|
|
56
|
+
(schema as ZodSchema)[PRESERVE_CASE_SYMBOL] = true;
|
|
57
|
+
return schema;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Checks if a Zod schema has the preserve case marker.
|
|
62
|
+
* Traverses through wrapper schemas (optional, nullable, union, transform, etc.)
|
|
63
|
+
* to find markers on inner schemas.
|
|
64
|
+
*/
|
|
65
|
+
const hasPreserveCaseMarker = (schema: unknown): boolean => {
|
|
66
|
+
if (schema == null || typeof schema !== "object") return false;
|
|
67
|
+
|
|
68
|
+
// Direct marker check
|
|
69
|
+
if (PRESERVE_CASE_SYMBOL in schema) return true;
|
|
70
|
+
|
|
71
|
+
const s = schema as ZodSchema;
|
|
72
|
+
const def: ZodDef | undefined = s._zod?.def;
|
|
73
|
+
if (def == null) return false;
|
|
74
|
+
|
|
75
|
+
// Traverse through wrappers with innerType (optional, nullable, default, catch)
|
|
76
|
+
if (def.innerType && hasPreserveCaseMarker(def.innerType)) return true;
|
|
77
|
+
|
|
78
|
+
// Traverse through unions - check all options
|
|
79
|
+
if (def.type === "union" && Array.isArray(def.options))
|
|
80
|
+
return def.options.some(hasPreserveCaseMarker);
|
|
81
|
+
|
|
82
|
+
// Traverse through pipes/transforms - check both ends
|
|
83
|
+
if (def.type === "pipe")
|
|
84
|
+
return hasPreserveCaseMarker(def.in) || hasPreserveCaseMarker(def.out);
|
|
85
|
+
|
|
86
|
+
return false;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Unwraps an array schema to get its element schema.
|
|
91
|
+
* Handles direct arrays and unions containing arrays (e.g., from nullishToEmpty).
|
|
92
|
+
* Returns undefined if the schema is not an array or is undefined.
|
|
93
|
+
*/
|
|
94
|
+
const getArrayElementSchema = (
|
|
95
|
+
schema: z.ZodType | z.core.SomeType | undefined,
|
|
96
|
+
): z.ZodType | undefined => {
|
|
97
|
+
if (schema == null) return undefined;
|
|
98
|
+
const def = (schema as ZodSchema)._zod?.def;
|
|
99
|
+
if (def?.type === "array" && def.element != null) return def.element as z.ZodType;
|
|
100
|
+
// Handle union types that may contain arrays (e.g., nullishToEmpty)
|
|
101
|
+
if (def?.type === "union" && Array.isArray(def.options))
|
|
102
|
+
for (const option of def.options) {
|
|
103
|
+
const result = getArrayElementSchema(option);
|
|
104
|
+
if (result != null) return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return undefined;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Extracts the shape (field name → ZodType map) from a Zod schema.
|
|
112
|
+
* Traverses through wrappers (optional, nullable, default, catch, union, pipe)
|
|
113
|
+
* to find the inner object schema's shape. Returns null for non-object schemas.
|
|
114
|
+
*/
|
|
115
|
+
const getSchemaShape = (
|
|
116
|
+
schema: z.ZodType | z.core.SomeType | undefined,
|
|
117
|
+
): Record<string, z.ZodType> | null => {
|
|
118
|
+
if (schema == null) return null;
|
|
119
|
+
const s = schema as ZodSchema;
|
|
120
|
+
if (s.shape != null) return s.shape;
|
|
121
|
+
if (typeof s.sourceType === "function") {
|
|
122
|
+
const st = s.sourceType();
|
|
123
|
+
if (st?.shape != null) return st.shape;
|
|
124
|
+
}
|
|
125
|
+
const def = s._zod?.def;
|
|
126
|
+
if (def == null) return null;
|
|
127
|
+
if (def.innerType != null) return getSchemaShape(def.innerType);
|
|
128
|
+
if (def.type === "union" && Array.isArray(def.options))
|
|
129
|
+
for (const option of def.options) {
|
|
130
|
+
const result = getSchemaShape(option);
|
|
131
|
+
if (result != null) return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (def.type === "pipe") return getSchemaShape(def.in) ?? getSchemaShape(def.out);
|
|
135
|
+
return null;
|
|
136
|
+
};
|
|
137
|
+
|
|
12
138
|
const snakeToCamelStr = (str: string): string => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (c.length
|
|
19
|
-
return
|
|
139
|
+
if (str.length === 0) return str;
|
|
140
|
+
const hasUnderscore = str.indexOf("_") !== -1;
|
|
141
|
+
const c = hasUnderscore ? str.replace(/_[a-z]/g, (m) => m[1].toUpperCase()) : str;
|
|
142
|
+
const first = c.charCodeAt(0);
|
|
143
|
+
if (first < 65 || first > 90) return c; // not uppercase A-Z
|
|
144
|
+
if (c.length > 1 && c.charCodeAt(1) >= 65 && c.charCodeAt(1) <= 90) return c;
|
|
145
|
+
return String.fromCharCode(first + 32) + c.slice(1);
|
|
20
146
|
};
|
|
147
|
+
|
|
21
148
|
/**
|
|
22
149
|
* Convert string keys in an object to snake_case format.
|
|
23
150
|
* @param obj: object to convert keys. If `obj` isn't a json object, `null` is returned.
|
|
24
151
|
* @param opt: (optional) Options parameter, default is non-recursive.
|
|
152
|
+
* @param schema: (optional) Zod schema to check for preserve case markers
|
|
25
153
|
*/
|
|
26
154
|
const createConverter = (
|
|
27
155
|
f: (v: string) => string,
|
|
28
156
|
): (<V>(obj: V, opt?: Options) => V) => {
|
|
29
157
|
const converter = <V>(obj: V, opt: Options = defaultOptions): V => {
|
|
30
|
-
if (typeof obj === "string") return f(obj) as
|
|
31
|
-
if (Array.isArray(obj))
|
|
158
|
+
if (typeof obj === "string") return f(obj) as V;
|
|
159
|
+
if (Array.isArray(obj)) {
|
|
160
|
+
const elementSchema = getArrayElementSchema(opt.schema);
|
|
161
|
+
const elemOpt: Options = {
|
|
162
|
+
recursive: opt.recursive,
|
|
163
|
+
recursiveInArray: opt.recursiveInArray,
|
|
164
|
+
schema: elementSchema,
|
|
165
|
+
};
|
|
166
|
+
return obj.map((v) => converter(v, elemOpt)) as V;
|
|
167
|
+
}
|
|
32
168
|
if (!isValidObject(obj)) return obj;
|
|
33
|
-
|
|
169
|
+
|
|
170
|
+
if (opt.schema != null && hasPreserveCaseMarker(opt.schema)) return obj;
|
|
171
|
+
|
|
172
|
+
const recursive = opt.recursive ?? true;
|
|
173
|
+
const recursiveInArray = opt.recursiveInArray ?? recursive;
|
|
174
|
+
const schema = opt.schema;
|
|
34
175
|
const res: record.Unknown = {};
|
|
35
176
|
const anyObj = obj as record.Unknown;
|
|
36
177
|
if ("toJSON" in anyObj && typeof anyObj.toJSON === "function")
|
|
37
178
|
return converter(anyObj.toJSON(), opt);
|
|
38
|
-
|
|
179
|
+
|
|
180
|
+
const shape = getSchemaShape(schema);
|
|
181
|
+
const childOpt: Options = { recursive, recursiveInArray, schema: undefined };
|
|
182
|
+
const keys = Object.keys(anyObj);
|
|
183
|
+
for (let i = 0; i < keys.length; i++) {
|
|
184
|
+
const key = keys[i];
|
|
39
185
|
let value = anyObj[key];
|
|
40
186
|
const nkey = f(key);
|
|
41
|
-
|
|
187
|
+
|
|
188
|
+
// Look up schema using BOTH original key and converted key since:
|
|
189
|
+
// - For snakeToCamel: schema has camelCase keys, input has snake_case, nkey is camelCase (matches)
|
|
190
|
+
// - For camelToSnake: schema has camelCase keys, input has camelCase, nkey is snake_case (key matches)
|
|
191
|
+
const propSchema: z.ZodType | undefined =
|
|
192
|
+
shape != null ? (shape[key] ?? shape[nkey] ?? undefined) : undefined;
|
|
193
|
+
|
|
194
|
+
if (recursive)
|
|
42
195
|
if (isValidObject(value)) {
|
|
43
|
-
if (!
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
196
|
+
if (!isPreservedType(value)) {
|
|
197
|
+
childOpt.schema = propSchema;
|
|
198
|
+
value = converter(value, childOpt);
|
|
199
|
+
}
|
|
200
|
+
} else if (recursiveInArray && Array.isArray(value)) {
|
|
201
|
+
const elementSchema = getArrayElementSchema(propSchema);
|
|
202
|
+
childOpt.schema = elementSchema;
|
|
203
|
+
value = (value as unknown[]).map((v) => {
|
|
47
204
|
if (isValidObject(v)) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// workaround by using an object holding array value
|
|
53
|
-
const temp: record.Unknown = converter({ key: v }, opt);
|
|
54
|
-
ret = temp.key;
|
|
205
|
+
if (!isPreservedType(v)) return converter(v, childOpt);
|
|
206
|
+
} else if (Array.isArray(v)) {
|
|
207
|
+
const temp: record.Unknown = converter({ key: v }, childOpt);
|
|
208
|
+
return temp.key;
|
|
55
209
|
}
|
|
56
|
-
return
|
|
210
|
+
return v;
|
|
57
211
|
});
|
|
212
|
+
}
|
|
213
|
+
|
|
58
214
|
res[nkey] = value;
|
|
59
|
-
}
|
|
215
|
+
}
|
|
60
216
|
|
|
61
217
|
return res as V;
|
|
62
218
|
};
|
|
@@ -113,33 +269,22 @@ export const capitalize = (str: string): string => {
|
|
|
113
269
|
* Example Date, RegExp. These types will be right-hand side of 'instanceof' operator.
|
|
114
270
|
*/
|
|
115
271
|
export interface Options {
|
|
116
|
-
recursive
|
|
272
|
+
recursive?: boolean;
|
|
117
273
|
recursiveInArray?: boolean;
|
|
274
|
+
schema?: z.ZodType;
|
|
118
275
|
}
|
|
119
276
|
|
|
120
|
-
const keepTypesOnRecursion = [Number, String, Uint8Array];
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Default options for convert function. This option is not recursive.
|
|
124
|
-
*/
|
|
125
277
|
const defaultOptions: Options = {
|
|
126
278
|
recursive: true,
|
|
127
279
|
recursiveInArray: true,
|
|
280
|
+
schema: undefined,
|
|
128
281
|
};
|
|
129
282
|
|
|
130
|
-
const validateOptions = (opt: Options = defaultOptions): Options => {
|
|
131
|
-
if (opt.recursive == null) opt = defaultOptions;
|
|
132
|
-
else opt.recursiveInArray ??= false;
|
|
133
|
-
return opt;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const isArrayObject = (obj: unknown): boolean => obj != null && Array.isArray(obj);
|
|
137
|
-
|
|
138
283
|
const isValidObject = (obj: unknown): boolean =>
|
|
139
284
|
obj != null && typeof obj === "object" && !Array.isArray(obj);
|
|
140
285
|
|
|
141
|
-
const
|
|
142
|
-
|
|
286
|
+
const isPreservedType = (obj: unknown): boolean =>
|
|
287
|
+
obj instanceof Uint8Array || obj instanceof Number || obj instanceof String;
|
|
143
288
|
|
|
144
289
|
/**
|
|
145
290
|
* Converts a string to kebab-case.
|
package/src/color/color.spec.ts
CHANGED
|
@@ -24,7 +24,7 @@ describe("color.Color", () => {
|
|
|
24
24
|
expect(color.rValue(c)).toEqual(122);
|
|
25
25
|
expect(color.gValue(c)).toEqual(44);
|
|
26
26
|
expect(color.bValue(c)).toEqual(38);
|
|
27
|
-
expect(color.aValue(c)).
|
|
27
|
+
expect(color.aValue(c)).toBeCloseTo(0.5);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
describe("from eight digit hex", () => {
|
|
@@ -33,14 +33,14 @@ describe("color.Color", () => {
|
|
|
33
33
|
expect(color.rValue(c)).toEqual(122);
|
|
34
34
|
expect(color.gValue(c)).toEqual(44);
|
|
35
35
|
expect(color.bValue(c)).toEqual(38);
|
|
36
|
-
expect(color.aValue(c)).
|
|
36
|
+
expect(color.aValue(c)).toBeCloseTo(1);
|
|
37
37
|
});
|
|
38
38
|
test("case 2", () => {
|
|
39
39
|
const c = color.construct("#7a2c2605");
|
|
40
40
|
expect(color.rValue(c)).toEqual(122);
|
|
41
41
|
expect(color.gValue(c)).toEqual(44);
|
|
42
42
|
expect(color.bValue(c)).toEqual(38);
|
|
43
|
-
expect(color.aValue(c)).
|
|
43
|
+
expect(color.aValue(c)).toBeCloseTo(5 / 255);
|
|
44
44
|
});
|
|
45
45
|
});
|
|
46
46
|
|
|
@@ -55,7 +55,7 @@ describe("color.Color", () => {
|
|
|
55
55
|
expect(color.rValue(c)).toEqual(122);
|
|
56
56
|
expect(color.gValue(c)).toEqual(44);
|
|
57
57
|
expect(color.bValue(c)).toEqual(38);
|
|
58
|
-
expect(color.aValue(c)).
|
|
58
|
+
expect(color.aValue(c)).toBeCloseTo(0.5);
|
|
59
59
|
});
|
|
60
60
|
test("from c", () => {
|
|
61
61
|
const c = color.construct(color.construct("#7a2c26"));
|
|
@@ -69,7 +69,7 @@ describe("color.Color", () => {
|
|
|
69
69
|
expect(color.rValue(c)).toEqual(122);
|
|
70
70
|
expect(color.gValue(c)).toEqual(44);
|
|
71
71
|
expect(color.bValue(c)).toEqual(38);
|
|
72
|
-
expect(color.aValue(c)).
|
|
72
|
+
expect(color.aValue(c)).toBeCloseTo(0.5);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
test("from rgba struct", () => {
|
|
@@ -105,16 +105,20 @@ describe("color.Color", () => {
|
|
|
105
105
|
});
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
describe("to
|
|
108
|
+
describe("to RGBA", () => {
|
|
109
109
|
test("with alpha", () => {
|
|
110
110
|
const c = color.construct("#7a2c26", 0.5);
|
|
111
|
-
|
|
112
|
-
expect(
|
|
111
|
+
expect(c[0]).toEqual(122);
|
|
112
|
+
expect(c[1]).toEqual(44);
|
|
113
|
+
expect(c[2]).toEqual(38);
|
|
114
|
+
expect(c[3]).toBeCloseTo(0.5);
|
|
113
115
|
});
|
|
114
116
|
test("without alpha", () => {
|
|
115
117
|
const c = color.construct("#7a2c26");
|
|
116
|
-
|
|
117
|
-
expect(
|
|
118
|
+
expect(c[0]).toEqual(122);
|
|
119
|
+
expect(c[1]).toEqual(44);
|
|
120
|
+
expect(c[2]).toEqual(38);
|
|
121
|
+
expect(c[3]).toBeCloseTo(1);
|
|
118
122
|
});
|
|
119
123
|
});
|
|
120
124
|
|
|
@@ -319,7 +323,7 @@ describe("color.Color", () => {
|
|
|
319
323
|
const semiTransparent: color.HSLA = [0, 100, 50, 0.5]; // Semi-transparent red
|
|
320
324
|
|
|
321
325
|
expect(color.fromHSLA(transparent)[3]).toEqual(0);
|
|
322
|
-
expect(color.fromHSLA(semiTransparent)[3]).
|
|
326
|
+
expect(color.fromHSLA(semiTransparent)[3]).toBeCloseTo(0.5);
|
|
323
327
|
});
|
|
324
328
|
});
|
|
325
329
|
|
|
@@ -362,8 +366,8 @@ describe("color.Color", () => {
|
|
|
362
366
|
// we use toBeCloseTo for HSL values with precision 0
|
|
363
367
|
for (let i = 0; i < 3; i++) expect(result[i]).toBeCloseTo(expected[i], 0);
|
|
364
368
|
|
|
365
|
-
// Alpha should match
|
|
366
|
-
expect(result[3]).
|
|
369
|
+
// Alpha should match
|
|
370
|
+
expect(result[3]).toBeCloseTo(expected[3]);
|
|
367
371
|
});
|
|
368
372
|
});
|
|
369
373
|
|
|
@@ -375,7 +379,7 @@ describe("color.Color", () => {
|
|
|
375
379
|
|
|
376
380
|
for (let i = 0; i < 3; i++) expect(result[i]).toBeCloseTo(expected[i], 0);
|
|
377
381
|
|
|
378
|
-
expect(result[3]).
|
|
382
|
+
expect(result[3]).toBeCloseTo(expected[3]);
|
|
379
383
|
});
|
|
380
384
|
|
|
381
385
|
test("handles RGB array input", () => {
|
|
@@ -386,7 +390,7 @@ describe("color.Color", () => {
|
|
|
386
390
|
|
|
387
391
|
for (let i = 0; i < 3; i++) expect(result[i]).toBeCloseTo(expected[i], 0);
|
|
388
392
|
|
|
389
|
-
expect(result[3]).
|
|
393
|
+
expect(result[3]).toBeCloseTo(1); // Default alpha
|
|
390
394
|
});
|
|
391
395
|
|
|
392
396
|
test("preserves original color after round-trip conversion", () => {
|
|
@@ -408,8 +412,8 @@ describe("color.Color", () => {
|
|
|
408
412
|
// Compare RGB values with some tolerance for rounding
|
|
409
413
|
for (let i = 0; i < 3; i++) expect(converted[i]).toBeCloseTo(original[i], 0);
|
|
410
414
|
|
|
411
|
-
// Alpha should match
|
|
412
|
-
expect(converted[3]).
|
|
415
|
+
// Alpha should match
|
|
416
|
+
expect(converted[3]).toBeCloseTo(original[3]);
|
|
413
417
|
}
|
|
414
418
|
});
|
|
415
419
|
|
|
@@ -422,7 +426,7 @@ describe("color.Color", () => {
|
|
|
422
426
|
expect(result[0]).toEqual(0); // Hue
|
|
423
427
|
expect(result[1]).toEqual(0); // Saturation
|
|
424
428
|
expect(result[2]).toBeCloseTo(39, 0); // Lightness ~39%
|
|
425
|
-
expect(result[3]).
|
|
429
|
+
expect(result[3]).toBeCloseTo(1); // Alpha
|
|
426
430
|
});
|
|
427
431
|
});
|
|
428
432
|
|
|
@@ -431,28 +435,40 @@ describe("color.Color", () => {
|
|
|
431
435
|
const rgb: color.RGB = [255, 0, 0];
|
|
432
436
|
const result = color.setAlpha(rgb, 0.5);
|
|
433
437
|
|
|
434
|
-
expect(result).toEqual(
|
|
438
|
+
expect(result[0]).toEqual(255);
|
|
439
|
+
expect(result[1]).toEqual(0);
|
|
440
|
+
expect(result[2]).toEqual(0);
|
|
441
|
+
expect(result[3]).toBeCloseTo(0.5);
|
|
435
442
|
});
|
|
436
443
|
|
|
437
444
|
test("sets alpha on RGBA color", () => {
|
|
438
445
|
const rgba: color.RGBA = [0, 255, 0, 1];
|
|
439
446
|
const result = color.setAlpha(rgba, 0.3);
|
|
440
447
|
|
|
441
|
-
expect(result).toEqual(
|
|
448
|
+
expect(result[0]).toEqual(0);
|
|
449
|
+
expect(result[1]).toEqual(255);
|
|
450
|
+
expect(result[2]).toEqual(0);
|
|
451
|
+
expect(result[3]).toBeCloseTo(0.3);
|
|
442
452
|
});
|
|
443
453
|
|
|
444
454
|
test("sets alpha on hex color", () => {
|
|
445
455
|
const hex = "#0000ff";
|
|
446
456
|
const result = color.setAlpha(hex, 0.7);
|
|
447
457
|
|
|
448
|
-
expect(result).toEqual(
|
|
458
|
+
expect(result[0]).toEqual(0);
|
|
459
|
+
expect(result[1]).toEqual(0);
|
|
460
|
+
expect(result[2]).toEqual(255);
|
|
461
|
+
expect(result[3]).toBeCloseTo(0.7);
|
|
449
462
|
});
|
|
450
463
|
|
|
451
464
|
test("overrides existing alpha in RGBA color", () => {
|
|
452
465
|
const rgba: color.RGBA = [128, 128, 128, 0.2];
|
|
453
466
|
const result = color.setAlpha(rgba, 0.8);
|
|
454
467
|
|
|
455
|
-
expect(result).toEqual(
|
|
468
|
+
expect(result[0]).toEqual(128);
|
|
469
|
+
expect(result[1]).toEqual(128);
|
|
470
|
+
expect(result[2]).toEqual(128);
|
|
471
|
+
expect(result[3]).toBeCloseTo(0.8);
|
|
456
472
|
});
|
|
457
473
|
|
|
458
474
|
test("handles alpha value of 0", () => {
|
|
@@ -466,19 +482,14 @@ describe("color.Color", () => {
|
|
|
466
482
|
const color1 = color.construct("#ff0000", 0.5);
|
|
467
483
|
const result = color.setAlpha(color1, 1);
|
|
468
484
|
|
|
469
|
-
expect(result[3]).
|
|
485
|
+
expect(result[3]).toBeCloseTo(1);
|
|
470
486
|
});
|
|
471
487
|
|
|
472
|
-
test("
|
|
488
|
+
test("accepts alpha values in 0-1 range", () => {
|
|
473
489
|
const color1 = color.construct("#ff0000");
|
|
474
|
-
const result = color.setAlpha(color1,
|
|
490
|
+
const result = color.setAlpha(color1, 0.5);
|
|
475
491
|
|
|
476
|
-
expect(result[3]).
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
test("throws error for alpha values > 100", () => {
|
|
480
|
-
const color1 = color.construct("#ff0000");
|
|
481
|
-
expect(() => color.setAlpha(color1, 101)).toThrow();
|
|
492
|
+
expect(result[3]).toBeCloseTo(0.5);
|
|
482
493
|
});
|
|
483
494
|
|
|
484
495
|
test("preserves RGB values when setting alpha", () => {
|
|
@@ -518,7 +529,9 @@ describe("color.Color", () => {
|
|
|
518
529
|
|
|
519
530
|
test("converts hex with alpha to CSS rgba string", () => {
|
|
520
531
|
const hex = "#ff000080";
|
|
521
|
-
|
|
532
|
+
const result = color.rgbaCSS(hex);
|
|
533
|
+
// Alpha 0x80 = 128/255 ≈ 0.502
|
|
534
|
+
expect(result).toMatch(/rgba\(255, 0, 0, 0\.5\d*\)/);
|
|
522
535
|
});
|
|
523
536
|
|
|
524
537
|
test("handles Color object", () => {
|
|
@@ -604,7 +617,7 @@ describe("color.Color", () => {
|
|
|
604
617
|
|
|
605
618
|
test("eight-digit hex and RGBA with same values are equal", () => {
|
|
606
619
|
const c1 = "#ff000080";
|
|
607
|
-
const c2: color.RGBA = [255, 0, 0,
|
|
620
|
+
const c2: color.RGBA = [255, 0, 0, 128 / 255];
|
|
608
621
|
expect(color.equals(c1, c2)).toBe(true);
|
|
609
622
|
});
|
|
610
623
|
|
|
@@ -652,7 +665,9 @@ describe("color.Color", () => {
|
|
|
652
665
|
|
|
653
666
|
test("handles eight-digit hex with alpha", () => {
|
|
654
667
|
const hex = "#00ff0080";
|
|
655
|
-
|
|
668
|
+
const result = color.cssString(hex);
|
|
669
|
+
// Alpha 0x80 = 128/255 ≈ 0.502
|
|
670
|
+
expect(result).toMatch(/rgba\(0, 255, 0, 0\.5\d*\)/);
|
|
656
671
|
});
|
|
657
672
|
|
|
658
673
|
test("handles Color object", () => {
|
|
@@ -711,7 +726,7 @@ describe("color.Color", () => {
|
|
|
711
726
|
});
|
|
712
727
|
|
|
713
728
|
test("rejects invalid RGBA arrays", () => {
|
|
714
|
-
expect(color.isCrude([255, 0, 0, 1.
|
|
729
|
+
expect(color.isCrude([255, 0, 0, 1.5])).toBe(false);
|
|
715
730
|
expect(color.isCrude([255, 0, 0, -0.1])).toBe(false);
|
|
716
731
|
});
|
|
717
732
|
|
|
@@ -754,7 +769,7 @@ describe("color.Color", () => {
|
|
|
754
769
|
expect(color.isColor([255, 0, 0, 0, 0])).toBe(false); // Too many elements
|
|
755
770
|
expect(color.isColor([255, 0, -1, 1])).toBe(false); // Negative value
|
|
756
771
|
expect(color.isColor([255, 0, 256, 1])).toBe(false); // Value > 255
|
|
757
|
-
expect(color.isColor([255, 0, 0, 1.
|
|
772
|
+
expect(color.isColor([255, 0, 0, 1.5])).toBe(false); // Alpha > 1
|
|
758
773
|
expect(color.isColor([255, 0, 0, -0.1])).toBe(false); // Alpha < 0
|
|
759
774
|
});
|
|
760
775
|
|
package/src/color/color.ts
CHANGED
|
@@ -9,25 +9,24 @@
|
|
|
9
9
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
|
+
import { zod } from "@/zod";
|
|
13
|
+
|
|
12
14
|
/** A regex to match hex colors. */
|
|
13
15
|
const hexRegex = /^#?([0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
14
16
|
|
|
15
17
|
/** A zod schema for a hex color. */
|
|
16
18
|
const hexZ = z.string().regex(hexRegex);
|
|
17
19
|
/** A zod schema for an RGB value. */
|
|
18
|
-
const rgbValueZ =
|
|
20
|
+
const rgbValueZ = zod.uint8;
|
|
19
21
|
/** A zod schema for an alpha value between 0 and 1. */
|
|
20
22
|
const alphaZ = z.number().min(0).max(1);
|
|
21
23
|
/** A zod schema for an RGBA color. */
|
|
22
24
|
const rgbaZ = z.tuple([rgbValueZ, rgbValueZ, rgbValueZ, alphaZ]);
|
|
23
25
|
/** A zod schema for an RGB color. */
|
|
24
26
|
const rgbZ = z.tuple([rgbValueZ, rgbValueZ, rgbValueZ]);
|
|
25
|
-
/** A zod schema for a legacy color object. */
|
|
26
27
|
const legacyObjectZ = z.object({ rgba255: rgbaZ });
|
|
27
28
|
/** A zod schema for an RGBA struct (r, g, b, a fields). */
|
|
28
29
|
const rgbaStructZ = z.object({ r: rgbValueZ, g: rgbValueZ, b: rgbValueZ, a: alphaZ });
|
|
29
|
-
/** An RGBA struct with named fields. */
|
|
30
|
-
export type RGBAStruct = z.infer<typeof rgbaStructZ>;
|
|
31
30
|
/** A zod schema for a hue value between 0 and 360. */
|
|
32
31
|
const hueZ = z.number().min(0).max(360);
|
|
33
32
|
/** A zod schema for a saturation value between 0 and 100. */
|
|
@@ -48,7 +47,8 @@ export type Hex = z.infer<typeof hexZ>;
|
|
|
48
47
|
|
|
49
48
|
/** A legacy color object. Used for backwards compatibility. */
|
|
50
49
|
type LegacyObject = z.infer<typeof legacyObjectZ>;
|
|
51
|
-
|
|
50
|
+
/** A color in RGBA format as a struct. */
|
|
51
|
+
type RGBAStruct = z.infer<typeof rgbaStructZ>;
|
|
52
52
|
/** A zod schema for a crude color representation. */
|
|
53
53
|
export const crudeZ = z.union([hexZ, rgbZ, rgbaZ, hslaZ, legacyObjectZ, rgbaStructZ]);
|
|
54
54
|
/**
|
|
@@ -203,8 +203,7 @@ export const hsla = (color: Crude): HSLA => rgbaToHSLA(construct(color));
|
|
|
203
203
|
/**
|
|
204
204
|
* @returns A new color with the given alpha.
|
|
205
205
|
* @param color - The color to set the alpha value on.
|
|
206
|
-
* @param alpha - The alpha value to set
|
|
207
|
-
* divided by 100.
|
|
206
|
+
* @param alpha - The alpha value to set (0-1).
|
|
208
207
|
*/
|
|
209
208
|
export const setAlpha = (color: Crude, alpha: number): Color => {
|
|
210
209
|
const [r, g, b] = construct(color);
|
|
@@ -353,7 +352,7 @@ export const fromCSS = (cssColor: string): Color | undefined => {
|
|
|
353
352
|
);
|
|
354
353
|
if (match) {
|
|
355
354
|
const [, r, g, b, a] = match;
|
|
356
|
-
return [parseInt(r), parseInt(g), parseInt(b), a ? parseFloat(a) : 1];
|
|
355
|
+
return [parseInt(r, 10), parseInt(g, 10), parseInt(b, 10), a ? parseFloat(a) : 1];
|
|
357
356
|
}
|
|
358
357
|
}
|
|
359
358
|
if (NAMED[trimmed]) return fromHex(NAMED[trimmed]);
|
package/src/control/control.ts
CHANGED
|
@@ -9,11 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
|
+
import { type Authority, type State, stateZ } from "@/control/types.gen";
|
|
12
13
|
import { type bounds } from "@/spatial";
|
|
13
14
|
|
|
14
|
-
export const authorityZ = z.int().min(0).max(255);
|
|
15
|
-
export type Authority = z.infer<typeof authorityZ>;
|
|
16
|
-
|
|
17
15
|
export const ABSOLUTE_AUTHORITY: Authority = 255;
|
|
18
16
|
export const ZERO_AUTHORITY: Authority = 0;
|
|
19
17
|
|
|
@@ -22,31 +20,8 @@ export const AUTHORITY_BOUNDS: bounds.Bounds<Authority> = {
|
|
|
22
20
|
upper: ABSOLUTE_AUTHORITY + 1,
|
|
23
21
|
};
|
|
24
22
|
|
|
25
|
-
export const subjectZ = z.object({
|
|
26
|
-
name: z.string(),
|
|
27
|
-
key: z.string(),
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
export interface Subject {
|
|
31
|
-
name: string;
|
|
32
|
-
key: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const stateZ = <R extends z.ZodType>(resource: R) =>
|
|
36
|
-
z.object({
|
|
37
|
-
subject: subjectZ,
|
|
38
|
-
resource,
|
|
39
|
-
authority: authorityZ,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
export interface State<R> {
|
|
43
|
-
subject: Subject;
|
|
44
|
-
resource: R;
|
|
45
|
-
authority: Authority;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
23
|
export const filterTransfersByChannelKey =
|
|
49
|
-
<R>(...resources: R[]) =>
|
|
24
|
+
<R extends z.ZodType>(...resources: z.infer<R>[]) =>
|
|
50
25
|
(transfers: Transfer<R>[]): Transfer<R>[] =>
|
|
51
26
|
transfers.filter((t) => {
|
|
52
27
|
let ok = false;
|
|
@@ -55,7 +30,7 @@ export const filterTransfersByChannelKey =
|
|
|
55
30
|
return ok;
|
|
56
31
|
});
|
|
57
32
|
|
|
58
|
-
interface Release<R> {
|
|
33
|
+
interface Release<R extends z.ZodType> {
|
|
59
34
|
from: State<R>;
|
|
60
35
|
to?: null;
|
|
61
36
|
}
|
|
@@ -63,21 +38,21 @@ interface Release<R> {
|
|
|
63
38
|
export const releaseZ = <R extends z.ZodType>(resource: R) =>
|
|
64
39
|
z.object({
|
|
65
40
|
from: stateZ(resource),
|
|
66
|
-
to: z.null(),
|
|
41
|
+
to: z.null().optional(),
|
|
67
42
|
});
|
|
68
43
|
|
|
69
|
-
interface Acquire<R> {
|
|
44
|
+
interface Acquire<R extends z.ZodType> {
|
|
70
45
|
from?: null;
|
|
71
46
|
to: State<R>;
|
|
72
47
|
}
|
|
73
48
|
|
|
74
49
|
export const acquireZ = <R extends z.ZodType>(resource: R) =>
|
|
75
50
|
z.object({
|
|
76
|
-
from: z.null(),
|
|
51
|
+
from: z.null().optional(),
|
|
77
52
|
to: stateZ(resource),
|
|
78
53
|
});
|
|
79
54
|
|
|
80
|
-
export type Transfer<R> =
|
|
55
|
+
export type Transfer<R extends z.ZodType> =
|
|
81
56
|
| {
|
|
82
57
|
from: State<R>;
|
|
83
58
|
to: State<R>;
|
|
@@ -7,16 +7,5 @@
|
|
|
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
|
-
import { color } from "@/color";
|
|
13
|
-
|
|
14
|
-
export const keyZ = z.uuid();
|
|
15
|
-
export type Key = z.infer<typeof keyZ>;
|
|
16
|
-
|
|
17
|
-
export const labelZ = z.object({
|
|
18
|
-
key: keyZ,
|
|
19
|
-
name: z.string().min(1),
|
|
20
|
-
color: color.colorZ,
|
|
21
|
-
});
|
|
22
|
-
export interface Label extends z.infer<typeof labelZ> {}
|
|
10
|
+
export * from "@/control/control";
|
|
11
|
+
export * from "@/control/types.gen";
|
package/src/control/index.ts
CHANGED