@synnaxlabs/x 0.55.0 → 0.56.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 +10 -13
- package/dist/src/array/nullable.d.ts +1 -1
- package/dist/src/array/nullable.d.ts.map +1 -1
- package/dist/src/caseconv/caseconv.d.ts.map +1 -1
- package/dist/src/compare/compare.d.ts +14 -0
- package/dist/src/compare/compare.d.ts.map +1 -1
- package/dist/src/debounce/debounce.d.ts +7 -2
- package/dist/src/debounce/debounce.d.ts.map +1 -1
- package/dist/src/destructor/destructor.d.ts +1 -0
- package/dist/src/destructor/destructor.d.ts.map +1 -1
- package/dist/src/errors/errors.d.ts +6 -10
- package/dist/src/errors/errors.d.ts.map +1 -1
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/notation/external.d.ts +3 -0
- package/dist/src/notation/external.d.ts.map +1 -0
- package/dist/src/notation/index.d.ts +1 -1
- package/dist/src/notation/notation.d.ts +5 -9
- package/dist/src/notation/notation.d.ts.map +1 -1
- package/dist/src/notation/types.gen.d.ts +9 -0
- package/dist/src/notation/types.gen.d.ts.map +1 -0
- package/dist/src/primitive/primitive.d.ts +16 -0
- package/dist/src/primitive/primitive.d.ts.map +1 -1
- package/dist/src/record/record.d.ts +8 -1
- package/dist/src/record/record.d.ts.map +1 -1
- package/dist/src/require/index.d.ts +2 -0
- package/dist/src/require/index.d.ts.map +1 -0
- package/dist/src/require/require.d.ts +2 -0
- package/dist/src/require/require.d.ts.map +1 -0
- package/dist/src/spatial/base.d.ts +1 -103
- package/dist/src/spatial/base.d.ts.map +1 -1
- package/dist/src/spatial/bounds/bounds.d.ts +3 -3
- package/dist/src/spatial/bounds/bounds.d.ts.map +1 -1
- package/dist/src/spatial/box/box.d.ts +7 -13
- package/dist/src/spatial/box/box.d.ts.map +1 -1
- package/dist/src/spatial/direction/direction.d.ts +17 -16
- package/dist/src/spatial/direction/direction.d.ts.map +1 -1
- package/dist/src/spatial/external.d.ts +1 -2
- package/dist/src/spatial/external.d.ts.map +1 -1
- package/dist/src/spatial/location/location.d.ts +28 -28
- package/dist/src/spatial/location/location.d.ts.map +1 -1
- package/dist/src/spatial/scale/scale.d.ts +2 -2
- package/dist/src/spatial/scale/scale.d.ts.map +1 -1
- package/dist/src/spatial/sticky/sticky.d.ts +15 -15
- package/dist/src/spatial/sticky/sticky.d.ts.map +1 -1
- package/dist/src/spatial/types.gen.d.ts +179 -2
- package/dist/src/spatial/types.gen.d.ts.map +1 -1
- package/dist/src/spatial/xy/xy.d.ts +4 -4
- package/dist/src/spatial/xy/xy.d.ts.map +1 -1
- package/dist/src/status/status.d.ts +11 -0
- package/dist/src/status/status.d.ts.map +1 -1
- package/dist/src/telem/external.d.ts +1 -0
- package/dist/src/telem/external.d.ts.map +1 -1
- package/dist/src/telem/series.d.ts +5 -2
- package/dist/src/telem/series.d.ts.map +1 -1
- package/dist/src/telem/telem.d.ts +42 -34
- package/dist/src/telem/telem.d.ts.map +1 -1
- package/dist/src/telem/types.gen.d.ts +19 -0
- package/dist/src/telem/types.gen.d.ts.map +1 -0
- package/dist/src/text/external.d.ts +3 -0
- package/dist/src/text/external.d.ts.map +1 -0
- package/dist/src/text/index.d.ts +2 -0
- package/dist/src/text/index.d.ts.map +1 -0
- package/dist/src/text/types.d.ts +21 -0
- package/dist/src/text/types.d.ts.map +1 -0
- package/dist/src/text/types.gen.d.ts +13 -0
- package/dist/src/text/types.gen.d.ts.map +1 -0
- package/dist/src/throttle/index.d.ts +2 -0
- package/dist/src/throttle/index.d.ts.map +1 -0
- package/dist/src/throttle/throttle.d.ts +3 -0
- package/dist/src/throttle/throttle.d.ts.map +1 -0
- package/dist/src/throttle/throttle.spec.d.ts +2 -0
- package/dist/src/throttle/throttle.spec.d.ts.map +1 -0
- package/dist/src/zod/parse.d.ts.map +1 -1
- package/dist/x.cjs +9 -9
- package/dist/x.js +1469 -1346
- package/package.json +11 -11
- package/src/array/nullable.ts +1 -4
- package/src/caseconv/caseconv.spec.ts +71 -0
- package/src/caseconv/caseconv.ts +15 -2
- package/src/compare/compare.spec.ts +115 -0
- package/src/compare/compare.ts +29 -0
- package/src/debounce/debounce.spec.ts +258 -24
- package/src/debounce/debounce.ts +49 -30
- package/src/deep/copy.spec.ts +13 -0
- package/src/deep/difference.ts +1 -1
- package/src/destructor/destructor.ts +2 -0
- package/src/errors/errors.spec.ts +30 -0
- package/src/errors/errors.ts +29 -17
- package/src/index.ts +4 -1
- package/src/notation/external.ts +11 -0
- package/src/notation/index.ts +1 -1
- package/src/notation/notation.spec.ts +260 -2
- package/src/notation/notation.ts +25 -7
- package/src/notation/types.gen.ts +16 -0
- package/src/primitive/primitive.spec.ts +58 -5
- package/src/primitive/primitive.ts +22 -0
- package/src/record/record.spec.ts +26 -0
- package/src/record/record.ts +20 -5
- package/src/require/index.ts +10 -0
- package/src/require/require.ts +10 -0
- package/src/spatial/base.ts +1 -93
- package/src/spatial/bounds/bounds.ts +10 -10
- package/src/spatial/box/box.ts +5 -5
- package/src/spatial/direction/direction.ts +16 -17
- package/src/spatial/external.ts +1 -2
- package/src/spatial/location/location.ts +19 -17
- package/src/spatial/scale/scale.ts +2 -2
- package/src/spatial/sticky/sticky.spec.ts +2 -2
- package/src/spatial/sticky/sticky.ts +6 -13
- package/src/spatial/types.gen.ts +140 -0
- package/src/spatial/xy/xy.ts +7 -7
- package/src/status/status.spec.ts +65 -0
- package/src/status/status.ts +20 -0
- package/src/telem/external.ts +8 -0
- package/src/telem/series.spec.ts +183 -0
- package/src/telem/series.ts +54 -16
- package/src/telem/telem.spec.ts +128 -9
- package/src/telem/telem.ts +91 -79
- package/src/telem/types.gen.ts +28 -0
- package/src/text/external.ts +11 -0
- package/src/text/index.ts +10 -0
- package/src/text/types.gen.ts +16 -0
- package/src/text/types.ts +37 -0
- package/src/{worker → throttle}/index.ts +1 -1
- package/src/throttle/throttle.spec.ts +147 -0
- package/src/throttle/throttle.ts +44 -0
- package/src/zod/parse.ts +2 -3
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/src/spatial/spatial.d.ts +0 -3
- package/dist/src/spatial/spatial.d.ts.map +0 -1
- package/dist/src/worker/index.d.ts +0 -2
- package/dist/src/worker/index.d.ts.map +0 -1
- package/dist/src/worker/worker.d.ts +0 -33
- package/dist/src/worker/worker.d.ts.map +0 -1
- package/dist/src/worker/worker.spec.d.ts +0 -2
- package/dist/src/worker/worker.spec.d.ts.map +0 -1
- package/src/spatial/spatial.ts +0 -44
- package/src/worker/worker.spec.ts +0 -41
- package/src/worker/worker.ts +0 -86
package/src/debounce/debounce.ts
CHANGED
|
@@ -7,38 +7,57 @@
|
|
|
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
|
-
func: F,
|
|
12
|
-
waitFor: number,
|
|
13
|
-
): F => {
|
|
14
|
-
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
15
|
-
if (waitFor === 0) return func;
|
|
10
|
+
import { type CrudeTimeSpan, TimeSpan } from "@/telem/telem";
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
};
|
|
12
|
+
// DebouncedFn is the function returned by debounce. It carries cancel and
|
|
13
|
+
// flush handles so callers (e.g., React cleanup paths) can drop pending
|
|
14
|
+
// invocations or force them to run immediately on demand. Shape matches
|
|
15
|
+
// lodash.debounce.
|
|
16
|
+
export interface DebouncedFn<Args extends unknown[]> {
|
|
17
|
+
(...args: Args): void;
|
|
18
|
+
cancel: () => void;
|
|
19
|
+
flush: () => void;
|
|
20
|
+
}
|
|
27
21
|
|
|
28
|
-
|
|
29
|
-
func: F,
|
|
30
|
-
waitFor: number,
|
|
31
|
-
): F => {
|
|
32
|
-
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
33
|
-
if (waitFor === 0) return func;
|
|
22
|
+
const noop = (): void => {};
|
|
34
23
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
24
|
+
export const debounce = <Args extends unknown[]>(
|
|
25
|
+
func: (...args: Args) => void,
|
|
26
|
+
waitFor: CrudeTimeSpan,
|
|
27
|
+
): DebouncedFn<Args> => {
|
|
28
|
+
const debouncePeriod = new TimeSpan(waitFor);
|
|
29
|
+
if (debouncePeriod.valueOf() <= 0) {
|
|
30
|
+
const passthrough = ((...args: Args) => func(...args)) as DebouncedFn<Args>;
|
|
31
|
+
passthrough.cancel = noop;
|
|
32
|
+
passthrough.flush = noop;
|
|
33
|
+
return passthrough;
|
|
34
|
+
}
|
|
35
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
36
|
+
let latestArgs: Args | null = null;
|
|
37
|
+
const invoke = (): void => {
|
|
38
|
+
if (latestArgs === null) return;
|
|
39
|
+
const args = latestArgs;
|
|
40
|
+
latestArgs = null;
|
|
41
|
+
func(...args);
|
|
41
42
|
};
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
const debounced = ((...args: Args): void => {
|
|
44
|
+
latestArgs = args;
|
|
45
|
+
clearTimeout(timeout);
|
|
46
|
+
timeout = setTimeout(() => {
|
|
47
|
+
timeout = undefined;
|
|
48
|
+
invoke();
|
|
49
|
+
}, debouncePeriod.milliseconds);
|
|
50
|
+
}) as DebouncedFn<Args>;
|
|
51
|
+
debounced.cancel = (): void => {
|
|
52
|
+
clearTimeout(timeout);
|
|
53
|
+
timeout = undefined;
|
|
54
|
+
latestArgs = null;
|
|
55
|
+
};
|
|
56
|
+
debounced.flush = (): void => {
|
|
57
|
+
if (timeout === undefined) return;
|
|
58
|
+
clearTimeout(timeout);
|
|
59
|
+
timeout = undefined;
|
|
60
|
+
invoke();
|
|
61
|
+
};
|
|
62
|
+
return debounced;
|
|
44
63
|
};
|
package/src/deep/copy.spec.ts
CHANGED
|
@@ -145,4 +145,17 @@ describe("copy", () => {
|
|
|
145
145
|
expect(copied.uint8).not.toBe(uint8);
|
|
146
146
|
} else expect(Array.from(copied.uint8 as Uint8Array)).toEqual([1, 2, 3]);
|
|
147
147
|
});
|
|
148
|
+
|
|
149
|
+
// Mirrors the immer Draft scenario: callers store snapshots of mutable
|
|
150
|
+
// proxy state in long-lived structures (e.g., undo entries) that outlive
|
|
151
|
+
// the proxy. The snapshot must own its data so it survives revocation.
|
|
152
|
+
it("should produce a snapshot detached from a revocable proxy source", () => {
|
|
153
|
+
const target = { nested: { value: 1 }, list: [1, 2, 3] };
|
|
154
|
+
const { proxy, revoke } = Proxy.revocable(target, {});
|
|
155
|
+
const copied = deep.copy(proxy);
|
|
156
|
+
revoke();
|
|
157
|
+
expect(copied).toEqual({ nested: { value: 1 }, list: [1, 2, 3] });
|
|
158
|
+
expect(copied.nested).not.toBe(target.nested);
|
|
159
|
+
expect(copied.list).not.toBe(target.list);
|
|
160
|
+
});
|
|
148
161
|
});
|
package/src/deep/difference.ts
CHANGED
|
@@ -30,7 +30,7 @@ export const difference = (
|
|
|
30
30
|
}
|
|
31
31
|
for (let i = 0; i < a.length; i++) compare(a[i], b[i], `${currentPath}[${i}]`);
|
|
32
32
|
} else {
|
|
33
|
-
const keys = new Set([...Object.keys(a
|
|
33
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
34
34
|
keys.forEach((key) => {
|
|
35
35
|
compare(
|
|
36
36
|
a[key as keyof typeof a],
|
|
@@ -96,6 +96,36 @@ describe("errors", () => {
|
|
|
96
96
|
const decoded = errors.decode(encoded);
|
|
97
97
|
expect(errors.Unknown.matches(decoded)).toBe(true);
|
|
98
98
|
});
|
|
99
|
+
|
|
100
|
+
it("should preserve name and stack across encode/decode for a generic Error", () => {
|
|
101
|
+
const error = new TypeError("boom");
|
|
102
|
+
const encoded = errors.encode(error);
|
|
103
|
+
expect(encoded.name).toEqual("TypeError");
|
|
104
|
+
expect(encoded.stack).toEqual(error.stack);
|
|
105
|
+
const decoded = errors.decode(encoded);
|
|
106
|
+
expect((decoded as Error).name).toEqual("TypeError");
|
|
107
|
+
expect((decoded as Error).stack).toEqual(error.stack);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should preserve name and stack when a typed error round-trips through a provider", () => {
|
|
111
|
+
errors.register({
|
|
112
|
+
encode: myCustomErrorEncoder,
|
|
113
|
+
decode: myCustomErrorDecoder,
|
|
114
|
+
});
|
|
115
|
+
const error = new ErrorOne("boom");
|
|
116
|
+
const encoded = errors.encode(error);
|
|
117
|
+
expect(encoded.name).toEqual(ErrorOne.TYPE);
|
|
118
|
+
expect(encoded.stack).toEqual(error.stack);
|
|
119
|
+
const decoded = errors.decode(encoded);
|
|
120
|
+
expect(ErrorOne.matches(decoded)).toBe(true);
|
|
121
|
+
expect((decoded as Error).stack).toEqual(error.stack);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should leave the decoded error untouched when the payload omits name and stack", () => {
|
|
125
|
+
const decoded = errors.decode({ type: errors.UNKNOWN, data: "no native fields" });
|
|
126
|
+
expect(decoded).toBeInstanceOf(Error);
|
|
127
|
+
expect((decoded as Error).message).toEqual("no native fields");
|
|
128
|
+
});
|
|
99
129
|
});
|
|
100
130
|
|
|
101
131
|
describe("matches", () => {
|
package/src/errors/errors.ts
CHANGED
|
@@ -169,6 +169,18 @@ interface Provider {
|
|
|
169
169
|
decode: Decoder;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
const withNative = (payload: Payload, error: Error): Payload => ({
|
|
173
|
+
...payload,
|
|
174
|
+
name: error.name,
|
|
175
|
+
stack: error.stack,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const applyNative = (error: Error, payload: Payload): Error => {
|
|
179
|
+
if (payload.name != null) error.name = payload.name;
|
|
180
|
+
if (payload.stack != null) error.stack = payload.stack;
|
|
181
|
+
return error;
|
|
182
|
+
};
|
|
183
|
+
|
|
172
184
|
class Registry {
|
|
173
185
|
private readonly providers: Provider[] = [];
|
|
174
186
|
|
|
@@ -181,9 +193,10 @@ class Registry {
|
|
|
181
193
|
if (isTyped(error))
|
|
182
194
|
for (const provider of this.providers) {
|
|
183
195
|
const payload = provider.encode(error);
|
|
184
|
-
if (payload != null) return payload;
|
|
196
|
+
if (payload != null) return withNative(payload, error);
|
|
185
197
|
}
|
|
186
|
-
if (error instanceof Error)
|
|
198
|
+
if (error instanceof Error)
|
|
199
|
+
return withNative({ type: UNKNOWN, data: error.message }, error);
|
|
187
200
|
if (typeof error === "string") return { type: UNKNOWN, data: error };
|
|
188
201
|
try {
|
|
189
202
|
return { type: UNKNOWN, data: JSON.stringify(error) };
|
|
@@ -194,12 +207,13 @@ class Registry {
|
|
|
194
207
|
|
|
195
208
|
decode(payload?: Payload | null): Error | null {
|
|
196
209
|
if (payload == null || payload.type === NONE) return null;
|
|
197
|
-
if (payload.type === UNKNOWN)
|
|
210
|
+
if (payload.type === UNKNOWN)
|
|
211
|
+
return applyNative(new Unknown(payload.data), payload);
|
|
198
212
|
for (const provider of this.providers) {
|
|
199
213
|
const error = provider.decode(payload);
|
|
200
|
-
if (error != null) return error;
|
|
214
|
+
if (error != null) return applyNative(error, payload);
|
|
201
215
|
}
|
|
202
|
-
return new Unknown(payload.data);
|
|
216
|
+
return applyNative(new Unknown(payload.data), payload);
|
|
203
217
|
}
|
|
204
218
|
}
|
|
205
219
|
|
|
@@ -242,8 +256,16 @@ export const decode = (payload?: Payload | null): Error | null => {
|
|
|
242
256
|
*/
|
|
243
257
|
export class Unknown extends createTyped("unknown") {}
|
|
244
258
|
|
|
245
|
-
/** Zod schema for validating error payloads
|
|
246
|
-
|
|
259
|
+
/** Zod schema for validating error payloads. `name` and `stack` are TypeScript-only
|
|
260
|
+
* fields; Go and Python don't populate them. They're carried opaquely across the wire
|
|
261
|
+
* and re-applied to the reconstructed error on decode, which keeps the original error
|
|
262
|
+
* name (e.g. "TypeError") and stack trace alive across worker / network boundaries. */
|
|
263
|
+
export const payloadZ = z.object({
|
|
264
|
+
type: z.string(),
|
|
265
|
+
data: z.string(),
|
|
266
|
+
name: z.string().optional(),
|
|
267
|
+
stack: z.string().optional(),
|
|
268
|
+
});
|
|
247
269
|
|
|
248
270
|
/** Network-portable representation of an error */
|
|
249
271
|
export type Payload = z.infer<typeof payloadZ>;
|
|
@@ -251,15 +273,5 @@ export type Payload = z.infer<typeof payloadZ>;
|
|
|
251
273
|
/** Error for representing the cancellation of an operation */
|
|
252
274
|
export class Canceled extends createTyped("canceled") {}
|
|
253
275
|
|
|
254
|
-
/** A payload representing a native JavaScript Error */
|
|
255
|
-
export interface NativePayload {
|
|
256
|
-
/** The name of the error */
|
|
257
|
-
name: string;
|
|
258
|
-
/** The message of the error */
|
|
259
|
-
message: string;
|
|
260
|
-
/** The stack trace of the error */
|
|
261
|
-
stack?: string;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
276
|
/** Error for representing a method that is not implemented */
|
|
265
277
|
export class NotImplemented extends createTyped("not_implemented") {}
|
package/src/index.ts
CHANGED
|
@@ -33,10 +33,12 @@ export * from "@/math";
|
|
|
33
33
|
export * from "@/migrate";
|
|
34
34
|
export * from "@/narrow";
|
|
35
35
|
export * from "@/notation";
|
|
36
|
+
export * from "@/numeric";
|
|
36
37
|
export * from "@/observe";
|
|
37
38
|
export * from "@/optional";
|
|
38
39
|
export * from "@/primitive";
|
|
39
40
|
export * from "@/record";
|
|
41
|
+
export * from "@/require";
|
|
40
42
|
export * from "@/runtime";
|
|
41
43
|
export * from "@/scheduler";
|
|
42
44
|
export * from "@/shallow";
|
|
@@ -47,9 +49,10 @@ export * from "@/strings";
|
|
|
47
49
|
export * from "@/sync";
|
|
48
50
|
export * from "@/telem";
|
|
49
51
|
export * from "@/testutil";
|
|
52
|
+
export * from "@/text";
|
|
53
|
+
export * from "@/throttle";
|
|
50
54
|
export * from "@/types";
|
|
51
55
|
export * from "@/unique";
|
|
52
56
|
export * from "@/url";
|
|
53
57
|
export * from "@/uuid";
|
|
54
|
-
export * from "@/worker";
|
|
55
58
|
export * from "@/zod";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Copyright 2026 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 "@/notation/notation";
|
|
11
|
+
export { type Notation, NOTATIONS, notationZ } from "@/notation/types.gen";
|
package/src/notation/index.ts
CHANGED
|
@@ -12,12 +12,13 @@ import { describe, expect, it } from "vitest";
|
|
|
12
12
|
import { notation } from "@/notation";
|
|
13
13
|
|
|
14
14
|
interface TestCase {
|
|
15
|
-
number: number;
|
|
15
|
+
number: number | bigint;
|
|
16
16
|
precision: number;
|
|
17
17
|
expected: Record<notation.Notation, string>;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const TEST_CASES: TestCase[] = [
|
|
21
|
+
// === Basic positive numbers ===
|
|
21
22
|
{
|
|
22
23
|
number: 12345.678,
|
|
23
24
|
precision: 1,
|
|
@@ -28,6 +29,7 @@ const TEST_CASES: TestCase[] = [
|
|
|
28
29
|
precision: 0,
|
|
29
30
|
expected: { standard: "12346", scientific: "1ᴇ4", engineering: "12ᴇ3" },
|
|
30
31
|
},
|
|
32
|
+
// === Zero ===
|
|
31
33
|
{
|
|
32
34
|
number: 0,
|
|
33
35
|
precision: 1,
|
|
@@ -38,6 +40,18 @@ const TEST_CASES: TestCase[] = [
|
|
|
38
40
|
precision: 0,
|
|
39
41
|
expected: { standard: "0", scientific: "0ᴇ0", engineering: "0ᴇ0" },
|
|
40
42
|
},
|
|
43
|
+
// === Negative zero — should render identically to positive zero ===
|
|
44
|
+
{
|
|
45
|
+
number: -0,
|
|
46
|
+
precision: 0,
|
|
47
|
+
expected: { standard: "0", scientific: "0ᴇ0", engineering: "0ᴇ0" },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
number: -0,
|
|
51
|
+
precision: 2,
|
|
52
|
+
expected: { standard: "0.00", scientific: "0.00ᴇ0", engineering: "0.00ᴇ0" },
|
|
53
|
+
},
|
|
54
|
+
// === Negative numbers ===
|
|
41
55
|
{
|
|
42
56
|
number: -1234.5678,
|
|
43
57
|
precision: 1,
|
|
@@ -48,21 +62,38 @@ const TEST_CASES: TestCase[] = [
|
|
|
48
62
|
precision: 0,
|
|
49
63
|
expected: { standard: "-1235", scientific: "-1ᴇ3", engineering: "-1ᴇ3" },
|
|
50
64
|
},
|
|
65
|
+
// === Special values (precision must be ignored) ===
|
|
51
66
|
{
|
|
52
67
|
number: NaN,
|
|
53
68
|
precision: 0,
|
|
54
69
|
expected: { standard: "NaN", scientific: "NaN", engineering: "NaN" },
|
|
55
70
|
},
|
|
71
|
+
{
|
|
72
|
+
number: NaN,
|
|
73
|
+
precision: 4,
|
|
74
|
+
expected: { standard: "NaN", scientific: "NaN", engineering: "NaN" },
|
|
75
|
+
},
|
|
56
76
|
{
|
|
57
77
|
number: Infinity,
|
|
58
78
|
precision: 0,
|
|
59
79
|
expected: { standard: "∞", scientific: "∞", engineering: "∞" },
|
|
60
80
|
},
|
|
81
|
+
{
|
|
82
|
+
number: Infinity,
|
|
83
|
+
precision: 4,
|
|
84
|
+
expected: { standard: "∞", scientific: "∞", engineering: "∞" },
|
|
85
|
+
},
|
|
61
86
|
{
|
|
62
87
|
number: -Infinity,
|
|
63
88
|
precision: 0,
|
|
64
89
|
expected: { standard: "-∞", scientific: "-∞", engineering: "-∞" },
|
|
65
90
|
},
|
|
91
|
+
{
|
|
92
|
+
number: -Infinity,
|
|
93
|
+
precision: 4,
|
|
94
|
+
expected: { standard: "-∞", scientific: "-∞", engineering: "-∞" },
|
|
95
|
+
},
|
|
96
|
+
// === Small fractional numbers ===
|
|
66
97
|
{
|
|
67
98
|
number: 0.0001234,
|
|
68
99
|
precision: 1,
|
|
@@ -73,11 +104,238 @@ const TEST_CASES: TestCase[] = [
|
|
|
73
104
|
precision: 0,
|
|
74
105
|
expected: { standard: "0", scientific: "1ᴇ-4", engineering: "123ᴇ-6" },
|
|
75
106
|
},
|
|
107
|
+
// === Negative small fractional numbers ===
|
|
108
|
+
{
|
|
109
|
+
number: -0.0001234,
|
|
110
|
+
precision: 1,
|
|
111
|
+
expected: { standard: "-0.0", scientific: "-1.2ᴇ-4", engineering: "-123.4ᴇ-6" },
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
number: -0.0001234,
|
|
115
|
+
precision: 0,
|
|
116
|
+
expected: { standard: "-0", scientific: "-1ᴇ-4", engineering: "-123ᴇ-6" },
|
|
117
|
+
},
|
|
118
|
+
// === Number 1 ===
|
|
119
|
+
{
|
|
120
|
+
number: 1,
|
|
121
|
+
precision: 0,
|
|
122
|
+
expected: { standard: "1", scientific: "1ᴇ0", engineering: "1ᴇ0" },
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
number: 1,
|
|
126
|
+
precision: 2,
|
|
127
|
+
expected: { standard: "1.00", scientific: "1.00ᴇ0", engineering: "1.00ᴇ0" },
|
|
128
|
+
},
|
|
129
|
+
// === Power-of-10 boundaries where engineering and scientific differ ===
|
|
130
|
+
{
|
|
131
|
+
number: 10,
|
|
132
|
+
precision: 0,
|
|
133
|
+
expected: { standard: "10", scientific: "1ᴇ1", engineering: "10ᴇ0" },
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
number: 100,
|
|
137
|
+
precision: 0,
|
|
138
|
+
expected: { standard: "100", scientific: "1ᴇ2", engineering: "100ᴇ0" },
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
number: 1000,
|
|
142
|
+
precision: 0,
|
|
143
|
+
expected: { standard: "1000", scientific: "1ᴇ3", engineering: "1ᴇ3" },
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
number: 10000,
|
|
147
|
+
precision: 0,
|
|
148
|
+
expected: { standard: "10000", scientific: "1ᴇ4", engineering: "10ᴇ3" },
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
number: 100000,
|
|
152
|
+
precision: 0,
|
|
153
|
+
expected: { standard: "100000", scientific: "1ᴇ5", engineering: "100ᴇ3" },
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
number: 1000000,
|
|
157
|
+
precision: 0,
|
|
158
|
+
expected: { standard: "1000000", scientific: "1ᴇ6", engineering: "1ᴇ6" },
|
|
159
|
+
},
|
|
160
|
+
// === Negative power-of-10 boundaries (fractions) ===
|
|
161
|
+
{
|
|
162
|
+
number: 0.1,
|
|
163
|
+
precision: 1,
|
|
164
|
+
expected: { standard: "0.1", scientific: "1.0ᴇ-1", engineering: "100.0ᴇ-3" },
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
number: 0.01,
|
|
168
|
+
precision: 1,
|
|
169
|
+
expected: { standard: "0.0", scientific: "1.0ᴇ-2", engineering: "10.0ᴇ-3" },
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
number: 0.001,
|
|
173
|
+
precision: 1,
|
|
174
|
+
expected: { standard: "0.0", scientific: "1.0ᴇ-3", engineering: "1.0ᴇ-3" },
|
|
175
|
+
},
|
|
176
|
+
// === Negative power-of-10 boundaries ===
|
|
177
|
+
{
|
|
178
|
+
number: -10,
|
|
179
|
+
precision: 0,
|
|
180
|
+
expected: { standard: "-10", scientific: "-1ᴇ1", engineering: "-10ᴇ0" },
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
number: -100,
|
|
184
|
+
precision: 0,
|
|
185
|
+
expected: { standard: "-100", scientific: "-1ᴇ2", engineering: "-100ᴇ0" },
|
|
186
|
+
},
|
|
187
|
+
// === Power of 10 with non-zero precision ===
|
|
188
|
+
{
|
|
189
|
+
number: 100,
|
|
190
|
+
precision: 2,
|
|
191
|
+
expected: { standard: "100.00", scientific: "1.00ᴇ2", engineering: "100.00ᴇ0" },
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
number: 1000,
|
|
195
|
+
precision: 3,
|
|
196
|
+
expected: { standard: "1000.000", scientific: "1.000ᴇ3", engineering: "1.000ᴇ3" },
|
|
197
|
+
},
|
|
198
|
+
// === Rounding overflow — toFixed pushes the mantissa past the canonical upper
|
|
199
|
+
// bound (>= 10 for scientific, >= 1000 for engineering). The exponent must be
|
|
200
|
+
// bumped so the mantissa stays in the canonical range. ===
|
|
201
|
+
{
|
|
202
|
+
number: 9.999,
|
|
203
|
+
precision: 1,
|
|
204
|
+
expected: { standard: "10.0", scientific: "1.0ᴇ1", engineering: "10.0ᴇ0" },
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
number: 9.999,
|
|
208
|
+
precision: 0,
|
|
209
|
+
expected: { standard: "10", scientific: "1ᴇ1", engineering: "10ᴇ0" },
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
number: 99.99,
|
|
213
|
+
precision: 0,
|
|
214
|
+
expected: { standard: "100", scientific: "1ᴇ2", engineering: "100ᴇ0" },
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
number: 999.99,
|
|
218
|
+
precision: 0,
|
|
219
|
+
expected: { standard: "1000", scientific: "1ᴇ3", engineering: "1ᴇ3" },
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
number: 9999.99,
|
|
223
|
+
precision: 0,
|
|
224
|
+
expected: { standard: "10000", scientific: "1ᴇ4", engineering: "10ᴇ3" },
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
number: -9.999,
|
|
228
|
+
precision: 1,
|
|
229
|
+
expected: { standard: "-10.0", scientific: "-1.0ᴇ1", engineering: "-10.0ᴇ0" },
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
number: -999.99,
|
|
233
|
+
precision: 0,
|
|
234
|
+
expected: { standard: "-1000", scientific: "-1ᴇ3", engineering: "-1ᴇ3" },
|
|
235
|
+
},
|
|
236
|
+
// === Positive bigint ===
|
|
237
|
+
{
|
|
238
|
+
number: 1n,
|
|
239
|
+
precision: 0,
|
|
240
|
+
expected: { standard: "1", scientific: "1ᴇ0", engineering: "1ᴇ0" },
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
number: 1n,
|
|
244
|
+
precision: 2,
|
|
245
|
+
expected: { standard: "1.00", scientific: "1.00ᴇ0", engineering: "1.00ᴇ0" },
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
number: 1778020940471336960n,
|
|
249
|
+
precision: 0,
|
|
250
|
+
expected: {
|
|
251
|
+
standard: "1778020940471336960",
|
|
252
|
+
scientific: "2ᴇ18",
|
|
253
|
+
engineering: "2ᴇ18",
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
number: 1778020940471336960n,
|
|
258
|
+
precision: 11,
|
|
259
|
+
expected: {
|
|
260
|
+
standard: "1778020940471336960.00000000000",
|
|
261
|
+
scientific: "1.77802094047ᴇ18",
|
|
262
|
+
engineering: "1.77802094047ᴇ18",
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
// === Negative bigint ===
|
|
266
|
+
{
|
|
267
|
+
number: -1n,
|
|
268
|
+
precision: 0,
|
|
269
|
+
expected: { standard: "-1", scientific: "-1ᴇ0", engineering: "-1ᴇ0" },
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
number: -1778020940471336960n,
|
|
273
|
+
precision: 0,
|
|
274
|
+
expected: {
|
|
275
|
+
standard: "-1778020940471336960",
|
|
276
|
+
scientific: "-2ᴇ18",
|
|
277
|
+
engineering: "-2ᴇ18",
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
number: -1778020940471336960n,
|
|
282
|
+
precision: 11,
|
|
283
|
+
expected: {
|
|
284
|
+
standard: "-1778020940471336960.00000000000",
|
|
285
|
+
scientific: "-1.77802094047ᴇ18",
|
|
286
|
+
engineering: "-1.77802094047ᴇ18",
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
// === Zero bigint ===
|
|
290
|
+
{
|
|
291
|
+
number: 0n,
|
|
292
|
+
precision: 0,
|
|
293
|
+
expected: { standard: "0", scientific: "0ᴇ0", engineering: "0ᴇ0" },
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
number: 0n,
|
|
297
|
+
precision: 11,
|
|
298
|
+
expected: {
|
|
299
|
+
standard: "0.00000000000",
|
|
300
|
+
scientific: "0.00000000000ᴇ0",
|
|
301
|
+
engineering: "0.00000000000ᴇ0",
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
// === Large-magnitude floats expressed with e-notation ===
|
|
305
|
+
// For values >= 10^21, JS's toFixed delegates to ToString, so standard output
|
|
306
|
+
// here is itself in e-notation. The scientific and engineering branches keep
|
|
307
|
+
// their canonical forms.
|
|
308
|
+
{
|
|
309
|
+
number: 9e124,
|
|
310
|
+
precision: 0,
|
|
311
|
+
expected: { standard: "9e+124", scientific: "9ᴇ124", engineering: "90ᴇ123" },
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
number: 9.99e124,
|
|
315
|
+
precision: 0,
|
|
316
|
+
expected: { standard: "9.99e+124", scientific: "1ᴇ125", engineering: "100ᴇ123" },
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
number: -3e10,
|
|
320
|
+
precision: 0,
|
|
321
|
+
expected: { standard: "-30000000000", scientific: "-3ᴇ10", engineering: "-30ᴇ9" },
|
|
322
|
+
},
|
|
323
|
+
// === Tiny-magnitude floats expressed with e-notation ===
|
|
324
|
+
{
|
|
325
|
+
number: 1e-200,
|
|
326
|
+
precision: 0,
|
|
327
|
+
expected: { standard: "0", scientific: "1ᴇ-200", engineering: "10ᴇ-201" },
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
number: 5e-50,
|
|
331
|
+
precision: 0,
|
|
332
|
+
expected: { standard: "0", scientific: "5ᴇ-50", engineering: "50ᴇ-51" },
|
|
333
|
+
},
|
|
76
334
|
];
|
|
77
335
|
|
|
78
336
|
describe("stringifyNumber", () => {
|
|
79
337
|
TEST_CASES.forEach(({ number, precision, expected }) =>
|
|
80
|
-
describe(
|
|
338
|
+
describe(`${typeof number === "bigint" ? "bigint " : ""}number: ${number}, precision: ${precision}`, () =>
|
|
81
339
|
notation.NOTATIONS.forEach((n) =>
|
|
82
340
|
it(`should format correctly in ${n} notation`, () => {
|
|
83
341
|
const result = notation.stringifyNumber(number, precision, n);
|
package/src/notation/notation.ts
CHANGED
|
@@ -7,11 +7,7 @@
|
|
|
7
7
|
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
export const NOTATIONS = ["standard", "scientific", "engineering"] as const;
|
|
13
|
-
export const notationZ = z.enum(NOTATIONS);
|
|
14
|
-
export type Notation = z.infer<typeof notationZ>;
|
|
10
|
+
import { type Notation } from "@/notation/types.gen";
|
|
15
11
|
|
|
16
12
|
/**
|
|
17
13
|
* Converts a number to a string representation with a specified precision and notation.
|
|
@@ -25,6 +21,9 @@ export type Notation = z.infer<typeof notationZ>;
|
|
|
25
21
|
* - If the value is `NaN`, returns "NaN".
|
|
26
22
|
* - If the value is `Infinity`, returns "∞".
|
|
27
23
|
* - If the value is `-Infinity`, returns "-∞".
|
|
24
|
+
* - Scientific and engineering output is renormalized after rounding so the mantissa
|
|
25
|
+
* stays in its canonical range (|m| < 10 for scientific, |m| < 1000 for engineering).
|
|
26
|
+
* For example, 9.999 at precision 1 in scientific renders as "1.0ᴇ1", not "10.0ᴇ0".
|
|
28
27
|
*
|
|
29
28
|
* Examples:
|
|
30
29
|
*
|
|
@@ -41,10 +40,16 @@ export type Notation = z.infer<typeof notationZ>;
|
|
|
41
40
|
* ```
|
|
42
41
|
*/
|
|
43
42
|
export const stringifyNumber = (
|
|
44
|
-
value: number,
|
|
43
|
+
value: number | bigint,
|
|
45
44
|
precision: number,
|
|
46
45
|
notation: Notation,
|
|
47
46
|
): string => {
|
|
47
|
+
// Standard notation on bigint must preserve full integer precision; coercing through
|
|
48
|
+
// Number() would quantize values above 2^53. All other branches are float-mantissa
|
|
49
|
+
// truncated by definition, so coercing bigint to number is safe.
|
|
50
|
+
if (typeof value === "bigint" && notation === "standard")
|
|
51
|
+
return precision === 0 ? value.toString() : `${value}.${"0".repeat(precision)}`;
|
|
52
|
+
if (typeof value === "bigint") value = Number(value);
|
|
48
53
|
if (Number.isNaN(value)) return "NaN";
|
|
49
54
|
if (value === Infinity) return "∞";
|
|
50
55
|
if (value === -Infinity) return "-∞";
|
|
@@ -56,6 +61,19 @@ export const stringifyNumber = (
|
|
|
56
61
|
let exp: number;
|
|
57
62
|
if (notation === "scientific") exp = Math.floor(Math.log10(Math.abs(value)));
|
|
58
63
|
else exp = Math.floor(Math.log10(Math.abs(value)) / 3) * 3;
|
|
59
|
-
|
|
64
|
+
let mantissa = value / 10 ** exp;
|
|
65
|
+
// After rounding via toFixed, the mantissa may have crossed the canonical upper
|
|
66
|
+
// bound (>= 10 for scientific, >= 1000 for engineering). Predict the rounded
|
|
67
|
+
// magnitude with Math.round — for non-negative values it matches toFixed's
|
|
68
|
+
// half-away-from-zero semantics, and we're already taking Math.abs — so we avoid
|
|
69
|
+
// having to parseFloat the formatted string back into a number. Bump the exponent
|
|
70
|
+
// if needed so e.g. 9.999 at precision 1 in scientific becomes "1.0ᴇ1" rather
|
|
71
|
+
// than "10.0ᴇ0".
|
|
72
|
+
const upperBound = notation === "scientific" ? 10 : 1000;
|
|
73
|
+
const factor = 10 ** precision;
|
|
74
|
+
if (Math.round(Math.abs(mantissa) * factor) / factor >= upperBound) {
|
|
75
|
+
exp += notation === "scientific" ? 1 : 3;
|
|
76
|
+
mantissa = value / 10 ** exp;
|
|
77
|
+
}
|
|
60
78
|
return `${mantissa.toFixed(precision)}ᴇ${exp}`;
|
|
61
79
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Copyright 2026 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
|
+
// Code generated by Oracle. DO NOT EDIT.
|
|
11
|
+
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
export const NOTATIONS = ["standard", "scientific", "engineering"] as const;
|
|
15
|
+
export const notationZ = z.enum(NOTATIONS);
|
|
16
|
+
export type Notation = z.infer<typeof notationZ>;
|