@synnaxlabs/x 0.40.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/.turbo/turbo-build.log +33 -33
  2. package/dist/binary.cjs +1 -1
  3. package/dist/binary.js +2 -2
  4. package/dist/bounds-BQo7rvs9.cjs +1 -0
  5. package/dist/{bounds-azUOoVVR.js → bounds-Bn5_l4Z3.js} +84 -86
  6. package/dist/bounds.cjs +1 -1
  7. package/dist/bounds.js +1 -1
  8. package/dist/box-0YrQibkB.cjs +1 -0
  9. package/dist/box-Cc8IzcNo.js +205 -0
  10. package/dist/box.cjs +1 -1
  11. package/dist/box.js +1 -1
  12. package/dist/compare.cjs +1 -1
  13. package/dist/compare.js +1 -1
  14. package/dist/deep.cjs +1 -1
  15. package/dist/deep.js +66 -69
  16. package/dist/{dimensions-PWy5QZoM.cjs → dimensions-D2QGoNXO.cjs} +1 -1
  17. package/dist/dimensions.cjs +1 -1
  18. package/dist/{external-CvWr1nhS.cjs → external-DWQITF5_.cjs} +1 -1
  19. package/dist/index-BywOGO8U.js +1074 -0
  20. package/dist/index-CYYjI7Uf.cjs +1 -0
  21. package/dist/index-C_6NXBlg.cjs +3 -0
  22. package/dist/{index-BVC_8Cg9.js → index-QGplUHuy.js} +1 -1
  23. package/dist/index.cjs +3 -3
  24. package/dist/index.js +736 -240
  25. package/dist/location-BGl5Ddds.cjs +1 -0
  26. package/dist/{location-BuYbIFHD.js → location-C3aeu046.js} +16 -12
  27. package/dist/location.cjs +1 -1
  28. package/dist/location.js +1 -1
  29. package/dist/{position-DTrNGtrm.cjs → position-Cai5-wi1.cjs} +1 -1
  30. package/dist/{position-DemzGvAY.js → position-DIglP1l2.js} +2 -2
  31. package/dist/position.cjs +1 -1
  32. package/dist/position.js +1 -1
  33. package/dist/record.js +3 -1
  34. package/dist/{scale-DpJM6__6.cjs → scale-BtZINJ-A.cjs} +1 -1
  35. package/dist/{scale-C0EllH-1.js → scale-DfJe9755.js} +4 -4
  36. package/dist/scale.cjs +1 -1
  37. package/dist/scale.js +1 -1
  38. package/dist/{series-CXnO-P0V.js → series-B9JERcqi.js} +541 -474
  39. package/dist/series-DqJ6f97G.cjs +11 -0
  40. package/dist/spatial.cjs +1 -1
  41. package/dist/spatial.js +6 -6
  42. package/dist/src/binary/{encoder.d.ts → codec.d.ts} +14 -8
  43. package/dist/src/binary/codec.d.ts.map +1 -0
  44. package/dist/src/binary/codec.spec.d.ts +2 -0
  45. package/dist/src/binary/codec.spec.d.ts.map +1 -0
  46. package/dist/src/binary/index.d.ts +1 -1
  47. package/dist/src/binary/index.d.ts.map +1 -1
  48. package/dist/src/breaker/breaker.d.ts +14 -21
  49. package/dist/src/breaker/breaker.d.ts.map +1 -1
  50. package/dist/src/change/change.d.ts +5 -18
  51. package/dist/src/change/change.d.ts.map +1 -1
  52. package/dist/src/color/color.d.ts +126 -0
  53. package/dist/src/color/color.d.ts.map +1 -0
  54. package/dist/src/color/color.spec.d.ts +2 -0
  55. package/dist/src/color/color.spec.d.ts.map +1 -0
  56. package/dist/src/color/external.d.ts +5 -0
  57. package/dist/src/color/external.d.ts.map +1 -0
  58. package/dist/src/color/gradient.d.ts +18 -0
  59. package/dist/src/color/gradient.d.ts.map +1 -0
  60. package/dist/src/color/index.d.ts +2 -0
  61. package/dist/src/color/index.d.ts.map +1 -0
  62. package/dist/src/color/palette.d.ts +19 -0
  63. package/dist/src/color/palette.d.ts.map +1 -0
  64. package/dist/src/color/transformColorsToHex.d.ts +6 -0
  65. package/dist/src/color/transformColorsToHex.d.ts.map +1 -0
  66. package/dist/src/control/control.d.ts +69 -74
  67. package/dist/src/control/control.d.ts.map +1 -1
  68. package/dist/src/deep/merge.d.ts +1 -1
  69. package/dist/src/deep/merge.d.ts.map +1 -1
  70. package/dist/src/errors/errors.d.ts +127 -7
  71. package/dist/src/errors/errors.d.ts.map +1 -1
  72. package/dist/src/errors/errors.spec.d.ts +2 -0
  73. package/dist/src/errors/errors.spec.d.ts.map +1 -0
  74. package/dist/src/index.d.ts +5 -0
  75. package/dist/src/index.d.ts.map +1 -1
  76. package/dist/src/jsonrpc/jsonrpc.d.ts +10 -7
  77. package/dist/src/jsonrpc/jsonrpc.d.ts.map +1 -1
  78. package/dist/src/kv/types.d.ts +1 -7
  79. package/dist/src/kv/types.d.ts.map +1 -1
  80. package/dist/src/math/external.d.ts +3 -0
  81. package/dist/src/math/external.d.ts.map +1 -0
  82. package/dist/src/math/index.d.ts +1 -1
  83. package/dist/src/math/index.d.ts.map +1 -1
  84. package/dist/src/math/round.d.ts +40 -0
  85. package/dist/src/math/round.d.ts.map +1 -0
  86. package/dist/src/math/round.spec.d.ts +2 -0
  87. package/dist/src/math/round.spec.d.ts.map +1 -0
  88. package/dist/src/migrate/migrate.d.ts +1 -1
  89. package/dist/src/notation/index.d.ts +2 -0
  90. package/dist/src/notation/index.d.ts.map +1 -0
  91. package/dist/src/notation/notation.d.ts +37 -0
  92. package/dist/src/notation/notation.d.ts.map +1 -0
  93. package/dist/src/notation/notation.spec.d.ts +2 -0
  94. package/dist/src/notation/notation.spec.d.ts.map +1 -0
  95. package/dist/src/record.d.ts +2 -1
  96. package/dist/src/record.d.ts.map +1 -1
  97. package/dist/src/replace.d.ts +2 -0
  98. package/dist/src/replace.d.ts.map +1 -0
  99. package/dist/src/runtime/os.d.ts +9 -1
  100. package/dist/src/runtime/os.d.ts.map +1 -1
  101. package/dist/src/singleton/define.d.ts +9 -0
  102. package/dist/src/singleton/define.d.ts.map +1 -0
  103. package/dist/src/singleton/define.spec.d.ts +2 -0
  104. package/dist/src/singleton/define.spec.d.ts.map +1 -0
  105. package/dist/src/singleton/index.d.ts +2 -0
  106. package/dist/src/singleton/index.d.ts.map +1 -0
  107. package/dist/src/spatial/base.d.ts +74 -70
  108. package/dist/src/spatial/base.d.ts.map +1 -1
  109. package/dist/src/spatial/box/box.d.ts +22 -76
  110. package/dist/src/spatial/box/box.d.ts.map +1 -1
  111. package/dist/src/spatial/dimensions/dimensions.d.ts +5 -29
  112. package/dist/src/spatial/dimensions/dimensions.d.ts.map +1 -1
  113. package/dist/src/spatial/direction/direction.d.ts +9 -1
  114. package/dist/src/spatial/direction/direction.d.ts.map +1 -1
  115. package/dist/src/spatial/location/location.d.ts +45 -24
  116. package/dist/src/spatial/location/location.d.ts.map +1 -1
  117. package/dist/src/spatial/scale/scale.d.ts +12 -120
  118. package/dist/src/spatial/scale/scale.d.ts.map +1 -1
  119. package/dist/src/spatial/xy/xy.d.ts +15 -29
  120. package/dist/src/spatial/xy/xy.d.ts.map +1 -1
  121. package/dist/src/sync/index.d.ts +2 -0
  122. package/dist/src/sync/index.d.ts.map +1 -0
  123. package/dist/src/sync/mutex.d.ts +8 -0
  124. package/dist/src/sync/mutex.d.ts.map +1 -0
  125. package/dist/src/telem/gl.d.ts +4 -1
  126. package/dist/src/telem/gl.d.ts.map +1 -1
  127. package/dist/src/telem/series.d.ts +46 -125
  128. package/dist/src/telem/series.d.ts.map +1 -1
  129. package/dist/src/telem/telem.d.ts +102 -85
  130. package/dist/src/telem/telem.d.ts.map +1 -1
  131. package/dist/src/toArray.d.ts +1 -1
  132. package/dist/src/toArray.d.ts.map +1 -1
  133. package/dist/src/zod/util.d.ts.map +1 -1
  134. package/dist/telem.cjs +1 -1
  135. package/dist/telem.js +1 -1
  136. package/dist/toArray.cjs +1 -1
  137. package/dist/toArray.js +1 -1
  138. package/dist/{xy-DyQSETQZ.cjs → xy-B7065J2S.cjs} +1 -1
  139. package/dist/{xy-DHBO1dG_.js → xy-D_LqxaGt.js} +8 -4
  140. package/dist/xy.cjs +1 -1
  141. package/dist/xy.js +1 -1
  142. package/dist/zod.cjs +1 -1
  143. package/package.json +11 -8
  144. package/src/binary/codec.spec.ts +370 -0
  145. package/src/binary/{encoder.ts → codec.ts} +55 -11
  146. package/src/binary/index.ts +1 -1
  147. package/src/breaker/breaker.spec.ts +16 -25
  148. package/src/breaker/breaker.ts +36 -19
  149. package/src/color/color.spec.ts +673 -0
  150. package/src/color/color.ts +317 -0
  151. package/src/color/external.ts +13 -0
  152. package/src/color/gradient.ts +78 -0
  153. package/src/color/index.ts +10 -0
  154. package/src/color/palette.ts +28 -0
  155. package/src/color/transformColorsToHex.ts +30 -0
  156. package/src/control/control.ts +30 -22
  157. package/src/deep/merge.ts +2 -8
  158. package/src/errors/errors.spec.ts +152 -0
  159. package/src/errors/errors.ts +225 -10
  160. package/src/index.ts +5 -0
  161. package/src/jsonrpc/jsonrpc.ts +12 -8
  162. package/src/math/external.ts +11 -0
  163. package/src/math/index.ts +1 -1
  164. package/src/math/round.spec.ts +81 -0
  165. package/src/math/round.ts +68 -0
  166. package/src/migrate/migrate.ts +2 -2
  167. package/src/notation/index.ts +10 -0
  168. package/src/notation/notation.spec.ts +88 -0
  169. package/src/notation/notation.ts +61 -0
  170. package/src/primitive.ts +1 -1
  171. package/src/record.ts +5 -1
  172. package/src/replace.ts +1 -0
  173. package/src/singleton/define.spec.ts +93 -0
  174. package/src/singleton/define.ts +27 -0
  175. package/src/singleton/index.ts +10 -0
  176. package/src/spatial/box/box.spec.ts +3 -3
  177. package/src/spatial/box/box.ts +8 -0
  178. package/src/spatial/location/location.ts +4 -4
  179. package/src/spatial/position/position.spec.ts +10 -10
  180. package/src/spatial/xy/xy.spec.ts +8 -0
  181. package/src/spatial/xy/xy.ts +14 -0
  182. package/src/sync/index.ts +1 -0
  183. package/src/sync/mutex.ts +16 -0
  184. package/src/telem/series.spec.ts +71 -0
  185. package/src/telem/series.ts +69 -46
  186. package/src/telem/telem.spec.ts +151 -10
  187. package/src/telem/telem.ts +134 -73
  188. package/src/toArray.ts +2 -2
  189. package/src/zod/util.spec.ts +17 -1
  190. package/src/zod/util.ts +4 -2
  191. package/tsconfig.tsbuildinfo +1 -1
  192. package/dist/bounds-Dwq6ZFHm.cjs +0 -1
  193. package/dist/box-Bzya27QS.cjs +0 -1
  194. package/dist/box-DrsrRNSe.js +0 -201
  195. package/dist/index-BG3Scw3G.cjs +0 -1
  196. package/dist/index-C3QzbIwt.js +0 -101
  197. package/dist/index-CnclyYpG.cjs +0 -3
  198. package/dist/location-BgpQ3rN2.cjs +0 -1
  199. package/dist/series-BgoCtU71.cjs +0 -11
  200. package/dist/src/binary/encoder.d.ts.map +0 -1
  201. package/dist/src/binary/encoder.spec.d.ts +0 -2
  202. package/dist/src/binary/encoder.spec.d.ts.map +0 -1
  203. package/src/binary/encoder.spec.ts +0 -174
@@ -0,0 +1,317 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { z } from "zod";
11
+
12
+ const hexRegex = /^#?([0-9a-f]{6}|[0-9a-f]{8})$/i;
13
+ const hexZ = z.string().regex(hexRegex);
14
+ const rgbValueZ = z.number().min(0).max(255);
15
+ const alphaZ = z.number().min(0).max(1);
16
+ const rgbaZ = z.tuple([rgbValueZ, rgbValueZ, rgbValueZ, alphaZ]);
17
+ const rgbZ = z.tuple([rgbValueZ, rgbValueZ, rgbValueZ]);
18
+
19
+ /** A color in RGBA format. */
20
+ export type RGBA = [number, number, number, number];
21
+ /** A color in HSLA format. */
22
+ export type HSLA = [number, number, number, number];
23
+ /** A color in RGB format. */
24
+ export type RGB = [number, number, number];
25
+ /** A color in hex format. */
26
+ export type Hex = z.infer<typeof hexZ>;
27
+ /** A color in RGBA format. Used as the standard representation of a color in this package. */
28
+ export type Color = RGBA;
29
+
30
+ /** @returns true if the given color can be parsed into a valid color object. */
31
+ export const isCrude = (color: unknown): color is Crude =>
32
+ colorZ.safeParse(color).success;
33
+
34
+ /** @returns true if the color is a true Color type. */
35
+ export const isColor = (color: unknown): color is Color =>
36
+ rgbaZ.safeParse(color).success;
37
+
38
+ /**
39
+ * An unparsed representation of a color i.e. a value that can be converted into
40
+ * a Color object.
41
+ */
42
+ export type Crude = Hex | RGBA | Color | string | RGB;
43
+
44
+ /**
45
+ * Converts a crude color to its most meaningful CSS format.
46
+ * @returns undefined if the color is undefined.
47
+ * @returns an RGBA CSS string if the color can be parsed into a Color.
48
+ * @returns the color directly if it is a css variable.
49
+ * @throws if the color does not match any of the preceding conditions.
50
+ */
51
+ export const cssString = (color?: Crude): string | undefined => {
52
+ if (color == null) return undefined;
53
+ const res = colorZ.safeParse(color);
54
+ if (res.success) return rgbaCSS(res.data);
55
+ if (typeof color === "string") return color;
56
+ throw res.error;
57
+ };
58
+
59
+ /**
60
+ * @constructor Creates a new color from the given color value. The color value can be
61
+ * a hex string, an array of RGB or RGBA values, or another color.
62
+ *
63
+ * @param color - The color value to create the color from. If the color value is a
64
+ * string, it must be a valid hex color (with or without the '#') with a hash-less
65
+ * length 6 or 8. If the hex color is 8 characters long, the last two characters are
66
+ * used as the alpha value. If the color value is an array, it must be an array of
67
+ * length 3 or 4, with each value between 0 and 255. If the color value is another
68
+ * color, the color will be copied.
69
+ *
70
+ * @param alpha - An optional alpha value to set. If the color value carries its own
71
+ * alpha value, this value will be ignored. Defaults to 1.
72
+ */
73
+ export const construct = (color: Crude, alpha: number = 1): Color => {
74
+ if (typeof color === "string") return fromHex(color, alpha);
75
+ if (Array.isArray(color)) {
76
+ if (color.length < 3 || color.length > 4)
77
+ throw new Error(`Invalid color: [${color.join(", ")}]`);
78
+ if (color.length === 3) return [...color, alpha] as RGBA;
79
+ return color;
80
+ }
81
+ throw new Error(`Invalid color: ${JSON.stringify(color)}`);
82
+ };
83
+
84
+ /**
85
+ * @returns true if the given color is semantically equal to this color. Different
86
+ * representations of the same color are considered equal (e.g. hex and rgba).
87
+ */
88
+ export const equals = (a?: Crude, b?: Crude): boolean => {
89
+ if (a == null || b == null) return false;
90
+ const a_ = construct(a);
91
+ const b_ = construct(b);
92
+ return a_.every((v, i) => v === b_[i]);
93
+ };
94
+
95
+ export interface ToHex {
96
+ (color: Crude): string;
97
+ (color?: Crude): string | undefined;
98
+ }
99
+
100
+ /**
101
+ * @returns the hex representation of the color. If the color has an opacity of 1,
102
+ * the returned hex will be 6 characters long. Otherwise, it will be 8 characters
103
+ * long.
104
+ */
105
+ export const hex = ((color?: Crude) => {
106
+ if (color == null) return undefined;
107
+ const [r, g, b, a] = construct(color);
108
+ return `#${rgbaToHex(r)}${rgbaToHex(g)}${rgbaToHex(b)}${
109
+ a === 1 ? "" : rgbaToHex(a * 255)
110
+ }`;
111
+ }) as ToHex;
112
+
113
+ /** @returns the color as a CSS RGBA string. i.e. rgba(r, g, b, a) */
114
+ export const rgbaCSS = (color: Crude): string => {
115
+ const [r, g, b, a] = construct(color);
116
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
117
+ };
118
+
119
+ /** @returns the color as a CSS RGB string with no alpha value. i.e. rgb(r, g, b) */
120
+ export const rgbCSS = (color: Crude): string => `rgb(${rgbString(color)})`;
121
+
122
+ /**
123
+ * @returns the color as an RGB string, with each color value between 0 and 255.
124
+ * @example "255, 255, 255"
125
+ */
126
+ export const rgbString = (color: Crude): string => {
127
+ const [r, g, b] = construct(color);
128
+ return `${r}, ${g}, ${b}`;
129
+ };
130
+
131
+ /**
132
+ * @returns the color as an RGBA tuple, with each color value between 0 and 1,
133
+ * and the alpha value between 0 and 1.
134
+ */
135
+ export const rgba1 = (color: Crude): RGBA => [...rgb1(color), aValue(color)];
136
+
137
+ /** @returns the color normalized as an RGB tuple between 0 and 1. */
138
+ const rgb1 = (color: Crude): RGB => [
139
+ rValue(color) / 255,
140
+ gValue(color) / 255,
141
+ bValue(color) / 255,
142
+ ];
143
+
144
+ /** @returns the red value of the color, between 0 and 255. */
145
+ export const rValue = (color: Crude): number => construct(color)[0];
146
+
147
+ /** @returns the green value of the color, between 0 and 255. */
148
+ export const gValue = (color: Crude): number => construct(color)[1];
149
+
150
+ /** @returns the blue value of the color, between 0 and 255. */
151
+ export const bValue = (color: Crude): number => construct(color)[2];
152
+
153
+ /** @returns the alpha value of the color, between 0 and 1. */
154
+ export const aValue = (color: Crude): number => construct(color)[3];
155
+
156
+ /** @returns true if all RGBA values are 0. */
157
+ export const isZero = (color?: Crude): boolean => equals(ZERO, color);
158
+
159
+ /** @returns the HSLA representation of the color. */
160
+ export const hsla = (color: Crude): HSLA => rgbaToHSLA(construct(color));
161
+
162
+ /**
163
+ * @returns A new color with the given alpha.
164
+ * @param color - The color to set the alpha value on.
165
+ * @param alpha - The alpha value to set. If the value is greater than 1, it will be
166
+ * divided by 100.
167
+ */
168
+ export const setAlpha = (color: Crude, alpha: number): Color => {
169
+ const [r, g, b] = construct(color);
170
+ if (alpha > 100)
171
+ throw new Error(`Color opacity must be between 0 and 100, got ${alpha}`);
172
+ if (alpha > 1) alpha /= 100;
173
+ return [r, g, b, alpha] as Color;
174
+ };
175
+
176
+ /** @returns the luminance of the color, between 0 and 1. */
177
+ export const luminance = (color: Crude): number => {
178
+ const [r, g, b] = rgb1(color).map((v) =>
179
+ v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4,
180
+ );
181
+ return Number((0.2126 * r + 0.7152 * g + 0.0722 * b).toFixed(3));
182
+ };
183
+
184
+ /** @returns the grayness of the color, between 0 and 1. */
185
+ export const grayness = (color: Crude): number => {
186
+ const [r, g, b] = rgb1(color);
187
+ const deviation = Math.max(r, g, b) - Math.min(r, g, b);
188
+ return 1 - deviation;
189
+ };
190
+
191
+ /**
192
+ * @returns the contrast ratio between this color and the given color. The contrast
193
+ * ratio is a number between 1 and 21, where 1 is the lowest contrast and 21 is the
194
+ * highest.
195
+ * @param other
196
+ * @returns
197
+ */
198
+ export const contrast = (a: Crude, b: Crude): number => {
199
+ const a_ = construct(a);
200
+ const b_ = construct(b);
201
+ const l1 = luminance(a_);
202
+ const l2 = luminance(b_);
203
+ return (Math.max(l1, l2) + 0.5) / (Math.min(l1, l2) + 0.5);
204
+ };
205
+
206
+ /**
207
+ * @returns the color with the highest contrast ratio to the given colors.
208
+ */
209
+ export const pickByContrast = (source: Crude, ...colors: Crude[]): Color => {
210
+ if (colors.length === 0)
211
+ throw new Error("[Color.pickByContrast] - must provide at least one color");
212
+ const source_ = construct(source);
213
+ const [best] = colors
214
+ .map((c) => construct(c))
215
+ .sort((a, b) => contrast(source_, b) - contrast(source_, a));
216
+ return best;
217
+ };
218
+
219
+ /** @returns true if the color is dark i.e. it has a luminance less than 0.5. */
220
+ export const isDark = (color: Crude): boolean => luminance(color) < 0.5;
221
+
222
+ /** @returns true if the color is light i.e. the luminance is greater than or equal to 0.5. */
223
+ export const isLight = (color: Crude): boolean => !isDark(color);
224
+
225
+ /** A zod schema to parse color values from various crude representations. */
226
+ export const colorZ = z.union([hexZ, rgbaZ, rgbZ]).transform((v) => construct(v));
227
+
228
+ /** @returns a color parsed from a hex string with an alpha value. */
229
+ const fromHex = (hex_: string, alpha: number = 1): RGBA => {
230
+ const valid = hexZ.safeParse(hex_);
231
+ if (!valid.success) throw new Error(`Invalid hex color: ${hex_}`);
232
+ hex_ = stripHash(hex_);
233
+ return [
234
+ hexToRgba(hex_, 0),
235
+ hexToRgba(hex_, 2),
236
+ hexToRgba(hex_, 4),
237
+ hex_.length === 8 ? hexToRgba(hex_, 6) / 255 : alpha,
238
+ ];
239
+ };
240
+
241
+ /** A totally zero color with no alpha. */
242
+ export const ZERO: Color = [0, 0, 0, 0];
243
+
244
+ const rgbaToHex = (n: number): string => Math.floor(n).toString(16).padStart(2, "0");
245
+ const hexToRgba = (s: string, n: number): number => parseInt(s.slice(n, n + 2), 16);
246
+ const stripHash = (hex: string): string => (hex.startsWith("#") ? hex.slice(1) : hex);
247
+
248
+ /** @returns a color parsed from an HSLA tuple. */
249
+ export const fromHSLA = (hsla: RGBA): RGBA => {
250
+ let [h, s, l] = hsla;
251
+ const a = hsla[3];
252
+ h /= 360;
253
+ s /= 100;
254
+ l /= 100;
255
+ let r, g, b;
256
+
257
+ if (s === 0)
258
+ r = g = b = l; // achromatic
259
+ else {
260
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
261
+ const p = 2 * l - q;
262
+ r = hueToRgb(p, q, h + 1 / 3);
263
+ g = hueToRgb(p, q, h);
264
+ b = hueToRgb(p, q, h - 1 / 3);
265
+ }
266
+
267
+ return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), a];
268
+ };
269
+
270
+ const hueToRgb = (p: number, q: number, t: number): number => {
271
+ if (t < 0) t += 1;
272
+ if (t > 1) t -= 1;
273
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
274
+ if (t < 1 / 2) return q;
275
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
276
+ return p;
277
+ };
278
+
279
+ const rgbaToHSLA = (rgba: RGBA): HSLA => {
280
+ let [r, g, b] = rgba;
281
+ const a = rgba[3];
282
+ r /= 255;
283
+ g /= 255;
284
+ b /= 255;
285
+
286
+ const max = Math.max(r, g, b);
287
+ const min = Math.min(r, g, b);
288
+ let h: number;
289
+ let s: number;
290
+ let l: number = (max + min) / 2;
291
+
292
+ if (max === min)
293
+ h = s = 0; // achromatic
294
+ else {
295
+ const d = max - min;
296
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
297
+ if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
298
+ else if (max === g) h = (b - r) / d + 2;
299
+ else h = (r - g) / d + 4;
300
+ h /= 6;
301
+ }
302
+
303
+ // Convert hue to degrees
304
+ h *= 360;
305
+ s *= 100;
306
+ l *= 100;
307
+
308
+ return [Math.round(h), Math.round(s), Math.round(l), a];
309
+ };
310
+
311
+ /** A zod schema for a crude color representation. */
312
+ export const crudeZ = z.union([hexZ, rgbaZ, z.string(), rgbZ]);
313
+
314
+ /** The color black. */
315
+ export const BLACK = construct("#000000");
316
+ /** The color white. */
317
+ export const WHITE = construct("#ffffff");
@@ -0,0 +1,13 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ export * from "@/color/color";
11
+ export * from "@/color/gradient";
12
+ export * from "@/color/palette";
13
+ export * from "@/color/transformColorsToHex";
@@ -0,0 +1,78 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { z } from "zod";
11
+
12
+ import {
13
+ aValue,
14
+ bValue,
15
+ type Color,
16
+ construct,
17
+ crudeZ,
18
+ gValue,
19
+ rValue,
20
+ ZERO,
21
+ } from "@/color/color";
22
+
23
+ export const stopZ = z.object({
24
+ key: z.string(),
25
+ color: crudeZ,
26
+ position: z.number(),
27
+ switched: z.boolean().optional(),
28
+ });
29
+
30
+ export type Stop = z.infer<typeof stopZ>;
31
+
32
+ export const gradientZ = z.array(stopZ);
33
+
34
+ export type Gradient = Stop[];
35
+
36
+ export const fromGradient = (gradient: Gradient, position: number): Color => {
37
+ if (gradient.length === 0) return ZERO;
38
+
39
+ gradient = gradient.slice().sort((a, b) => a.position - b.position);
40
+ if (position <= gradient[0].position) return construct(gradient[0].color);
41
+ if (position >= gradient[gradient.length - 1].position)
42
+ return construct(gradient[gradient.length - 1].color);
43
+
44
+ // Find the two stops between which the position lies
45
+ for (let i = 0; i < gradient.length - 1; i++) {
46
+ const start = gradient[i];
47
+ const end = gradient[i + 1];
48
+
49
+ if (position >= start.position && position <= end.position) {
50
+ if (position === start.position) return construct(start.color);
51
+
52
+ if (position === end.position) return construct(end.color);
53
+
54
+ // Interpolate
55
+ const t = (position - start.position) / (end.position - start.position);
56
+
57
+ // Convert colors to RGBA
58
+ const startColor = construct(start.color);
59
+ const endColor = construct(end.color);
60
+
61
+ const r = Math.round(
62
+ rValue(startColor) + t * (rValue(endColor) - rValue(startColor)),
63
+ );
64
+ const g = Math.round(
65
+ gValue(startColor) + t * (gValue(endColor) - gValue(startColor)),
66
+ );
67
+ const b = Math.round(
68
+ bValue(startColor) + t * (bValue(endColor) - bValue(startColor)),
69
+ );
70
+ const a = aValue(startColor) + t * (aValue(endColor) - aValue(startColor)); // Interpolate alpha directly
71
+
72
+ return construct([r, g, b, a]);
73
+ }
74
+ }
75
+
76
+ // If position didn't match any interval, return the last color
77
+ return construct(gradient[gradient.length - 1].color);
78
+ };
@@ -0,0 +1,10 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ export * as color from "@/color/external";
@@ -0,0 +1,28 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { z } from "zod";
11
+
12
+ import { colorZ } from "@/color/color";
13
+
14
+ const styleZ = z.object({
15
+ key: z.string(),
16
+ name: z.string(),
17
+ color: colorZ,
18
+ });
19
+
20
+ export type Style = z.infer<typeof styleZ>;
21
+
22
+ export const paletteZ = z.object({
23
+ key: z.string(),
24
+ name: z.string(),
25
+ swatches: z.array(styleZ),
26
+ });
27
+
28
+ export type Palette = z.infer<typeof paletteZ>;
@@ -0,0 +1,30 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { type Color, colorZ, hex } from "@/color/color";
11
+
12
+ export type ReplaceColorWithHex<T> = T extends Color
13
+ ? string
14
+ : T extends Array<infer U>
15
+ ? Array<ReplaceColorWithHex<U>>
16
+ : T extends object
17
+ ? { [K in keyof T]: ReplaceColorWithHex<T[K]> }
18
+ : T;
19
+
20
+ export const transformColorsToHex = <T>(obj: T): ReplaceColorWithHex<T> => {
21
+ const parsed = colorZ.safeParse(obj);
22
+ if (parsed.success) return hex(parsed.data) as ReplaceColorWithHex<T>;
23
+ if (typeof obj === "object" && obj != null) {
24
+ const newObj: any = Array.isArray(obj) ? [] : {};
25
+ for (const key of Object.keys(obj))
26
+ newObj[key] = transformColorsToHex((obj as any)[key]);
27
+ return newObj as ReplaceColorWithHex<T>;
28
+ }
29
+ return obj as ReplaceColorWithHex<T>;
30
+ };
@@ -11,27 +11,16 @@ import { z } from "zod";
11
11
 
12
12
  import { type bounds } from "@/spatial";
13
13
 
14
- export class Authority extends Number {
15
- static readonly ABSOLUTE = 255;
16
- static readonly MINIMUM = 0;
17
-
18
- static readonly BOUNDS: bounds.Bounds<number> = {
19
- lower: Authority.MINIMUM,
20
- // upper bound is exclusive, so we add 1
21
- upper: Authority.ABSOLUTE + 1,
22
- };
23
-
24
- static readonly z = z.union([
25
- z.instanceof(Authority),
26
- z
27
- .number()
28
- .int()
29
- .min(0)
30
- .max(255)
31
- .transform((n) => new Authority(n)),
32
- z.instanceof(Number).transform((n) => new Authority(n)),
33
- ]);
34
- }
14
+ export const authorityZ = z.number().int().min(0).max(255);
15
+ export type Authority = z.infer<typeof authorityZ>;
16
+
17
+ export const ABSOLUTE_AUTHORITY: Authority = 255;
18
+ export const ZERO_AUTHORITY: Authority = 0;
19
+
20
+ export const AUTHORITY_BOUNDS: bounds.Bounds<Authority> = {
21
+ lower: ZERO_AUTHORITY,
22
+ upper: ABSOLUTE_AUTHORITY + 1,
23
+ };
35
24
 
36
25
  export const subjectZ = z.object({
37
26
  name: z.string(),
@@ -47,7 +36,7 @@ export const stateZ = <T extends z.ZodTypeAny>(r: T) =>
47
36
  z.object({
48
37
  subject: subjectZ,
49
38
  resource: r,
50
- authority: Authority.z,
39
+ authority: authorityZ,
51
40
  });
52
41
 
53
42
  export interface State<R> {
@@ -71,11 +60,21 @@ interface Release<R> {
71
60
  to?: null;
72
61
  }
73
62
 
63
+ export const releaseZ = z.object({
64
+ from: stateZ(z.any()),
65
+ to: z.null(),
66
+ });
67
+
74
68
  interface Acquire<R> {
75
69
  from?: null;
76
70
  to: State<R>;
77
71
  }
78
72
 
73
+ export const acquireZ = z.object({
74
+ from: z.null(),
75
+ to: stateZ(z.any()),
76
+ });
77
+
79
78
  export type Transfer<R> =
80
79
  | {
81
80
  from: State<R>;
@@ -83,3 +82,12 @@ export type Transfer<R> =
83
82
  }
84
83
  | Release<R>
85
84
  | Acquire<R>;
85
+
86
+ export const transferZ = z.union([
87
+ releaseZ,
88
+ acquireZ,
89
+ z.object({
90
+ from: stateZ(z.any()),
91
+ to: stateZ(z.any()),
92
+ }),
93
+ ]);
package/src/deep/merge.ts CHANGED
@@ -40,7 +40,7 @@ export const override = <T>(base: T, ...overrides: Array<Partial<T>>): T => {
40
40
  export const overrideValidItems = <A, B>(
41
41
  base: A,
42
42
  override: B,
43
- schema: z.ZodType<A, any, any>,
43
+ schema: z.ZodType<A>,
44
44
  ): A => {
45
45
  const mergeValidFields = (
46
46
  baseObj: any,
@@ -50,7 +50,7 @@ export const overrideValidItems = <A, B>(
50
50
  // Iterate over each property in the override object
51
51
  for (const key in overrideObj) {
52
52
  const overrideValue = overrideObj[key];
53
- const shape = getSchemaShape(currentSchema);
53
+ const shape = currentSchema.shape;
54
54
  if (shape?.[key]) {
55
55
  const result = shape[key].safeParse(overrideValue);
56
56
  // Check if parsing succeeded
@@ -71,9 +71,3 @@ export const overrideValidItems = <A, B>(
71
71
 
72
72
  return mergeValidFields({ ...base }, override, schema);
73
73
  };
74
-
75
- const getSchemaShape = (schema: z.ZodType<any, any, any>) => {
76
- if (schema._def?.typeName === "ZodEffects") return getSchemaShape(schema._def.schema);
77
- /// @ts-expect-error - shape is not defined on zod effects
78
- return schema?.shape;
79
- };