@synnaxlabs/x 0.46.2 → 0.48.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 +18 -18
- package/dist/{bounds-DeUXrllt.js → bounds-4BWKPqaP.js} +1 -4
- package/dist/bounds.js +1 -1
- package/dist/{index-C452Pas0.js → compare-Bnx9CdjS.js} +37 -47
- package/dist/compare-GPoFaKRW.cjs +1 -0
- package/dist/compare.cjs +1 -1
- package/dist/compare.js +34 -2
- package/dist/eslint.config.d.ts +3 -0
- package/dist/eslint.config.d.ts.map +1 -0
- package/dist/{index-D4NCYiQB.js → index-Bv029kh3.js} +2 -2
- package/dist/{index-udOjA9d-.cjs → index-CqisIWWC.cjs} +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.js +489 -465
- package/dist/{scale-BBWhTUqJ.js → scale-DJCMZbfU.js} +1 -1
- package/dist/scale.js +1 -1
- package/dist/series-B7l2au4y.cjs +6 -0
- package/dist/{series-Clbw-fZI.js → series-TpAaBlEg.js} +172 -145
- package/dist/spatial.js +2 -2
- package/dist/src/array/nullable.d.ts.map +1 -1
- package/dist/src/compare/binary.d.ts +25 -0
- package/dist/src/compare/binary.d.ts.map +1 -0
- package/dist/src/compare/binary.spec.d.ts +2 -0
- package/dist/src/compare/binary.spec.d.ts.map +1 -0
- package/dist/src/compare/external.d.ts +3 -0
- package/dist/src/compare/external.d.ts.map +1 -0
- package/dist/src/compare/index.d.ts +1 -1
- package/dist/src/compare/index.d.ts.map +1 -1
- package/dist/src/csv/csv.d.ts +11 -0
- package/dist/src/csv/csv.d.ts.map +1 -0
- package/dist/src/csv/csv.spec.d.ts +2 -0
- package/dist/src/csv/csv.spec.d.ts.map +1 -0
- package/dist/src/csv/index.d.ts +2 -0
- package/dist/src/csv/index.d.ts.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/migrate/migrate.d.ts +1 -1
- package/dist/src/migrate/migrate.d.ts.map +1 -1
- package/dist/src/spatial/bounds/bounds.d.ts.map +1 -1
- package/dist/src/spatial/box/box.d.ts +5 -5
- package/dist/src/spatial/box/box.d.ts.map +1 -1
- package/dist/src/spatial/scale/scale.d.ts +6 -6
- package/dist/src/spatial/scale/scale.d.ts.map +1 -1
- package/dist/src/telem/telem.d.ts +14 -0
- package/dist/src/telem/telem.d.ts.map +1 -1
- package/dist/telem.cjs +1 -1
- package/dist/telem.js +1 -1
- package/dist/unique.cjs +1 -1
- package/dist/unique.js +1 -1
- package/package.json +9 -9
- package/src/array/nullable.ts +9 -0
- package/src/compare/binary.spec.ts +308 -0
- package/src/compare/binary.ts +50 -0
- package/src/compare/external.ts +11 -0
- package/src/compare/index.ts +1 -1
- package/src/csv/csv.spec.ts +28 -0
- package/src/csv/csv.ts +26 -0
- package/src/csv/index.ts +10 -0
- package/src/index.ts +1 -0
- package/src/jsonrpc/jsonrpc.spec.ts +9 -0
- package/src/math/round.spec.ts +2 -1
- package/src/migrate/migrate.spec.ts +238 -0
- package/src/migrate/migrate.ts +62 -4
- package/src/spatial/bounds/bounds.spec.ts +1 -1
- package/src/spatial/bounds/bounds.ts +9 -12
- package/src/spatial/box/box.spec.ts +3 -3
- package/src/spatial/box/box.ts +5 -5
- package/src/spatial/dimensions/dimensions.spec.ts +1 -1
- package/src/spatial/direction/direction.spec.ts +1 -1
- package/src/spatial/location/location.spec.ts +1 -1
- package/src/spatial/scale/scale.spec.ts +1 -1
- package/src/spatial/scale/scale.ts +7 -7
- package/src/telem/telem.spec.ts +87 -0
- package/src/telem/telem.ts +48 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/index-xaxa1hoa.cjs +0 -1
- package/dist/series-B2zqvP8A.cjs +0 -6
- /package/{eslint.config.js → eslint.config.ts} +0 -0
|
@@ -56,6 +56,59 @@ const migrations: migrate.Migrations = {
|
|
|
56
56
|
"1.0.0": migrateV2,
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
+
describe("semVerZ", () => {
|
|
60
|
+
describe("valid versions", () => {
|
|
61
|
+
it("should accept standard semver format", () => {
|
|
62
|
+
expect(() => migrate.semVerZ.parse("1.0.0")).not.toThrow();
|
|
63
|
+
expect(() => migrate.semVerZ.parse("0.0.0")).not.toThrow();
|
|
64
|
+
expect(() => migrate.semVerZ.parse("99.99.99")).not.toThrow();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should accept pre-release versions with single identifier", () => {
|
|
68
|
+
expect(() => migrate.semVerZ.parse("1.0.0-alpha")).not.toThrow();
|
|
69
|
+
expect(() => migrate.semVerZ.parse("1.0.0-beta")).not.toThrow();
|
|
70
|
+
expect(() => migrate.semVerZ.parse("1.0.0-rc")).not.toThrow();
|
|
71
|
+
expect(() => migrate.semVerZ.parse("0.48.0-rc")).not.toThrow();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should accept pre-release versions with numeric identifiers", () => {
|
|
75
|
+
expect(() => migrate.semVerZ.parse("1.0.0-1")).not.toThrow();
|
|
76
|
+
expect(() => migrate.semVerZ.parse("1.0.0-0")).not.toThrow();
|
|
77
|
+
expect(() => migrate.semVerZ.parse("1.0.0-99")).not.toThrow();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should accept pre-release versions with multiple identifiers", () => {
|
|
81
|
+
expect(() => migrate.semVerZ.parse("1.0.0-alpha.1")).not.toThrow();
|
|
82
|
+
expect(() => migrate.semVerZ.parse("1.0.0-rc.1")).not.toThrow();
|
|
83
|
+
expect(() => migrate.semVerZ.parse("1.0.0-beta.2.3")).not.toThrow();
|
|
84
|
+
expect(() => migrate.semVerZ.parse("1.0.0-0.3.7")).not.toThrow();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should accept pre-release versions with hyphens", () => {
|
|
88
|
+
expect(() => migrate.semVerZ.parse("1.0.0-x-beta")).not.toThrow();
|
|
89
|
+
expect(() => migrate.semVerZ.parse("1.0.0-alpha-beta")).not.toThrow();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("invalid versions", () => {
|
|
94
|
+
it("should reject versions without patch", () => {
|
|
95
|
+
expect(() => migrate.semVerZ.parse("1.0")).toThrow();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should reject versions without minor", () => {
|
|
99
|
+
expect(() => migrate.semVerZ.parse("1")).toThrow();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should reject versions with build metadata (not supported)", () => {
|
|
103
|
+
expect(() => migrate.semVerZ.parse("1.0.0+build")).toThrow();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should reject versions with empty pre-release", () => {
|
|
107
|
+
expect(() => migrate.semVerZ.parse("1.0.0-")).toThrow();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
59
112
|
describe("compareSemVer", () => {
|
|
60
113
|
it("should return true when the major version is higher", () => {
|
|
61
114
|
expect(migrate.compareSemVer("1.0.0", "0.0.0")).toBeGreaterThan(0);
|
|
@@ -139,6 +192,98 @@ describe("compareSemVer", () => {
|
|
|
139
192
|
).toBeLessThan(0);
|
|
140
193
|
});
|
|
141
194
|
});
|
|
195
|
+
|
|
196
|
+
describe("pre-release versions", () => {
|
|
197
|
+
it("should consider release version newer than pre-release", () => {
|
|
198
|
+
expect(migrate.compareSemVer("1.0.0", "1.0.0-rc")).toBeGreaterThan(0);
|
|
199
|
+
expect(migrate.compareSemVer("1.0.0", "1.0.0-alpha")).toBeGreaterThan(0);
|
|
200
|
+
expect(migrate.compareSemVer("1.0.0", "1.0.0-beta")).toBeGreaterThan(0);
|
|
201
|
+
expect(migrate.compareSemVer("0.48.0", "0.48.0-rc")).toBeGreaterThan(0);
|
|
202
|
+
expect(migrate.semVerNewer("1.0.0", "1.0.0-rc")).toBe(true);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("should consider pre-release version older than release", () => {
|
|
206
|
+
expect(migrate.compareSemVer("1.0.0-rc", "1.0.0")).toBeLessThan(0);
|
|
207
|
+
expect(migrate.compareSemVer("1.0.0-alpha", "1.0.0")).toBeLessThan(0);
|
|
208
|
+
expect(migrate.compareSemVer("0.48.0-rc", "0.48.0")).toBeLessThan(0);
|
|
209
|
+
expect(migrate.semVerOlder("1.0.0-rc", "1.0.0")).toBe(true);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("should compare pre-release versions lexicographically", () => {
|
|
213
|
+
expect(migrate.compareSemVer("1.0.0-alpha", "1.0.0-beta")).toBeLessThan(0);
|
|
214
|
+
expect(migrate.compareSemVer("1.0.0-beta", "1.0.0-rc")).toBeLessThan(0);
|
|
215
|
+
expect(migrate.compareSemVer("1.0.0-rc", "1.0.0-alpha")).toBeGreaterThan(0);
|
|
216
|
+
expect(migrate.semVerNewer("1.0.0-rc", "1.0.0-alpha")).toBe(true);
|
|
217
|
+
expect(migrate.semVerOlder("1.0.0-alpha", "1.0.0-beta")).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("should compare numeric pre-release identifiers numerically", () => {
|
|
221
|
+
expect(migrate.compareSemVer("1.0.0-1", "1.0.0-2")).toBeLessThan(0);
|
|
222
|
+
expect(migrate.compareSemVer("1.0.0-2", "1.0.0-10")).toBeLessThan(0);
|
|
223
|
+
expect(migrate.compareSemVer("1.0.0-10", "1.0.0-2")).toBeGreaterThan(0);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("should compare pre-release versions with multiple identifiers", () => {
|
|
227
|
+
expect(migrate.compareSemVer("1.0.0-rc.1", "1.0.0-rc.2")).toBeLessThan(0);
|
|
228
|
+
expect(migrate.compareSemVer("1.0.0-rc.2", "1.0.0-rc.10")).toBeLessThan(0);
|
|
229
|
+
expect(migrate.compareSemVer("1.0.0-alpha.1", "1.0.0-alpha.2")).toBeLessThan(0);
|
|
230
|
+
expect(
|
|
231
|
+
migrate.compareSemVer("1.0.0-alpha.beta", "1.0.0-alpha.gamma"),
|
|
232
|
+
).toBeLessThan(0);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("should consider numeric identifiers lower than alphanumeric", () => {
|
|
236
|
+
expect(migrate.compareSemVer("1.0.0-1", "1.0.0-alpha")).toBeLessThan(0);
|
|
237
|
+
expect(migrate.compareSemVer("1.0.0-alpha", "1.0.0-1")).toBeGreaterThan(0);
|
|
238
|
+
expect(migrate.compareSemVer("1.0.0-rc.1", "1.0.0-rc.beta")).toBeLessThan(0);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("should consider longer pre-release identifiers higher precedence", () => {
|
|
242
|
+
expect(migrate.compareSemVer("1.0.0-rc", "1.0.0-rc.1")).toBeLessThan(0);
|
|
243
|
+
expect(migrate.compareSemVer("1.0.0-rc.1", "1.0.0-rc")).toBeGreaterThan(0);
|
|
244
|
+
expect(migrate.compareSemVer("1.0.0-alpha.1", "1.0.0-alpha.1.2")).toBeLessThan(0);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("should consider equal pre-release versions equal", () => {
|
|
248
|
+
expect(migrate.compareSemVer("1.0.0-rc", "1.0.0-rc")).toBe(0);
|
|
249
|
+
expect(migrate.compareSemVer("1.0.0-alpha.1", "1.0.0-alpha.1")).toBe(0);
|
|
250
|
+
expect(migrate.versionsEqual("1.0.0-rc", "1.0.0-rc")).toBe(true);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should handle complex pre-release comparison chains", () => {
|
|
254
|
+
const versions = [
|
|
255
|
+
"1.0.0-alpha",
|
|
256
|
+
"1.0.0-alpha.1",
|
|
257
|
+
"1.0.0-alpha.beta",
|
|
258
|
+
"1.0.0-beta",
|
|
259
|
+
"1.0.0-beta.2",
|
|
260
|
+
"1.0.0-beta.11",
|
|
261
|
+
"1.0.0-rc.1",
|
|
262
|
+
"1.0.0",
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
for (let i = 0; i < versions.length - 1; i++) {
|
|
266
|
+
expect(migrate.compareSemVer(versions[i], versions[i + 1])).toBeLessThan(0);
|
|
267
|
+
expect(migrate.compareSemVer(versions[i + 1], versions[i])).toBeGreaterThan(0);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("should respect checkMajor/checkMinor/checkPatch with pre-release", () => {
|
|
272
|
+
expect(
|
|
273
|
+
migrate.compareSemVer("2.0.0-rc", "1.0.0", {
|
|
274
|
+
checkMinor: false,
|
|
275
|
+
checkPatch: false,
|
|
276
|
+
}),
|
|
277
|
+
).toBeGreaterThan(0);
|
|
278
|
+
|
|
279
|
+
expect(
|
|
280
|
+
migrate.compareSemVer("1.2.0-rc", "1.1.0", {
|
|
281
|
+
checkMajor: false,
|
|
282
|
+
checkPatch: false,
|
|
283
|
+
}),
|
|
284
|
+
).toBeGreaterThan(0);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
142
287
|
});
|
|
143
288
|
|
|
144
289
|
describe("migrator", () => {
|
|
@@ -172,4 +317,97 @@ describe("migrator", () => {
|
|
|
172
317
|
})(entity);
|
|
173
318
|
expect(migrated).toEqual(entity);
|
|
174
319
|
});
|
|
320
|
+
|
|
321
|
+
describe("with pre-release versions", () => {
|
|
322
|
+
const entityV1RC = z.object({
|
|
323
|
+
version: z.literal("1.0.0-rc"),
|
|
324
|
+
title: z.string(),
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
type EntityV1RC = z.infer<typeof entityV1RC>;
|
|
328
|
+
|
|
329
|
+
const migrateV1RC = migrate.createMigration<EntityV1RC, EntityV2>({
|
|
330
|
+
name: "entity",
|
|
331
|
+
inputSchema: entityV1RC,
|
|
332
|
+
outputSchema: entityV2,
|
|
333
|
+
migrate: (entity) => ({ ...entity, version: "2.0.0", description: "" }),
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const migrationsWithRC: migrate.Migrations = {
|
|
337
|
+
"0.0.0": migrateV1,
|
|
338
|
+
"1.0.0-rc": migrateV1RC,
|
|
339
|
+
"1.0.0": migrateV2,
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
it("should migrate from pre-release version to stable", () => {
|
|
343
|
+
const entity: EntityV1RC = { version: "1.0.0-rc", title: "foo" };
|
|
344
|
+
const DEFAULT: EntityV2 = { version: "2.0.0", title: "", description: "" };
|
|
345
|
+
const migrated = migrate.migrator({
|
|
346
|
+
name: "entity",
|
|
347
|
+
migrations: migrationsWithRC,
|
|
348
|
+
def: DEFAULT,
|
|
349
|
+
})(entity);
|
|
350
|
+
expect(migrated).toEqual({ version: "2.0.0", title: "foo", description: "" });
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("should handle version sorting with pre-release correctly", () => {
|
|
354
|
+
const versions = ["0.0.0", "1.0.0-rc", "1.0.0"];
|
|
355
|
+
const sorted = versions.sort(migrate.compareSemVer);
|
|
356
|
+
expect(sorted).toEqual(["0.0.0", "1.0.0-rc", "1.0.0"]);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("should not migrate if current version is newer than pre-release target", () => {
|
|
360
|
+
const entity: EntityV1 = { version: "1.0.0", title: "foo" };
|
|
361
|
+
const DEFAULT: EntityV1RC = { version: "1.0.0-rc", title: "" };
|
|
362
|
+
const migrated = migrate.migrator({
|
|
363
|
+
name: "entity",
|
|
364
|
+
migrations: { "0.0.0": migrateV1 },
|
|
365
|
+
def: DEFAULT,
|
|
366
|
+
})(entity);
|
|
367
|
+
expect(migrated).toEqual({ version: "1.0.0", title: "foo" });
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("should migrate through multiple pre-release versions", () => {
|
|
371
|
+
interface EntityV1Alpha {
|
|
372
|
+
version: "1.0.0-alpha";
|
|
373
|
+
title: string;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
interface EntityV1Beta extends Omit<EntityV1Alpha, "version"> {
|
|
377
|
+
version: "1.0.0-beta";
|
|
378
|
+
newField: string;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const migrateAlphaToBeta = migrate.createMigration<EntityV1Alpha, EntityV1Beta>({
|
|
382
|
+
name: "entity",
|
|
383
|
+
migrate: (entity) => ({
|
|
384
|
+
...entity,
|
|
385
|
+
version: "1.0.0-beta",
|
|
386
|
+
newField: "added",
|
|
387
|
+
}),
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const migrateBetaToRC = migrate.createMigration<EntityV1Beta, EntityV1RC>({
|
|
391
|
+
name: "entity",
|
|
392
|
+
migrate: (entity) => {
|
|
393
|
+
const { newField, ...rest } = entity;
|
|
394
|
+
return { ...rest, version: "1.0.0-rc" };
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const preReleaseMigrations: migrate.Migrations = {
|
|
399
|
+
"1.0.0-alpha": migrateAlphaToBeta,
|
|
400
|
+
"1.0.0-beta": migrateBetaToRC,
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const entity: EntityV1Alpha = { version: "1.0.0-alpha", title: "test" };
|
|
404
|
+
const DEFAULT: EntityV1RC = { version: "1.0.0-rc", title: "" };
|
|
405
|
+
const migrated = migrate.migrator({
|
|
406
|
+
name: "entity",
|
|
407
|
+
migrations: preReleaseMigrations,
|
|
408
|
+
def: DEFAULT,
|
|
409
|
+
})(entity);
|
|
410
|
+
expect(migrated).toEqual({ version: "1.0.0-rc", title: "test" });
|
|
411
|
+
});
|
|
412
|
+
});
|
|
175
413
|
});
|
package/src/migrate/migrate.ts
CHANGED
|
@@ -12,7 +12,9 @@ import { z } from "zod";
|
|
|
12
12
|
import { compare } from "@/compare";
|
|
13
13
|
import { type Optional } from "@/optional";
|
|
14
14
|
|
|
15
|
-
export const semVerZ = z
|
|
15
|
+
export const semVerZ = z
|
|
16
|
+
.string()
|
|
17
|
+
.regex(/^\d+\.\d+\.\d+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/);
|
|
16
18
|
|
|
17
19
|
export type SemVer = z.infer<typeof semVerZ>;
|
|
18
20
|
|
|
@@ -34,6 +36,48 @@ export interface CompareSemVerOptions {
|
|
|
34
36
|
checkPatch?: boolean;
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Compares two pre-release identifiers according to semver spec.
|
|
41
|
+
* @param a - First pre-release identifier (without leading hyphen)
|
|
42
|
+
* @param b - Second pre-release identifier (without leading hyphen)
|
|
43
|
+
* @returns compare.LESS_THAN if a < b, compare.GREATER_THAN if a > b, compare.EQUAL if equal
|
|
44
|
+
*/
|
|
45
|
+
const comparePreRelease = (a: string, b: string): number => {
|
|
46
|
+
const aParts = a.split(".");
|
|
47
|
+
const bParts = b.split(".");
|
|
48
|
+
const maxLength = Math.max(aParts.length, bParts.length);
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < maxLength; i++) {
|
|
51
|
+
const aPart = aParts[i];
|
|
52
|
+
const bPart = bParts[i];
|
|
53
|
+
|
|
54
|
+
// A larger set of pre-release fields has higher precedence
|
|
55
|
+
if (aPart === undefined) return compare.LESS_THAN;
|
|
56
|
+
if (bPart === undefined) return compare.GREATER_THAN;
|
|
57
|
+
|
|
58
|
+
const aIsNumeric = /^\d+$/.test(aPart);
|
|
59
|
+
const bIsNumeric = /^\d+$/.test(bPart);
|
|
60
|
+
|
|
61
|
+
// Numeric identifiers always have lower precedence than non-numeric
|
|
62
|
+
if (aIsNumeric && !bIsNumeric) return compare.LESS_THAN;
|
|
63
|
+
if (!aIsNumeric && bIsNumeric) return compare.GREATER_THAN;
|
|
64
|
+
|
|
65
|
+
if (aIsNumeric && bIsNumeric) {
|
|
66
|
+
// Compare numerically
|
|
67
|
+
const aNum = parseInt(aPart);
|
|
68
|
+
const bNum = parseInt(bPart);
|
|
69
|
+
if (aNum < bNum) return compare.LESS_THAN;
|
|
70
|
+
if (aNum > bNum) return compare.GREATER_THAN;
|
|
71
|
+
} else {
|
|
72
|
+
// Compare lexically (ASCII sort order)
|
|
73
|
+
if (aPart < bPart) return compare.LESS_THAN;
|
|
74
|
+
if (aPart > bPart) return compare.GREATER_THAN;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return compare.EQUAL;
|
|
79
|
+
};
|
|
80
|
+
|
|
37
81
|
/**
|
|
38
82
|
* Compares the two semantic versions.
|
|
39
83
|
*
|
|
@@ -55,8 +99,14 @@ export const compareSemVer = ((
|
|
|
55
99
|
opts.checkPatch ??= true;
|
|
56
100
|
const semA = semVerZ.parse(a);
|
|
57
101
|
const semB = semVerZ.parse(b);
|
|
58
|
-
|
|
59
|
-
|
|
102
|
+
|
|
103
|
+
// Split version and pre-release parts
|
|
104
|
+
const [aCore, aPreRelease] = semA.split("-");
|
|
105
|
+
const [bCore, bPreRelease] = semB.split("-");
|
|
106
|
+
|
|
107
|
+
const [aMajor, aMinor, aPatch] = aCore.split(".").map(Number);
|
|
108
|
+
const [bMajor, bMinor, bPatch] = bCore.split(".").map(Number);
|
|
109
|
+
|
|
60
110
|
if (opts.checkMajor) {
|
|
61
111
|
if (aMajor < bMajor) return compare.LESS_THAN;
|
|
62
112
|
if (aMajor > bMajor) return compare.GREATER_THAN;
|
|
@@ -69,7 +119,15 @@ export const compareSemVer = ((
|
|
|
69
119
|
if (aPatch < bPatch) return compare.LESS_THAN;
|
|
70
120
|
if (aPatch > bPatch) return compare.GREATER_THAN;
|
|
71
121
|
}
|
|
72
|
-
|
|
122
|
+
|
|
123
|
+
// When major.minor.patch are equal, compare pre-release versions
|
|
124
|
+
// Version without pre-release > version with pre-release
|
|
125
|
+
if (aPreRelease === undefined && bPreRelease === undefined) return compare.EQUAL;
|
|
126
|
+
if (aPreRelease === undefined) return compare.GREATER_THAN;
|
|
127
|
+
if (bPreRelease === undefined) return compare.LESS_THAN;
|
|
128
|
+
|
|
129
|
+
// Both have pre-release, compare them
|
|
130
|
+
return comparePreRelease(aPreRelease, bPreRelease);
|
|
73
131
|
}) satisfies compare.Comparator<SemVer>;
|
|
74
132
|
|
|
75
133
|
/**
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { describe, expect, it, test } from "vitest";
|
|
11
11
|
|
|
12
12
|
import { type numeric } from "@/numeric";
|
|
13
|
-
import
|
|
13
|
+
import { bounds } from "@/spatial/bounds";
|
|
14
14
|
import { testutil } from "@/testutil";
|
|
15
15
|
|
|
16
16
|
describe("Bounds", () => {
|
|
@@ -7,7 +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 {
|
|
10
|
+
import { abs, add, equal as mathEqual, min as mathMin, sub } from "@/math/math";
|
|
11
11
|
import { type numeric } from "@/numeric";
|
|
12
12
|
import { type Bounds, bounds, type CrudeBounds } from "@/spatial/base";
|
|
13
13
|
|
|
@@ -479,7 +479,7 @@ export const buildInsertionPlan = <T extends numeric.Value>(
|
|
|
479
479
|
}
|
|
480
480
|
let deleteInBetween = upper.index - lower.index;
|
|
481
481
|
let insertInto = lower.index;
|
|
482
|
-
let removeBefore =
|
|
482
|
+
let removeBefore = sub(Number(span(_bounds[lower.index])), lower.position);
|
|
483
483
|
// If we're overlapping with the previous bound, we need to slice out one less
|
|
484
484
|
// and insert one further up.
|
|
485
485
|
if (lower.position !== 0) {
|
|
@@ -596,7 +596,7 @@ export const traverse = <T extends numeric.Value = number>(
|
|
|
596
596
|
let remainingDist = dist;
|
|
597
597
|
let currentPosition = start as number | bigint;
|
|
598
598
|
|
|
599
|
-
while (
|
|
599
|
+
while (mathEqual(remainingDist, 0) === false) {
|
|
600
600
|
// Find the bound we're currently in or adjacent to
|
|
601
601
|
const index = _bounds.findIndex((b) => {
|
|
602
602
|
if (dir > 0) return currentPosition >= b.lower && currentPosition < b.upper;
|
|
@@ -606,19 +606,16 @@ export const traverse = <T extends numeric.Value = number>(
|
|
|
606
606
|
if (index !== -1) {
|
|
607
607
|
const b = _bounds[index];
|
|
608
608
|
let distanceInBound: T;
|
|
609
|
-
if (dir > 0) distanceInBound =
|
|
610
|
-
else distanceInBound =
|
|
609
|
+
if (dir > 0) distanceInBound = sub(b.upper, currentPosition);
|
|
610
|
+
else distanceInBound = sub(currentPosition, b.lower) as T;
|
|
611
611
|
|
|
612
612
|
if (distanceInBound > (0 as T)) {
|
|
613
|
-
const moveDist =
|
|
614
|
-
currentPosition =
|
|
615
|
-
|
|
616
|
-
dir > 0 ? moveDist : -moveDist,
|
|
617
|
-
) as T;
|
|
618
|
-
remainingDist = math.sub<T>(remainingDist, dir > 0 ? moveDist : -moveDist);
|
|
613
|
+
const moveDist = mathMin(abs(remainingDist), distanceInBound);
|
|
614
|
+
currentPosition = add(currentPosition, dir > 0 ? moveDist : -moveDist) as T;
|
|
615
|
+
remainingDist = sub<T>(remainingDist, dir > 0 ? moveDist : -moveDist);
|
|
619
616
|
|
|
620
617
|
// If we've exhausted the distance, return the current position
|
|
621
|
-
if (
|
|
618
|
+
if (mathEqual(remainingDist, 0)) return currentPosition as T;
|
|
622
619
|
continue;
|
|
623
620
|
}
|
|
624
621
|
}
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
import { describe, expect, it, test } from "vitest";
|
|
11
11
|
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import type
|
|
12
|
+
import { box } from "@/spatial/box";
|
|
13
|
+
import { location } from "@/spatial/location";
|
|
14
|
+
import { type xy } from "@/spatial/xy";
|
|
15
15
|
|
|
16
16
|
describe("Box", () => {
|
|
17
17
|
describe("construction", () => {
|
package/src/spatial/box/box.ts
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
|
-
import type
|
|
13
|
-
import type
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
12
|
+
import { type bounds } from "@/spatial/bounds";
|
|
13
|
+
import { type dimensions } from "@/spatial/dimensions";
|
|
14
|
+
import { direction } from "@/spatial/direction";
|
|
15
|
+
import { location } from "@/spatial/location";
|
|
16
|
+
import { xy } from "@/spatial/xy";
|
|
17
17
|
|
|
18
18
|
const cssPos = z.union([z.number(), z.string()]);
|
|
19
19
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { describe, expect, it, test } from "vitest";
|
|
11
11
|
|
|
12
|
-
import
|
|
12
|
+
import { box } from "@/spatial/box";
|
|
13
13
|
import { Scale, XY } from "@/spatial/scale/scale";
|
|
14
14
|
|
|
15
15
|
type ScaleSpec = [name: string, scale: Scale<number>, i: number, o: number];
|
|
@@ -11,12 +11,12 @@ import { z } from "zod";
|
|
|
11
11
|
|
|
12
12
|
import { clamp } from "@/clamp/clamp";
|
|
13
13
|
import { type numeric } from "@/numeric";
|
|
14
|
-
import
|
|
14
|
+
import { bounds } from "@/spatial/bounds";
|
|
15
|
+
import { box } from "@/spatial/box";
|
|
15
16
|
import { type Box, isBox } from "@/spatial/box/box";
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import * as xy from "@/spatial/xy/xy";
|
|
17
|
+
import { type dimensions } from "@/spatial/dimensions";
|
|
18
|
+
import { location } from "@/spatial/location";
|
|
19
|
+
import { xy } from "@/spatial/xy";
|
|
20
20
|
|
|
21
21
|
export const crudeXYTransform = z.object({ offset: xy.crudeZ, scale: xy.crudeZ });
|
|
22
22
|
export type XYTransformT = z.infer<typeof crudeXYTransform>;
|
|
@@ -449,7 +449,7 @@ export class XY {
|
|
|
449
449
|
return new XY().magnify(xy);
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
-
static scale(box:
|
|
452
|
+
static scale(box: dimensions.Dimensions | Box): XY {
|
|
453
453
|
return new XY().scale(box);
|
|
454
454
|
}
|
|
455
455
|
|
|
@@ -484,7 +484,7 @@ export class XY {
|
|
|
484
484
|
return next;
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
-
scale(b: Box |
|
|
487
|
+
scale(b: Box | dimensions.Dimensions): XY {
|
|
488
488
|
const next = this.copy();
|
|
489
489
|
if (isBox(b)) {
|
|
490
490
|
const prevRoot = this.currRoot;
|
package/src/telem/telem.spec.ts
CHANGED
|
@@ -98,6 +98,55 @@ describe("TimeStamp", () => {
|
|
|
98
98
|
).toBe(true);
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
describe("datetime-local format parsing", () => {
|
|
102
|
+
test("should parse as UTC by default", () => {
|
|
103
|
+
const ts = new TimeStamp("2025-11-03T17:44:45.500");
|
|
104
|
+
const utcTS = new TimeStamp("2025-11-03T17:44:45.500Z");
|
|
105
|
+
|
|
106
|
+
expect(ts.valueOf()).toEqual(utcTS.valueOf());
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("should handle 1-digit milliseconds with default UTC", () => {
|
|
110
|
+
const ts = new TimeStamp("2025-11-03T17:44:45.5");
|
|
111
|
+
expect(ts.millisecond).toBe(500);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("should handle 1-digit milliseconds with local", () => {
|
|
115
|
+
const ts = new TimeStamp("2025-11-03T17:44:45.5", "local");
|
|
116
|
+
expect(ts.millisecond).toBe(500);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("should handle 2-digit milliseconds", () => {
|
|
120
|
+
const ts = new TimeStamp("2025-11-03T17:44:45.50");
|
|
121
|
+
expect(ts.millisecond).toBe(500);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("should handle 3-digit milliseconds", () => {
|
|
125
|
+
const ts = new TimeStamp("2025-11-03T17:44:45.809");
|
|
126
|
+
expect(ts.millisecond).toBe(809);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("should handle 810 milliseconds", () => {
|
|
130
|
+
const ts = new TimeStamp("2025-11-03T17:44:45.810");
|
|
131
|
+
expect(ts.millisecond).toBeGreaterThanOrEqual(809);
|
|
132
|
+
expect(ts.millisecond).toBeLessThanOrEqual(810);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("should handle datetime without milliseconds", () => {
|
|
136
|
+
const ts = new TimeStamp("2025-11-03T17:44:45");
|
|
137
|
+
expect(ts.millisecond).toBe(0);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("should round-trip when using local tzInfo", () => {
|
|
141
|
+
const input = "2025-11-03T17:44:45.809";
|
|
142
|
+
const ts1 = new TimeStamp(input, "local");
|
|
143
|
+
const output = ts1.toString("ISO", "local").slice(0, -1);
|
|
144
|
+
const ts2 = new TimeStamp(output, "local");
|
|
145
|
+
|
|
146
|
+
expect(ts1.valueOf()).toEqual(ts2.valueOf());
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
101
150
|
test("construct from date", () => {
|
|
102
151
|
const ts = new TimeStamp([2021, 1, 1], "UTC");
|
|
103
152
|
expect(ts.date().getUTCFullYear()).toEqual(2021);
|
|
@@ -734,6 +783,44 @@ describe("TimeStamp", () => {
|
|
|
734
783
|
});
|
|
735
784
|
});
|
|
736
785
|
});
|
|
786
|
+
|
|
787
|
+
describe("formatBySpan", () => {
|
|
788
|
+
test("should return 'shortDate' for spans >= 30 days", () => {
|
|
789
|
+
const ts = new TimeStamp([2022, 12, 15], "UTC");
|
|
790
|
+
const span = TimeSpan.days(30);
|
|
791
|
+
expect(ts.formatBySpan(span)).toBe("shortDate");
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
test("should return 'dateTime' for spans >= 1 day", () => {
|
|
795
|
+
const ts = new TimeStamp([2022, 12, 15], "UTC");
|
|
796
|
+
const span = TimeSpan.days(1);
|
|
797
|
+
expect(ts.formatBySpan(span)).toBe("dateTime");
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
test("should return 'time' for spans >= 1 hour", () => {
|
|
801
|
+
const ts = new TimeStamp([2022, 12, 15], "UTC");
|
|
802
|
+
const span = TimeSpan.hours(1);
|
|
803
|
+
expect(ts.formatBySpan(span)).toBe("time");
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
test("should return 'preciseTime' for spans >= 1 second", () => {
|
|
807
|
+
const ts = new TimeStamp([2022, 12, 15], "UTC");
|
|
808
|
+
const span = TimeSpan.seconds(1);
|
|
809
|
+
expect(ts.formatBySpan(span)).toBe("preciseTime");
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
test("should return 'ISOTime' for spans < 1 second", () => {
|
|
813
|
+
const ts = new TimeStamp([2022, 12, 15], "UTC");
|
|
814
|
+
const span = TimeSpan.milliseconds(500);
|
|
815
|
+
expect(ts.formatBySpan(span)).toBe("ISOTime");
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
test("should work with very small spans", () => {
|
|
819
|
+
const ts = new TimeStamp([2022, 12, 15], "UTC");
|
|
820
|
+
const span = TimeSpan.microseconds(100);
|
|
821
|
+
expect(ts.formatBySpan(span)).toBe("ISOTime");
|
|
822
|
+
});
|
|
823
|
+
});
|
|
737
824
|
});
|
|
738
825
|
|
|
739
826
|
describe("TimeSpan", () => {
|