@linxiraos/pi-omptype 1.2.0 → 1.3.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/CHANGELOG.md +7 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/js/from-json-schema.js +5 -4
- package/dist/js/index.js +1 -0
- package/dist/js/local-ref.js +43 -0
- package/dist/js/typebox.js +12 -15
- package/dist/types/index.d.ts +1 -0
- package/dist/types/local-ref.d.ts +18 -0
- package/dist/types/typebox.d.ts +2 -0
- package/package.json +3 -3
- package/src/from-json-schema.ts +6 -4
- package/src/index.ts +1 -0
- package/src/local-ref.ts +41 -0
- package/src/typebox.ts +14 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.3.0] - 2026-10-18
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Fixed TypeBox numeric bounds written in exponent notation throwing during schema construction, while preserving validation and nested JSON Schema constraints ([#15925](https://github.com/can1357/oh-my-pi/pull/15925) by [@jchanghong023](https://github.com/jchanghong023)).
|
|
8
|
+
- Fixed importing local JSON Schema definition references containing URI percent encoding or JSON Pointer escapes ([#15926](https://github.com/can1357/oh-my-pi/pull/15926) by [@jchanghong023](https://github.com/jchanghong023)).
|
|
9
|
+
|
|
3
10
|
## [1.2.0] - 2026-10-12
|
|
4
11
|
|
|
5
12
|
- 版本线推进至 1.1.28;本版无独立用户可见变化。
|
package/THIRD-PARTY-NOTICES.txt
CHANGED
|
@@ -8,8 +8,8 @@ no rights in third-party code, fonts, data, or marks identified below.
|
|
|
8
8
|
PYTHON DEPENDENCY DISTRIBUTION ROLE
|
|
9
9
|
===================================
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
Uvicorn, HTTPX, Pydantic, pydantic-settings, python-dotenv, Click, and
|
|
11
|
+
zeta-rpc has no third-party runtime dependencies. robomp declares FastAPI,
|
|
12
|
+
Uvicorn, HTTPX, Pydantic, pydantic-settings, python-dotenv, Click, and zeta-rpc
|
|
13
13
|
as ordinary installer-resolved dependencies. The Python wheels do not vendor
|
|
14
14
|
those projects; each dependency remains a separately installed distribution
|
|
15
15
|
carrying its own license metadata and files. Container/system assemblers must
|
|
@@ -79,9 +79,9 @@ uutils coreutils (https://github.com/uutils/coreutils), MIT
|
|
|
79
79
|
--------------------------------------------------------------------------------
|
|
80
80
|
Covers: base32, base64, basename, cat, cksum (shared checksum machinery),
|
|
81
81
|
b2sum, md5sum, sha1sum, sha224sum, sha256sum, sha384sum, sha512sum, comm, cp,
|
|
82
|
-
cut, date, dirname, head, hostname, ln, ls, mkdir, mktemp, mv,
|
|
83
|
-
printenv, readlink, realpath, rm, seq, sort, stat, tac, tail, tee,
|
|
84
|
-
truncate, uname, uniq, wc, whoami, yes.
|
|
82
|
+
cut, date, dirname, head, hostname, ln, ls, mkdir, mktemp, mv, nl, nproc,
|
|
83
|
+
paste, printenv, readlink, realpath, rm, seq, sort, stat, tac, tail, tee,
|
|
84
|
+
touch, tr, truncate, uname, uniq, wc, whoami, yes.
|
|
85
85
|
|
|
86
86
|
Copyright (c) uutils developers
|
|
87
87
|
|
|
@@ -824,6 +824,56 @@ public licenses.
|
|
|
824
824
|
|
|
825
825
|
Creative Commons may be contacted at creativecommons.org.
|
|
826
826
|
|
|
827
|
+
-------------------------------------------------------------------------------
|
|
828
|
+
scripts/brand/fonts/ANSI Shadow.flf, scripts/brand/fonts/mini.flf
|
|
829
|
+
|
|
830
|
+
Two FIGlet font files from the standard FIGlet font distribution — the
|
|
831
|
+
figlet.org font set shipped by figlet.js
|
|
832
|
+
(https://github.com/patorjk/figlet.js/tree/master/fonts), fetched from
|
|
833
|
+
figlet.js 1.8.0 — vendored so the welcome/splash brand art
|
|
834
|
+
(`packages/tui/src/prompt/welcome.ts`) is generated reproducibly and offline
|
|
835
|
+
by `bun scripts/brand/generate-logo-art.ts`. Both files are unmodified and
|
|
836
|
+
retain their original FIGlet headers. They are build-time inputs only: no font
|
|
837
|
+
file or font data is compiled into, or shipped with, a Zeta release artifact.
|
|
838
|
+
The fonts' own headers carry their terms:
|
|
839
|
+
|
|
840
|
+
- `ANSI Shadow.flf` — "ANSI Shadow". Author unknown ("Font Author: ?" in the
|
|
841
|
+
header); a TheDraw-archive font converted with the FIGlet font editor
|
|
842
|
+
(http://patorjk.com/figfont-editor). Distributed under the FIGlet font
|
|
843
|
+
license as carried by the standard FIGlet font set: free to use and
|
|
844
|
+
redistribute with the header intact.
|
|
845
|
+
- `mini.flf` — "mini" by Glenn Chappell (4/93), from figlet release 2.1
|
|
846
|
+
(12 Aug 1994), "Includes ISO Latin-1"; modified by Paul Burton
|
|
847
|
+
<solution@earthlink.net> 12/96. Header grant: "Permission is hereby given to
|
|
848
|
+
modify this font, as long as the modifier's name is placed on a comment
|
|
849
|
+
line."
|
|
850
|
+
|
|
851
|
+
FIGlet renderer — `figlet` 1.8.0 (https://github.com/patorjk/figlet.js), a
|
|
852
|
+
root devDependency used only to generate the art at build time; it is not
|
|
853
|
+
imported by any runtime bundle or shipped artifact.
|
|
854
|
+
|
|
855
|
+
MIT License
|
|
856
|
+
|
|
857
|
+
Copyright (C) 2014-present Patrick Gillespie and other contributors
|
|
858
|
+
|
|
859
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
860
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
861
|
+
in the Software without restriction, including without limitation the rights
|
|
862
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
863
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
864
|
+
furnished to do so, subject to the following conditions:
|
|
865
|
+
|
|
866
|
+
The above copyright notice and this permission notice shall be included in
|
|
867
|
+
all copies or substantial portions of the Software.
|
|
868
|
+
|
|
869
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
870
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
871
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
872
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
873
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
874
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
875
|
+
SOFTWARE.
|
|
876
|
+
|
|
827
877
|
BUN / NPM DEPENDENCY INVENTORY
|
|
828
878
|
==============================
|
|
829
879
|
This inventory is traversed from bun.lock, not from the host node_modules tree.
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* omptype IR and passes the rest through.
|
|
13
13
|
*/
|
|
14
14
|
import { OmpTypeError } from "./errors.js";
|
|
15
|
+
import { parseLocalRefPointer } from "./local-ref.js";
|
|
15
16
|
import { IR_BRAND } from "./ir.js";
|
|
16
17
|
import { keywordIR, patternIR } from "./keywords.js";
|
|
17
18
|
import { type } from "./type.js";
|
|
@@ -40,11 +41,11 @@ class Importer {
|
|
|
40
41
|
target = this.#root;
|
|
41
42
|
}
|
|
42
43
|
else {
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
44
|
+
const pointer = parseLocalRefPointer(ref);
|
|
45
|
+
if (pointer === undefined || pointer.length !== 2 || (pointer[0] !== "$defs" && pointer[0] !== "definitions"))
|
|
45
46
|
throw new OmpTypeError(`unsupported $ref: ${ref}`);
|
|
46
|
-
const defs = this.#root[
|
|
47
|
-
target = typeof defs === "object" && defs !== null ? defs[
|
|
47
|
+
const defs = this.#root[pointer[0]];
|
|
48
|
+
target = typeof defs === "object" && defs !== null ? defs[pointer[1]] : undefined;
|
|
48
49
|
if (target === undefined)
|
|
49
50
|
throw new OmpTypeError(`unresolved $ref: ${ref}`);
|
|
50
51
|
}
|
package/dist/js/index.js
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared parsing for local `$ref` URI fragments (RFC 6901 JSON Pointers).
|
|
3
|
+
* omptype's JSON Schema importer and pi-ai's schema resolvers decode refs
|
|
4
|
+
* through these helpers so percent-encoded, pointer-escaped, and empty tokens
|
|
5
|
+
* resolve identically everywhere.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Parse a local `$ref` URI fragment into decoded JSON Pointer tokens:
|
|
9
|
+
* percent-decode the fragment, split on `/`, then unescape `~1`/`~0` per
|
|
10
|
+
* token. Returns `[]` for the root ref `#`, and `undefined` for non-local
|
|
11
|
+
* refs or fragments that do not percent-decode.
|
|
12
|
+
*/
|
|
13
|
+
export function parseLocalRefPointer(ref) {
|
|
14
|
+
if (!ref.startsWith("#"))
|
|
15
|
+
return undefined;
|
|
16
|
+
let pointer;
|
|
17
|
+
try {
|
|
18
|
+
pointer = decodeURIComponent(ref.slice(1));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
if (pointer.length > 0 && !pointer.startsWith("/"))
|
|
24
|
+
return undefined;
|
|
25
|
+
return pointer
|
|
26
|
+
.split("/")
|
|
27
|
+
.slice(1)
|
|
28
|
+
.map(token => token.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Encode JSON Pointer tokens back into a local `$ref` URI fragment — the
|
|
32
|
+
* inverse of {@link parseLocalRefPointer}.
|
|
33
|
+
*/
|
|
34
|
+
export function formatLocalRefPointer(tokens) {
|
|
35
|
+
if (tokens.length === 0)
|
|
36
|
+
return "#";
|
|
37
|
+
// `$` and the other URI sub-delims `encodeURIComponent` escapes are legal
|
|
38
|
+
// in fragments, so keep `$defs` readable; `%` stays encoded to round-trip.
|
|
39
|
+
const encoded = tokens
|
|
40
|
+
.map(token => encodeURIComponent(token.replace(/~/g, "~0").replace(/\//g, "~1")))
|
|
41
|
+
.map(token => token.replace(/%24/g, "$"));
|
|
42
|
+
return `#/${encoded.join("/")}`;
|
|
43
|
+
}
|
package/dist/js/typebox.js
CHANGED
|
@@ -129,22 +129,19 @@ function tNumber(opts, integer = false) {
|
|
|
129
129
|
upper = { value: opts.exclusiveMaximum, exclusive: true };
|
|
130
130
|
}
|
|
131
131
|
const keyword = integer ? "number.integer" : "number";
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
src = `${lower.value} ${lower.exclusive ? "<" : "<="} ${keyword} ${upper.exclusive ? "<" : "<="} ${upper.value}`;
|
|
132
|
+
if (lower &&
|
|
133
|
+
upper &&
|
|
134
|
+
(lower.value > upper.value || (lower.value === upper.value && (lower.exclusive || upper.exclusive)))) {
|
|
135
|
+
throw new OmpTypeError("numeric range is unsatisfiable");
|
|
137
136
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
let schema = asRuntime(type.raw(src));
|
|
137
|
+
// Fluent bounds write min/max straight into the IR, so decimal and
|
|
138
|
+
// exponent-form bounds share one path that renders the same structured
|
|
139
|
+
// errors; the DSL's decimal-only literals cannot spell the exponent form.
|
|
140
|
+
let schema = asRuntime(type.raw(keyword));
|
|
141
|
+
if (lower)
|
|
142
|
+
schema = lower.exclusive ? schema.moreThan(lower.value) : schema.atLeast(lower.value);
|
|
143
|
+
if (upper)
|
|
144
|
+
schema = upper.exclusive ? schema.lessThan(upper.value) : schema.atMost(upper.value);
|
|
148
145
|
if (opts?.multipleOf !== undefined) {
|
|
149
146
|
const divisor = opts.multipleOf;
|
|
150
147
|
schema = schema.narrow((value, ctx) => {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared parsing for local `$ref` URI fragments (RFC 6901 JSON Pointers).
|
|
3
|
+
* omptype's JSON Schema importer and pi-ai's schema resolvers decode refs
|
|
4
|
+
* through these helpers so percent-encoded, pointer-escaped, and empty tokens
|
|
5
|
+
* resolve identically everywhere.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Parse a local `$ref` URI fragment into decoded JSON Pointer tokens:
|
|
9
|
+
* percent-decode the fragment, split on `/`, then unescape `~1`/`~0` per
|
|
10
|
+
* token. Returns `[]` for the root ref `#`, and `undefined` for non-local
|
|
11
|
+
* refs or fragments that do not percent-decode.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseLocalRefPointer(ref: string): string[] | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Encode JSON Pointer tokens back into a local `$ref` URI fragment — the
|
|
16
|
+
* inverse of {@link parseLocalRefPointer}.
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatLocalRefPointer(tokens: readonly string[]): string;
|
package/dist/types/typebox.d.ts
CHANGED
|
@@ -125,6 +125,8 @@ interface RuntimeType<T> extends OmpType<T> {
|
|
|
125
125
|
atMostLength(bound: number): RuntimeType<T>;
|
|
126
126
|
atLeast(bound: number): RuntimeType<T>;
|
|
127
127
|
atMost(bound: number): RuntimeType<T>;
|
|
128
|
+
moreThan(bound: number): RuntimeType<T>;
|
|
129
|
+
lessThan(bound: number): RuntimeType<T>;
|
|
128
130
|
narrow<N extends T>(predicate: (value: T, ctx: NarrowContext) => value is N): RuntimeType<N>;
|
|
129
131
|
narrow(predicate: (value: T, ctx: NarrowContext) => boolean): RuntimeType<T>;
|
|
130
132
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@linxiraos/pi-omptype",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.0",
|
|
5
5
|
"description": "ArkType-compatible runtime schema validation with lazy JIT compilation",
|
|
6
6
|
"homepage": "https://linxira-os.github.io/zeta/",
|
|
7
7
|
"author": "Linxira-OS",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"types": "./dist/types/index.d.ts",
|
|
25
25
|
"scripts": {
|
|
26
26
|
"check": "oxlint . && oxfmt --check --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts' && bun run check:types",
|
|
27
|
-
"check:types": "
|
|
27
|
+
"check:types": "tsc -p tsconfig.json --noEmit",
|
|
28
28
|
"lint": "oxlint .",
|
|
29
29
|
"fix": "oxlint --fix --fix-suggestions . && bun run fmt",
|
|
30
30
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@ark/schema": "0.56.2",
|
|
37
37
|
"@ark/util": "0.56.2",
|
|
38
38
|
"@sinclair/typebox": "^0.34.0",
|
|
39
|
-
"@types/bun": "
|
|
39
|
+
"@types/bun": "1.4.3",
|
|
40
40
|
"arkregex": "0.0.8",
|
|
41
41
|
"arktype": "2.2.3"
|
|
42
42
|
},
|
package/src/from-json-schema.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* omptype IR and passes the rest through.
|
|
13
13
|
*/
|
|
14
14
|
import { OmpTypeError } from "./errors";
|
|
15
|
+
import { parseLocalRefPointer } from "./local-ref";
|
|
15
16
|
import { type EmbeddableSchema, type IR, IR_BRAND, type PropIR, type TupleItemIR } from "./ir";
|
|
16
17
|
import { keywordIR, patternIR } from "./keywords";
|
|
17
18
|
import { type BaseType, type } from "./type";
|
|
@@ -45,10 +46,11 @@ class Importer {
|
|
|
45
46
|
if (ref === "#") {
|
|
46
47
|
target = this.#root;
|
|
47
48
|
} else {
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const pointer = parseLocalRefPointer(ref);
|
|
50
|
+
if (pointer === undefined || pointer.length !== 2 || (pointer[0] !== "$defs" && pointer[0] !== "definitions"))
|
|
51
|
+
throw new OmpTypeError(`unsupported $ref: ${ref}`);
|
|
52
|
+
const defs = this.#root[pointer[0]];
|
|
53
|
+
target = typeof defs === "object" && defs !== null ? (defs as JsonSchema)[pointer[1]!] : undefined;
|
|
52
54
|
if (target === undefined) throw new OmpTypeError(`unresolved $ref: ${ref}`);
|
|
53
55
|
}
|
|
54
56
|
|
package/src/index.ts
CHANGED
package/src/local-ref.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared parsing for local `$ref` URI fragments (RFC 6901 JSON Pointers).
|
|
3
|
+
* omptype's JSON Schema importer and pi-ai's schema resolvers decode refs
|
|
4
|
+
* through these helpers so percent-encoded, pointer-escaped, and empty tokens
|
|
5
|
+
* resolve identically everywhere.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse a local `$ref` URI fragment into decoded JSON Pointer tokens:
|
|
10
|
+
* percent-decode the fragment, split on `/`, then unescape `~1`/`~0` per
|
|
11
|
+
* token. Returns `[]` for the root ref `#`, and `undefined` for non-local
|
|
12
|
+
* refs or fragments that do not percent-decode.
|
|
13
|
+
*/
|
|
14
|
+
export function parseLocalRefPointer(ref: string): string[] | undefined {
|
|
15
|
+
if (!ref.startsWith("#")) return undefined;
|
|
16
|
+
let pointer: string;
|
|
17
|
+
try {
|
|
18
|
+
pointer = decodeURIComponent(ref.slice(1));
|
|
19
|
+
} catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
if (pointer.length > 0 && !pointer.startsWith("/")) return undefined;
|
|
23
|
+
return pointer
|
|
24
|
+
.split("/")
|
|
25
|
+
.slice(1)
|
|
26
|
+
.map(token => token.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Encode JSON Pointer tokens back into a local `$ref` URI fragment — the
|
|
31
|
+
* inverse of {@link parseLocalRefPointer}.
|
|
32
|
+
*/
|
|
33
|
+
export function formatLocalRefPointer(tokens: readonly string[]): string {
|
|
34
|
+
if (tokens.length === 0) return "#";
|
|
35
|
+
// `$` and the other URI sub-delims `encodeURIComponent` escapes are legal
|
|
36
|
+
// in fragments, so keep `$defs` readable; `%` stays encoded to round-trip.
|
|
37
|
+
const encoded = tokens
|
|
38
|
+
.map(token => encodeURIComponent(token.replace(/~/g, "~0").replace(/\//g, "~1")))
|
|
39
|
+
.map(token => token.replace(/%24/g, "$"));
|
|
40
|
+
return `#/${encoded.join("/")}`;
|
|
41
|
+
}
|
package/src/typebox.ts
CHANGED
|
@@ -135,6 +135,8 @@ interface RuntimeType<T> extends OmpType<T> {
|
|
|
135
135
|
atMostLength(bound: number): RuntimeType<T>;
|
|
136
136
|
atLeast(bound: number): RuntimeType<T>;
|
|
137
137
|
atMost(bound: number): RuntimeType<T>;
|
|
138
|
+
moreThan(bound: number): RuntimeType<T>;
|
|
139
|
+
lessThan(bound: number): RuntimeType<T>;
|
|
138
140
|
narrow<N extends T>(predicate: (value: T, ctx: NarrowContext) => value is N): RuntimeType<N>;
|
|
139
141
|
narrow(predicate: (value: T, ctx: NarrowContext) => boolean): RuntimeType<T>;
|
|
140
142
|
}
|
|
@@ -271,19 +273,19 @@ function tNumber(opts?: NumberOpts, integer = false): TNumber {
|
|
|
271
273
|
upper = { value: opts.exclusiveMaximum, exclusive: true };
|
|
272
274
|
}
|
|
273
275
|
const keyword = integer ? "number.integer" : "number";
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
src = `${keyword} ${lower.exclusive ? ">" : ">="} ${lower.value}`;
|
|
281
|
-
} else if (upper) {
|
|
282
|
-
src = `${keyword} ${upper.exclusive ? "<" : "<="} ${upper.value}`;
|
|
283
|
-
} else {
|
|
284
|
-
src = keyword;
|
|
276
|
+
if (
|
|
277
|
+
lower &&
|
|
278
|
+
upper &&
|
|
279
|
+
(lower.value > upper.value || (lower.value === upper.value && (lower.exclusive || upper.exclusive)))
|
|
280
|
+
) {
|
|
281
|
+
throw new OmpTypeError("numeric range is unsatisfiable");
|
|
285
282
|
}
|
|
286
|
-
|
|
283
|
+
// Fluent bounds write min/max straight into the IR, so decimal and
|
|
284
|
+
// exponent-form bounds share one path that renders the same structured
|
|
285
|
+
// errors; the DSL's decimal-only literals cannot spell the exponent form.
|
|
286
|
+
let schema = asRuntime<number>(type.raw(keyword));
|
|
287
|
+
if (lower) schema = lower.exclusive ? schema.moreThan(lower.value) : schema.atLeast(lower.value);
|
|
288
|
+
if (upper) schema = upper.exclusive ? schema.lessThan(upper.value) : schema.atMost(upper.value);
|
|
287
289
|
if (opts?.multipleOf !== undefined) {
|
|
288
290
|
const divisor = opts.multipleOf;
|
|
289
291
|
schema = schema.narrow((value, ctx) => {
|