@nudojs/core 0.1.0 → 0.2.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.
@@ -0,0 +1,194 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { T, typeValueEquals } from "../type-value.ts";
3
+ import { dispatchBinaryOp, dispatchMethod, dispatchProperty } from "../ops.ts";
4
+ import { createTemplate, isTemplate } from "../refinements/template.ts";
5
+ import { createRange } from "../refinements/range.ts";
6
+
7
+ describe("dispatchBinaryOp", () => {
8
+ describe("refined fallback", () => {
9
+ it("falls back to base type when no ops defined", () => {
10
+ const odd = T.refine(T.number, { name: "odd", meta: {} });
11
+ const result = dispatchBinaryOp("+", odd, T.literal(1));
12
+ expect(result.kind).toBe("primitive");
13
+ });
14
+
15
+ it("uses custom ops when defined", () => {
16
+ const odd = T.refine(T.number, {
17
+ name: "odd",
18
+ meta: {},
19
+ ops: {
20
+ "%"(self, other) {
21
+ if (other.kind === "literal" && other.value === 2) return T.literal(1);
22
+ return undefined;
23
+ },
24
+ },
25
+ });
26
+ const result = dispatchBinaryOp("%", odd, T.literal(2));
27
+ expect(typeValueEquals(result, T.literal(1))).toBe(true);
28
+ });
29
+
30
+ it("falls back when custom op returns undefined", () => {
31
+ const odd = T.refine(T.number, {
32
+ name: "odd",
33
+ meta: {},
34
+ ops: {
35
+ "%"(self, other) {
36
+ if (other.kind === "literal" && other.value === 2) return T.literal(1);
37
+ return undefined;
38
+ },
39
+ },
40
+ });
41
+ const result = dispatchBinaryOp("%", odd, T.literal(3));
42
+ expect(result.kind).toBe("primitive");
43
+ });
44
+ });
45
+
46
+ describe("template string via add", () => {
47
+ it("literal string + T.string produces template", () => {
48
+ const result = dispatchBinaryOp("+", T.literal("xy"), T.string);
49
+ expect(isTemplate(result)).toBe(true);
50
+ });
51
+
52
+ it("T.string + literal string produces template", () => {
53
+ const result = dispatchBinaryOp("+", T.string, T.literal("!"));
54
+ expect(isTemplate(result)).toBe(true);
55
+ });
56
+
57
+ it("template + template concatenates", () => {
58
+ const left = createTemplate([T.literal("a"), T.string]);
59
+ const right = createTemplate([T.literal("b"), T.string]);
60
+ const result = dispatchBinaryOp("+", left, right);
61
+ expect(isTemplate(result)).toBe(true);
62
+ });
63
+
64
+ it("two literal strings still produce literal", () => {
65
+ const result = dispatchBinaryOp("+", T.literal("a"), T.literal("b"));
66
+ expect(typeValueEquals(result, T.literal("ab"))).toBe(true);
67
+ });
68
+
69
+ it("T.string + T.string stays T.string", () => {
70
+ const result = dispatchBinaryOp("+", T.string, T.string);
71
+ expect(typeValueEquals(result, T.string)).toBe(true);
72
+ });
73
+ });
74
+
75
+ describe("range comparison ops", () => {
76
+ it("range(min:0) >= 0 is true", () => {
77
+ const r = createRange({ min: 0 });
78
+ const result = dispatchBinaryOp(">=", r, T.literal(0));
79
+ expect(typeValueEquals(result, T.literal(true))).toBe(true);
80
+ });
81
+
82
+ it("range(min:0) >= -1 is true", () => {
83
+ const r = createRange({ min: 0 });
84
+ const result = dispatchBinaryOp(">=", r, T.literal(-1));
85
+ expect(typeValueEquals(result, T.literal(true))).toBe(true);
86
+ });
87
+
88
+ it("range(max:100) > 200 is false", () => {
89
+ const r = createRange({ max: 100 });
90
+ const result = dispatchBinaryOp(">", r, T.literal(200));
91
+ expect(typeValueEquals(result, T.literal(false))).toBe(true);
92
+ });
93
+
94
+ it("range(min:0, max:100) > 50 is uncertain (boolean)", () => {
95
+ const r = createRange({ min: 0, max: 100 });
96
+ const result = dispatchBinaryOp(">", r, T.literal(50));
97
+ expect(typeValueEquals(result, T.boolean)).toBe(true);
98
+ });
99
+
100
+ it("range(max:10) <= 10 is true", () => {
101
+ const r = createRange({ max: 10 });
102
+ const result = dispatchBinaryOp("<=", r, T.literal(10));
103
+ expect(typeValueEquals(result, T.literal(true))).toBe(true);
104
+ });
105
+
106
+ it("range(min:5) < 3 is false", () => {
107
+ const r = createRange({ min: 5 });
108
+ const result = dispatchBinaryOp("<", r, T.literal(3));
109
+ expect(typeValueEquals(result, T.literal(false))).toBe(true);
110
+ });
111
+ });
112
+ });
113
+
114
+ describe("dispatchMethod", () => {
115
+ describe("template startsWith", () => {
116
+ it("known prefix matches", () => {
117
+ const tmpl = createTemplate([T.literal("xy"), T.string]);
118
+ const result = dispatchMethod(tmpl, "startsWith", [T.literal("x")]);
119
+ expect(result).toBeDefined();
120
+ expect(typeValueEquals(result!, T.literal(true))).toBe(true);
121
+ });
122
+
123
+ it("known prefix full match", () => {
124
+ const tmpl = createTemplate([T.literal("xy"), T.string]);
125
+ const result = dispatchMethod(tmpl, "startsWith", [T.literal("xy")]);
126
+ expect(typeValueEquals(result!, T.literal(true))).toBe(true);
127
+ });
128
+
129
+ it("known prefix mismatch", () => {
130
+ const tmpl = createTemplate([T.literal("xy"), T.string]);
131
+ const result = dispatchMethod(tmpl, "startsWith", [T.literal("a")]);
132
+ expect(typeValueEquals(result!, T.literal(false))).toBe(true);
133
+ });
134
+
135
+ it("uncertain when search extends beyond prefix", () => {
136
+ const tmpl = createTemplate([T.literal("xy"), T.string]);
137
+ const result = dispatchMethod(tmpl, "startsWith", [T.literal("xyz")]);
138
+ expect(result).toBeUndefined();
139
+ });
140
+ });
141
+
142
+ describe("template endsWith", () => {
143
+ it("known suffix matches", () => {
144
+ const tmpl = createTemplate([T.string, T.literal("!")]);
145
+ const result = dispatchMethod(tmpl, "endsWith", [T.literal("!")]);
146
+ expect(typeValueEquals(result!, T.literal(true))).toBe(true);
147
+ });
148
+
149
+ it("known suffix mismatch", () => {
150
+ const tmpl = createTemplate([T.string, T.literal("!")]);
151
+ const result = dispatchMethod(tmpl, "endsWith", [T.literal("?")]);
152
+ expect(typeValueEquals(result!, T.literal(false))).toBe(true);
153
+ });
154
+ });
155
+
156
+ describe("template includes", () => {
157
+ it("known fixed text includes substring", () => {
158
+ const tmpl = createTemplate([T.literal("hello"), T.string, T.literal("world")]);
159
+ const result = dispatchMethod(tmpl, "includes", [T.literal("hello")]);
160
+ expect(typeValueEquals(result!, T.literal(true))).toBe(true);
161
+ });
162
+
163
+ it("uncertain when not in fixed text", () => {
164
+ const tmpl = createTemplate([T.literal("hello"), T.string]);
165
+ const result = dispatchMethod(tmpl, "includes", [T.literal("xyz")]);
166
+ expect(result).toBeUndefined();
167
+ });
168
+ });
169
+
170
+ it("returns undefined for unknown methods", () => {
171
+ const tmpl = createTemplate([T.literal("x"), T.string]);
172
+ const result = dispatchMethod(tmpl, "unknownMethod", []);
173
+ expect(result).toBeUndefined();
174
+ });
175
+
176
+ it("returns undefined for non-refined types", () => {
177
+ const result = dispatchMethod(T.string, "startsWith", [T.literal("x")]);
178
+ expect(result).toBeUndefined();
179
+ });
180
+ });
181
+
182
+ describe("dispatchProperty", () => {
183
+ it("template length returns range", () => {
184
+ const tmpl = createTemplate([T.literal("xy"), T.string]);
185
+ const result = dispatchProperty(tmpl, "length");
186
+ expect(result).toBeDefined();
187
+ expect(result!.kind).toBe("refined");
188
+ });
189
+
190
+ it("returns undefined for non-refined types", () => {
191
+ const result = dispatchProperty(T.string, "length");
192
+ expect(result).toBeUndefined();
193
+ });
194
+ });
@@ -0,0 +1,210 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ T,
4
+ typeValueEquals,
5
+ isSubtypeOf,
6
+ typeValueToString,
7
+ deepCloneTypeValue,
8
+ getPrimitiveTypeOf,
9
+ getRefinedBase,
10
+ } from "../type-value.ts";
11
+ import { createTemplate, isTemplate, getTemplateParts, getKnownPrefix, getKnownSuffix } from "../refinements/template.ts";
12
+ import { createRange, isRange, getRangeMeta } from "../refinements/range.ts";
13
+
14
+ describe("Refined TypeValue", () => {
15
+ describe("T.refine", () => {
16
+ it("creates a refined type", () => {
17
+ const odd = T.refine(T.number, { name: "odd", meta: {} });
18
+ expect(odd.kind).toBe("refined");
19
+ if (odd.kind === "refined") {
20
+ expect(odd.base).toBe(T.number);
21
+ expect(odd.refinement.name).toBe("odd");
22
+ }
23
+ });
24
+ });
25
+
26
+ describe("typeValueEquals", () => {
27
+ it("equal refined types with same name and base", () => {
28
+ const a = T.refine(T.number, { name: "odd", meta: {} });
29
+ const b = T.refine(T.number, { name: "odd", meta: {} });
30
+ expect(typeValueEquals(a, b)).toBe(true);
31
+ });
32
+
33
+ it("different name means not equal", () => {
34
+ const a = T.refine(T.number, { name: "odd", meta: {} });
35
+ const b = T.refine(T.number, { name: "even", meta: {} });
36
+ expect(typeValueEquals(a, b)).toBe(false);
37
+ });
38
+
39
+ it("different base means not equal", () => {
40
+ const a = T.refine(T.number, { name: "x", meta: {} });
41
+ const b = T.refine(T.string, { name: "x", meta: {} });
42
+ expect(typeValueEquals(a, b)).toBe(false);
43
+ });
44
+ });
45
+
46
+ describe("isSubtypeOf", () => {
47
+ it("refined is subtype of its base", () => {
48
+ const odd = T.refine(T.number, { name: "odd", meta: {} });
49
+ expect(isSubtypeOf(odd, T.number)).toBe(true);
50
+ });
51
+
52
+ it("refined is subtype of unknown", () => {
53
+ const odd = T.refine(T.number, { name: "odd", meta: {} });
54
+ expect(isSubtypeOf(odd, T.unknown)).toBe(true);
55
+ });
56
+
57
+ it("literal is subtype of refined when check passes", () => {
58
+ const odd = T.refine(T.number, {
59
+ name: "odd",
60
+ meta: {},
61
+ check: (v) => typeof v === "number" && v % 2 !== 0,
62
+ });
63
+ expect(isSubtypeOf(T.literal(3), odd)).toBe(true);
64
+ expect(isSubtypeOf(T.literal(4), odd)).toBe(false);
65
+ });
66
+
67
+ it("nested refined is subtype of outer base", () => {
68
+ const integer = T.refine(T.number, { name: "integer", meta: {} });
69
+ const positiveInt = T.refine(integer, { name: "positive integer", meta: {} });
70
+ expect(isSubtypeOf(positiveInt, T.number)).toBe(true);
71
+ expect(isSubtypeOf(positiveInt, integer)).toBe(true);
72
+ });
73
+ });
74
+
75
+ describe("typeValueToString", () => {
76
+ it("uses refinement name", () => {
77
+ const odd = T.refine(T.number, { name: "odd", meta: {} });
78
+ expect(typeValueToString(odd)).toBe("odd");
79
+ });
80
+ });
81
+
82
+ describe("getPrimitiveTypeOf", () => {
83
+ it("delegates to base", () => {
84
+ const odd = T.refine(T.number, { name: "odd", meta: {} });
85
+ expect(getPrimitiveTypeOf(odd)).toBe("number");
86
+ });
87
+
88
+ it("handles nested refined", () => {
89
+ const inner = T.refine(T.string, { name: "inner", meta: {} });
90
+ const outer = T.refine(inner, { name: "outer", meta: {} });
91
+ expect(getPrimitiveTypeOf(outer)).toBe("string");
92
+ });
93
+ });
94
+
95
+ describe("getRefinedBase", () => {
96
+ it("unwraps to primitive", () => {
97
+ const inner = T.refine(T.number, { name: "a", meta: {} });
98
+ const outer = T.refine(inner, { name: "b", meta: {} });
99
+ expect(getRefinedBase(outer)).toBe(T.number);
100
+ });
101
+
102
+ it("returns non-refined as-is", () => {
103
+ expect(getRefinedBase(T.string)).toBe(T.string);
104
+ });
105
+ });
106
+
107
+ describe("deepCloneTypeValue", () => {
108
+ it("clones refined type", () => {
109
+ const odd = T.refine(T.number, { name: "odd", meta: { x: 1 } });
110
+ const cloned = deepCloneTypeValue(odd);
111
+ expect(typeValueEquals(cloned, odd)).toBe(true);
112
+ expect(cloned).not.toBe(odd);
113
+ });
114
+ });
115
+ });
116
+
117
+ describe("Template String", () => {
118
+ it("creates template from parts", () => {
119
+ const tmpl = createTemplate([T.literal("xy"), T.string]);
120
+ expect(isTemplate(tmpl)).toBe(true);
121
+ expect(tmpl.kind).toBe("refined");
122
+ });
123
+
124
+ it("collapses to literal when all parts are literal", () => {
125
+ const tmpl = createTemplate([T.literal("hello"), T.literal(" world")]);
126
+ expect(tmpl.kind).toBe("literal");
127
+ if (tmpl.kind === "literal") expect(tmpl.value).toBe("hello world");
128
+ });
129
+
130
+ it("collapses to T.string when only T.string", () => {
131
+ const tmpl = createTemplate([T.string]);
132
+ expect(tmpl.kind).toBe("primitive");
133
+ });
134
+
135
+ it("getKnownPrefix extracts leading literals", () => {
136
+ expect(getKnownPrefix([T.literal("xy"), T.string])).toBe("xy");
137
+ expect(getKnownPrefix([T.string, T.literal("z")])).toBe("");
138
+ expect(getKnownPrefix([T.literal("a"), T.literal("b"), T.string])).toBe("ab");
139
+ });
140
+
141
+ it("getKnownSuffix extracts trailing literals", () => {
142
+ expect(getKnownSuffix([T.string, T.literal("!")])).toBe("!");
143
+ expect(getKnownSuffix([T.literal("x"), T.string])).toBe("");
144
+ });
145
+
146
+ it("getTemplateParts returns parts", () => {
147
+ const tmpl = createTemplate([T.literal("x"), T.string]);
148
+ const parts = getTemplateParts(tmpl);
149
+ expect(parts).toHaveLength(2);
150
+ });
151
+
152
+ it("toString formats as template literal", () => {
153
+ const tmpl = createTemplate([T.literal("xy"), T.string]);
154
+ expect(typeValueToString(tmpl)).toBe("`xy${string}`");
155
+ });
156
+
157
+ it("is subtype of T.string", () => {
158
+ const tmpl = createTemplate([T.literal("x"), T.string]);
159
+ expect(isSubtypeOf(tmpl, T.string)).toBe(true);
160
+ });
161
+
162
+ it("check validates concrete strings", () => {
163
+ const tmpl = createTemplate([T.literal("xy"), T.string]);
164
+ if (tmpl.kind === "refined" && tmpl.refinement.check) {
165
+ expect(tmpl.refinement.check("xyhello")).toBe(true);
166
+ expect(tmpl.refinement.check("xy")).toBe(true);
167
+ expect(tmpl.refinement.check("ahello")).toBe(false);
168
+ }
169
+ });
170
+ });
171
+
172
+ describe("Range", () => {
173
+ it("creates range type", () => {
174
+ const r = createRange({ min: 0 });
175
+ expect(isRange(r)).toBe(true);
176
+ expect(r.kind).toBe("refined");
177
+ });
178
+
179
+ it("collapses to literal when min === max", () => {
180
+ const r = createRange({ min: 5, max: 5 });
181
+ expect(r.kind).toBe("literal");
182
+ if (r.kind === "literal") expect(r.value).toBe(5);
183
+ });
184
+
185
+ it("getRangeMeta returns metadata", () => {
186
+ const r = createRange({ min: 0, max: 100, integer: true });
187
+ const meta = getRangeMeta(r);
188
+ expect(meta).toEqual({ min: 0, max: 100, integer: true });
189
+ });
190
+
191
+ it("is subtype of T.number", () => {
192
+ const r = createRange({ min: 0 });
193
+ expect(isSubtypeOf(r, T.number)).toBe(true);
194
+ });
195
+
196
+ it("toString formats with constraints", () => {
197
+ expect(typeValueToString(createRange({ min: 0 }))).toBe("number (>= 0)");
198
+ expect(typeValueToString(createRange({ min: 0, max: 100 }))).toBe("number (>= 0, <= 100)");
199
+ expect(typeValueToString(createRange({ integer: true }))).toBe("integer");
200
+ });
201
+
202
+ it("check validates concrete numbers", () => {
203
+ const r = createRange({ min: 0, max: 10 });
204
+ if (r.kind === "refined" && r.refinement.check) {
205
+ expect(r.refinement.check(5)).toBe(true);
206
+ expect(r.refinement.check(-1)).toBe(false);
207
+ expect(r.refinement.check(11)).toBe(false);
208
+ }
209
+ });
210
+ });
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export {
2
2
  type TypeValue,
3
3
  type LiteralValue,
4
+ type Refinement,
4
5
  T,
5
6
  typeValueEquals,
6
7
  simplifyUnion,
@@ -10,13 +11,30 @@ export {
10
11
  narrowType,
11
12
  subtractType,
12
13
  getPrimitiveTypeOf,
14
+ getRefinedBase,
13
15
  deepCloneTypeValue,
14
16
  mergeObjectProperties,
15
17
  } from "./type-value.ts";
16
18
 
17
- export { Ops, applyBinaryOp } from "./ops.ts";
19
+ export { Ops, applyBinaryOp, dispatchBinaryOp, dispatchMethod, dispatchProperty } from "./ops.ts";
18
20
 
19
21
  export {
20
22
  type Environment,
21
23
  createEnvironment,
22
24
  } from "./environment.ts";
25
+
26
+ export {
27
+ createTemplate,
28
+ isTemplate,
29
+ getTemplateParts,
30
+ concatTemplates,
31
+ getKnownPrefix,
32
+ getKnownSuffix,
33
+ } from "./refinements/template.ts";
34
+
35
+ export {
36
+ createRange,
37
+ isRange,
38
+ getRangeMeta,
39
+ type RangeMeta,
40
+ } from "./refinements/range.ts";
package/src/ops.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { type TypeValue, T, isSubtypeOf } from "./type-value.ts";
1
+ import { type TypeValue, T, isSubtypeOf, getRefinedBase } from "./type-value.ts";
2
+ import { concatTemplates, isTemplate } from "./refinements/template.ts";
2
3
 
3
4
  function bothLiteral(
4
5
  l: TypeValue,
@@ -16,7 +17,17 @@ export const Ops = {
16
17
  if (lit) {
17
18
  return T.literal((lit.lv as any) + (lit.rv as any));
18
19
  }
19
- if (isSubtypeOf(left, T.string) || isSubtypeOf(right, T.string)) {
20
+
21
+ const leftIsString = isSubtypeOf(left, T.string) || isTemplate(left);
22
+ const rightIsString = isSubtypeOf(right, T.string) || isTemplate(right);
23
+ if (leftIsString || rightIsString) {
24
+ const hasStructure =
25
+ (left.kind === "literal" && typeof left.value === "string") ||
26
+ (right.kind === "literal" && typeof right.value === "string") ||
27
+ isTemplate(left) || isTemplate(right);
28
+ if (hasStructure) {
29
+ return concatTemplates(left, right);
30
+ }
20
31
  return T.string;
21
32
  }
22
33
  if (isSubtypeOf(left, T.number) && isSubtypeOf(right, T.number)) {
@@ -100,6 +111,7 @@ export const Ops = {
100
111
  return T.literal(typeof v);
101
112
  }
102
113
  if (operand.kind === "primitive") return T.literal(operand.type);
114
+ if (operand.kind === "refined") return Ops.typeof_(getRefinedBase(operand));
103
115
  if (operand.kind === "object") return T.literal("object");
104
116
  if (operand.kind === "array" || operand.kind === "tuple")
105
117
  return T.literal("object");
@@ -145,3 +157,54 @@ export function applyBinaryOp(
145
157
  if (!fn) return T.unknown;
146
158
  return fn(left, right);
147
159
  }
160
+
161
+ export function dispatchBinaryOp(
162
+ op: string,
163
+ left: TypeValue,
164
+ right: TypeValue,
165
+ ): TypeValue {
166
+ if (left.kind === "refined" && left.refinement.ops?.[op]) {
167
+ const result = left.refinement.ops[op](left, right);
168
+ if (result !== undefined) return result;
169
+ }
170
+ if (right.kind === "refined" && right.refinement.ops?.[op]) {
171
+ const result = right.refinement.ops[op](right, left);
172
+ if (result !== undefined) return result;
173
+ }
174
+
175
+ const baseLeft = left.kind === "refined" ? left.base : left;
176
+ const baseRight = right.kind === "refined" ? right.base : right;
177
+ if (baseLeft !== left || baseRight !== right) {
178
+ return dispatchBinaryOp(op, baseLeft, baseRight);
179
+ }
180
+ return applyBinaryOp(op, left, right);
181
+ }
182
+
183
+ export function dispatchMethod(
184
+ receiver: TypeValue,
185
+ name: string,
186
+ args: TypeValue[],
187
+ ): TypeValue | undefined {
188
+ if (receiver.kind === "refined" && receiver.refinement.methods?.[name]) {
189
+ const result = receiver.refinement.methods[name](receiver, args);
190
+ if (result !== undefined) return result;
191
+ }
192
+ if (receiver.kind === "refined") {
193
+ return dispatchMethod(receiver.base, name, args);
194
+ }
195
+ return undefined;
196
+ }
197
+
198
+ export function dispatchProperty(
199
+ receiver: TypeValue,
200
+ name: string,
201
+ ): TypeValue | undefined {
202
+ if (receiver.kind === "refined" && receiver.refinement.properties?.[name]) {
203
+ const result = receiver.refinement.properties[name](receiver);
204
+ if (result !== undefined) return result;
205
+ }
206
+ if (receiver.kind === "refined") {
207
+ return dispatchProperty(receiver.base, name);
208
+ }
209
+ return undefined;
210
+ }
@@ -0,0 +1,2 @@
1
+ export { createTemplate, isTemplate, getTemplateParts, concatTemplates, getKnownPrefix, getKnownSuffix } from "./template.ts";
2
+ export { createRange, isRange, getRangeMeta, type RangeMeta } from "./range.ts";
@@ -0,0 +1,86 @@
1
+ import { type TypeValue, type Refinement, T } from "../type-value.ts";
2
+
3
+ export type RangeMeta = {
4
+ min?: number;
5
+ max?: number;
6
+ integer?: boolean;
7
+ };
8
+
9
+ function formatRangeName(meta: RangeMeta): string {
10
+ const parts: string[] = [];
11
+ if (meta.integer) parts.push("integer");
12
+ else parts.push("number");
13
+
14
+ const constraints: string[] = [];
15
+ if (meta.min != null) constraints.push(`>= ${meta.min}`);
16
+ if (meta.max != null) constraints.push(`<= ${meta.max}`);
17
+ if (constraints.length > 0) parts.push(`(${constraints.join(", ")})`);
18
+
19
+ return parts.join(" ");
20
+ }
21
+
22
+ function createRangeRefinement(meta: RangeMeta): Refinement {
23
+ return {
24
+ name: formatRangeName(meta),
25
+ meta: { ...meta },
26
+ check(value: unknown) {
27
+ if (typeof value !== "number") return false;
28
+ if (meta.integer && !Number.isInteger(value)) return false;
29
+ if (meta.min != null && value < meta.min) return false;
30
+ if (meta.max != null && value > meta.max) return false;
31
+ return true;
32
+ },
33
+ ops: {
34
+ ">="(self: TypeValue, other: TypeValue) {
35
+ const m = getRangeMeta(self);
36
+ if (!m || other.kind !== "literal" || typeof other.value !== "number") return undefined;
37
+ if (m.min != null && m.min >= other.value) return T.literal(true);
38
+ if (m.max != null && m.max < other.value) return T.literal(false);
39
+ return undefined;
40
+ },
41
+ ">"(self: TypeValue, other: TypeValue) {
42
+ const m = getRangeMeta(self);
43
+ if (!m || other.kind !== "literal" || typeof other.value !== "number") return undefined;
44
+ if (m.min != null && m.min > other.value) return T.literal(true);
45
+ if (m.max != null && m.max <= other.value) return T.literal(false);
46
+ return undefined;
47
+ },
48
+ "<="(self: TypeValue, other: TypeValue) {
49
+ const m = getRangeMeta(self);
50
+ if (!m || other.kind !== "literal" || typeof other.value !== "number") return undefined;
51
+ if (m.max != null && m.max <= other.value) return T.literal(true);
52
+ if (m.min != null && m.min > other.value) return T.literal(false);
53
+ return undefined;
54
+ },
55
+ "<"(self: TypeValue, other: TypeValue) {
56
+ const m = getRangeMeta(self);
57
+ if (!m || other.kind !== "literal" || typeof other.value !== "number") return undefined;
58
+ if (m.max != null && m.max < other.value) return T.literal(true);
59
+ if (m.min != null && m.min >= other.value) return T.literal(false);
60
+ return undefined;
61
+ },
62
+ },
63
+ };
64
+ }
65
+
66
+ export function createRange(meta: RangeMeta): TypeValue {
67
+ if (meta.min != null && meta.max != null && meta.min === meta.max) {
68
+ return T.literal(meta.min);
69
+ }
70
+ return T.refine(T.number, createRangeRefinement(meta));
71
+ }
72
+
73
+ export function isRange(tv: TypeValue): boolean {
74
+ if (tv.kind !== "refined") return false;
75
+ const m = tv.refinement.meta;
76
+ return m.min !== undefined || m.max !== undefined || m.integer !== undefined;
77
+ }
78
+
79
+ export function getRangeMeta(tv: TypeValue): RangeMeta | undefined {
80
+ if (tv.kind !== "refined") return undefined;
81
+ const m = tv.refinement.meta;
82
+ if (m.min !== undefined || m.max !== undefined || m.integer !== undefined) {
83
+ return m as RangeMeta;
84
+ }
85
+ return undefined;
86
+ }