@synnaxlabs/x 0.47.0 → 0.49.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 (244) hide show
  1. package/.turbo/turbo-build.log +7 -105
  2. package/dist/src/deep/join.d.ts.map +1 -0
  3. package/dist/src/deep/path.d.ts +1 -1
  4. package/dist/src/deep/path.d.ts.map +1 -1
  5. package/dist/src/destructor/destructor.d.ts +7 -0
  6. package/dist/src/destructor/destructor.d.ts.map +1 -0
  7. package/dist/src/destructor/index.d.ts +2 -0
  8. package/dist/src/destructor/index.d.ts.map +1 -0
  9. package/dist/src/index.d.ts +5 -10
  10. package/dist/src/index.d.ts.map +1 -1
  11. package/dist/src/math/constants.d.ts +1 -0
  12. package/dist/src/math/constants.d.ts.map +1 -1
  13. package/dist/src/migrate/migrate.d.ts +4 -4
  14. package/dist/src/migrate/migrate.d.ts.map +1 -1
  15. package/dist/src/narrow/index.d.ts +2 -0
  16. package/dist/src/narrow/index.d.ts.map +1 -0
  17. package/dist/src/narrow/narrow.d.ts +4 -0
  18. package/dist/src/narrow/narrow.d.ts.map +1 -0
  19. package/dist/src/narrow/narrow.spec.d.ts +2 -0
  20. package/dist/src/narrow/narrow.spec.d.ts.map +1 -0
  21. package/dist/src/numeric/numeric.d.ts +0 -1
  22. package/dist/src/numeric/numeric.d.ts.map +1 -1
  23. package/dist/src/observe/observe.d.ts +4 -4
  24. package/dist/src/observe/observe.d.ts.map +1 -1
  25. package/dist/src/optional/index.d.ts +2 -0
  26. package/dist/src/optional/index.d.ts.map +1 -0
  27. package/dist/src/optional/optional.d.ts.map +1 -0
  28. package/dist/src/scheduler/index.d.ts +2 -0
  29. package/dist/src/scheduler/index.d.ts.map +1 -0
  30. package/dist/src/{flush.d.ts → scheduler/scheduler.d.ts} +1 -1
  31. package/dist/src/scheduler/scheduler.d.ts.map +1 -0
  32. package/dist/src/scheduler/scheduler.spec.d.ts +2 -0
  33. package/dist/src/scheduler/scheduler.spec.d.ts.map +1 -0
  34. package/dist/src/shallow/copy.d.ts +2 -0
  35. package/dist/src/shallow/copy.d.ts.map +1 -0
  36. package/dist/src/shallow/copy.spec.d.ts +2 -0
  37. package/dist/src/shallow/copy.spec.d.ts.map +1 -0
  38. package/dist/src/shallow/index.d.ts +2 -0
  39. package/dist/src/shallow/index.d.ts.map +1 -0
  40. package/dist/src/spatial/base.d.ts +5 -38
  41. package/dist/src/spatial/base.d.ts.map +1 -1
  42. package/dist/src/spatial/direction/direction.d.ts +2 -1
  43. package/dist/src/spatial/direction/direction.d.ts.map +1 -1
  44. package/dist/src/spatial/external.d.ts +2 -1
  45. package/dist/src/spatial/external.d.ts.map +1 -1
  46. package/dist/src/spatial/location/location.d.ts +2 -2
  47. package/dist/src/spatial/location/location.d.ts.map +1 -1
  48. package/dist/src/spatial/sticky/index.d.ts +2 -0
  49. package/dist/src/spatial/sticky/index.d.ts.map +1 -0
  50. package/dist/src/spatial/sticky/sticky.d.ts +74 -0
  51. package/dist/src/spatial/sticky/sticky.d.ts.map +1 -0
  52. package/dist/src/spatial/sticky/sticky.spec.d.ts +2 -0
  53. package/dist/src/spatial/sticky/sticky.spec.d.ts.map +1 -0
  54. package/dist/src/spatial/xy/xy.d.ts +10 -2
  55. package/dist/src/spatial/xy/xy.d.ts.map +1 -1
  56. package/dist/src/status/status.d.ts +2 -2
  57. package/dist/src/status/status.d.ts.map +1 -1
  58. package/dist/src/telem/series.d.ts +4 -4
  59. package/dist/src/telem/series.d.ts.map +1 -1
  60. package/dist/src/telem/telem.d.ts +14 -1
  61. package/dist/src/telem/telem.d.ts.map +1 -1
  62. package/dist/src/types/index.d.ts +2 -0
  63. package/dist/src/types/index.d.ts.map +1 -0
  64. package/dist/src/zod/external.d.ts +1 -0
  65. package/dist/src/zod/external.d.ts.map +1 -1
  66. package/dist/src/zod/schemas.d.ts +3 -0
  67. package/dist/src/zod/schemas.d.ts.map +1 -0
  68. package/dist/src/zod/schemas.spec.d.ts +2 -0
  69. package/dist/src/zod/schemas.spec.d.ts.map +1 -0
  70. package/dist/x.cjs +12 -0
  71. package/dist/x.js +5521 -0
  72. package/package.json +12 -150
  73. package/src/binary/codec.ts +2 -2
  74. package/src/deep/merge.ts +3 -3
  75. package/src/deep/path.ts +1 -1
  76. package/src/{destructor.ts → destructor/destructor.ts} +1 -1
  77. package/src/{invert.ts → destructor/index.ts} +1 -1
  78. package/src/index.ts +5 -10
  79. package/src/math/constants.ts +1 -0
  80. package/src/migrate/migrate.spec.ts +238 -0
  81. package/src/migrate/migrate.ts +67 -7
  82. package/src/{mock → narrow}/index.ts +1 -1
  83. package/src/narrow/narrow.spec.ts +70 -0
  84. package/src/{identity.ts → narrow/narrow.ts} +6 -0
  85. package/src/numeric/numeric.ts +0 -5
  86. package/src/observe/observe.ts +4 -4
  87. package/src/{replace.ts → optional/index.ts} +1 -1
  88. package/src/scheduler/index.ts +10 -0
  89. package/src/scheduler/scheduler.spec.ts +82 -0
  90. package/src/shallow/copy.spec.ts +141 -0
  91. package/src/{shallowCopy.ts → shallow/copy.ts} +1 -1
  92. package/src/shallow/index.ts +10 -0
  93. package/src/spatial/base.ts +6 -15
  94. package/src/spatial/direction/direction.ts +2 -0
  95. package/src/spatial/external.ts +2 -1
  96. package/src/spatial/location/location.spec.ts +16 -0
  97. package/src/spatial/location/location.ts +7 -7
  98. package/src/spatial/sticky/index.ts +10 -0
  99. package/src/spatial/sticky/sticky.spec.ts +584 -0
  100. package/src/spatial/sticky/sticky.ts +98 -0
  101. package/src/spatial/xy/xy.spec.ts +55 -0
  102. package/src/spatial/xy/xy.ts +27 -2
  103. package/src/status/status.ts +5 -5
  104. package/src/telem/series.ts +5 -6
  105. package/src/telem/telem.spec.ts +87 -28
  106. package/src/telem/telem.ts +49 -9
  107. package/src/{clamp → types}/index.ts +1 -1
  108. package/src/zod/external.ts +1 -0
  109. package/src/zod/schemas.spec.ts +51 -0
  110. package/src/zod/schemas.ts +14 -0
  111. package/tsconfig.tsbuildinfo +1 -1
  112. package/vite.config.ts +1 -36
  113. package/dist/array.cjs +0 -1
  114. package/dist/array.js +0 -4
  115. package/dist/base-DRybODwJ.js +0 -42
  116. package/dist/base-KIBsp6TI.cjs +0 -1
  117. package/dist/binary.cjs +0 -1
  118. package/dist/binary.js +0 -4
  119. package/dist/bounds-4BWKPqaP.js +0 -183
  120. package/dist/bounds-C2TKFgVk.cjs +0 -1
  121. package/dist/bounds.cjs +0 -1
  122. package/dist/bounds.js +0 -4
  123. package/dist/box-BXWXSkKu.js +0 -203
  124. package/dist/box-rH3ggwXk.cjs +0 -1
  125. package/dist/box.cjs +0 -1
  126. package/dist/box.js +0 -4
  127. package/dist/caseconv.cjs +0 -1
  128. package/dist/caseconv.js +0 -4
  129. package/dist/change-C-YELKx6.cjs +0 -1
  130. package/dist/change-DLl6DccR.js +0 -12
  131. package/dist/change.cjs +0 -1
  132. package/dist/change.js +0 -4
  133. package/dist/compare-Bnx9CdjS.js +0 -119
  134. package/dist/compare-GPoFaKRW.cjs +0 -1
  135. package/dist/compare.cjs +0 -1
  136. package/dist/compare.js +0 -36
  137. package/dist/debounce.cjs +0 -1
  138. package/dist/debounce.js +0 -17
  139. package/dist/deep.cjs +0 -1
  140. package/dist/deep.js +0 -247
  141. package/dist/destructor.cjs +0 -1
  142. package/dist/destructor.js +0 -1
  143. package/dist/dimensions-Cg5Owbwn.cjs +0 -1
  144. package/dist/dimensions-DC0uLPwn.js +0 -43
  145. package/dist/dimensions.cjs +0 -1
  146. package/dist/dimensions.js +0 -4
  147. package/dist/direction-C_b4tfRN.js +0 -19
  148. package/dist/direction-DqQB9M37.cjs +0 -1
  149. package/dist/direction.cjs +0 -1
  150. package/dist/direction.js +0 -4
  151. package/dist/external-2YWy569j.js +0 -23
  152. package/dist/external-B6edOwoQ.cjs +0 -1
  153. package/dist/external-B80i4ymZ.js +0 -29
  154. package/dist/external-B9AAGv50.cjs +0 -1
  155. package/dist/external-BYuXBYJh.js +0 -40
  156. package/dist/external-BxmTQZ6m.cjs +0 -1
  157. package/dist/external-DLiGrXn7.cjs +0 -1
  158. package/dist/external-Du5qzfYv.js +0 -35
  159. package/dist/get-CtJEJIC_.js +0 -82
  160. package/dist/get-D2VRwUw4.cjs +0 -1
  161. package/dist/identity.cjs +0 -1
  162. package/dist/identity.js +0 -4
  163. package/dist/index-Bfvg0v-N.cjs +0 -3
  164. package/dist/index-Bv029kh3.js +0 -19
  165. package/dist/index-CqisIWWC.cjs +0 -1
  166. package/dist/index-CyNZHQFw.cjs +0 -1
  167. package/dist/index-qmkoZBNO.js +0 -57
  168. package/dist/index-yz34Wc2p.js +0 -92
  169. package/dist/index.cjs +0 -5
  170. package/dist/index.js +0 -985
  171. package/dist/kv.cjs +0 -1
  172. package/dist/kv.js +0 -4
  173. package/dist/link.cjs +0 -1
  174. package/dist/link.js +0 -10
  175. package/dist/location-0qDBiCqP.cjs +0 -1
  176. package/dist/location-BIzpxczO.js +0 -95
  177. package/dist/location.cjs +0 -1
  178. package/dist/location.js +0 -4
  179. package/dist/observe.cjs +0 -1
  180. package/dist/observe.js +0 -48
  181. package/dist/record-BwjIgrpU.cjs +0 -1
  182. package/dist/record-tSFQKmdG.js +0 -19
  183. package/dist/record.cjs +0 -1
  184. package/dist/record.js +0 -4
  185. package/dist/runtime.cjs +0 -1
  186. package/dist/runtime.js +0 -4
  187. package/dist/scale-BXy1w8R_.cjs +0 -1
  188. package/dist/scale-DJCMZbfU.js +0 -228
  189. package/dist/scale.cjs +0 -1
  190. package/dist/scale.js +0 -4
  191. package/dist/series-Bh0pHoUF.cjs +0 -6
  192. package/dist/series-Cf8rT2IX.js +0 -2810
  193. package/dist/spatial-DnsaOypA.js +0 -11
  194. package/dist/spatial-DrxzaD5U.cjs +0 -1
  195. package/dist/spatial.cjs +0 -1
  196. package/dist/spatial.js +0 -18
  197. package/dist/src/clamp/index.d.ts +0 -2
  198. package/dist/src/clamp/index.d.ts.map +0 -1
  199. package/dist/src/destructor.d.ts +0 -7
  200. package/dist/src/destructor.d.ts.map +0 -1
  201. package/dist/src/flush.d.ts.map +0 -1
  202. package/dist/src/identity.d.ts +0 -3
  203. package/dist/src/identity.d.ts.map +0 -1
  204. package/dist/src/invert.d.ts +0 -2
  205. package/dist/src/invert.d.ts.map +0 -1
  206. package/dist/src/join.d.ts.map +0 -1
  207. package/dist/src/mock/index.d.ts +0 -2
  208. package/dist/src/mock/index.d.ts.map +0 -1
  209. package/dist/src/optional.d.ts.map +0 -1
  210. package/dist/src/renderable.d.ts +0 -5
  211. package/dist/src/renderable.d.ts.map +0 -1
  212. package/dist/src/replace.d.ts +0 -2
  213. package/dist/src/replace.d.ts.map +0 -1
  214. package/dist/src/shallowCopy.d.ts +0 -2
  215. package/dist/src/shallowCopy.d.ts.map +0 -1
  216. package/dist/src/telem/generate.d.ts +0 -3
  217. package/dist/src/telem/generate.d.ts.map +0 -1
  218. package/dist/src/transform.d.ts +0 -6
  219. package/dist/src/transform.d.ts.map +0 -1
  220. package/dist/src/undefined.d.ts +0 -2
  221. package/dist/src/undefined.d.ts.map +0 -1
  222. package/dist/telem.cjs +0 -1
  223. package/dist/telem.js +0 -18
  224. package/dist/unique.cjs +0 -1
  225. package/dist/unique.js +0 -4
  226. package/dist/url.cjs +0 -1
  227. package/dist/url.js +0 -51
  228. package/dist/worker.cjs +0 -1
  229. package/dist/worker.js +0 -43
  230. package/dist/xy-C-MUIjVs.cjs +0 -1
  231. package/dist/xy-DnrCAZaw.js +0 -154
  232. package/dist/xy.cjs +0 -1
  233. package/dist/xy.js +0 -4
  234. package/dist/zod.cjs +0 -1
  235. package/dist/zod.js +0 -4
  236. package/src/renderable.ts +0 -20
  237. package/src/telem/generate.ts +0 -17
  238. package/src/transform.ts +0 -17
  239. package/src/undefined.ts +0 -14
  240. /package/dist/src/{join.d.ts → deep/join.d.ts} +0 -0
  241. /package/dist/src/{optional.d.ts → optional/optional.d.ts} +0 -0
  242. /package/src/{join.ts → deep/join.ts} +0 -0
  243. /package/src/{optional.ts → optional/optional.ts} +0 -0
  244. /package/src/{flush.ts → scheduler/scheduler.ts} +0 -0
@@ -268,4 +268,59 @@ describe("XY", () => {
268
268
  expect(result).toEqual({ x: 7, y: 17 });
269
269
  });
270
270
  });
271
+
272
+ describe("set", () => {
273
+ it("should set x coordinate to target value", () => {
274
+ expect(xy.set([5, 10], "x", 20)).toEqual({ x: 20, y: 10 });
275
+ });
276
+
277
+ it("should set y coordinate to target value", () => {
278
+ expect(xy.set([5, 10], "y", 20)).toEqual({ x: 5, y: 20 });
279
+ });
280
+
281
+ it("should accept crude directions like locations", () => {
282
+ expect(xy.set([5, 10], "left", 20)).toEqual({ x: 20, y: 10 });
283
+ expect(xy.set([5, 10], "right", 20)).toEqual({ x: 20, y: 10 });
284
+ expect(xy.set([5, 10], "top", 20)).toEqual({ x: 5, y: 20 });
285
+ expect(xy.set([5, 10], "bottom", 20)).toEqual({ x: 5, y: 20 });
286
+ });
287
+
288
+ it("should work with different coordinate input formats", () => {
289
+ expect(xy.set({ width: 5, height: 10 }, "x", 15)).toEqual({ x: 15, y: 10 });
290
+ expect(xy.set({ x: 3, y: 4 }, "y", 8)).toEqual({ x: 3, y: 8 });
291
+ });
292
+ });
293
+
294
+ describe("rotate", () => {
295
+ it("should rotate a point 90 degrees clockwise around the origin", () => {
296
+ const point = { x: 1, y: 0 };
297
+ const center = { x: 0, y: 0 };
298
+ const result = xy.rotate(point, center, "clockwise");
299
+ expect(result.x).toBeCloseTo(0);
300
+ expect(result.y).toBeCloseTo(1);
301
+ });
302
+
303
+ it("should rotate a point 90 degrees counterclockwise around the origin", () => {
304
+ const point = { x: 1, y: 0 };
305
+ const center = { x: 0, y: 0 };
306
+ const result = xy.rotate(point, center, "counterclockwise");
307
+ expect(result.x).toBeCloseTo(0);
308
+ expect(result.y).toBeCloseTo(-1);
309
+ });
310
+
311
+ it("should rotate a point around a non-origin center", () => {
312
+ const point = { x: 2, y: 1 };
313
+ const center = { x: 1, y: 1 };
314
+ const result = xy.rotate(point, center, "clockwise");
315
+ expect(result.x).toBeCloseTo(1);
316
+ expect(result.y).toBeCloseTo(2);
317
+ });
318
+
319
+ it("should return the same point when rotating around itself", () => {
320
+ const point = { x: 5, y: 5 };
321
+ const result = xy.rotate(point, point, "clockwise");
322
+ expect(result.x).toBeCloseTo(5);
323
+ expect(result.y).toBeCloseTo(5);
324
+ });
325
+ });
271
326
  });
@@ -10,8 +10,10 @@
10
10
  import { z } from "zod";
11
11
 
12
12
  import {
13
+ type AngularDirection,
13
14
  type ClientXY,
14
15
  clientXY,
16
+ type CrudeDirection,
15
17
  dimensions,
16
18
  type Direction,
17
19
  type NumberCouple,
@@ -20,6 +22,7 @@ import {
20
22
  type XY,
21
23
  xy,
22
24
  } from "@/spatial/base";
25
+ import { direction as dir } from "@/spatial/direction";
23
26
  import { type location } from "@/spatial/location";
24
27
 
25
28
  export { type ClientXY as Client, clientXY, type XY, xy };
@@ -139,9 +142,10 @@ export const translate: Translate = (a, b, v, ...cb): XY => {
139
142
  * @returns the given coordinate the given direction set to the given value.
140
143
  * @example set({ x: 1, y: 2 }, "x", 3) // { x: 3, y: 2 }
141
144
  */
142
- export const set = (c: Crude, direction: Direction, value: number): XY => {
145
+ export const set = (c: Crude, direction: CrudeDirection, value: number): XY => {
143
146
  const xy = construct(c);
144
- if (direction === "x") return { x: value, y: xy.y };
147
+ const d = dir.construct(direction);
148
+ if (d === "x") return { x: value, y: xy.y };
145
149
  return { x: xy.x, y: value };
146
150
  };
147
151
 
@@ -328,3 +332,24 @@ export const reciprocal = (a: Crude): XY => {
328
332
  const xy = construct(a);
329
333
  return { x: 1 / xy.x, y: 1 / xy.y };
330
334
  };
335
+
336
+ /**
337
+ * Rotates a point 90 degrees around a center point.
338
+ * @param point - The point to rotate.
339
+ * @param center - The center point to rotate around.
340
+ * @param dir - The direction to rotate (clockwise or counterclockwise).
341
+ * @returns The rotated point.
342
+ */
343
+ export const rotate = (point: Crude, center: Crude, dir: AngularDirection): XY => {
344
+ const p = construct(point);
345
+ const c = construct(center);
346
+ const angle = dir === "clockwise" ? Math.PI / 2 : -Math.PI / 2;
347
+ const relativeX = p.x - c.x;
348
+ const relativeY = p.y - c.y;
349
+ const rotatedX = relativeX * Math.cos(angle) - relativeY * Math.sin(angle);
350
+ const rotatedY = relativeX * Math.sin(angle) + relativeY * Math.cos(angle);
351
+ return {
352
+ x: rotatedX + c.x,
353
+ y: rotatedY + c.y,
354
+ };
355
+ };
@@ -12,7 +12,7 @@ import { z } from "zod";
12
12
  import { array } from "@/array";
13
13
  import { id } from "@/id";
14
14
  import { label } from "@/label";
15
- import { type Optional } from "@/optional";
15
+ import { type optional } from "@/optional";
16
16
  import { TimeStamp } from "@/telem";
17
17
 
18
18
  export const variantZ = z.enum([
@@ -75,10 +75,10 @@ type Base<V extends Variant> = {
75
75
  export type Status<DetailsSchema = z.ZodNever, V extends Variant = Variant> = Base<V> &
76
76
  ([DetailsSchema] extends [z.ZodNever] ? {} : { details: z.output<DetailsSchema> });
77
77
 
78
- export type Crude<DetailsSchema = z.ZodNever, V extends Variant = Variant> = Optional<
79
- Base<V>,
80
- "key" | "time" | "name"
81
- > &
78
+ export type Crude<
79
+ DetailsSchema = z.ZodNever,
80
+ V extends Variant = Variant,
81
+ > = optional.Optional<Base<V>, "key" | "time" | "name"> &
82
82
  ([DetailsSchema] extends [z.ZodNever] ? {} : { details: z.output<DetailsSchema> });
83
83
 
84
84
  export const exceptionDetailsSchema = z.object({
@@ -14,7 +14,7 @@ import { caseconv } from "@/caseconv";
14
14
  import { compare } from "@/compare";
15
15
  import { id } from "@/id";
16
16
  import { instance } from "@/instance";
17
- import { type math } from "@/math";
17
+ import { math } from "@/math";
18
18
  import { bounds } from "@/spatial";
19
19
  import {
20
20
  type GLBufferController,
@@ -22,7 +22,6 @@ import {
22
22
  glBufferUsageZ,
23
23
  } from "@/telem/gl";
24
24
  import {
25
- addSamples,
26
25
  convertDataType,
27
26
  type CrudeDataType,
28
27
  type CrudeTimeStamp,
@@ -200,7 +199,7 @@ export class Series<T extends TelemValue = TelemValue>
200
199
  z.instanceof(ArrayBuffer),
201
200
  z.instanceof(Uint8Array),
202
201
  ]),
203
- glBufferUsage: glBufferUsageZ.optional().default("static").optional(),
202
+ glBufferUsage: glBufferUsageZ.default("static").optional(),
204
203
  });
205
204
 
206
205
  /**
@@ -635,7 +634,7 @@ export class Series<T extends TelemValue = TelemValue>
635
634
  throw new Error("cannot calculate maximum on a variable length data type");
636
635
  if (this.writePos === 0) return -Infinity;
637
636
  this.cachedMax ??= this.calcRawMax();
638
- return addSamples(this.cachedMax, this.sampleOffset);
637
+ return math.add(this.cachedMax, this.sampleOffset);
639
638
  }
640
639
 
641
640
  private calcRawMin(): math.Numeric {
@@ -661,7 +660,7 @@ export class Series<T extends TelemValue = TelemValue>
661
660
  throw new Error("cannot calculate minimum on a variable length data type");
662
661
  if (this.writePos === 0) return Infinity;
663
662
  this.cachedMin ??= this.calcRawMin();
664
- return addSamples(this.cachedMin, this.sampleOffset);
663
+ return math.add(this.cachedMin, this.sampleOffset);
665
664
  }
666
665
 
667
666
  /** @returns the bounds of the series. */
@@ -726,7 +725,7 @@ export class Series<T extends TelemValue = TelemValue>
726
725
  if (required === true) throw new Error(`[series] - no value at index ${index}`);
727
726
  return undefined;
728
727
  }
729
- return addSamples(v, this.sampleOffset) as T;
728
+ return math.add(v, this.sampleOffset) as T;
730
729
  }
731
730
 
732
731
  private atUUID(index: number, required: boolean): string | undefined {
@@ -11,7 +11,6 @@ import { describe, expect, it, test } from "vitest";
11
11
 
12
12
  import { binary } from "@/binary";
13
13
  import {
14
- addSamples,
15
14
  type CrudeDataType,
16
15
  DataType,
17
16
  Density,
@@ -98,6 +97,55 @@ describe("TimeStamp", () => {
98
97
  ).toBe(true);
99
98
  });
100
99
 
100
+ describe("datetime-local format parsing", () => {
101
+ test("should parse as UTC by default", () => {
102
+ const ts = new TimeStamp("2025-11-03T17:44:45.500");
103
+ const utcTS = new TimeStamp("2025-11-03T17:44:45.500Z");
104
+
105
+ expect(ts.valueOf()).toEqual(utcTS.valueOf());
106
+ });
107
+
108
+ test("should handle 1-digit milliseconds with default UTC", () => {
109
+ const ts = new TimeStamp("2025-11-03T17:44:45.5");
110
+ expect(ts.millisecond).toBe(500);
111
+ });
112
+
113
+ test("should handle 1-digit milliseconds with local", () => {
114
+ const ts = new TimeStamp("2025-11-03T17:44:45.5", "local");
115
+ expect(ts.millisecond).toBe(500);
116
+ });
117
+
118
+ test("should handle 2-digit milliseconds", () => {
119
+ const ts = new TimeStamp("2025-11-03T17:44:45.50");
120
+ expect(ts.millisecond).toBe(500);
121
+ });
122
+
123
+ test("should handle 3-digit milliseconds", () => {
124
+ const ts = new TimeStamp("2025-11-03T17:44:45.809");
125
+ expect(ts.millisecond).toBe(809);
126
+ });
127
+
128
+ test("should handle 810 milliseconds", () => {
129
+ const ts = new TimeStamp("2025-11-03T17:44:45.810");
130
+ expect(ts.millisecond).toBeGreaterThanOrEqual(809);
131
+ expect(ts.millisecond).toBeLessThanOrEqual(810);
132
+ });
133
+
134
+ test("should handle datetime without milliseconds", () => {
135
+ const ts = new TimeStamp("2025-11-03T17:44:45");
136
+ expect(ts.millisecond).toBe(0);
137
+ });
138
+
139
+ test("should round-trip when using local tzInfo", () => {
140
+ const input = "2025-11-03T17:44:45.809";
141
+ const ts1 = new TimeStamp(input, "local");
142
+ const output = ts1.toString("ISO", "local").slice(0, -1);
143
+ const ts2 = new TimeStamp(output, "local");
144
+
145
+ expect(ts1.valueOf()).toEqual(ts2.valueOf());
146
+ });
147
+ });
148
+
101
149
  test("construct from date", () => {
102
150
  const ts = new TimeStamp([2021, 1, 1], "UTC");
103
151
  expect(ts.date().getUTCFullYear()).toEqual(2021);
@@ -734,6 +782,44 @@ describe("TimeStamp", () => {
734
782
  });
735
783
  });
736
784
  });
785
+
786
+ describe("formatBySpan", () => {
787
+ test("should return 'shortDate' for spans >= 30 days", () => {
788
+ const ts = new TimeStamp([2022, 12, 15], "UTC");
789
+ const span = TimeSpan.days(30);
790
+ expect(ts.formatBySpan(span)).toBe("shortDate");
791
+ });
792
+
793
+ test("should return 'dateTime' for spans >= 1 day", () => {
794
+ const ts = new TimeStamp([2022, 12, 15], "UTC");
795
+ const span = TimeSpan.days(1);
796
+ expect(ts.formatBySpan(span)).toBe("dateTime");
797
+ });
798
+
799
+ test("should return 'time' for spans >= 1 hour", () => {
800
+ const ts = new TimeStamp([2022, 12, 15], "UTC");
801
+ const span = TimeSpan.hours(1);
802
+ expect(ts.formatBySpan(span)).toBe("time");
803
+ });
804
+
805
+ test("should return 'preciseTime' for spans >= 1 second", () => {
806
+ const ts = new TimeStamp([2022, 12, 15], "UTC");
807
+ const span = TimeSpan.seconds(1);
808
+ expect(ts.formatBySpan(span)).toBe("preciseTime");
809
+ });
810
+
811
+ test("should return 'ISOTime' for spans < 1 second", () => {
812
+ const ts = new TimeStamp([2022, 12, 15], "UTC");
813
+ const span = TimeSpan.milliseconds(500);
814
+ expect(ts.formatBySpan(span)).toBe("ISOTime");
815
+ });
816
+
817
+ test("should work with very small spans", () => {
818
+ const ts = new TimeStamp([2022, 12, 15], "UTC");
819
+ const span = TimeSpan.microseconds(100);
820
+ expect(ts.formatBySpan(span)).toBe("ISOTime");
821
+ });
822
+ });
737
823
  });
738
824
 
739
825
  describe("TimeSpan", () => {
@@ -1935,30 +2021,3 @@ describe("Size", () => {
1935
2021
  });
1936
2022
  });
1937
2023
  });
1938
-
1939
- describe("addSamples", () => {
1940
- test("adds two numbers", () => {
1941
- expect(addSamples(1, 2)).toBe(3);
1942
- expect(addSamples(1.5, 2.5)).toBe(4);
1943
- expect(addSamples(-1, 1)).toBe(0);
1944
- });
1945
-
1946
- test("adds two bigints", () => {
1947
- expect(addSamples(1n, 2n)).toBe(3n);
1948
- expect(addSamples(-1n, 1n)).toBe(0n);
1949
- expect(addSamples(9007199254740991n, 1n)).toBe(9007199254740992n);
1950
- });
1951
-
1952
- test("handles mixed numeric types", () => {
1953
- expect(addSamples(1, 2n)).toBe(3);
1954
- expect(addSamples(2n, 1)).toBe(3);
1955
- expect(addSamples(1.5, 2n)).toBe(3.5);
1956
- expect(addSamples(2n, 1.5)).toBe(3.5);
1957
- });
1958
-
1959
- test("handles edge cases", () => {
1960
- expect(addSamples(0, 0)).toBe(0);
1961
- expect(addSamples(Number.MAX_SAFE_INTEGER, 1)).toBe(Number.MAX_SAFE_INTEGER + 1);
1962
- expect(addSamples(Number.MIN_SAFE_INTEGER, -1)).toBe(Number.MIN_SAFE_INTEGER - 1);
1963
- });
1964
- });
@@ -154,6 +154,32 @@ export class TimeStamp
154
154
  private static parseDateTimeString(str: string, tzInfo: TZInfo = "UTC"): bigint {
155
155
  if (!str.includes("/") && !str.includes("-"))
156
156
  return TimeStamp.parseTimeString(str, tzInfo);
157
+
158
+ const isDateTimeLocal =
159
+ str.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?$/) != null;
160
+
161
+ if (isDateTimeLocal) {
162
+ let datePart = str;
163
+ let ms = 0;
164
+
165
+ if (str.includes(".")) {
166
+ const parts = str.split(".");
167
+ datePart = parts[0];
168
+ const msPart = parts[1] || "0";
169
+ ms = parseInt(msPart.padEnd(3, "0").slice(0, 3));
170
+ }
171
+
172
+ const d =
173
+ tzInfo === "local"
174
+ ? new Date(datePart.replace("T", " "))
175
+ : new Date(`${datePart}Z`);
176
+
177
+ const baseBigInt = BigInt(d.getTime()) * TimeStamp.MILLISECOND.valueOf();
178
+ const msBigInt = BigInt(ms) * TimeStamp.MILLISECOND.valueOf();
179
+
180
+ return baseBigInt + msBigInt;
181
+ }
182
+
157
183
  const d = new Date(str);
158
184
  // Essential to note that this makes the date midnight in UTC! Not local!
159
185
  // As a result, we need to add the tzInfo offset back in.
@@ -560,6 +586,28 @@ export class TimeStamp
560
586
  return this.sub(this.remainder(span));
561
587
  }
562
588
 
589
+ /**
590
+ * Determines the appropriate string format based on the span magnitude.
591
+ *
592
+ * @param span - The span that provides context for format selection
593
+ * @returns The appropriate TimeStampStringFormat
594
+ *
595
+ * Rules:
596
+ * - For spans >= 30 days: "shortDate" (e.g., "Nov 5")
597
+ * - For spans >= 1 day: "dateTime" (e.g., "Nov 5 14:23:45")
598
+ * - For spans >= 1 hour: "time" (e.g., "14:23:45")
599
+ * - For spans >= 1 second: "preciseTime" (e.g., "14:23:45.123")
600
+ * - For spans < 1 second: "ISOTime" (full precision time)
601
+ */
602
+ formatBySpan(span: TimeSpan): TimeStampStringFormat {
603
+ if (span.greaterThanOrEqual(TimeSpan.days(30))) return "shortDate";
604
+ if (span.greaterThanOrEqual(TimeSpan.DAY)) return "dateTime";
605
+ if (span.greaterThanOrEqual(TimeSpan.HOUR)) return "time";
606
+ if (span.greaterThanOrEqual(TimeSpan.SECOND)) return "preciseTime";
607
+
608
+ return "ISOTime";
609
+ }
610
+
563
611
  /**
564
612
  * @returns A new TimeStamp representing the current time in UTC. It's important to
565
613
  * note that this TimeStamp is only accurate to the millisecond level (that's the best
@@ -2250,13 +2298,5 @@ export const convertDataType = (
2250
2298
  if (source.usesBigInt && !target.usesBigInt) return Number(value) - Number(offset);
2251
2299
  if (!source.usesBigInt && target.usesBigInt)
2252
2300
  return BigInt(value.valueOf()) - BigInt(offset.valueOf());
2253
- return addSamples(value, -offset);
2254
- };
2255
-
2256
- export const addSamples = (a: math.Numeric, b: math.Numeric): math.Numeric => {
2257
- if (b == 0) return a;
2258
- if (a == 0) return b;
2259
- if (typeof a === "bigint" && typeof b === "bigint") return a + b;
2260
- if (typeof a === "number" && typeof b === "number") return a + b;
2261
- return Number(a) + Number(b);
2301
+ return math.sub(value, offset);
2262
2302
  };
@@ -7,4 +7,4 @@
7
7
  // License, use of this software will be governed by the Apache License, Version 2.0,
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
- export * as clamp from "@/clamp/clamp";
10
+ export type NotUndefined = {} | null;
@@ -8,5 +8,6 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  export * from "@/zod/nullToUndefined";
11
+ export * from "@/zod/schemas";
11
12
  export * from "@/zod/toArray";
12
13
  export * from "@/zod/util";
@@ -0,0 +1,51 @@
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 { describe, expect, it } from "vitest";
11
+
12
+ import { zod } from "@/zod";
13
+
14
+ describe("zod", () => {
15
+ describe("uint12", () => {
16
+ describe("success", () => {
17
+ interface Spec {
18
+ input: number;
19
+ expected: number;
20
+ }
21
+ const specs: Spec[] = [
22
+ { input: 0, expected: 0 },
23
+ { input: 1, expected: 1 },
24
+ { input: 2 ** 12 - 1, expected: 2 ** 12 - 1 },
25
+ ];
26
+ specs.forEach(({ input, expected }) => {
27
+ it(`should parse ${input} as ${expected}`, () => {
28
+ const result = zod.uint12.parse(input);
29
+ expect(result).toBe(expected);
30
+ });
31
+ });
32
+ });
33
+ describe("failure", () => {
34
+ interface Spec {
35
+ input: unknown;
36
+ expected: string;
37
+ }
38
+ const specs: Spec[] = [
39
+ { input: -1, expected: "Too small: expected number to be >=0" },
40
+ { input: 2 ** 12, expected: "Too big: expected number to be <=4095" },
41
+ { input: "1", expected: "Invalid input: expected number, received string" },
42
+ { input: 1.5, expected: "Invalid input: expected int, received number" },
43
+ ];
44
+ specs.forEach(({ input, expected }) => {
45
+ it(`should throw for ${String(input)}`, () => {
46
+ expect(() => zod.uint12.parse(input)).toThrow(expected);
47
+ });
48
+ });
49
+ });
50
+ });
51
+ });
@@ -0,0 +1,14 @@
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 { MAX_UINT12 } from "@/math/constants";
13
+
14
+ export const uint12 = z.int().min(0).max(MAX_UINT12);