@gridfox/codegen
v0.3.0
Published
Schema-driven Gridfox TypeScript code generator
Keywords
Readme
@gridfox/codegen
Quick start (implementation)
pnpm install
pnpm --filter @gridfox/codegen run buildRun generator:
node dist/cli/main.js generate --input ./tables.json --output ./src/generated/gridfox
# optional: emit fluent SDK wrapper that imports @gridfox/sdk
node dist/cli/main.js generate --input ./tables.json --output ./src/generated/gridfox --clientGenerate directly from Gridfox API:
# populate .env.example values first
node dist/cli/main.js generate \
--output ./src/generated/gridfox \
--api-key "$GRIDFOX_API_KEY"Preview changes without writing files:
node dist/cli/main.js generate --input ./tables.json --output ./src/generated/gridfox --dry-runCI check mode:
node dist/cli/main.js generate --input ./tables.json --output ./src/generated/gridfox --checkRun validation:
pnpm --filter @gridfox/codegen run test
pnpm --filter @gridfox/codegen run typecheckMaintainer release process: see RELEASING.md.
Run local real-project checks (live Gridfox API):
# from npm (published package): starts interactive flow
npx @gridfox/codegen validate
# shortest path: defaults to interactive generate flow when no command is provided
npx @gridfox/codegen
# non-interactive:
export GRIDFOX_API_KEY="your-key"
node dist/cli/main.js validate --table "Assets" --yes-live-writes
# npm script wrapper:
pnpm --filter @gridfox/codegen run test:real -- --table "Assets" --yes-live-writes
# debug options:
# pnpm --filter @gridfox/codegen run test:real -- --verbose
# pnpm --filter @gridfox/codegen run test:real -- --keep-temp
# pnpm --filter @gridfox/codegen run test:real -- --plan-only
# pnpm --filter @gridfox/codegen run test:real -- --allow-table-regex "^(Assets|Collections)$" --yes-live-writes
# pnpm --filter @gridfox/codegen run test:real -- --jsonProgrammatic usage:
import { generateFromTables } from '@gridfox/codegen'
await generateFromTables(tables, { output: './src/generated/gridfox' })1. Overview
Product name: @gridfox/codegen
Purpose:
@gridfox/codegen is a schema-driven TypeScript code generator for Gridfox projects. It reads Gridfox table metadata JSON and emits stable, typed TypeScript artifacts that remove stringly-typed table access, reduce field alias drift, and centralize schema knowledge for application code and AI-assisted development.
Primary outcome:
Given a Gridfox /tables payload, the tool generates TypeScript modules that expose canonical table constants, field constants, metadata, list unions, and basic record/input types.
Useful resource: https://api.gridfox.com/swagger/v1/swagger.json
2. Why this product exists
Application code built on top of Gridfox tends to accumulate repeated, bespoke logic in several areas:
- Repeated field name strings like
"Location","Customer","Difference" - Alias drift between human-friendly names, local aliases, and ad hoc constants
- Repetitive CRUD, value coercion, and status-handling code
- Repeated linked-record parsing and token-to-record mapping
- Large AI prompts because the schema has to be re-explained in context
- Dashboard logic that becomes embedded in UI components instead of remaining schema-driven
This leads to several concrete problems:
- Field drift: the same field is referred to differently in different files.
- Weak typing: field usage often relies on raw strings and
unknownvalues. - Boilerplate: each workspace reimplements schema helpers, record coercion, and ad hoc joins.
- Slower iteration: changing or adding fields requires many manual updates.
- Larger AI context: prompts must repeatedly include field names, table relationships, and conventions.
@gridfox/codegen exists to make Gridfox integration:
- more type-safe
- more maintainable
- easier to scale across workspaces
- easier to reason about for both humans and AI tools
3. What problem it solves
3.1 Immediate problems solved by code generation
From a single schema payload, @gridfox/codegen can generate:
- stable table constants
- stable field constants
- strongly typed list unions for enum-like fields
- typed record shapes
- typed create and update inputs
- table metadata for future runtime helpers
This alone solves:
- alias drift
- repeated schema transcription
- raw string field access
- inconsistent naming conventions
- many category errors when reading or writing fields
3.2 Longer-term problems it enables us to solve
Once codegen establishes a typed schema layer, we can later add shared runtime helpers that use generated metadata to centralize:
- field alias resolution
- linked-record parsing
- record-id extraction
- generic repositories
- shared filter helpers
- metrics and dashboard definitions
The code generator is therefore both a product in its own right and the foundation for a broader Gridfox SDK.
4. Product goals
4.1 Primary goals
- Generate stable TypeScript artifacts from Gridfox table metadata
- Eliminate raw string field access in application code
- Provide canonical field aliases like
Products.fields.location - Reduce repeated schema knowledge in app code and prompts
- Create a durable foundation for future runtime helpers
4.2 Secondary goals
- Keep generated files deterministic and diff-friendly
- Make the generator easy to run locally and in CI
- Keep the MVP narrow enough to ship quickly
- Support gradual adoption table by table
4.3 Non-goals for the MVP
The initial version should not try to solve everything.
Out of scope for MVP:
- runtime repositories
- transport/client abstractions
- filter/query DSLs
- link token parsing
- error extraction
- metrics engine
- UI integration
- OpenAPI generation
- code mutation of existing handwritten files
5. Users and use cases
5.1 Primary users
- application developers integrating with Gridfox-backed workspaces
- teams maintaining multiple Gridfox-heavy codebases
- AI-assisted development workflows that need small, stable schema references
5.2 Main use cases
Use case 1: Safe field access
Instead of:
record.fields["Location"]Use:
record.fields[Products.fields.location]Use case 2: Safer enum usage
Instead of arbitrary strings:
const location = "In Office"Use generated unions:
const location: ProductLocation = "In Office"Use case 3: Typed input payloads
Instead of loosely shaped update payloads:
const input = {
"Product Name": "Signet Ring",
Location: "In Office",
}Use generated input types:
const input: ProductCreateInput = {
"Product Name": "Signet Ring",
Location: "In Office",
}Use case 4: Reduced AI prompt context
Prompts can refer to canonical references like:
Products.fields.locationOrders.fields.statusQuoteComponentRequirements.fields.difference
rather than pasting long field lists repeatedly.
6. Product scope
6.1 MVP scope
The MVP should be codegen-only and generate these artifacts:
Per table
- table constant
- field constants
- optional reverse alias map
- lightweight field metadata map
- list-field literal unions
- record type
- create input type
- update input type
- writable/readonly field classification constants
Shared output
index.tsbarrel- optional
tables.tsregistry - optional shared primitive types file
Example generated API
export const Products = {
tableName: "Products",
singularName: "Product",
referenceFieldName: "SKU",
fields: {
sku: "SKU",
productName: "Product Name",
location: "Location",
productTemplate: "Product Template",
},
} as const
export type ProductLocation =
| "Virtual Product"
| "In Production"
| "At Assay Office"
| "In Office"
| "On Approval"
| "Sold"
export interface ProductRecord {
id?: string
fields: {
SKU?: number
"Product Name"?: string | null
Location?: ProductLocation | null
}
}7. Functional requirements
7.1 Input
The generator must accept Gridfox table metadata as JSON.
Supported input forms for v1:
- local JSON file
- programmatic input from a JS/TS API
Later versions may support:
- direct API fetching from Gridfox with API key
7.2 Output
The generator must emit TypeScript source files to a configured directory.
7.3 Deterministic generation
The same input must always produce the same output ordering and formatting.
7.4 Name normalization
The generator must convert human-readable table and field names into valid, predictable TypeScript identifiers.
Examples:
Quote Component Requirements->QuoteComponentRequirementsQR Code->qrCodeSub-Products->subProductsCustomer ID->customerId
7.5 Field typing
The generator must map Gridfox field types into sensible TypeScript types.
7.6 Writability rules
The generator must distinguish likely writable fields from readonly/computed fields.
Default readonly types in MVP:
autoCounterformulachild
7.7 List unions
For list fields, the generator must emit literal unions from allowed values.
For multiSelectList, the generator may emit array unions when item values are available.
7.8 Metadata emission
The generator should emit lightweight metadata for future runtime consumers.
8. Non-functional requirements
- Fast: generation should complete quickly on typical workspace schemas
- Deterministic: stable ordering and formatting
- Diff-friendly: generated files should produce readable git diffs
- Strict-TypeScript compatible: output should compile under
strict - Minimal magic: generated code should be easy to inspect and understand
- Low lock-in: output should remain usable even without the generator runtime
9. Proposed architecture
The product should be split into a small number of clearly separated layers.
9.1 High-level pipeline
- Read config
- Read input JSON
- Validate and normalize schema
- Build internal model
- Generate per-table artifacts
- Generate shared artifacts
- Format output
- Write files
9.2 Internal modules
packages/codegen/
src/
cli/
main.ts
config/
schema.ts
loadConfig.ts
input/
readInput.ts
model/
zodSchemas.ts
normalizeTables.ts
internalTypes.ts
naming/
tableNames.ts
fieldAliases.ts
reservedWords.ts
typing/
mapFieldType.ts
writability.ts
generators/
generateTableModule.ts
generateIndexFile.ts
generateRegistryFile.ts
generateSharedTypes.ts
emit/
writer.ts
formatter.ts
utils/
sort.ts
strings.ts9.3 Internal data model
The generator should convert raw Gridfox metadata into a normalized internal representation before emitting code.
Example conceptual model:
interface NormalizedTable {
tableName: string
singularName: string
symbolName: string
referenceFieldName: string
fields: NormalizedField[]
}
interface NormalizedField {
fieldName: string
alias: string
kind: string
tsReadType: string
tsWriteType: string
writable: boolean
required: boolean
unique: boolean
relatedTableName?: string
options?: string[]
}This internal model should be the source of truth for emission.
10. Dependencies and how we will use them
The generator should rely on a few focused packages rather than building custom infrastructure for every layer.
10.1 zod
Why: runtime-safe validation and normalization of input JSON and config.
How we will use it:
- validate the Gridfox
/tablespayload shape - validate generator config
- normalize optional
properties - infer TypeScript types for internal parser inputs
Benefit: reduces hand-written validation code and makes bad schema failures easier to diagnose.
10.2 change-case
Why: deterministic, reusable identifier casing.
How we will use it:
- convert table names into exported symbol names
- convert field names into alias keys
- generate filename-safe identifiers
- standardize enum and type names
Benefit: removes bespoke string transformation logic and makes naming rules consistent.
10.3 ts-poet or ts-morph
We should choose one generation strategy.
Option A: ts-poet
Why: lightweight TS code generation with import handling.
How we will use it:
- build per-table source modules
- emit imports and exports cleanly
- compose type aliases, constants, and interfaces
Benefit: simpler than AST generation, less fragile than raw string templates.
Option B: ts-morph
Why: AST-based generation on top of the TypeScript compiler API.
How we will use it:
- create source files structurally
- emit interfaces, type aliases, constants, and barrels
- support future advanced generation or file mutation workflows
Benefit: more structured and maintainable for long-term evolution.
Recommendation:
For the MVP, use ts-poet unless we know we will need AST-level editing soon. It keeps the generator simpler while still removing most import and formatting headaches.
10.4 prettier
Why: final formatting of generated TypeScript.
How we will use it:
- format generated source before writing to disk
- keep output deterministic and readable
- align with existing repo formatting if Prettier is already used
Benefit: avoids writing custom formatting logic and keeps snapshots stable.
10.5 commander or cac
Why: CLI parsing.
How we will use it:
- parse flags like
--input,--output,--config - provide help text and subcommands later if needed
Benefit: keeps CLI concerns simple and standard.
Recommendation: cac if we want minimal surface area, commander if we want a more traditional CLI framework.
10.6 Node built-ins
Use Node built-ins where possible instead of extra dependencies:
fs/promisesfor file I/Opathfor path resolutionurlif needed for ESM helpers
10.7 Testing stack
Recommended test packages:
vitestfor unit and snapshot teststypescriptfor compile verification in CI
How we will use them:
- unit test parsing, naming, typing, and writability rules
- snapshot test generated files
- verify generated output compiles under strict settings
11. Generation strategy
11.1 Output structure
Recommended generated output:
src/generated/gridfox/
index.ts
tables.ts
shared.ts
Customers.ts
Orders.ts
Products.ts
ProductVariants.ts
Quotes.ts
QuoteComponentRequirements.ts11.2 Per-table module contents
Each generated table module should include:
- exported table constant
- exported metadata object
- exported literal unions for list fields
- exported record type
- exported create input type
- exported update input type
- exported writable and readonly field arrays
- exported reverse alias map if enabled
11.3 Shared types file
shared.ts should define lightweight placeholders for field types whose shapes may vary by endpoint.
Example:
export type GridfoxLinkedValue = unknown
export type GridfoxFileValue = unknown
export type GridfoxImageValue = unknownThis lets the MVP ship without fully standardizing every complex value shape.
12. Type mapping rules
The generator should define a single, explicit mapping from Gridfox field types to TypeScript output types.
12.1 Read types
text->string | nullemail->string | nulltextArea->string | nullrichText->string | nulluser->string | nullfor MVPnumber->number | nullmoney->number | nullpercentage->number | nullformula->number | nullfor numeric formula types (number,money,percentage,autoCounter),string | nullfor explicit non-numeric formula types, andnumber | string | nullwhen formula subtype is absentautoCounter->number | nullcheckbox->boolean | nulldate->string | nulldateTime->string | nulllist-> generated union |nullmultiSelectList-> union array by default when options exist; configurable to alwaysstring[] | nullviamultiSelectMode: "stringArray"parent->GridfoxLinkedValue | nullchild->GridfoxLinkedValue[] | nullmanyToMany->GridfoxLinkedValue[] | nullfile->GridfoxFileValue[] | nullimage->GridfoxImageValue | null
12.2 Write types
For the MVP, create/update inputs should omit:
autoCounterformulachild
May include:
- scalar fields
- list fields
- parent/manyToMany fields
- file/image fields as placeholder types if supported
13. Naming and collision rules
13.1 Table symbols
Generate PascalCase symbols from table names.
Examples:
Customers->CustomersProduct Variants->ProductVariantsQuote Component Requirements->QuoteComponentRequirements
13.2 Field aliases
Generate camelCase alias keys.
Examples:
Customer ID->customerIdQR Code->qrCodeProduct Template->productTemplateSub-Products->subProducts
13.3 Reserved words
Reserved or unsafe identifiers should be adjusted.
Examples:
default->defaultFielddelete->deleteFieldclass->classField
13.4 Collision policy
If two field names normalize to the same alias:
- fail generation by default
- include a clear error message explaining the collision
- optionally support config overrides later
14. CLI and configuration
14.1 CLI shape
Proposed CLI:
npx @gridfox/codegen generate --input ./gridfox-tables.json --output ./src/generated/gridfoxOptional config file:
npx @gridfox/codegen generate --config ./gridfox.config.tsOptional direct API mode:
npx @gridfox/codegen generate --output ./src/generated/gridfox --api-key "$GRIDFOX_API_KEY"14.2 Config shape
export interface GridfoxCodegenConfig {
input?: string
output: string
includeTables?: string[]
excludeTables?: string[]
apiKey?: string
apiBaseUrl?: string
apiTimeoutMs?: number
multiSelectMode?: 'union' | 'stringArray'
emitRegistry?: boolean
emitClient?: boolean
emitReverseAliasMap?: boolean
emitMetadata?: boolean
format?: boolean
}14.3 Initial config recommendations
Defaults:
multiSelectMode: "union"emitRegistry: trueemitClient: falseemitMetadata: trueemitReverseAliasMap: falseformat: trueapiBaseUrl: "https://api.gridfox.com"(when API mode is used)apiTimeoutMs: 10000(when API mode is used)
Additional CLI flags:
--multi-select-mode <union|stringArray>--client(generateclient.tsthat imports@gridfox/sdk)--dry-run(show per-file new/changed/unchanged summary, no writes)--check(same comparison, exits non-zero when files are outdated)--api-key <key>--api-base-url <url>--api-timeout-ms <ms>
Environment variables for API mode:
GRIDFOX_API_KEYGRIDFOX_API_BASE_URLGRIDFOX_API_TIMEOUT_MS
14.4 Recommended generate/check workflow
Local development:
npx @gridfox/codegen generate --input ./tables.json --output ./src/generated/gridfoxCI validation:
npx @gridfox/codegen generate --input ./tables.json --output ./src/generated/gridfox --check15. Implementation plan
Phase 1: Foundation
Build the generator core:
- config loading
- JSON input loading
- zod validation
- normalized internal model
- naming utilities
- field type mapping utilities
Phase 2: Table module generation
Generate per-table modules with:
- table constant
- field constants
- list unions
- record type
- create/update input types
- metadata
Phase 3: Shared artifacts
Generate:
- barrel file
- registry file
- shared primitive placeholder types
Phase 4: Formatting and file writing
- format output with Prettier
- write only changed files if practical
- improve diff stability
Phase 5: Test coverage and adoption
- snapshot tests
- compile tests
- integrate with one real workspace
- replace raw string field access incrementally
16. Example generated output
Example: Products
export const Products = {
tableName: "Products",
singularName: "Product",
referenceFieldName: "SKU",
fields: {
sku: "SKU",
productName: "Product Name",
productDescription: "Product Description",
internalProductId: "Internal Product ID",
totalCost: "Total Cost",
location: "Location",
productState: "Product State",
productType: "Product Type",
basePrice: "Base Price",
retailPrice: "Retail Price",
multiplier: "Multiplier",
category: "Category",
active: "Active",
images: "Images",
qrCode: "QR Code",
productVariants: "Product Variants",
mainImage: "Main Image",
subProducts: "Sub-Products",
productCostComponents: "Product Cost Components",
productTemplate: "Product Template",
productMovements: "Product Movements",
supplierTransactions: "Supplier Transactions",
},
} as const
export type ProductLocation =
| "Virtual Product"
| "In Production"
| "At Assay Office"
| "In Office"
| "On Approval"
| "Sold"Example usage
product.fields[Products.fields.location]
quoteRequirement.fields[QuoteComponentRequirements.fields.difference]17. Testing strategy
Testing should focus on the highest-risk transformation points.
17.1 Unit tests
Parsing and validation
- valid Gridfox payloads are accepted
- malformed payloads fail with useful errors
- optional properties normalize correctly
Naming
- table names normalize correctly
- field names normalize correctly
- acronym handling works as expected
- reserved words are handled safely
- collisions fail loudly
Type mapping
- each Gridfox field type maps to expected read type
- writable field selection is correct
- list unions are emitted correctly
- multi-select behavior is correct
Metadata generation
- related table names are preserved
- list options are preserved
- required and unique flags are preserved
17.2 Snapshot tests
Use real sample schemas to snapshot generated files.
Important snapshot fixtures:
- small schema with 1 to 2 tables
- realistic schema like the provided Customers/Orders/Products/Quotes example
- schema containing edge-case field names and collisions
17.3 Compile tests
Run TypeScript over generated output under strict mode.
Verify:
- emitted files compile
- imports resolve cleanly
- no duplicate identifier issues exist
17.4 Integration tests
End-to-end test the CLI:
- read input JSON
- generate files
- format output
- verify expected file set and contents
18. Risks and edge cases
18.1 Inconsistent field value shapes
Some Gridfox field kinds may have shapes that differ by endpoint or usage. The MVP should treat complex field values conservatively using placeholder types.
18.2 Formula typing ambiguity
Formula fields may vary by formula subtype. The MVP may intentionally simplify these until more precise behavior is known.
18.3 Name collisions
Different field names may normalize to the same alias. The generator must fail clearly instead of guessing.
18.4 Writability assumptions
A field being required does not always mean it must appear in updates. The MVP should focus on likely writable vs likely readonly rather than perfect API semantics.
18.5 Schema evolution
Table or field renames will change generated symbols. Consumers should be encouraged to import from the generated layer rather than hardcode names elsewhere.
19. Rollout plan
Step 1
Build the generator using fixture-driven development and the provided sample schema.
Step 2
Generate a small subset of high-value tables first:
ProductsOrdersQuotesQuoteComponentRequirementsCustomers
Step 3
Replace raw string field access in one app surface, such as Catalog or Quotes.
Step 4
Measure immediate impact:
- fewer raw field strings
- fewer bespoke field constants
- fewer schema snippets in prompts
- simpler type-safe access patterns
Step 5
Expand to remaining tables and adopt generated imports broadly.
20. Success metrics
The MVP should be considered successful if it achieves most of the following:
- raw string field access drops significantly
- generated output is committed and stable in git
- developers prefer generated constants over handwritten aliases
- prompts and docs can reference generated symbols instead of copying schema text
- the generator becomes the source of truth for field names and table metadata
Potential measurable indicators:
- number of hardcoded field strings removed
- number of bespoke schema helper files deleted
- lines of duplicated alias code removed
- number of tables successfully generated
21. Future roadmap
After the codegen-only MVP is stable, likely next steps are:
21.1 Shared runtime helpers
Future additions to @gridfox/sdk may provide:
- field alias resolver
- linked-record parser
- record-id extraction
- error extraction
- value coercion helpers
21.2 Generic repositories
Generated tables and metadata can later back generic repositories with methods like:
findAllcreateupdateremoveresolveTable
21.3 Link resolution helpers
Helpers like:
resolveLinkedRecordresolveLinkedRecordsmapTokensToField
21.4 Declarative metrics engine
Schema-driven dashboard helpers like:
countByStatuscountByLocationriskRules
These should come after the schema layer is proven.
22. Recommended MVP summary
The recommended first version of @gridfox/codegen is:
A CLI and library that reads Gridfox table metadata JSON and emits per-table TypeScript modules containing canonical field constants, list unions, metadata, and basic record/input types.
This version delivers immediate value while keeping scope small, implementation risk low, and future extension paths clear.
23. Open decisions
A few implementation choices still need to be finalized:
- Should emission use
ts-poetorts-morph? - Should reverse alias maps be enabled by default?
- Should file and image fields remain
unknownin MVP, or use minimal placeholder interfaces? - Should registry generation be on by default?
Current recommendation:
- use
ts-poet - emit metadata by default
- emit registry by default
- keep complex field shapes conservative in MVP
- keep reverse alias maps optional
24. Appendix: Suggested package.json dependencies
Example initial dependency set:
{
"dependencies": {
"change-case": "^5.4.4",
"zod": "^3.25.0",
"ts-poet": "^6.12.0",
"cac": "^6.7.14"
},
"devDependencies": {
"prettier": "^3.5.0",
"typescript": "^5.8.0",
"vitest": "^3.0.0"
}
}If we choose ts-morph instead of ts-poet, swap it in accordingly.
25. Final recommendation
Build the MVP as a focused code generator, not a full SDK.
Keep it narrow, deterministic, and schema-first.
That gives us the fastest route to:
- killing alias drift
- reducing schema boilerplate
- shrinking AI context
- establishing a stable base for future Gridfox tooling
