@shing.wong/sure-factor
v0.4.0
Published
Database schema to production-grade UI — type catalog, validation, sanitization, i18n, audio, and tiered code generation.
Maintainers
Readme
sure-factor
Database schema → production-grade UI components. A code generation framework that introspects SQL schemas, matches columns to a typed catalog, and generates validated, sanitized, internationalized form UIs at three tiers — vibe, prototype, and production.
import { introspectSchemaFromDdl } from '@shing.wong/sure-factor/introspect'
import { matchColumnToTypeSync, loadAllTypesSync } from '@shing.wong/sure-factor'
import { generateForTier } from '@shing.wong/sure-factor/generate'
const schema = introspectSchemaFromDdl(`CREATE TABLE patients (
id UUID PRIMARY KEY,
email VARCHAR(255) NOT NULL,
full_name VARCHAR(100),
zip VARCHAR(5)
);`)
const output = generateForTier(schema, { tier: 'production', component: 'form' })
// → { routes, template, i18n, styles, validation, sanitization, ... }Why sure-factor?
| Problem | How sure-factor solves it |
|---------|--------------------------|
| Forms are repetitive | SQL schema → full form UI in one step. Introspect, match, generate — no hand-written forms. |
| Validation is scattered | Type definitions encode regex, length, and lookup validation per tier. Same validation for vibe (quick) and production (strict). |
| Sanitization is an afterthought | Declarative pipeline per type: [trim, lowercase, normalize(NFKC), slice(0, 254)]. Input and output pipelines are separate. |
| i18n is tedious | Type hints include English, Spanish, and French placeholders, help text, and error messages. Generated forms are multi-language from day one. |
| Code quality varies by stage | Three tiers: vibe (quick regex), prototype (full validation + 2-3 languages), production (lookup datasets + encryption + audit). |
| No standard type catalog | 15 built-in types (email, zip5, icd10, ssn, phone, url, ...) with match rules, validation, sanitization, i18n, and security policies. |
How it compares
| | sure-factor | QuickDBD / dbdiagram | Prisma | Low-Code platforms | |---|---|---|---|---| | SQL schema → UI | ✅ Full pipeline | ❌ Diagram only | ⚠️ Schema only | ❌ Proprietary DSL | | Type catalog | ✅ 15 types, YAML-defined | ❌ | ⚠️ Native types only | ❌ | | Tiered generation | ✅ vibe / prototype / production | ❌ | ❌ | ❌ | | i18n built-in | ✅ en/es/fr per type | ❌ | ❌ | ❌ | | Sanitization pipeline | ✅ Declarative input + output steps | ❌ | ❌ | ❌ | | Lookup datasets | ✅ ICD-10, ZIP codes | ❌ | ❌ | ❌ | | Audio feedback | ✅ Error bell, success chime, voice help | ❌ | ❌ | ❌ | | Export formats | ✅ HTML + CSS + JS + YAML + JSON + XML | ❌ | ❌ | ❌ | | Framework | Agnostic (generates HTML/CSS/JS) | ❌ | ❌ | ❌ | | File size | ~1 MB (catalog + engine) | N/A | ~15 MB | N/A |
Architecture
┌──────────┐
DDL or SQL ─────▶│introspect│
│DDL→Schema│
└────┬─────┘
│ SchemaInfo
▼
┌──────────┐ ┌─────────────┐
│ match │◀───│catalog/types │
│ col→type │ │15 YAML defs │
└────┬─────┘ └─────────────┘
│ TypeMatchResult
▼
┌──────────┐ ┌────────────────┐
│ generate │◀───│catalog/components│
│ tiered │ │5 templates │
│ output │ └────────────────┘
└────┬─────┘
│ routes, template, i18n, styles,
│ validation, sanitization, scripts
▼
┌──────────┐
│ format │
│(Prettier)│
└──────────┘Three phases:
- Introspect — Parse DDL or query
information_schema→SchemaInfo(tables, columns, types, constraints, foreign keys) - Match — Match each column to catalog type definitions using name patterns + data type rules →
TypeMatchResultwith confidence scoring - Generate — Produce tier-aware output: routes, templates, i18n, sanitization pipeline, validation rules, theme CSS, and sure-state stores
Installation
npm install @shing.wong/sure-factorNo peer dependencies. pg and prettier are regular dependencies, so live
schema introspection and code formatting work without extra setup.
Requires Node >= 20.
Quick Start
import { introspectSchemaFromDdl } from '@shing.wong/sure-factor/introspect'
import { loadAllTypesSync, matchColumnToTypeSync } from '@shing.wong/sure-factor'
import { generateForTier } from '@shing.wong/sure-factor/generate'
import { generateStore } from '@shing.wong/sure-factor/generate-store'
import { sanitize } from '@shing.wong/sure-factor/sanitize'
import { convert } from '@shing.wong/sure-factor/serialize'
// 1. Introspect — parse DDL into typed schema
const schema = introspectSchemaFromDdl(`
CREATE TABLE patients (
id UUID PRIMARY KEY,
email VARCHAR(255) NOT NULL,
full_name VARCHAR(100) NOT NULL,
diagnosis VARCHAR(8),
zip VARCHAR(5)
);
`)
// …or introspect a live database (the `pg` driver ships as a dependency):
// import { introspectSchema } from '@shing.wong/sure-factor'
// const live = await introspectSchema(process.env.DATABASE_URL!, ['public'])
// 2. Match — match columns to catalog types
const allTypes = loadAllTypesSync()
const emailCol = schema.tables[0]!.columns.find(c => c.columnName === 'email')!
const result = matchColumnToTypeSync(emailCol, allTypes)
// → { type: { name: 'email', confidence: 1 }, confidence: 1 }
// 3. Generate — produce tiered UI output
const output = generateForTier(schema, { tier: 'production', component: 'form' })
// → {
// routes: "app.post('/patients', ...)",
// template: "<form class='sure-form'>...",
// i18n: { email_placeholder: '[email protected]', ... },
// styles: "@layer sure.tokens, ... sure-ui compose [form, buttons]...",
// notifications: "…showNotification() runtime… | null (inline-only)",
// validation: "z.string().regex(/^[a-zA-Z0-9.../).max(254)",
// sanitization: "[trim, lowercase, normalize(NFKC), ...]"
// }
// 4. Generate a sure-state store
const store = generateStore({
tableName: 'patients',
columns: schema.tables[0]!.columns,
tier: 'production',
})
// → { interfaceCode, apiCode, storeCode, fullCode }
// 5. Sanitize input
sanitize(' [email protected] ', ['trim', 'lowercase', 'normalize(NFKC)'])
// → '[email protected]'
// 6. Serialize between formats
convert(yamlString, 'yaml', 'json') // YAML → JSON
convert(jsonString, 'json', 'md') // JSON → MarkdownType Catalog
15 built-in type definitions in catalog/types/:
| Type | Match patterns | Validation | Sanitization | Security |
|------|---------------|------------|--------------|----------|
| email | %email%, %mail% | RFC 5322 regex | trim, lowercase, normalize(NFKC) | Unicode normalization |
| zip5 | %zip%, %postal% | ^\d{5}$ | stripNonDigits, slice(5) | — |
| zip9 | %zip9%, %zip+4% | ^\d{5}-\d{4}$ | format to 5+4 | — |
| ssn | %ssn%, %social% | ^\d{3}-\d{2}-\d{4}$ | stripNonDigits, format | Encrypt at rest |
| ein | %ein% | ^\d{2}-\d{7}$ | stripNonDigits, format | Encrypt at rest |
| full-name | %name%, %full_name% | 2-100 chars | collapseWhitespace, title case | — |
| icd10 | %diagnosis%, %icd%, %code% | ^[A-Z]\d{2}\.\d{1,2}$ | uppercase | Lookup dataset |
| intl-phone | %phone% | E.164 | stripNonDigits | — |
| us-phone | %phone% with US hint | ^\d{10}$ | format (XXX) XXX-XXXX | — |
| us-address | %address%, %street% | Multi-line | collapseWhitespace | — |
| date-iso | %date%, %created% | ISO 8601 | trim | — |
| date-us | %date% with US hint | MM/DD/YYYY | format | — |
| password | %password%, %pwd% | 8+ chars, complexity | N/A (hash server-side) | Bcrypt |
| credit-card | %card%, %cc% | Luhn check | stripNonDigits | Encrypt at rest |
| url | %url%, %website% | URL regex | trim, lowercase | — |
Example type definition
# catalog/types/email.yaml
name: email
description: Email address conforming to RFC 5322
match:
sql: column_name LIKE '%email%' OR column_name LIKE '%mail%'
regex: ^[^@]+@[^@]+\.[^@]+$
maxLength: 254
validation:
regex: ^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$
maxLength: 254
sanitize:
input: [trim, lowercase, normalize(NFKC), slice(0, 254)]
output: [htmlEscape]
hints:
en: { placeholder: '[email protected]', help: 'Email address' }
es: { placeholder: '[email protected]', help: 'Dirección de correo' }
fr: { placeholder: '[email protected]', help: 'Adresse e-mail' }
tiers:
vibe:
validation: { regex: ^[^@]+@[^@]+\.[^@]+$ }
sanitize: [trim, lowercase]
prototype:
validation: { regex: [full RFC], maxLength: 254 }
sanitize: [trim, lowercase, normalize(NFKC), slice(254)]
hints: { en, es }
production:
validation: { regex: [full RFC], maxLength: 254, lookup: null }
sanitize: [trim, lowercase, normalize(NFKC), slice(254)]
hints: { en, es, fr }
security:
encrypt: false
notes:
- 'Normalize Unicode NFKC to prevent homoglyph attacks'
- 'Lowercase to ensure case-insensitive matching'Component Catalog
5 component templates in catalog/components/:
| Component | Description | Notification Modes | CSS Classes |
|-----------|-------------|-------------------|-------------|
| form | Single-column create/edit form | inline, toast, statusBar, sidePanel | .sure-form__* |
| form-modal | Modal dialog form | inline, toast, statusBar | .sure-modal__* |
| data-table | Read-only data table | toast | .sure-table__* |
| crud-resource | Full CRUD with table + modal | inline, toast, statusBar, sidePanel | .sure-crud__* |
| search-filter | Search with filter controls | inline, toast | .sure-search__* |
Tiers
Generation progresses through three quality tiers — same schema, different output depth:
| Feature | Vibe | Prototype | Production | |---------|------|-----------|------------| | Validation | Basic regex | Type + regex + DB constraints | Full + lookup datasets | | Sanitization | Trim + escape | Full input pipeline | + DOMPurify for rich text | | i18n | English | en + es | en + es + fr | | Notifications | Inline | inline + toast + statusBar | All 4 modes | | Audio | None | Error bell | Chime + speech | | Security | Param queries | + CSRF | + Encryption + audit | | State sync | — | — | sure-state + versioning | | CSS themes | sure-ui compose (7 themes) | sure-ui compose (7 themes) | sure-ui compose (7 themes) |
Sanitization Pipeline
Declarative step functions that compose into input and output pipelines:
import { sanitize, sanitizeInput, sanitizeOutput } from '@shing.wong/sure-factor/sanitize'
// Available steps: trim, lowercase, uppercase, stripNonDigits,
// stripDirectionOverrides, stripZeroWidth, stripControl,
// collapseWhitespace, htmlEscape, normalize(form), slice(n)
sanitize(' [email protected] ', ['trim', 'lowercase', 'normalize(NFKC)'])
// → '[email protected]'
sanitize('<script>alert("xss")</script>', ['htmlEscape'])
// → '<script>alert("xss")</script>'Rich text output
sanitizeOutput(value, true) filters rich text through a tag and attribute
allowlist rather than passing it through:
sanitizeOutput('<p>hi <strong>there</strong></p>', true)
// → '<p>hi <strong>there</strong></p>'
sanitizeOutput('<img src=x onerror=alert(1)>', true)
// → '' (element dropped)
sanitizeOutput('<a href="javascript:alert(1)">x</a>', true)
// → '<a>x</a>' (unsafe URL dropped)Permitted elements are a, b, blockquote, br, code, del, em,
h1-h6, hr, i, li, ol, p, pre, s, span, strong, sub,
sup, u and ul, with class/title on any element and
href/title/rel/target on links. Everything else — including table,
img, form and every on* handler — is dropped. This was a pass-through
before 0.2.0; see CHANGELOG.md before upgrading.
Serialization
Convert between YAML, JSON, XML, and Markdown — useful for exporting catalog definitions:
import { convert, toYaml, toJson, fromJson, toMarkdown, toXml, fromXml } from '@shing.wong/sure-factor/serialize'
convert(yamlString, 'yaml', 'json') // YAML → JSON
convert(jsonString, 'json', 'yaml') // JSON → YAML
convert(yamlString, 'yaml', 'xml') // YAML → XML
convert(xmlString, 'xml', 'md') // XML → MarkdownStore Generation
Generate a sure-state compatible store from a schema:
import { generateStore } from '@shing.wong/sure-factor/generate-store'
const store = generateStore({
tableName: 'patients',
columns: schema.tables[0]!.columns,
tier: 'production',
sync: 'server-first',
versioning: true,
})
// store.interfaceCode — TypeScript interface
// store.apiCode — API client methods
// store.storeCode — sure-state createEntityStore call
// store.fullCode — complete fileFormatting
Generated output can be formatted via Prettier:
import { formatCode, formatGeneratedOutput, formatGeneratedStoreOutput } from '@shing.wong/sure-factor/format'
const formatted = await formatGeneratedOutput(output)
// → same as output, but with formattedRoutes, formattedTemplate, etc.Assets
| Asset | Path | Description |
|-------|------|-------------|
| i18n | catalog/assets/i18n/{en,es,fr}.json | Locale strings for components |
| Audio | catalog/assets/audio/ | Error bell, success chime, voice help (JS audio generators) |
| Lookup data | catalog/assets/data/{icd10-codes,us-zip-codes}.json | Validation datasets |
| Agent skill | .opencode/skills/sure-factor/SKILL.md | How an agent should drive this package |
Theme CSS is not vendored: styles is composed on demand from
@shing.wong/sure-ui — pass any of
its seven themes via generateForTier(schema, { theme: 'forest' }).
Agent skill
The package ships an OpenCode skill covering the parts of this API that are not obvious from the signatures — scoping the DDL before introspecting it, taking columns out of the schema rather than constructing them, reading the catalog YAML instead of dumping the loader, and editing the spec rather than the generated output.
Point OpenCode at it from your opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"skills": ["./node_modules/@shing.wong/sure-factor/.opencode/skills"]
}Only the skill's name and description are loaded up front; the body is read when the agent decides the skill applies. Its claims are executed by this package's test suite, so it cannot drift from the code without a test failing.
It is plain Markdown — copy it anywhere you prefer to keep instructions, or
point the skills array at any local directory or HTTP catalog.
Related Projects
| Project | Role | |---------|------| | sure-ui | Theme CSS + notification runtime for generated output | | sure-state | Client-server state sync (Zustand + WebSocket) | | sure-gentic | Agent framework for LLM-powered code generation | | sure-web-testing | Browser testing via MCP for generated UIs |
Development
git clone [email protected]:ShingWong/sure-factor.git
cd sure-factor
npm install
npm run build
npm test # 176 tests (173 unit + 3 Playwright render)
npm run test:unit # 173, no browser required
npm run test:e2e # 3, needs `npx playwright install chromium`
npm run lint # tsc --noEmit, includes test files
npm run verify # lint + build + test + export check (what CI runs)npm run test:e2e runs only the tests under src/e2e/, which drive a real
Chromium. npm run test:unit covers everything else and needs no browser.
