@synnaxlabs/x 0.55.0 → 0.56.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +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/deep/merge.d.ts.map +1 -1
- package/dist/src/deep/set.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 +18 -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/migrate/migrate.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 +10 -10
- package/dist/x.js +1497 -1365
- 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/deep/merge.ts +2 -1
- package/src/deep/set.ts +2 -1
- package/src/destructor/destructor.ts +2 -0
- package/src/errors/errors.spec.ts +122 -0
- package/src/errors/errors.ts +51 -17
- package/src/index.ts +4 -1
- package/src/migrate/migrate.ts +2 -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 +82 -2
- package/src/status/status.ts +35 -9
- 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
|
@@ -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>;
|
|
@@ -54,13 +54,13 @@ describe("primitive", () => {
|
|
|
54
54
|
|
|
55
55
|
describe("isStringer", () => {
|
|
56
56
|
it("should return true for a stringer", () => {
|
|
57
|
-
expect(primitive.isStringer(new ExampleStringer("cat"))).
|
|
57
|
+
expect(primitive.isStringer(new ExampleStringer("cat"))).toBe(true);
|
|
58
58
|
});
|
|
59
59
|
it("should return false for a non-stringer", () => {
|
|
60
|
-
expect(primitive.isStringer(0)).
|
|
60
|
+
expect(primitive.isStringer(0)).toBe(false);
|
|
61
61
|
});
|
|
62
62
|
it("should return false for null", () => {
|
|
63
|
-
expect(primitive.isStringer(null)).
|
|
63
|
+
expect(primitive.isStringer(null)).toBe(false);
|
|
64
64
|
});
|
|
65
65
|
});
|
|
66
66
|
|
|
@@ -178,12 +178,65 @@ describe("primitive", () => {
|
|
|
178
178
|
|
|
179
179
|
describe("isCrudeValueExtension", () => {
|
|
180
180
|
it("should return true for a CrudeValueExtension", () => {
|
|
181
|
-
expect(primitive.isCrudeValueExtension({ value: 12n })).
|
|
181
|
+
expect(primitive.isCrudeValueExtension({ value: 12n })).toBe(true);
|
|
182
182
|
});
|
|
183
183
|
it("should return false for a non-CrudeValueExtension", () => {
|
|
184
|
-
expect(primitive.isCrudeValueExtension(12n)).
|
|
184
|
+
expect(primitive.isCrudeValueExtension(12n)).toBe(false);
|
|
185
185
|
});
|
|
186
186
|
});
|
|
187
187
|
});
|
|
188
188
|
});
|
|
189
|
+
|
|
190
|
+
describe("isHashable", () => {
|
|
191
|
+
class HashableThing implements primitive.Hashable {
|
|
192
|
+
constructor(private readonly v: string) {}
|
|
193
|
+
hash(): string {
|
|
194
|
+
return this.v;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
it("returns true for an object with a hash() method", () => {
|
|
199
|
+
expect(primitive.isHashable(new HashableThing("x"))).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("returns true for a plain object literal with a hash function", () => {
|
|
203
|
+
expect(primitive.isHashable({ hash: () => "x" })).toBe(true);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("narrows the type for downstream calls", () => {
|
|
207
|
+
const v: unknown = new HashableThing("abc");
|
|
208
|
+
if (primitive.isHashable(v)) expect(v.hash()).toEqual("abc");
|
|
209
|
+
else throw new Error("expected isHashable to narrow");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("returns false for null", () => {
|
|
213
|
+
expect(primitive.isHashable(null)).toBe(false);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("returns false for undefined", () => {
|
|
217
|
+
expect(primitive.isHashable(undefined)).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("returns false for primitives", () => {
|
|
221
|
+
expect(primitive.isHashable("x")).toBe(false);
|
|
222
|
+
expect(primitive.isHashable(42)).toBe(false);
|
|
223
|
+
expect(primitive.isHashable(42n)).toBe(false);
|
|
224
|
+
expect(primitive.isHashable(true)).toBe(false);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("returns false for plain objects without a hash function", () => {
|
|
228
|
+
expect(primitive.isHashable({})).toBe(false);
|
|
229
|
+
expect(primitive.isHashable({ key: "x" })).toBe(false);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("returns false when hash is not a function", () => {
|
|
233
|
+
expect(primitive.isHashable({ hash: "not a function" })).toBe(false);
|
|
234
|
+
expect(primitive.isHashable({ hash: 42 })).toBe(false);
|
|
235
|
+
expect(primitive.isHashable({ hash: null })).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("returns false for arrays", () => {
|
|
239
|
+
expect(primitive.isHashable([1, 2, 3])).toBe(false);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
189
242
|
});
|
|
@@ -61,6 +61,28 @@ export interface Stringer {
|
|
|
61
61
|
export const isStringer = (value: unknown): boolean =>
|
|
62
62
|
value != null && typeof value === "object" && "toString" in value;
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Hashable is a duck-typed protocol for class instances that can produce a stable,
|
|
66
|
+
* canonical string representation of themselves. Implementers commit to: (1) the
|
|
67
|
+
* returned string uniquely identifies the value's logical content, and (2) two
|
|
68
|
+
* instances representing the same logical value return equal strings.
|
|
69
|
+
*
|
|
70
|
+
* Used by cache-key derivation paths (e.g. flux queryCache) so non-primitive query
|
|
71
|
+
* fields hash to a stable identifier rather than relying on enumerable own-property
|
|
72
|
+
* iteration, which is fragile for class instances.
|
|
73
|
+
*/
|
|
74
|
+
export interface Hashable {
|
|
75
|
+
/** @returns a stable, canonical string representation of the value. */
|
|
76
|
+
hash: () => string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** @returns true if the value implements primitive.Hashable, otherwise returns false. */
|
|
80
|
+
export const isHashable = (value: unknown): value is Hashable =>
|
|
81
|
+
value != null &&
|
|
82
|
+
typeof value === "object" &&
|
|
83
|
+
"hash" in value &&
|
|
84
|
+
typeof value.hash === "function";
|
|
85
|
+
|
|
64
86
|
/**
|
|
65
87
|
* Type representing zero values for each primitive type
|
|
66
88
|
*/
|
|
@@ -353,6 +353,32 @@ describe("record", () => {
|
|
|
353
353
|
const obj = { nested: { key: "value" }, arr: [1, 2, 3] };
|
|
354
354
|
expect(record.nullishToEmpty().parse(obj)).toEqual(obj);
|
|
355
355
|
});
|
|
356
|
+
|
|
357
|
+
describe("with typed key and value schemas", () => {
|
|
358
|
+
const schema = record.nullishToEmpty(z.string(), z.number());
|
|
359
|
+
|
|
360
|
+
it("should coerce null to empty object", () => {
|
|
361
|
+
expect(schema.parse(null)).toEqual({});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("should coerce undefined to empty object", () => {
|
|
365
|
+
expect(schema.parse(undefined)).toEqual({});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("should pass through populated record matching the value schema", () => {
|
|
369
|
+
expect(schema.parse({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 });
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("should reject values that don't match the value schema", () => {
|
|
373
|
+
expect(() => schema.parse({ a: "not a number" })).toThrow();
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("should preserve the inferred typed shape", () => {
|
|
377
|
+
const parsed = schema.parse({ a: 1 });
|
|
378
|
+
const value: number = parsed.a;
|
|
379
|
+
expect(value).toBe(1);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
356
382
|
});
|
|
357
383
|
|
|
358
384
|
describe("omit", () => {
|
package/src/record/record.ts
CHANGED
|
@@ -141,6 +141,14 @@ export const omit = <T, K extends keyof T>(obj: T, ...keys: K[]): Omit<T, K> =>
|
|
|
141
141
|
return result;
|
|
142
142
|
};
|
|
143
143
|
|
|
144
|
+
export interface NullishToEmpty {
|
|
145
|
+
(): z.ZodType<Unknown>;
|
|
146
|
+
<K extends z.ZodType<Key>, V extends z.ZodType>(
|
|
147
|
+
key: K,
|
|
148
|
+
value: V,
|
|
149
|
+
): z.ZodType<Record<z.infer<K>, z.infer<V>>>;
|
|
150
|
+
}
|
|
151
|
+
|
|
144
152
|
/**
|
|
145
153
|
* For required JSON/record fields: coerces null/undefined to empty object {}.
|
|
146
154
|
* Use when the record must always be present and iterable.
|
|
@@ -149,9 +157,16 @@ export const omit = <T, K extends keyof T>(obj: T, ...keys: K[]): Omit<T, K> =>
|
|
|
149
157
|
* - undefined → {}
|
|
150
158
|
* - {} → {}
|
|
151
159
|
* - {data} → {data}
|
|
160
|
+
*
|
|
161
|
+
* Without arguments the value type is unknown. Pass key and value zod schemas
|
|
162
|
+
* to type the resulting record (e.g. for `map<string, Color>` fields).
|
|
152
163
|
*/
|
|
153
|
-
export const nullishToEmpty = (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
export const nullishToEmpty = ((key?: z.ZodType, value?: z.ZodType) => {
|
|
165
|
+
if (key === undefined || value === undefined)
|
|
166
|
+
return z
|
|
167
|
+
.union([z.null().transform<Unknown>(() => ({})), unknownZ()])
|
|
168
|
+
.default(() => ({}));
|
|
169
|
+
return z
|
|
170
|
+
.union([z.null().transform(() => ({})), z.record(key as z.ZodType<Key>, value)])
|
|
171
|
+
.default(() => ({}));
|
|
172
|
+
}) as NullishToEmpty;
|
|
@@ -0,0 +1,10 @@
|
|
|
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 * as require from "@/require/require";
|
|
@@ -0,0 +1,10 @@
|
|
|
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 type Require<T, K extends keyof T> = Pick<Required<T>, K> & Omit<T, K>;
|