Skip to content

Commit 6fa3dfb

Browse files
Copilothotlong
andcommitted
refactor: address code review - optimize levenshtein, typed error map interface, use entries()
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent e041fb8 commit 6fa3dfb

4 files changed

Lines changed: 39 additions & 22 deletions

File tree

DX_ROADMAP.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ This roadmap prioritizes improvements based on the **"Time to First Wow"** metri
2222
| **Test Coverage** | ⭐⭐⭐⭐⭐ | 191 test files, 5,157+ tests |
2323
| **IDE Autocomplete** | ⭐⭐⭐⭐ | Bundled `objectstack.json`, `.describe()` tooltips |
2424
| **Getting Started** | ⭐⭐⭐ | Docs exist but no interactive playground |
25-
| **Error Messages** | ⭐⭐⭐ | Zod defaults + some custom refinements |
26-
| **Helper Functions** | ⭐⭐⭐ | `Field.*` excellent; `ObjectSchema.create()`, `defineStack()` minimal |
25+
| **Error Messages** | ⭐⭐⭐ | Custom error map with contextual messages and "Did you mean?" suggestions |
26+
| **Helper Functions** | ⭐⭐⭐ | `Field.*`, `ObjectSchema.create()`, `defineStack()`, `defineView()`, `defineApp()`, `defineFlow()`, `defineAgent()` + strict mode |
2727
| **Reference Docs** | ⭐⭐⭐ | API docs generated but no field type gallery or error code reference |
2828
| **Examples** | ⭐⭐⭐ | 4 examples but missing "How to Run" instructions |
2929
| **Migration Story** | ⭐⭐ | V3 guide exists but no automated `codemod` tooling |
@@ -111,11 +111,11 @@ This roadmap prioritizes improvements based on the **"Time to First Wow"** metri
111111
- [x] Implement `defineView()` with column type inference
112112
- [x] Implement `defineApp()` with navigation builder
113113
- [x] Implement `defineFlow()` with step type inference
114-
- [ ] Create custom Zod error map with contextual messages
115-
- [ ] Add "Did you mean?" suggestions for FieldType typos
116-
- [ ] Create pretty-print validation error formatter for CLI
117-
- [ ] Add branded types for ObjectName, FieldName, ViewName
118-
- [ ] Add strict cross-reference validation mode to `defineStack()`
114+
- [x] Create custom Zod error map with contextual messages
115+
- [x] Add "Did you mean?" suggestions for FieldType typos
116+
- [x] Create pretty-print validation error formatter for CLI
117+
- [x] Add branded types for ObjectName, FieldName, ViewName
118+
- [x] Add strict cross-reference validation mode to `defineStack()`
119119

120120
---
121121

@@ -362,4 +362,4 @@ This roadmap prioritizes improvements based on the **"Time to First Wow"** metri
362362

363363
**Last Updated:** 2026-02-12
364364
**Maintainers:** ObjectStack Core Team
365-
**Status:** 🆕 Active — Phase 1 Ready to Start
365+
**Status:** 🔄 Active — Phase 2 Complete, Phase 3 Ready to Start

packages/spec/src/shared/error-map.zod.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@ import { z } from 'zod';
44
import { suggestFieldType, formatSuggestion, findClosestMatches } from './suggestions.zod';
55
import { FieldType } from '../data/field.zod';
66

7+
/**
8+
* Zod v4 raw issue structure (subset used by the error map).
9+
*/
10+
export interface ObjectStackRawIssue {
11+
code: string;
12+
path?: (string | number)[];
13+
input?: unknown;
14+
values?: unknown[];
15+
origin?: string;
16+
minimum?: number;
17+
maximum?: number;
18+
expected?: string;
19+
format?: string;
20+
keys?: string[];
21+
[key: string]: unknown;
22+
}
23+
724
/**
825
* ObjectStack Custom Zod Error Map
926
*
@@ -22,7 +39,7 @@ import { FieldType } from '../data/field.zod';
2239
* SomeSchema.safeParse(data, { error: objectStackErrorMap });
2340
* ```
2441
*/
25-
export const objectStackErrorMap = (issue: { code: string; [key: string]: unknown }): { message: string } | null => {
42+
export const objectStackErrorMap = (issue: ObjectStackRawIssue): { message: string } | null => {
2643
// --- Invalid value (enum) with suggestions ---
2744
if (issue.code === 'invalid_value') {
2845
const values = issue.values as unknown[];

packages/spec/src/shared/suggestions.zod.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { FieldType } from '../data/field.zod';
1818

1919
/**
2020
* Compute Levenshtein edit distance between two strings.
21-
* Used to rank how similar a typo is to known valid values.
21+
* Uses space-optimized two-row approach (O(min(m,n)) space).
2222
*/
2323
export function levenshteinDistance(a: string, b: string): number {
2424
const la = a.length;
@@ -27,27 +27,28 @@ export function levenshteinDistance(a: string, b: string): number {
2727
if (la === 0) return lb;
2828
if (lb === 0) return la;
2929

30-
const matrix: number[][] = [];
30+
// Use only two rows for space efficiency
31+
let prev = new Array<number>(lb + 1);
32+
let curr = new Array<number>(lb + 1);
3133

32-
for (let i = 0; i <= la; i++) {
33-
matrix[i] = [i];
34-
}
3534
for (let j = 0; j <= lb; j++) {
36-
matrix[0][j] = j;
35+
prev[j] = j;
3736
}
3837

3938
for (let i = 1; i <= la; i++) {
39+
curr[0] = i;
4040
for (let j = 1; j <= lb; j++) {
4141
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
42-
matrix[i][j] = Math.min(
43-
matrix[i - 1][j] + 1, // deletion
44-
matrix[i][j - 1] + 1, // insertion
45-
matrix[i - 1][j - 1] + cost, // substitution
42+
curr[j] = Math.min(
43+
prev[j] + 1, // deletion
44+
curr[j - 1] + 1, // insertion
45+
prev[j - 1] + cost, // substitution
4646
);
4747
}
48+
[prev, curr] = [curr, prev];
4849
}
4950

50-
return matrix[la][lb];
51+
return prev[lb];
5152
}
5253

5354
/**

packages/spec/src/stack.zod.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,7 @@ function validateCrossReferences(config: ObjectStackDefinition): string[] {
276276

277277
// Validate view data source → object references (nested in data.object)
278278
if (config.views) {
279-
for (let i = 0; i < config.views.length; i++) {
280-
const view = config.views[i];
279+
for (const [i, view] of config.views.entries()) {
281280
const checkViewData = (data: unknown, viewLabel: string) => {
282281
if (data && typeof data === 'object' && 'provider' in data && 'object' in data) {
283282
const d = data as { provider: string; object: string };

0 commit comments

Comments
 (0)