@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,178 @@
1
+ import { type TypeValue, type Refinement, T, typeValueToString } from "../type-value.ts";
2
+ import { createRange } from "./range.ts";
3
+
4
+ export function getKnownPrefix(parts: TypeValue[]): string {
5
+ let prefix = "";
6
+ for (const p of parts) {
7
+ if (p.kind === "literal" && typeof p.value === "string") {
8
+ prefix += p.value;
9
+ } else {
10
+ break;
11
+ }
12
+ }
13
+ return prefix;
14
+ }
15
+
16
+ export function getKnownSuffix(parts: TypeValue[]): string {
17
+ let suffix = "";
18
+ for (let i = parts.length - 1; i >= 0; i--) {
19
+ const p = parts[i];
20
+ if (p.kind === "literal" && typeof p.value === "string") {
21
+ suffix = p.value + suffix;
22
+ } else {
23
+ break;
24
+ }
25
+ }
26
+ return suffix;
27
+ }
28
+
29
+ function getFixedLength(parts: TypeValue[]): number {
30
+ let len = 0;
31
+ for (const p of parts) {
32
+ if (p.kind === "literal" && typeof p.value === "string") {
33
+ len += p.value.length;
34
+ }
35
+ }
36
+ return len;
37
+ }
38
+
39
+ function getAllFixedText(parts: TypeValue[]): string {
40
+ return parts
41
+ .filter((p): p is TypeValue & { kind: "literal" } => p.kind === "literal" && typeof p.value === "string")
42
+ .map((p) => p.value as string)
43
+ .join("");
44
+ }
45
+
46
+ function formatTemplateName(parts: TypeValue[]): string {
47
+ const inner = parts
48
+ .map((p) => (p.kind === "literal" && typeof p.value === "string" ? p.value : `\${${typeValueToString(p)}}`))
49
+ .join("");
50
+ return `\`${inner}\``;
51
+ }
52
+
53
+ function normalizeParts(parts: TypeValue[]): TypeValue[] {
54
+ const result: TypeValue[] = [];
55
+ for (const p of parts) {
56
+ const last = result[result.length - 1];
57
+ if (
58
+ last?.kind === "literal" && typeof last.value === "string" &&
59
+ p.kind === "literal" && typeof p.value === "string"
60
+ ) {
61
+ result[result.length - 1] = T.literal(last.value + p.value);
62
+ } else if (
63
+ last?.kind === "primitive" && last.type === "string" &&
64
+ p.kind === "primitive" && p.type === "string"
65
+ ) {
66
+ // T.string + T.string collapses to T.string
67
+ } else {
68
+ result.push(p);
69
+ }
70
+ }
71
+ return result;
72
+ }
73
+
74
+ function createTemplateRefinement(parts: TypeValue[]): Refinement {
75
+ return {
76
+ name: formatTemplateName(parts),
77
+ meta: { parts },
78
+ check(value: unknown) {
79
+ if (typeof value !== "string") return false;
80
+ return matchesTemplate(value, parts);
81
+ },
82
+ ops: {
83
+ "+"(self: TypeValue, other: TypeValue) {
84
+ return concatTemplates(self, other);
85
+ },
86
+ },
87
+ methods: {
88
+ startsWith(_self: TypeValue, args: TypeValue[]) {
89
+ const arg = args[0];
90
+ if (arg?.kind !== "literal" || typeof arg.value !== "string") return undefined;
91
+ const prefix = getKnownPrefix((_self as any).refinement.meta.parts as TypeValue[]);
92
+ const search = arg.value;
93
+ if (prefix.length >= search.length) {
94
+ return T.literal(prefix.startsWith(search));
95
+ }
96
+ if (search.startsWith(prefix)) return undefined;
97
+ return T.literal(false);
98
+ },
99
+ endsWith(_self: TypeValue, args: TypeValue[]) {
100
+ const arg = args[0];
101
+ if (arg?.kind !== "literal" || typeof arg.value !== "string") return undefined;
102
+ const suffix = getKnownSuffix((_self as any).refinement.meta.parts as TypeValue[]);
103
+ const search = arg.value;
104
+ if (suffix.length >= search.length) {
105
+ return T.literal(suffix.endsWith(search));
106
+ }
107
+ if (search.endsWith(suffix)) return undefined;
108
+ return T.literal(false);
109
+ },
110
+ includes(_self: TypeValue, args: TypeValue[]) {
111
+ const arg = args[0];
112
+ if (arg?.kind !== "literal" || typeof arg.value !== "string") return undefined;
113
+ const fixed = getAllFixedText((_self as any).refinement.meta.parts as TypeValue[]);
114
+ if (fixed.includes(arg.value)) return T.literal(true);
115
+ return undefined;
116
+ },
117
+ },
118
+ properties: {
119
+ length(_self: TypeValue) {
120
+ const parts = (_self as any).refinement.meta.parts as TypeValue[];
121
+ const hasAbstract = parts.some((p) => p.kind !== "literal");
122
+ if (!hasAbstract) return undefined;
123
+ const minLen = getFixedLength(parts);
124
+ return createRange({ min: minLen });
125
+ },
126
+ },
127
+ };
128
+ }
129
+
130
+ function matchesTemplate(value: string, parts: TypeValue[]): boolean {
131
+ let pos = 0;
132
+ for (let i = 0; i < parts.length; i++) {
133
+ const p = parts[i];
134
+ if (p.kind === "literal" && typeof p.value === "string") {
135
+ if (!value.startsWith(p.value, pos)) return false;
136
+ pos += p.value.length;
137
+ } else {
138
+ if (i === parts.length - 1) return true;
139
+ const next = parts[i + 1];
140
+ if (next?.kind === "literal" && typeof next.value === "string") {
141
+ const idx = value.indexOf(next.value, pos);
142
+ if (idx === -1) return false;
143
+ pos = idx;
144
+ } else {
145
+ return true;
146
+ }
147
+ }
148
+ }
149
+ return pos === value.length;
150
+ }
151
+
152
+ export function createTemplate(parts: TypeValue[]): TypeValue {
153
+ const normalized = normalizeParts(parts);
154
+ if (normalized.length === 1 && normalized[0].kind === "literal") {
155
+ return normalized[0];
156
+ }
157
+ if (normalized.length === 1 && normalized[0].kind === "primitive" && normalized[0].type === "string") {
158
+ return T.string;
159
+ }
160
+ return T.refine(T.string, createTemplateRefinement(normalized));
161
+ }
162
+
163
+ export function isTemplate(tv: TypeValue): boolean {
164
+ return tv.kind === "refined" && Array.isArray(tv.refinement.meta.parts);
165
+ }
166
+
167
+ export function getTemplateParts(tv: TypeValue): TypeValue[] | undefined {
168
+ if (tv.kind === "refined" && Array.isArray(tv.refinement.meta.parts)) {
169
+ return tv.refinement.meta.parts as TypeValue[];
170
+ }
171
+ return undefined;
172
+ }
173
+
174
+ export function concatTemplates(left: TypeValue, right: TypeValue): TypeValue {
175
+ const leftParts = getTemplateParts(left) ?? [left];
176
+ const rightParts = getTemplateParts(right) ?? [right];
177
+ return createTemplate([...leftParts, ...rightParts]);
178
+ }
package/src/type-value.ts CHANGED
@@ -5,12 +5,22 @@ import type { Environment } from "./environment.ts";
5
5
 
6
6
  export type LiteralValue = string | number | boolean | null | undefined;
7
7
 
8
+ export type Refinement = {
9
+ name: string;
10
+ meta: Record<string, unknown>;
11
+ check?: (value: unknown) => boolean;
12
+ ops?: Record<string, (self: TypeValue, other: TypeValue) => TypeValue | undefined>;
13
+ methods?: Record<string, (self: TypeValue, args: TypeValue[]) => TypeValue | undefined>;
14
+ properties?: Record<string, (self: TypeValue) => TypeValue | undefined>;
15
+ };
16
+
8
17
  export type TypeValue =
9
18
  | { kind: "literal"; value: LiteralValue }
10
19
  | {
11
20
  kind: "primitive";
12
21
  type: "number" | "string" | "boolean" | "bigint" | "symbol";
13
22
  }
23
+ | { kind: "refined"; base: TypeValue; refinement: Refinement }
14
24
  | { kind: "object"; properties: Record<string, TypeValue>; id: symbol }
15
25
  | { kind: "array"; element: TypeValue }
16
26
  | { kind: "tuple"; elements: TypeValue[] }
@@ -68,6 +78,11 @@ export const T = {
68
78
  body,
69
79
  closure,
70
80
  }),
81
+ refine: (base: TypeValue, refinement: Refinement): TypeValue => ({
82
+ kind: "refined",
83
+ base,
84
+ refinement,
85
+ }),
71
86
  } as const;
72
87
 
73
88
  // --- Helpers ---
@@ -78,6 +93,9 @@ export function typeValueEquals(a: TypeValue, b: TypeValue): boolean {
78
93
  if (a.kind === "primitive" && b.kind === "primitive") return a.type === b.type;
79
94
  if (a.kind === "never" && b.kind === "never") return true;
80
95
  if (a.kind === "unknown" && b.kind === "unknown") return true;
96
+ if (a.kind === "refined" && b.kind === "refined") {
97
+ return a.refinement.name === b.refinement.name && typeValueEquals(a.base, b.base);
98
+ }
81
99
  if (a.kind === "promise" && b.kind === "promise") {
82
100
  return typeValueEquals(a.value, b.value);
83
101
  }
@@ -141,6 +159,18 @@ export function isSubtypeOf(a: TypeValue, b: TypeValue): boolean {
141
159
  return false;
142
160
  }
143
161
 
162
+ if (a.kind === "literal" && b.kind === "refined") {
163
+ if (!isSubtypeOf(a, b.base)) return false;
164
+ return b.refinement.check ? b.refinement.check(a.value) : false;
165
+ }
166
+
167
+ if (a.kind === "refined") {
168
+ if (b.kind === "refined" && a.refinement.name === b.refinement.name) {
169
+ return isSubtypeOf(a.base, b.base);
170
+ }
171
+ return isSubtypeOf(a.base, b);
172
+ }
173
+
144
174
  if (a.kind === "object" && b.kind === "object") {
145
175
  return Object.entries(b.properties).every(
146
176
  ([k, bv]) => k in a.properties && isSubtypeOf(a.properties[k], bv),
@@ -226,6 +256,9 @@ export function deepCloneTypeValue(tv: TypeValue, idMap?: Map<symbol, symbol>):
226
256
  }
227
257
  return { kind: "instance", className: tv.className, properties: newProps };
228
258
  }
259
+ if (tv.kind === "refined") {
260
+ return { kind: "refined", base: deepCloneTypeValue(tv.base, map), refinement: tv.refinement };
261
+ }
229
262
  if (tv.kind === "union") {
230
263
  return simplifyUnion(tv.members.map((m) => deepCloneTypeValue(m, map)));
231
264
  }
@@ -261,6 +294,8 @@ export function typeValueToString(tv: TypeValue): string {
261
294
  }
262
295
  case "primitive":
263
296
  return tv.type;
297
+ case "refined":
298
+ return tv.refinement.name;
264
299
  case "object": {
265
300
  const entries = Object.entries(tv.properties);
266
301
  if (entries.length === 0) return "{}";
@@ -323,6 +358,7 @@ export function getPrimitiveTypeOf(tv: TypeValue): string | undefined {
323
358
  return typeof v;
324
359
  }
325
360
  if (tv.kind === "primitive") return tv.type;
361
+ if (tv.kind === "refined") return getPrimitiveTypeOf(tv.base);
326
362
  if (tv.kind === "object") return "object";
327
363
  if (tv.kind === "array" || tv.kind === "tuple") return "object";
328
364
  if (tv.kind === "function") return "function";
@@ -330,3 +366,7 @@ export function getPrimitiveTypeOf(tv: TypeValue): string | undefined {
330
366
  if (tv.kind === "instance") return "object";
331
367
  return undefined;
332
368
  }
369
+
370
+ export function getRefinedBase(tv: TypeValue): TypeValue {
371
+ return tv.kind === "refined" ? getRefinedBase(tv.base) : tv;
372
+ }