@cavelang/core 0.20.0 → 0.27.3
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/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/time.d.ts +97 -0
- package/dist/src/time.d.ts.map +1 -0
- package/dist/src/time.js +209 -0
- package/dist/src/time.js.map +1 -0
- package/dist/src/uuidv7.d.ts.map +1 -1
- package/dist/src/uuidv7.js +3 -2
- package/dist/src/uuidv7.js.map +1 -1
- package/dist/src/value.d.ts +38 -5
- package/dist/src/value.d.ts.map +1 -1
- package/dist/src/value.js +103 -3
- package/dist/src/value.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/time.ts +236 -0
- package/src/uuidv7.ts +3 -3
- package/src/value.ts +133 -9
package/dist/src/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export * as Entity from './entity.ts';
|
|
|
16
16
|
export * as Key from './key.ts';
|
|
17
17
|
export * as Multiplier from './multiplier.ts';
|
|
18
18
|
export * as Tag from './tag.ts';
|
|
19
|
+
export * as Time from './time.ts';
|
|
19
20
|
export * as Uncertainty from './uncertainty.ts';
|
|
20
21
|
export * as Uuidv7 from './uuidv7.ts';
|
|
21
22
|
export * as Value from './value.ts';
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAC/C,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAC/C,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA"}
|
package/dist/src/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export * as Entity from "./entity.js";
|
|
|
16
16
|
export * as Key from "./key.js";
|
|
17
17
|
export * as Multiplier from "./multiplier.js";
|
|
18
18
|
export * as Tag from "./tag.js";
|
|
19
|
+
export * as Time from "./time.js";
|
|
19
20
|
export * as Uncertainty from "./uncertainty.js";
|
|
20
21
|
export * as Uuidv7 from "./uuidv7.js";
|
|
21
22
|
export * as Value from "./value.js";
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAC/C,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAC/C,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid time — periods, ranges, instants (spec §32).
|
|
3
|
+
*
|
|
4
|
+
* A date-like context names a calendar *period*, read as a UTC interval
|
|
5
|
+
* `[start, end)`: `@2025` the year, `@2026-04` the month, `@2026-04-10`
|
|
6
|
+
* the day, `@2026-Q1` / `@2026-H2` the quarter/half, `@2026-W15` the ISO
|
|
7
|
+
* week. A *range* context joins two points with `..` — `@2025..2028`,
|
|
8
|
+
* open-ended `@..2025` / `@2026..` — and covers whole periods at both
|
|
9
|
+
* ends. Ranges are lexically ordinary contexts (spec §6.1 already admits
|
|
10
|
+
* `.` in context atoms); this module is the semantic pass that reads
|
|
11
|
+
* them, exactly like `REVERSE` declarations are ordinary claims read
|
|
12
|
+
* semantically (spec §5.5).
|
|
13
|
+
*
|
|
14
|
+
* These are *valid-time* semantics — when a claim applies in the world —
|
|
15
|
+
* independent of transaction time (when the store learned it, spec §9.1,
|
|
16
|
+
* §12.3). `cave query --at` filters and interpolates on this axis.
|
|
17
|
+
*/
|
|
18
|
+
/** Calendar period as a UTC interval `[start, end)`, in ms since epoch. */
|
|
19
|
+
export type Period = {
|
|
20
|
+
readonly start: number;
|
|
21
|
+
readonly end: number;
|
|
22
|
+
};
|
|
23
|
+
export type t = Period;
|
|
24
|
+
/** How a context reads as time: a point period, or a `..` range. */
|
|
25
|
+
export type TimeContext = {
|
|
26
|
+
readonly kind: 'point';
|
|
27
|
+
readonly period: Period;
|
|
28
|
+
} | {
|
|
29
|
+
readonly kind: 'range';
|
|
30
|
+
readonly start?: Period;
|
|
31
|
+
readonly end?: Period;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Parses a date-like time point as the calendar period it names:
|
|
35
|
+
* `2025`, `2026-04`, `2026-04-10`, `2026-Q1`, `2026-H2`, `2026-W15`.
|
|
36
|
+
* @returns `undefined` when `text` is not a time point (including
|
|
37
|
+
* out-of-calendar dates like `2026-02-30`).
|
|
38
|
+
*/
|
|
39
|
+
export declare const parsePeriod: (text: string) => undefined | Period;
|
|
40
|
+
/**
|
|
41
|
+
* Parses a `A..B` / `..B` / `A..` time range. The end point of a closed
|
|
42
|
+
* range may abbreviate by dropping *leading* numeric segments, inherited
|
|
43
|
+
* from the start point (spec §32.2): `2026-04-10..04-11` reads as
|
|
44
|
+
* `2026-04-10..2026-04-11`, `2026-04-10..11` likewise. `Q`/`H`/`W`
|
|
45
|
+
* points are always written in full. A closed range must be non-empty
|
|
46
|
+
* (`2028..2025` is not a time range).
|
|
47
|
+
* @returns `undefined` when `text` is not a range.
|
|
48
|
+
*/
|
|
49
|
+
export declare const parseRange: (text: string) => undefined | {
|
|
50
|
+
start?: Period;
|
|
51
|
+
end?: Period;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Reads a context as time, when it is one (spec §32.2): a bare or
|
|
55
|
+
* `time:`-prefixed date-like point or `..` range. Every other context —
|
|
56
|
+
* `production`, `src:filing`, `auth.ts:42` — is opaque and returns
|
|
57
|
+
* `undefined`; a context that fails to parse as time is opaque too, the
|
|
58
|
+
* robust-extraction default (spec §1.6).
|
|
59
|
+
*/
|
|
60
|
+
export declare const ofContext: (context: string) => undefined | TimeContext;
|
|
61
|
+
/**
|
|
62
|
+
* Parses a query anchor into an instant (ms since epoch): a date-like
|
|
63
|
+
* period reads as its *start* instant (`2026` is 2026-01-01T00:00:00Z —
|
|
64
|
+
* name the finer period to anchor inside one), a `T` timestamp reads
|
|
65
|
+
* exactly. @returns `undefined` when `text` is neither.
|
|
66
|
+
*/
|
|
67
|
+
export declare const parseInstant: (text: string) => undefined | number;
|
|
68
|
+
/** @returns whether a time context covers the instant (spec §32.4). */
|
|
69
|
+
export declare const covers: (context: TimeContext, instant: number) => boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Whether a claim with these contexts applies at the instant
|
|
72
|
+
* (spec §32.4): timeless claims — no time context — always apply; a
|
|
73
|
+
* time-anchored claim applies when *any* of its time contexts covers
|
|
74
|
+
* the instant. Opaque contexts never participate.
|
|
75
|
+
*/
|
|
76
|
+
export declare const appliesAt: (contexts: readonly string[], instant: number) => boolean;
|
|
77
|
+
/**
|
|
78
|
+
* The single closed range among a claim's contexts — the interval a
|
|
79
|
+
* trajectory value interpolates over (spec §32.3). @returns `undefined`
|
|
80
|
+
* when there is none, or more than one (ambiguous — no interpolation).
|
|
81
|
+
*/
|
|
82
|
+
export declare const closedRangeOf: (contexts: readonly string[]) => undefined | {
|
|
83
|
+
start: Period;
|
|
84
|
+
end: Period;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Where an instant sits along a closed range, as the interpolation
|
|
88
|
+
* fraction in [0, 1] (spec §32.3): endpoint values anchor at the *start*
|
|
89
|
+
* instants of the periods that name them — `20B -> 40B @2025..2028` is
|
|
90
|
+
* 20B at 2025-01-01 and 40B at 2028-01-01 — and the fraction clamps to 1
|
|
91
|
+
* through the end period's tail ("40B *in* 2028" holds all of 2028).
|
|
92
|
+
*/
|
|
93
|
+
export declare const fractionAt: (range: {
|
|
94
|
+
start: Period;
|
|
95
|
+
end: Period;
|
|
96
|
+
}, instant: number) => number;
|
|
97
|
+
//# sourceMappingURL=time.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/time.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,2EAA2E;AAC3E,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,CAAC,GAAG,MAAM,CAAA;AAEtB,oEAAoE;AACpE,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAgB9E;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,SAAS,GAAG,MAwCtD,CAAA;AAID;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,SAAS,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAiCnF,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,GAAI,SAAS,MAAM,KAAG,SAAS,GAAG,WAQvD,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,SAAS,GAAG,MAUvD,CAAA;AAED,uEAAuE;AACvE,eAAO,MAAM,MAAM,GAAI,SAAS,WAAW,EAAE,SAAS,MAAM,KAAG,OAIH,CAAA;AAE5D;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,UAAU,SAAS,MAAM,EAAE,EAAE,SAAS,MAAM,KAAG,OAaxE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,UAAU,SAAS,MAAM,EAAE,KAAG,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAanG,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,SAAS,MAAM,KAAG,MAMnF,CAAA"}
|
package/dist/src/time.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid time — periods, ranges, instants (spec §32).
|
|
3
|
+
*
|
|
4
|
+
* A date-like context names a calendar *period*, read as a UTC interval
|
|
5
|
+
* `[start, end)`: `@2025` the year, `@2026-04` the month, `@2026-04-10`
|
|
6
|
+
* the day, `@2026-Q1` / `@2026-H2` the quarter/half, `@2026-W15` the ISO
|
|
7
|
+
* week. A *range* context joins two points with `..` — `@2025..2028`,
|
|
8
|
+
* open-ended `@..2025` / `@2026..` — and covers whole periods at both
|
|
9
|
+
* ends. Ranges are lexically ordinary contexts (spec §6.1 already admits
|
|
10
|
+
* `.` in context atoms); this module is the semantic pass that reads
|
|
11
|
+
* them, exactly like `REVERSE` declarations are ordinary claims read
|
|
12
|
+
* semantically (spec §5.5).
|
|
13
|
+
*
|
|
14
|
+
* These are *valid-time* semantics — when a claim applies in the world —
|
|
15
|
+
* independent of transaction time (when the store learned it, spec §9.1,
|
|
16
|
+
* §12.3). `cave query --at` filters and interpolates on this axis.
|
|
17
|
+
*/
|
|
18
|
+
const dayMs = 86_400_000;
|
|
19
|
+
const utcDay = (year, month, day) => Date.UTC(year, month - 1, day);
|
|
20
|
+
/** Monday starting ISO week `week` of `year` (ISO 8601: week 1 contains Jan 4). */
|
|
21
|
+
const isoWeekStart = (year, week) => {
|
|
22
|
+
const jan4 = utcDay(year, 1, 4);
|
|
23
|
+
const monday = jan4 - ((new Date(jan4).getUTCDay() + 6) % 7) * dayMs;
|
|
24
|
+
return monday + (week - 1) * 7 * dayMs;
|
|
25
|
+
};
|
|
26
|
+
const periodRe = /^(\d{4})(?:-(?:Q([1-4])|H([1-2])|W(\d{1,2})|(\d{2})(?:-(\d{2}))?))?$/;
|
|
27
|
+
/**
|
|
28
|
+
* Parses a date-like time point as the calendar period it names:
|
|
29
|
+
* `2025`, `2026-04`, `2026-04-10`, `2026-Q1`, `2026-H2`, `2026-W15`.
|
|
30
|
+
* @returns `undefined` when `text` is not a time point (including
|
|
31
|
+
* out-of-calendar dates like `2026-02-30`).
|
|
32
|
+
*/
|
|
33
|
+
export const parsePeriod = (text) => {
|
|
34
|
+
const match = periodRe.exec(text);
|
|
35
|
+
if (!match) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const year = Number(match[1]);
|
|
39
|
+
const [, , quarter, half, week, month, day] = match;
|
|
40
|
+
if (quarter !== undefined) {
|
|
41
|
+
const q = Number(quarter);
|
|
42
|
+
return { start: utcDay(year, (q - 1) * 3 + 1, 1), end: utcDay(year, q * 3 + 1, 1) };
|
|
43
|
+
}
|
|
44
|
+
if (half !== undefined) {
|
|
45
|
+
const h = Number(half);
|
|
46
|
+
return { start: utcDay(year, (h - 1) * 6 + 1, 1), end: utcDay(year, h * 6 + 1, 1) };
|
|
47
|
+
}
|
|
48
|
+
if (week !== undefined) {
|
|
49
|
+
const w = Number(week);
|
|
50
|
+
if (w < 1 || w > 53) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const start = isoWeekStart(year, w);
|
|
54
|
+
return { start, end: start + 7 * dayMs };
|
|
55
|
+
}
|
|
56
|
+
if (month !== undefined) {
|
|
57
|
+
const m = Number(month);
|
|
58
|
+
if (m < 1 || m > 12) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
if (day !== undefined) {
|
|
62
|
+
const d = Number(day);
|
|
63
|
+
const start = utcDay(year, m, d);
|
|
64
|
+
const date = new Date(start);
|
|
65
|
+
if (date.getUTCMonth() !== m - 1 || date.getUTCDate() !== d) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
return { start, end: start + dayMs };
|
|
69
|
+
}
|
|
70
|
+
return { start: utcDay(year, m, 1), end: utcDay(year, m + 1, 1) };
|
|
71
|
+
}
|
|
72
|
+
return { start: utcDay(year, 1, 1), end: utcDay(year + 1, 1, 1) };
|
|
73
|
+
};
|
|
74
|
+
const numericPointRe = /^[\d-]+$/;
|
|
75
|
+
/**
|
|
76
|
+
* Parses a `A..B` / `..B` / `A..` time range. The end point of a closed
|
|
77
|
+
* range may abbreviate by dropping *leading* numeric segments, inherited
|
|
78
|
+
* from the start point (spec §32.2): `2026-04-10..04-11` reads as
|
|
79
|
+
* `2026-04-10..2026-04-11`, `2026-04-10..11` likewise. `Q`/`H`/`W`
|
|
80
|
+
* points are always written in full. A closed range must be non-empty
|
|
81
|
+
* (`2028..2025` is not a time range).
|
|
82
|
+
* @returns `undefined` when `text` is not a range.
|
|
83
|
+
*/
|
|
84
|
+
export const parseRange = (text) => {
|
|
85
|
+
const at = text.indexOf('..');
|
|
86
|
+
if (at === -1 || text.indexOf('..', at + 2) !== -1) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const leftText = text.slice(0, at);
|
|
90
|
+
const rightText = text.slice(at + 2);
|
|
91
|
+
if (leftText === '' && rightText === '') {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
if (leftText === '') {
|
|
95
|
+
const end = parsePeriod(rightText);
|
|
96
|
+
return end === undefined ? undefined : { end };
|
|
97
|
+
}
|
|
98
|
+
const start = parsePeriod(leftText);
|
|
99
|
+
if (start === undefined) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
if (rightText === '') {
|
|
103
|
+
return { start };
|
|
104
|
+
}
|
|
105
|
+
let end = parsePeriod(rightText);
|
|
106
|
+
if (end === undefined && numericPointRe.test(leftText) && numericPointRe.test(rightText)) {
|
|
107
|
+
const leftSegments = leftText.split('-');
|
|
108
|
+
const rightSegments = rightText.split('-');
|
|
109
|
+
if (rightSegments.length < leftSegments.length) {
|
|
110
|
+
end = parsePeriod([...leftSegments.slice(0, leftSegments.length - rightSegments.length), ...rightSegments].join('-'));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (end === undefined || end.end <= start.start) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
return { start, end };
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Reads a context as time, when it is one (spec §32.2): a bare or
|
|
120
|
+
* `time:`-prefixed date-like point or `..` range. Every other context —
|
|
121
|
+
* `production`, `src:filing`, `auth.ts:42` — is opaque and returns
|
|
122
|
+
* `undefined`; a context that fails to parse as time is opaque too, the
|
|
123
|
+
* robust-extraction default (spec §1.6).
|
|
124
|
+
*/
|
|
125
|
+
export const ofContext = (context) => {
|
|
126
|
+
const body = context.startsWith('time:') ? context.slice('time:'.length) : context;
|
|
127
|
+
if (body.includes('..')) {
|
|
128
|
+
const range = parseRange(body);
|
|
129
|
+
return range === undefined ? undefined : { kind: 'range', ...range };
|
|
130
|
+
}
|
|
131
|
+
const period = parsePeriod(body);
|
|
132
|
+
return period === undefined ? undefined : { kind: 'point', period };
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Parses a query anchor into an instant (ms since epoch): a date-like
|
|
136
|
+
* period reads as its *start* instant (`2026` is 2026-01-01T00:00:00Z —
|
|
137
|
+
* name the finer period to anchor inside one), a `T` timestamp reads
|
|
138
|
+
* exactly. @returns `undefined` when `text` is neither.
|
|
139
|
+
*/
|
|
140
|
+
export const parseInstant = (text) => {
|
|
141
|
+
const period = parsePeriod(text);
|
|
142
|
+
if (period !== undefined) {
|
|
143
|
+
return period.start;
|
|
144
|
+
}
|
|
145
|
+
if (!text.includes('T')) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
const parsed = Date.parse(text);
|
|
149
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
150
|
+
};
|
|
151
|
+
/** @returns whether a time context covers the instant (spec §32.4). */
|
|
152
|
+
export const covers = (context, instant) => context.kind === 'point' ?
|
|
153
|
+
instant >= context.period.start && instant < context.period.end :
|
|
154
|
+
(context.start === undefined || instant >= context.start.start) &&
|
|
155
|
+
(context.end === undefined || instant < context.end.end);
|
|
156
|
+
/**
|
|
157
|
+
* Whether a claim with these contexts applies at the instant
|
|
158
|
+
* (spec §32.4): timeless claims — no time context — always apply; a
|
|
159
|
+
* time-anchored claim applies when *any* of its time contexts covers
|
|
160
|
+
* the instant. Opaque contexts never participate.
|
|
161
|
+
*/
|
|
162
|
+
export const appliesAt = (contexts, instant) => {
|
|
163
|
+
let anchored = false;
|
|
164
|
+
for (const context of contexts) {
|
|
165
|
+
const time = ofContext(context);
|
|
166
|
+
if (time === undefined) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
anchored = true;
|
|
170
|
+
if (covers(time, instant)) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return !anchored;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* The single closed range among a claim's contexts — the interval a
|
|
178
|
+
* trajectory value interpolates over (spec §32.3). @returns `undefined`
|
|
179
|
+
* when there is none, or more than one (ambiguous — no interpolation).
|
|
180
|
+
*/
|
|
181
|
+
export const closedRangeOf = (contexts) => {
|
|
182
|
+
let found;
|
|
183
|
+
for (const context of contexts) {
|
|
184
|
+
const time = ofContext(context);
|
|
185
|
+
if (time === undefined || time.kind !== 'range' || time.start === undefined || time.end === undefined) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (found !== undefined) {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
found = { start: time.start, end: time.end };
|
|
192
|
+
}
|
|
193
|
+
return found;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Where an instant sits along a closed range, as the interpolation
|
|
197
|
+
* fraction in [0, 1] (spec §32.3): endpoint values anchor at the *start*
|
|
198
|
+
* instants of the periods that name them — `20B -> 40B @2025..2028` is
|
|
199
|
+
* 20B at 2025-01-01 and 40B at 2028-01-01 — and the fraction clamps to 1
|
|
200
|
+
* through the end period's tail ("40B *in* 2028" holds all of 2028).
|
|
201
|
+
*/
|
|
202
|
+
export const fractionAt = (range, instant) => {
|
|
203
|
+
const span = range.end.start - range.start.start;
|
|
204
|
+
if (span <= 0) {
|
|
205
|
+
return 1;
|
|
206
|
+
}
|
|
207
|
+
return Math.min(1, Math.max(0, (instant - range.start.start) / span));
|
|
208
|
+
};
|
|
209
|
+
//# sourceMappingURL=time.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time.js","sourceRoot":"","sources":["../../src/time.ts"],"names":[],"mappings":"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"}
|
package/dist/src/uuidv7.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uuidv7.d.ts","sourceRoot":"","sources":["../../src/uuidv7.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"uuidv7.d.ts","sourceRoot":"","sources":["../../src/uuidv7.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH;;;;;;;GAOG;AACH,eAAO,MAAM,EAAE,GAAI,IAAI,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,UAAU,KAAG,MAmB9D,CAAA;AAKD;;;GAGG;AACH,eAAO,MAAM,IAAI,GAAI,MAAK,MAAM,MAAiB,KAAG,MAkBnD,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,IAAI,GAAI,IAAI,MAAM,KAAG,MACqB,CAAA;AAIvD,6DAA6D;AAC7D,eAAO,MAAM,EAAE,GAAI,GAAG,MAAM,KAAG,OACf,CAAA;AAMhB;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,GAAI,IAAI,MAAM,KAAG,IAUpC,CAAA"}
|
package/dist/src/uuidv7.js
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* increment a 12-bit sequence in the `rand_a` field, and a backwards system
|
|
10
10
|
* clock never produces a smaller id.
|
|
11
11
|
*/
|
|
12
|
-
import { randomFillSync } from 'node:crypto';
|
|
13
12
|
const hex = (n, digits) => n.toString(16).padStart(digits, '0');
|
|
14
13
|
/**
|
|
15
14
|
* Formats a UUIDv7 from parts — pure, for tests and deterministic imports.
|
|
@@ -60,7 +59,9 @@ export const next = (now = Date.now) => {
|
|
|
60
59
|
}
|
|
61
60
|
lastMs = ms;
|
|
62
61
|
const rand = new Uint8Array(8);
|
|
63
|
-
|
|
62
|
+
// Web Crypto is available in supported Node.js releases and browsers,
|
|
63
|
+
// keeping UUID generation portable without weakening randomness.
|
|
64
|
+
globalThis.crypto.getRandomValues(rand);
|
|
64
65
|
return at(ms, lastSeq, rand);
|
|
65
66
|
};
|
|
66
67
|
/**
|
package/dist/src/uuidv7.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uuidv7.js","sourceRoot":"","sources":["../../src/uuidv7.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,
|
|
1
|
+
{"version":3,"file":"uuidv7.js","sourceRoot":"","sources":["../../src/uuidv7.ts"],"names":[],"mappings":"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"}
|
package/dist/src/value.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Values, units and multipliers (spec §7.1).
|
|
2
|
+
* Values, units and multipliers (spec §7.1), and trajectories (spec §32.3).
|
|
3
3
|
*
|
|
4
4
|
* A value is the payload of an attribute claim (`HAS attr: value`), a metric
|
|
5
5
|
* claim (`metric IS value`), or a `+/-` uncertainty delta. Parsing keeps the
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* - `~20B USD/yr` → the same, `approx` set
|
|
12
12
|
* - `94.5%` → num 94.5, unit `%`
|
|
13
13
|
* - `20 conn` → num 20, unit `conn`
|
|
14
|
+
* - `20B -> 40B USD/yr` → trajectory, from 2e10 to 4e10, unit `USD/yr`
|
|
14
15
|
* - `2026-H2` → date-like, kept textual
|
|
15
16
|
* - `token-expiry` → atom, kept textual
|
|
16
17
|
*
|
|
@@ -18,17 +19,25 @@
|
|
|
18
19
|
* with their kind already known; use {@link ofText} / {@link ofCode}.
|
|
19
20
|
*/
|
|
20
21
|
/** How the value text was classified. */
|
|
21
|
-
export type Kind = 'number' | 'date' | 'atom' | 'text' | 'code';
|
|
22
|
+
export type Kind = 'number' | 'trajectory' | 'date' | 'atom' | 'text' | 'code';
|
|
22
23
|
export type Value = {
|
|
23
24
|
/** Exactly as written, including `~` and multiplier letter. */
|
|
24
25
|
readonly raw: string;
|
|
25
26
|
readonly kind: Kind;
|
|
26
27
|
/** `~` prefix (spec §7.1): the value is approximate. */
|
|
27
28
|
readonly approx: boolean;
|
|
28
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Normalized numeric value with multiplier expanded, when parseable.
|
|
31
|
+
* Unset for trajectories — a trajectory is not one number, so every
|
|
32
|
+
* scalar consumer (fusion, filters, σ) conservatively skips it.
|
|
33
|
+
*/
|
|
29
34
|
readonly num?: number;
|
|
30
35
|
/** Normalized unit expression (`USD/yr`, `ms`, `%`), when present. */
|
|
31
36
|
readonly unit?: string;
|
|
37
|
+
/** Trajectory start value, multiplier expanded (kind `trajectory`). */
|
|
38
|
+
readonly from?: number;
|
|
39
|
+
/** Trajectory end value, multiplier expanded (kind `trajectory`). */
|
|
40
|
+
readonly to?: number;
|
|
32
41
|
};
|
|
33
42
|
export type t = Value;
|
|
34
43
|
/** @returns `true` if `s` is a valid unit expression (`USD/yr`, `ms`, `%`). */
|
|
@@ -40,10 +49,34 @@ export declare const isUnit: (s: string) => boolean;
|
|
|
40
49
|
export declare const isDateLike: (s: string) => boolean;
|
|
41
50
|
/**
|
|
42
51
|
* Parses an unquoted value string (spec §16 `value`), classifying it as
|
|
43
|
-
* number, date or atom. The raw text (including `~` and
|
|
44
|
-
* is preserved verbatim.
|
|
52
|
+
* number, trajectory, date or atom. The raw text (including `~` and
|
|
53
|
+
* multiplier letters) is preserved verbatim.
|
|
45
54
|
*/
|
|
46
55
|
export declare const parse: (raw: string) => Value;
|
|
56
|
+
/**
|
|
57
|
+
* Plain-decimal text of a finite number. The CAVE number grammar
|
|
58
|
+
* (spec §16) has no exponent form, so `String`'s occasional `1e-7` /
|
|
59
|
+
* `1.5e+21` output is expanded to `0.0000001` / `1500000000000000000000`.
|
|
60
|
+
* The digits are JavaScript's shortest round-trip representation, so
|
|
61
|
+
* `parse(formatNumber(n)).num === n`. Every generator writing numbers
|
|
62
|
+
* into CAVE text must go through this (or emit a multiplier form built
|
|
63
|
+
* on it) — `String(n)` alone round-trips tiny and huge magnitudes as
|
|
64
|
+
* atoms. Throws on non-finite input: CAVE has no text for it.
|
|
65
|
+
*/
|
|
66
|
+
export declare const formatNumber: (n: number) => string;
|
|
67
|
+
/**
|
|
68
|
+
* Linear interpolation of a trajectory at `fraction` ∈ [0, 1], clamped
|
|
69
|
+
* (spec §32.3). @returns `undefined` for non-trajectory values.
|
|
70
|
+
*/
|
|
71
|
+
export declare const interpolate: (value: Value, fraction: number) => undefined | number;
|
|
72
|
+
/**
|
|
73
|
+
* Canonical scalar text of a trajectory at `fraction`, in the
|
|
74
|
+
* trajectory's own style (spec §32.3): multipliers re-compress when the
|
|
75
|
+
* endpoints used them, a glued unit stays glued, 4 significant digits.
|
|
76
|
+
* `20B -> 40B USD/yr` at 0.5 → `30B USD/yr`; `5ms -> 800ms` at 0.5 →
|
|
77
|
+
* `402.5ms`. @returns `undefined` for non-trajectory values.
|
|
78
|
+
*/
|
|
79
|
+
export declare const formatAt: (value: Value, fraction: number) => undefined | string;
|
|
47
80
|
/** @returns value for a double-quoted natural-language literal (spec §4.2). */
|
|
48
81
|
export declare const ofText: (text: string) => Value;
|
|
49
82
|
/** @returns value for a backticked exact code literal (spec §4.2). */
|
package/dist/src/value.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"value.d.ts","sourceRoot":"","sources":["../../src/value.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"value.d.ts","sourceRoot":"","sources":["../../src/value.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,yCAAyC;AACzC,MAAM,MAAM,IAAI,GACZ,QAAQ,GACR,YAAY,GACZ,MAAM,GACN,MAAM,GACN,MAAM,GACN,MAAM,CAAA;AAEV,MAAM,MAAM,KAAK,GAAG;IAClB,+DAA+D;IAC/D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,sEAAsE;IACtE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,uEAAuE;IACvE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,CAAC,GAAG,KAAK,CAAA;AAMrB,+EAA+E;AAC/E,eAAO,MAAM,MAAM,GAAI,GAAG,MAAM,KAAG,OACnB,CAAA;AAEhB;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,GAAG,MAAM,KAAG,OACvB,CAAA;AA8EhB;;;;GAIG;AACH,eAAO,MAAM,KAAK,GAAI,KAAK,MAAM,KAAG,KAoBnC,CAAA;AAID;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,GAAI,GAAG,MAAM,KAAG,MAcxC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,KAAK,EAAE,UAAU,MAAM,KAAG,SAAS,GAAG,MAGI,CAAA;AAE7E;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,GAAI,OAAO,KAAK,EAAE,UAAU,MAAM,KAAG,SAAS,GAAG,MAqBrE,CAAA;AAED,+EAA+E;AAC/E,eAAO,MAAM,MAAM,GAAI,MAAM,MAAM,KAAG,KACQ,CAAA;AAE9C,sEAAsE;AACtE,eAAO,MAAM,MAAM,GAAI,MAAM,MAAM,KAAG,KACQ,CAAA;AAE9C;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,KAAK,KAAG,MASrC,CAAA"}
|
package/dist/src/value.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Values, units and multipliers (spec §7.1).
|
|
2
|
+
* Values, units and multipliers (spec §7.1), and trajectories (spec §32.3).
|
|
3
3
|
*
|
|
4
4
|
* A value is the payload of an attribute claim (`HAS attr: value`), a metric
|
|
5
5
|
* claim (`metric IS value`), or a `+/-` uncertainty delta. Parsing keeps the
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* - `~20B USD/yr` → the same, `approx` set
|
|
12
12
|
* - `94.5%` → num 94.5, unit `%`
|
|
13
13
|
* - `20 conn` → num 20, unit `conn`
|
|
14
|
+
* - `20B -> 40B USD/yr` → trajectory, from 2e10 to 4e10, unit `USD/yr`
|
|
14
15
|
* - `2026-H2` → date-like, kept textual
|
|
15
16
|
* - `token-expiry` → atom, kept textual
|
|
16
17
|
*
|
|
@@ -63,14 +64,51 @@ const parseNumeric = (body) => {
|
|
|
63
64
|
}
|
|
64
65
|
return unit === undefined ? { num } : { num, unit };
|
|
65
66
|
};
|
|
67
|
+
const multiplierHeadRe = /^-?\d+(?:\.\d+)?[TBMK]$/;
|
|
68
|
+
/**
|
|
69
|
+
* Splits `body` as trajectory `from -> to [unit]` (spec §32.3). Each side
|
|
70
|
+
* parses like a scalar; the unit is shared — glued per endpoint or spaced
|
|
71
|
+
* after either — and both sides naming different units is not a
|
|
72
|
+
* trajectory. Style (glue, multipliers) is kept for {@link formatAt}.
|
|
73
|
+
*/
|
|
74
|
+
const parseTrajectory = (body) => {
|
|
75
|
+
const parts = body.split(' -> ');
|
|
76
|
+
if (parts.length !== 2 || parts[0] === '' || parts[1] === '') {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const [leftText, rightText] = parts;
|
|
80
|
+
const left = parseNumeric(leftText);
|
|
81
|
+
const right = parseNumeric(rightText);
|
|
82
|
+
if (left === undefined || right === undefined) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
if (left.unit !== undefined && right.unit !== undefined && left.unit !== right.unit) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const unit = left.unit ?? right.unit;
|
|
89
|
+
return {
|
|
90
|
+
num: left.num,
|
|
91
|
+
to: right.num,
|
|
92
|
+
...unit === undefined ? {} : { unit },
|
|
93
|
+
glued: unit !== undefined && !leftText.includes(' ') && !rightText.includes(' '),
|
|
94
|
+
multiplier: multiplierHeadRe.test(leftText.split(' ')[0]) || multiplierHeadRe.test(rightText.split(' ')[0])
|
|
95
|
+
};
|
|
96
|
+
};
|
|
66
97
|
/**
|
|
67
98
|
* Parses an unquoted value string (spec §16 `value`), classifying it as
|
|
68
|
-
* number, date or atom. The raw text (including `~` and
|
|
69
|
-
* is preserved verbatim.
|
|
99
|
+
* number, trajectory, date or atom. The raw text (including `~` and
|
|
100
|
+
* multiplier letters) is preserved verbatim.
|
|
70
101
|
*/
|
|
71
102
|
export const parse = (raw) => {
|
|
72
103
|
const approx = raw.startsWith('~');
|
|
73
104
|
const body = approx ? raw.slice(1) : raw;
|
|
105
|
+
const trajectory = body.includes(' -> ') ? parseTrajectory(body) : undefined;
|
|
106
|
+
if (trajectory !== undefined) {
|
|
107
|
+
return {
|
|
108
|
+
raw, kind: 'trajectory', approx, from: trajectory.num, to: trajectory.to,
|
|
109
|
+
...trajectory.unit === undefined ? {} : { unit: trajectory.unit }
|
|
110
|
+
};
|
|
111
|
+
}
|
|
74
112
|
const numeric = parseNumeric(body);
|
|
75
113
|
if (numeric !== undefined) {
|
|
76
114
|
return numeric.unit === undefined ?
|
|
@@ -82,6 +120,68 @@ export const parse = (raw) => {
|
|
|
82
120
|
}
|
|
83
121
|
return { raw, kind: 'atom', approx };
|
|
84
122
|
};
|
|
123
|
+
const exponentRe = /^(-?)(\d+)(?:\.(\d+))?e([+-]\d+)$/;
|
|
124
|
+
/**
|
|
125
|
+
* Plain-decimal text of a finite number. The CAVE number grammar
|
|
126
|
+
* (spec §16) has no exponent form, so `String`'s occasional `1e-7` /
|
|
127
|
+
* `1.5e+21` output is expanded to `0.0000001` / `1500000000000000000000`.
|
|
128
|
+
* The digits are JavaScript's shortest round-trip representation, so
|
|
129
|
+
* `parse(formatNumber(n)).num === n`. Every generator writing numbers
|
|
130
|
+
* into CAVE text must go through this (or emit a multiplier form built
|
|
131
|
+
* on it) — `String(n)` alone round-trips tiny and huge magnitudes as
|
|
132
|
+
* atoms. Throws on non-finite input: CAVE has no text for it.
|
|
133
|
+
*/
|
|
134
|
+
export const formatNumber = (n) => {
|
|
135
|
+
if (!Number.isFinite(n)) {
|
|
136
|
+
throw new Error(`Expected a finite number, got ${n}.`);
|
|
137
|
+
}
|
|
138
|
+
const match = exponentRe.exec(String(n));
|
|
139
|
+
if (match === null) {
|
|
140
|
+
return String(n);
|
|
141
|
+
}
|
|
142
|
+
const [, sign = '', whole = '', fraction = '', exponent = '0'] = match;
|
|
143
|
+
const digits = `${whole}${fraction}`;
|
|
144
|
+
const point = whole.length + Number(exponent);
|
|
145
|
+
return point <= 0 ? `${sign}0.${'0'.repeat(-point)}${digits}` :
|
|
146
|
+
point >= digits.length ? `${sign}${digits}${'0'.repeat(point - digits.length)}` :
|
|
147
|
+
`${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Linear interpolation of a trajectory at `fraction` ∈ [0, 1], clamped
|
|
151
|
+
* (spec §32.3). @returns `undefined` for non-trajectory values.
|
|
152
|
+
*/
|
|
153
|
+
export const interpolate = (value, fraction) => value.from === undefined || value.to === undefined ?
|
|
154
|
+
undefined :
|
|
155
|
+
value.from + (value.to - value.from) * Math.min(1, Math.max(0, fraction));
|
|
156
|
+
/**
|
|
157
|
+
* Canonical scalar text of a trajectory at `fraction`, in the
|
|
158
|
+
* trajectory's own style (spec §32.3): multipliers re-compress when the
|
|
159
|
+
* endpoints used them, a glued unit stays glued, 4 significant digits.
|
|
160
|
+
* `20B -> 40B USD/yr` at 0.5 → `30B USD/yr`; `5ms -> 800ms` at 0.5 →
|
|
161
|
+
* `402.5ms`. @returns `undefined` for non-trajectory values.
|
|
162
|
+
*/
|
|
163
|
+
export const formatAt = (value, fraction) => {
|
|
164
|
+
const num = interpolate(value, fraction);
|
|
165
|
+
if (num === undefined) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
const style = parseTrajectory(value.approx ? value.raw.slice(1) : value.raw);
|
|
169
|
+
let scaled = num;
|
|
170
|
+
let letter = '';
|
|
171
|
+
if (style?.multiplier === true) {
|
|
172
|
+
for (const m of ['T', 'B', 'M', 'K']) {
|
|
173
|
+
if (Math.abs(num) >= Multiplier.factor(m)) {
|
|
174
|
+
scaled = num / Multiplier.factor(m);
|
|
175
|
+
letter = m;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const digits = formatNumber(Number(scaled.toPrecision(4)));
|
|
181
|
+
return value.unit === undefined ?
|
|
182
|
+
`${digits}${letter}` :
|
|
183
|
+
`${digits}${letter}${style?.glued === true ? '' : ' '}${value.unit}`;
|
|
184
|
+
};
|
|
85
185
|
/** @returns value for a double-quoted natural-language literal (spec §4.2). */
|
|
86
186
|
export const ofText = (text) => ({ raw: text, kind: 'text', approx: false });
|
|
87
187
|
/** @returns value for a backticked exact code literal (spec §4.2). */
|
package/dist/src/value.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"value.js","sourceRoot":"","sources":["../../src/value.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"value.js","sourceRoot":"","sources":["../../src/value.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * as Entity from './entity.ts'
|
|
|
17
17
|
export * as Key from './key.ts'
|
|
18
18
|
export * as Multiplier from './multiplier.ts'
|
|
19
19
|
export * as Tag from './tag.ts'
|
|
20
|
+
export * as Time from './time.ts'
|
|
20
21
|
export * as Uncertainty from './uncertainty.ts'
|
|
21
22
|
export * as Uuidv7 from './uuidv7.ts'
|
|
22
23
|
export * as Value from './value.ts'
|
package/src/time.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid time — periods, ranges, instants (spec §32).
|
|
3
|
+
*
|
|
4
|
+
* A date-like context names a calendar *period*, read as a UTC interval
|
|
5
|
+
* `[start, end)`: `@2025` the year, `@2026-04` the month, `@2026-04-10`
|
|
6
|
+
* the day, `@2026-Q1` / `@2026-H2` the quarter/half, `@2026-W15` the ISO
|
|
7
|
+
* week. A *range* context joins two points with `..` — `@2025..2028`,
|
|
8
|
+
* open-ended `@..2025` / `@2026..` — and covers whole periods at both
|
|
9
|
+
* ends. Ranges are lexically ordinary contexts (spec §6.1 already admits
|
|
10
|
+
* `.` in context atoms); this module is the semantic pass that reads
|
|
11
|
+
* them, exactly like `REVERSE` declarations are ordinary claims read
|
|
12
|
+
* semantically (spec §5.5).
|
|
13
|
+
*
|
|
14
|
+
* These are *valid-time* semantics — when a claim applies in the world —
|
|
15
|
+
* independent of transaction time (when the store learned it, spec §9.1,
|
|
16
|
+
* §12.3). `cave query --at` filters and interpolates on this axis.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Calendar period as a UTC interval `[start, end)`, in ms since epoch. */
|
|
20
|
+
export type Period = {
|
|
21
|
+
readonly start: number
|
|
22
|
+
readonly end: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type t = Period
|
|
26
|
+
|
|
27
|
+
/** How a context reads as time: a point period, or a `..` range. */
|
|
28
|
+
export type TimeContext =
|
|
29
|
+
| { readonly kind: 'point', readonly period: Period }
|
|
30
|
+
| { readonly kind: 'range', readonly start?: Period, readonly end?: Period }
|
|
31
|
+
|
|
32
|
+
const dayMs = 86_400_000
|
|
33
|
+
|
|
34
|
+
const utcDay = (year: number, month: number, day: number): number =>
|
|
35
|
+
Date.UTC(year, month - 1, day)
|
|
36
|
+
|
|
37
|
+
/** Monday starting ISO week `week` of `year` (ISO 8601: week 1 contains Jan 4). */
|
|
38
|
+
const isoWeekStart = (year: number, week: number): number => {
|
|
39
|
+
const jan4 = utcDay(year, 1, 4)
|
|
40
|
+
const monday = jan4 - ((new Date(jan4).getUTCDay() + 6) % 7) * dayMs
|
|
41
|
+
return monday + (week - 1) * 7 * dayMs
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const periodRe = /^(\d{4})(?:-(?:Q([1-4])|H([1-2])|W(\d{1,2})|(\d{2})(?:-(\d{2}))?))?$/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Parses a date-like time point as the calendar period it names:
|
|
48
|
+
* `2025`, `2026-04`, `2026-04-10`, `2026-Q1`, `2026-H2`, `2026-W15`.
|
|
49
|
+
* @returns `undefined` when `text` is not a time point (including
|
|
50
|
+
* out-of-calendar dates like `2026-02-30`).
|
|
51
|
+
*/
|
|
52
|
+
export const parsePeriod = (text: string): undefined | Period => {
|
|
53
|
+
const match = periodRe.exec(text)
|
|
54
|
+
if (!match) {
|
|
55
|
+
return undefined
|
|
56
|
+
}
|
|
57
|
+
const year = Number(match[1])
|
|
58
|
+
const [, , quarter, half, week, month, day] = match
|
|
59
|
+
if (quarter !== undefined) {
|
|
60
|
+
const q = Number(quarter)
|
|
61
|
+
return { start: utcDay(year, (q - 1) * 3 + 1, 1), end: utcDay(year, q * 3 + 1, 1) }
|
|
62
|
+
}
|
|
63
|
+
if (half !== undefined) {
|
|
64
|
+
const h = Number(half)
|
|
65
|
+
return { start: utcDay(year, (h - 1) * 6 + 1, 1), end: utcDay(year, h * 6 + 1, 1) }
|
|
66
|
+
}
|
|
67
|
+
if (week !== undefined) {
|
|
68
|
+
const w = Number(week)
|
|
69
|
+
if (w < 1 || w > 53) {
|
|
70
|
+
return undefined
|
|
71
|
+
}
|
|
72
|
+
const start = isoWeekStart(year, w)
|
|
73
|
+
return { start, end: start + 7 * dayMs }
|
|
74
|
+
}
|
|
75
|
+
if (month !== undefined) {
|
|
76
|
+
const m = Number(month)
|
|
77
|
+
if (m < 1 || m > 12) {
|
|
78
|
+
return undefined
|
|
79
|
+
}
|
|
80
|
+
if (day !== undefined) {
|
|
81
|
+
const d = Number(day)
|
|
82
|
+
const start = utcDay(year, m, d)
|
|
83
|
+
const date = new Date(start)
|
|
84
|
+
if (date.getUTCMonth() !== m - 1 || date.getUTCDate() !== d) {
|
|
85
|
+
return undefined
|
|
86
|
+
}
|
|
87
|
+
return { start, end: start + dayMs }
|
|
88
|
+
}
|
|
89
|
+
return { start: utcDay(year, m, 1), end: utcDay(year, m + 1, 1) }
|
|
90
|
+
}
|
|
91
|
+
return { start: utcDay(year, 1, 1), end: utcDay(year + 1, 1, 1) }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const numericPointRe = /^[\d-]+$/
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Parses a `A..B` / `..B` / `A..` time range. The end point of a closed
|
|
98
|
+
* range may abbreviate by dropping *leading* numeric segments, inherited
|
|
99
|
+
* from the start point (spec §32.2): `2026-04-10..04-11` reads as
|
|
100
|
+
* `2026-04-10..2026-04-11`, `2026-04-10..11` likewise. `Q`/`H`/`W`
|
|
101
|
+
* points are always written in full. A closed range must be non-empty
|
|
102
|
+
* (`2028..2025` is not a time range).
|
|
103
|
+
* @returns `undefined` when `text` is not a range.
|
|
104
|
+
*/
|
|
105
|
+
export const parseRange = (text: string): undefined | { start?: Period, end?: Period } => {
|
|
106
|
+
const at = text.indexOf('..')
|
|
107
|
+
if (at === -1 || text.indexOf('..', at + 2) !== -1) {
|
|
108
|
+
return undefined
|
|
109
|
+
}
|
|
110
|
+
const leftText = text.slice(0, at)
|
|
111
|
+
const rightText = text.slice(at + 2)
|
|
112
|
+
if (leftText === '' && rightText === '') {
|
|
113
|
+
return undefined
|
|
114
|
+
}
|
|
115
|
+
if (leftText === '') {
|
|
116
|
+
const end = parsePeriod(rightText)
|
|
117
|
+
return end === undefined ? undefined : { end }
|
|
118
|
+
}
|
|
119
|
+
const start = parsePeriod(leftText)
|
|
120
|
+
if (start === undefined) {
|
|
121
|
+
return undefined
|
|
122
|
+
}
|
|
123
|
+
if (rightText === '') {
|
|
124
|
+
return { start }
|
|
125
|
+
}
|
|
126
|
+
let end = parsePeriod(rightText)
|
|
127
|
+
if (end === undefined && numericPointRe.test(leftText) && numericPointRe.test(rightText)) {
|
|
128
|
+
const leftSegments = leftText.split('-')
|
|
129
|
+
const rightSegments = rightText.split('-')
|
|
130
|
+
if (rightSegments.length < leftSegments.length) {
|
|
131
|
+
end = parsePeriod([...leftSegments.slice(0, leftSegments.length - rightSegments.length), ...rightSegments].join('-'))
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (end === undefined || end.end <= start.start) {
|
|
135
|
+
return undefined
|
|
136
|
+
}
|
|
137
|
+
return { start, end }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Reads a context as time, when it is one (spec §32.2): a bare or
|
|
142
|
+
* `time:`-prefixed date-like point or `..` range. Every other context —
|
|
143
|
+
* `production`, `src:filing`, `auth.ts:42` — is opaque and returns
|
|
144
|
+
* `undefined`; a context that fails to parse as time is opaque too, the
|
|
145
|
+
* robust-extraction default (spec §1.6).
|
|
146
|
+
*/
|
|
147
|
+
export const ofContext = (context: string): undefined | TimeContext => {
|
|
148
|
+
const body = context.startsWith('time:') ? context.slice('time:'.length) : context
|
|
149
|
+
if (body.includes('..')) {
|
|
150
|
+
const range = parseRange(body)
|
|
151
|
+
return range === undefined ? undefined : { kind: 'range', ...range }
|
|
152
|
+
}
|
|
153
|
+
const period = parsePeriod(body)
|
|
154
|
+
return period === undefined ? undefined : { kind: 'point', period }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Parses a query anchor into an instant (ms since epoch): a date-like
|
|
159
|
+
* period reads as its *start* instant (`2026` is 2026-01-01T00:00:00Z —
|
|
160
|
+
* name the finer period to anchor inside one), a `T` timestamp reads
|
|
161
|
+
* exactly. @returns `undefined` when `text` is neither.
|
|
162
|
+
*/
|
|
163
|
+
export const parseInstant = (text: string): undefined | number => {
|
|
164
|
+
const period = parsePeriod(text)
|
|
165
|
+
if (period !== undefined) {
|
|
166
|
+
return period.start
|
|
167
|
+
}
|
|
168
|
+
if (!text.includes('T')) {
|
|
169
|
+
return undefined
|
|
170
|
+
}
|
|
171
|
+
const parsed = Date.parse(text)
|
|
172
|
+
return Number.isNaN(parsed) ? undefined : parsed
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** @returns whether a time context covers the instant (spec §32.4). */
|
|
176
|
+
export const covers = (context: TimeContext, instant: number): boolean =>
|
|
177
|
+
context.kind === 'point' ?
|
|
178
|
+
instant >= context.period.start && instant < context.period.end :
|
|
179
|
+
(context.start === undefined || instant >= context.start.start) &&
|
|
180
|
+
(context.end === undefined || instant < context.end.end)
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Whether a claim with these contexts applies at the instant
|
|
184
|
+
* (spec §32.4): timeless claims — no time context — always apply; a
|
|
185
|
+
* time-anchored claim applies when *any* of its time contexts covers
|
|
186
|
+
* the instant. Opaque contexts never participate.
|
|
187
|
+
*/
|
|
188
|
+
export const appliesAt = (contexts: readonly string[], instant: number): boolean => {
|
|
189
|
+
let anchored = false
|
|
190
|
+
for (const context of contexts) {
|
|
191
|
+
const time = ofContext(context)
|
|
192
|
+
if (time === undefined) {
|
|
193
|
+
continue
|
|
194
|
+
}
|
|
195
|
+
anchored = true
|
|
196
|
+
if (covers(time, instant)) {
|
|
197
|
+
return true
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return !anchored
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* The single closed range among a claim's contexts — the interval a
|
|
205
|
+
* trajectory value interpolates over (spec §32.3). @returns `undefined`
|
|
206
|
+
* when there is none, or more than one (ambiguous — no interpolation).
|
|
207
|
+
*/
|
|
208
|
+
export const closedRangeOf = (contexts: readonly string[]): undefined | { start: Period, end: Period } => {
|
|
209
|
+
let found: undefined | { start: Period, end: Period }
|
|
210
|
+
for (const context of contexts) {
|
|
211
|
+
const time = ofContext(context)
|
|
212
|
+
if (time === undefined || time.kind !== 'range' || time.start === undefined || time.end === undefined) {
|
|
213
|
+
continue
|
|
214
|
+
}
|
|
215
|
+
if (found !== undefined) {
|
|
216
|
+
return undefined
|
|
217
|
+
}
|
|
218
|
+
found = { start: time.start, end: time.end }
|
|
219
|
+
}
|
|
220
|
+
return found
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Where an instant sits along a closed range, as the interpolation
|
|
225
|
+
* fraction in [0, 1] (spec §32.3): endpoint values anchor at the *start*
|
|
226
|
+
* instants of the periods that name them — `20B -> 40B @2025..2028` is
|
|
227
|
+
* 20B at 2025-01-01 and 40B at 2028-01-01 — and the fraction clamps to 1
|
|
228
|
+
* through the end period's tail ("40B *in* 2028" holds all of 2028).
|
|
229
|
+
*/
|
|
230
|
+
export const fractionAt = (range: { start: Period, end: Period }, instant: number): number => {
|
|
231
|
+
const span = range.end.start - range.start.start
|
|
232
|
+
if (span <= 0) {
|
|
233
|
+
return 1
|
|
234
|
+
}
|
|
235
|
+
return Math.min(1, Math.max(0, (instant - range.start.start) / span))
|
|
236
|
+
}
|
package/src/uuidv7.ts
CHANGED
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
* clock never produces a smaller id.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { randomFillSync } from 'node:crypto'
|
|
14
|
-
|
|
15
13
|
const hex = (n: number, digits: number): string =>
|
|
16
14
|
n.toString(16).padStart(digits, '0')
|
|
17
15
|
|
|
@@ -65,7 +63,9 @@ export const next = (now: () => number = Date.now): string => {
|
|
|
65
63
|
}
|
|
66
64
|
lastMs = ms
|
|
67
65
|
const rand = new Uint8Array(8)
|
|
68
|
-
|
|
66
|
+
// Web Crypto is available in supported Node.js releases and browsers,
|
|
67
|
+
// keeping UUID generation portable without weakening randomness.
|
|
68
|
+
globalThis.crypto.getRandomValues(rand)
|
|
69
69
|
return at(ms, lastSeq, rand)
|
|
70
70
|
}
|
|
71
71
|
|
package/src/value.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Values, units and multipliers (spec §7.1).
|
|
2
|
+
* Values, units and multipliers (spec §7.1), and trajectories (spec §32.3).
|
|
3
3
|
*
|
|
4
4
|
* A value is the payload of an attribute claim (`HAS attr: value`), a metric
|
|
5
5
|
* claim (`metric IS value`), or a `+/-` uncertainty delta. Parsing keeps the
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* - `~20B USD/yr` → the same, `approx` set
|
|
12
12
|
* - `94.5%` → num 94.5, unit `%`
|
|
13
13
|
* - `20 conn` → num 20, unit `conn`
|
|
14
|
+
* - `20B -> 40B USD/yr` → trajectory, from 2e10 to 4e10, unit `USD/yr`
|
|
14
15
|
* - `2026-H2` → date-like, kept textual
|
|
15
16
|
* - `token-expiry` → atom, kept textual
|
|
16
17
|
*
|
|
@@ -22,11 +23,12 @@ import * as Multiplier from './multiplier.ts'
|
|
|
22
23
|
|
|
23
24
|
/** How the value text was classified. */
|
|
24
25
|
export type Kind =
|
|
25
|
-
| 'number'
|
|
26
|
-
| '
|
|
27
|
-
| '
|
|
28
|
-
| '
|
|
29
|
-
| '
|
|
26
|
+
| 'number' // numeric, possibly with multiplier and unit
|
|
27
|
+
| 'trajectory' // two numeric endpoints: 20B -> 40B USD/yr (spec §32.3)
|
|
28
|
+
| 'date' // date-like: 2026-H2, 2026-Q1, 2026-04-10
|
|
29
|
+
| 'atom' // bare word(s): token-expiry, critical
|
|
30
|
+
| 'text' // double-quoted natural-language literal
|
|
31
|
+
| 'code' // backticked exact literal
|
|
30
32
|
|
|
31
33
|
export type Value = {
|
|
32
34
|
/** Exactly as written, including `~` and multiplier letter. */
|
|
@@ -34,10 +36,18 @@ export type Value = {
|
|
|
34
36
|
readonly kind: Kind
|
|
35
37
|
/** `~` prefix (spec §7.1): the value is approximate. */
|
|
36
38
|
readonly approx: boolean
|
|
37
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Normalized numeric value with multiplier expanded, when parseable.
|
|
41
|
+
* Unset for trajectories — a trajectory is not one number, so every
|
|
42
|
+
* scalar consumer (fusion, filters, σ) conservatively skips it.
|
|
43
|
+
*/
|
|
38
44
|
readonly num?: number
|
|
39
45
|
/** Normalized unit expression (`USD/yr`, `ms`, `%`), when present. */
|
|
40
46
|
readonly unit?: string
|
|
47
|
+
/** Trajectory start value, multiplier expanded (kind `trajectory`). */
|
|
48
|
+
readonly from?: number
|
|
49
|
+
/** Trajectory end value, multiplier expanded (kind `trajectory`). */
|
|
50
|
+
readonly to?: number
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
export type t = Value
|
|
@@ -93,14 +103,61 @@ const parseNumeric = (body: string): undefined | Numeric => {
|
|
|
93
103
|
return unit === undefined ? { num } : { num, unit }
|
|
94
104
|
}
|
|
95
105
|
|
|
106
|
+
type Trajectory = Numeric & {
|
|
107
|
+
to: number
|
|
108
|
+
/** The unit was glued to the endpoint numbers (`5ms -> 800ms`). */
|
|
109
|
+
glued: boolean
|
|
110
|
+
/** An endpoint used a multiplier letter (`20B -> 40B`). */
|
|
111
|
+
multiplier: boolean
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const multiplierHeadRe = /^-?\d+(?:\.\d+)?[TBMK]$/
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Splits `body` as trajectory `from -> to [unit]` (spec §32.3). Each side
|
|
118
|
+
* parses like a scalar; the unit is shared — glued per endpoint or spaced
|
|
119
|
+
* after either — and both sides naming different units is not a
|
|
120
|
+
* trajectory. Style (glue, multipliers) is kept for {@link formatAt}.
|
|
121
|
+
*/
|
|
122
|
+
const parseTrajectory = (body: string): undefined | Trajectory => {
|
|
123
|
+
const parts = body.split(' -> ')
|
|
124
|
+
if (parts.length !== 2 || parts[0] === '' || parts[1] === '') {
|
|
125
|
+
return undefined
|
|
126
|
+
}
|
|
127
|
+
const [leftText, rightText] = parts as [string, string]
|
|
128
|
+
const left = parseNumeric(leftText)
|
|
129
|
+
const right = parseNumeric(rightText)
|
|
130
|
+
if (left === undefined || right === undefined) {
|
|
131
|
+
return undefined
|
|
132
|
+
}
|
|
133
|
+
if (left.unit !== undefined && right.unit !== undefined && left.unit !== right.unit) {
|
|
134
|
+
return undefined
|
|
135
|
+
}
|
|
136
|
+
const unit = left.unit ?? right.unit
|
|
137
|
+
return {
|
|
138
|
+
num: left.num,
|
|
139
|
+
to: right.num,
|
|
140
|
+
...unit === undefined ? {} : { unit },
|
|
141
|
+
glued: unit !== undefined && !leftText.includes(' ') && !rightText.includes(' '),
|
|
142
|
+
multiplier: multiplierHeadRe.test(leftText.split(' ')[0]!) || multiplierHeadRe.test(rightText.split(' ')[0]!)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
96
146
|
/**
|
|
97
147
|
* Parses an unquoted value string (spec §16 `value`), classifying it as
|
|
98
|
-
* number, date or atom. The raw text (including `~` and
|
|
99
|
-
* is preserved verbatim.
|
|
148
|
+
* number, trajectory, date or atom. The raw text (including `~` and
|
|
149
|
+
* multiplier letters) is preserved verbatim.
|
|
100
150
|
*/
|
|
101
151
|
export const parse = (raw: string): Value => {
|
|
102
152
|
const approx = raw.startsWith('~')
|
|
103
153
|
const body = approx ? raw.slice(1) : raw
|
|
154
|
+
const trajectory = body.includes(' -> ') ? parseTrajectory(body) : undefined
|
|
155
|
+
if (trajectory !== undefined) {
|
|
156
|
+
return {
|
|
157
|
+
raw, kind: 'trajectory', approx, from: trajectory.num, to: trajectory.to,
|
|
158
|
+
...trajectory.unit === undefined ? {} : { unit: trajectory.unit }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
104
161
|
const numeric = parseNumeric(body)
|
|
105
162
|
if (numeric !== undefined) {
|
|
106
163
|
return numeric.unit === undefined ?
|
|
@@ -113,6 +170,73 @@ export const parse = (raw: string): Value => {
|
|
|
113
170
|
return { raw, kind: 'atom', approx }
|
|
114
171
|
}
|
|
115
172
|
|
|
173
|
+
const exponentRe = /^(-?)(\d+)(?:\.(\d+))?e([+-]\d+)$/
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Plain-decimal text of a finite number. The CAVE number grammar
|
|
177
|
+
* (spec §16) has no exponent form, so `String`'s occasional `1e-7` /
|
|
178
|
+
* `1.5e+21` output is expanded to `0.0000001` / `1500000000000000000000`.
|
|
179
|
+
* The digits are JavaScript's shortest round-trip representation, so
|
|
180
|
+
* `parse(formatNumber(n)).num === n`. Every generator writing numbers
|
|
181
|
+
* into CAVE text must go through this (or emit a multiplier form built
|
|
182
|
+
* on it) — `String(n)` alone round-trips tiny and huge magnitudes as
|
|
183
|
+
* atoms. Throws on non-finite input: CAVE has no text for it.
|
|
184
|
+
*/
|
|
185
|
+
export const formatNumber = (n: number): string => {
|
|
186
|
+
if (!Number.isFinite(n)) {
|
|
187
|
+
throw new Error(`Expected a finite number, got ${n}.`)
|
|
188
|
+
}
|
|
189
|
+
const match = exponentRe.exec(String(n))
|
|
190
|
+
if (match === null) {
|
|
191
|
+
return String(n)
|
|
192
|
+
}
|
|
193
|
+
const [, sign = '', whole = '', fraction = '', exponent = '0'] = match
|
|
194
|
+
const digits = `${whole}${fraction}`
|
|
195
|
+
const point = whole.length + Number(exponent)
|
|
196
|
+
return point <= 0 ? `${sign}0.${'0'.repeat(-point)}${digits}` :
|
|
197
|
+
point >= digits.length ? `${sign}${digits}${'0'.repeat(point - digits.length)}` :
|
|
198
|
+
`${sign}${digits.slice(0, point)}.${digits.slice(point)}`
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Linear interpolation of a trajectory at `fraction` ∈ [0, 1], clamped
|
|
203
|
+
* (spec §32.3). @returns `undefined` for non-trajectory values.
|
|
204
|
+
*/
|
|
205
|
+
export const interpolate = (value: Value, fraction: number): undefined | number =>
|
|
206
|
+
value.from === undefined || value.to === undefined ?
|
|
207
|
+
undefined :
|
|
208
|
+
value.from + (value.to - value.from) * Math.min(1, Math.max(0, fraction))
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Canonical scalar text of a trajectory at `fraction`, in the
|
|
212
|
+
* trajectory's own style (spec §32.3): multipliers re-compress when the
|
|
213
|
+
* endpoints used them, a glued unit stays glued, 4 significant digits.
|
|
214
|
+
* `20B -> 40B USD/yr` at 0.5 → `30B USD/yr`; `5ms -> 800ms` at 0.5 →
|
|
215
|
+
* `402.5ms`. @returns `undefined` for non-trajectory values.
|
|
216
|
+
*/
|
|
217
|
+
export const formatAt = (value: Value, fraction: number): undefined | string => {
|
|
218
|
+
const num = interpolate(value, fraction)
|
|
219
|
+
if (num === undefined) {
|
|
220
|
+
return undefined
|
|
221
|
+
}
|
|
222
|
+
const style = parseTrajectory(value.approx ? value.raw.slice(1) : value.raw)
|
|
223
|
+
let scaled = num
|
|
224
|
+
let letter = ''
|
|
225
|
+
if (style?.multiplier === true) {
|
|
226
|
+
for (const m of ['T', 'B', 'M', 'K'] as const) {
|
|
227
|
+
if (Math.abs(num) >= Multiplier.factor(m)) {
|
|
228
|
+
scaled = num / Multiplier.factor(m)
|
|
229
|
+
letter = m
|
|
230
|
+
break
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const digits = formatNumber(Number(scaled.toPrecision(4)))
|
|
235
|
+
return value.unit === undefined ?
|
|
236
|
+
`${digits}${letter}` :
|
|
237
|
+
`${digits}${letter}${style?.glued === true ? '' : ' '}${value.unit}`
|
|
238
|
+
}
|
|
239
|
+
|
|
116
240
|
/** @returns value for a double-quoted natural-language literal (spec §4.2). */
|
|
117
241
|
export const ofText = (text: string): Value =>
|
|
118
242
|
({ raw: text, kind: 'text', approx: false })
|