| name | gangof4-sdk |
| description | GangOf4 Adapter SDK — a metadata compiler that ingests ERPNext app metadata losslessly, normalizes it into a versioned Canon schema, and generates a standalone stack (Drizzle DB + Zod contracts + API routes + React UI + OpenAPI spec) targeting Neon serverless Postgres. Use for any task involving ERPNext refactoring, Canon schema, code generation, or the go4 pipeline. |
GangOf4 Adapter SDK
The GangOf4 SDK is a metadata compiler pipeline that reads ERPNext application source code, normalizes it into a deterministic Canon schema, and generates a complete standalone stack — no ERPNext runtime required.
When to Use This Skill
- Refactoring ERPNext modules into standalone apps
- Generating DB schemas, API routes, or UI from ERPNext doctypes
- Working with Canon schema (parsing, validating, transforming)
- Running the
go4 CLI pipeline
- Generating Neon serverless DB configurations
- Understanding ERPNext doctype metadata structure
Architecture: 3-Layer Pipeline
Extract (Layer B) Canon (Layer A) Generate (Layer C)
┌─────────────────┐ ┌──────────────────────┐ ┌────────────────────┐
│ FsReader │ ──► │ RawERPNextGraph │ │ Drizzle schemas │
│ DumpReader │ │ (lossless, no opinion)│ │ Zod contracts │
│ HttpReader │ └──────────┬───────────┘ │ API routes │
└─────────────────┘ │ Transformer │ React UI scaffolds │
▼ │ OpenAPI spec │
┌──────────────────────┐ │ RLS policies │
│ CanonGraph │◄────│ TanStack hooks │
│ (deterministic, strict)│ │ Neon DB config │
└──────────────────────┘ └────────────────────┘
Hard rule: Extractors never rename, never fix, never infer. All normalization happens in the Transformer.
Quick Start
Full pipeline (programmatic)
import { FsReader } from "@go4/erpnext-reader";
import { transformGraph } from "@go4/transform-erpnext-to-canon";
import { generate } from "@go4/generate-stack";
const reader = new FsReader({
basePath: "./erpnext",
additionalPaths: ["./hrms"],
});
const raw = await reader.readAll(["HR", "Setup"]);
const canon = transformGraph(raw, {
app: "hrms",
appMap: { HR: "hrms", Payroll: "hrms", Setup: "erpnext" },
});
const result = generate(canon, {
targets: ["db", "zod", "api", "ui", "hooks", "openapi", "neon"],
foreignKeys: true,
enumMode: true,
neon: { useWebSocket: false },
});
for (const [path, content] of result.files) {
}
CLI
go4 migrate --source ./erpnext --module HR --out ./output --targets db,zod,api,ui
go4 extract --source ./erpnext --module HR
go4 transform --raw ./raw.json --out ./canon --app hrms
go4 generate --canon ./canon.json --targets db,zod,api,ui,neon --out ./output
go4 migrate --source ./erpnext --module HR --out ./output --watch
Push to Neon DB
npx tsx scripts/push-to-neon.ts
Overview of References
Package APIs
| Package | Reference | When to Use |
|---|
@go4/canon | references/canon.md | Canon schema types, parsing, serialization, validation |
@go4/erpnext-reader | references/erpnext-reader.md | Reading ERPNext source (FsReader, DumpReader, HttpReader) |
@go4/transform-erpnext-to-canon | references/transform.md | Transforming raw ERPNext data to Canon schema |
@go4/generate-stack | references/generate-stack.md | All code generators (DB, Zod, API, UI, OpenAPI, Neon, etc.) |
@go4/cli | references/cli.md | CLI commands, pipeline orchestration, engine API |
Concepts
| Area | Reference | When to Use |
|---|
| Canon Schema | references/canon-schema.md | Understanding CanonDoctype, CanonField, CanonGraph structure |
| Generation Targets | references/targets.md | What each target generates and its output structure |
| Neon DB Integration | references/neon.md | Neon serverless Postgres setup, connection, migration |
Key Concepts
Canon Schema
The Canon schema is the single source of truth between extractors and generators. It is:
- Versioned (
canonVersion: "1.0.0")
- Deterministic (same input → same output, byte-for-byte)
- Strict or lenient parsing modes
Core types: CanonDoctype, CanonField, CanonPermission, CanonChildTable, CanonDocEvent, CanonWorkflow, CanonGraph
Generation Targets
| Target | Output | Description |
|---|
db | db/schema.ts | Drizzle ORM schema (pgTable, columns, FK, enums) |
zod | types/*.ts | Zod validation contracts + TypeScript types |
api | api/*.route.ts | Express-style CRUD routes per doctype |
ui | ui/*Form.tsx, ui/*List.tsx | React form + list scaffolds; optional uiLinkBehavior: 'search' for searchable Link inputs |
rls | db/rls.sql | Row-level security policies |
hooks | hooks/*.hooks.ts | TanStack Query hooks (useList, useGet, useCreate, etc.) |
openapi | openapi/spec.yaml | OpenAPI 3.1 specification |
neon | db/neon.ts, drizzle.config.ts, etc. | Neon serverless DB configuration |
Recommended targets: For production-ready output use --targets db,zod,api,ui,neon,rls or --targets full. Wire Form/List into your own app shell and router.
ERPNext → Canon Field Type Mapping
ERPNext → Canon
Data → "data"
Data (Email) → "data:email"
Link → "link"
Select → "select"
Table → "table"
Int → "int"
Currency → "currency"
Check → "check"
Date/Datetime → "date" / "datetime"
Image → "attach_image"
Section Break → "layout:section"
Column Break → "layout:column"
Tab Break → "layout:tab"
DB Canon Policy
| ERPNext Pattern | Generated Output |
|---|
Link field | varchar(140) + optional FK |
Table child | Separate table with (id, parent_id, parent_field, idx) |
Select options | varchar or pgEnum (configurable) |
is_tree | Add lft, rgt, parent_id columns |
is_submittable | Add docstatus INT DEFAULT 0 |
reqd: 1 | NOT NULL (unless mandatory_depends_on → nullable) |
SDK Version History
v0.1.0 — Foundation (Steps 1–3)
Packages: @go4/canon, @go4/erpnext-reader, @go4/transform-erpnext-to-canon
Tests: 128 pass, 0 failures
- @go4/canon: Zod schemas (CanonDoctype, CanonField, CanonGraph, etc.), CanonicalSerializer (deterministic JSON), ContentHasher (SHA256 fingerprints), strict/lenient parsing
- @go4/erpnext-reader: FsReader (filesystem), RawERPNextGraph, hooks-parser (doc_events), 30+ fieldtype mapping
- @go4/transform-erpnext-to-canon: fieldtype-map, options-parser (discriminated union), naming-strategy, field-transformer, doctype-transformer, graph-transformer (deterministic sorting)
v0.2.0 — Generators + CLI (Steps 4–5)
Packages: + @go4/generate-stack, @go4/cli
Tests: 229 pass
- @go4/generate-stack: gen-drizzle (pgTable), gen-zod (contracts + types), gen-api (CRUD routes), gen-ui (React forms + lists), gen-migration (SQL), gen-indexes, gen-relations, gen-manifest
- @go4/cli: CLI entry point, arg parser, pipeline orchestrator (extract/transform/generate/migrate), file-writer
v0.3.0 — HRMS MVP Validation (Step 6)
Tests: 489 pass
- Real-fixtures e2e test (Employee 60+ fields, Leave Application submittable, Expense Claim 3 child tables)
- Live-repo e2e test (158 doctypes from erpnext + hrms repos)
- Bug fix:
localeCompare sort was non-deterministic — switched to byte-order comparison
v0.4.0 — Phase 2 "Instance Truth"
Tests: 411 pass
- @go4/erpnext-reader: DumpReader (JSON export), HttpReader (REST API), RawPropertySetter, RawCustomField, RawWorkflow types, PipelineWarning structured errors
- @go4/transform-erpnext-to-canon: property-setter-applier, custom-field-merger, workflow-transformer
- @go4/canon: migrator.ts (schema migration framework, v1.0.0 baseline)
v0.5.0 — Production Hardening (Priority 1)
Tests: 514 pass
- Generated code compilation test (
tsc --noEmit on output)
- CLI disk output e2e test (file existence, content validity, manifest consistency, idempotency)
- npm publish setup (all 5 packages)
- Bug fixes: gen-ui section nesting, tab break div close, gen-drizzle/gen-migration tree duplicate columns, gen-api
export * collision → named re-exports
v0.6.0 — Generator Improvements (Priority 2)
Tests: 546 pass
foreignKeys: true — FK constraints on Link columns (Drizzle .references() + migration SQL REFERENCES)
enumMode: true — pgEnum() for Select fields with choices
- UI: conditional visibility (
dependsOn → JSX conditions), child table editable grids, Link field search inputs
- Migration diffing:
diffCanonDoctypes() → ALTER TABLE ADD/DROP/ALTER COLUMN SQL
v0.7.0 — Phase 3 Features
Tests: 571 pass
- Watch mode (
--watch flag) with debounce for .json/.py files
- Multi-app
appMap (module → app resolution)
- Canon v1.1:
overrideClass from hooks.py override_doctype_class
- Server Scripts + Client Scripts: raw types, DumpReader/HttpReader support, Canon schemas, graph-transformer attachment
v0.8.0 — Developer Experience + Scripts (Priority 4–5)
Tests: 586 pass
- Root + per-package READMEs,
gen-linkage.ts (scans 14 ERPNext apps, 1069 doctypes)
gen-scripts.ts: server script stubs (DocType Event, API, Scheduler, Permission Query), client script stubs (named exports + allClientScripts registry), scripts/index.ts barrel
v0.9.0 — OpenAPI Rewrite + Neon DB (Priority 8 + 10)
Tests: 704 pass (including 60 Playwright E2E)
gen-openapi.ts rewritten to openapi3-ts builder + yaml serializer, buildOpenApiDocument() typed API
gen-neon-config.ts: db/neon.ts (HTTP/WebSocket driver), db/migrate.ts, drizzle.config.ts, docker-compose.yml, .env.example, db/package-deps.json
'neon' added to GenerateTarget union
HRMS Extraction Audit (2026-02-08)
Generation run: targets: full | Canon v1.0.0 | All 9 targets verified.
Verified Output
- db/schema.ts: 14,801 lines, pgEnum + pgTable with FK
.references(), reserved word _col suffix
- db/relations.ts: Drizzle
relations() with proper one/many mappings
- db/migration.sql:
CREATE TABLE IF NOT EXISTS with FK REFERENCES, BEGIN/COMMIT
- db/indexes.sql:
CREATE INDEX IF NOT EXISTS + CREATE UNIQUE INDEX
- api/*.route.ts: CRUD (list/get/create/update/delete) + submit/cancel for submittable doctypes
- ui/*Form.tsx: Tab/section/column layout, conditional visibility, child table grids, Link inputs
- types/*.ts: Zod schemas +
z.infer types + InsertSchema (.omit({ id: true }))
- tests/*.sample.test.ts: Vitest tests validating Zod schemas (valid, insert, missing required, wrong type)
- Neon config: HTTP driver, migrate runner, drizzle.config, docker-compose, .env.example
- hooks/*.hooks.ts: TanStack Query hooks (useList, useGet, useCreate, useUpdate, useDelete)
- openapi/spec.yaml: OpenAPI 3.1 spec
- db/rls.sql: Row-Level Security policies
- shadcn/: shadcn/ui + React Hook Form + TanStack Table components
Note: scripts/ directory is empty when using FsReader (scripts live in ERPNext DB). Use DumpReader or HttpReader to populate.
v0.9.1 — Bug Fix (2026-02-09)
Tests: 246 pass (generate-stack), 194 pass (cli), 0 failures
- Fixed:
gen-ui.ts now emits ui/index.ts barrel with re-exports of all Form + List components, matching the pattern used by api/index.ts and types/index.ts
v0.9.2 — shadcn/ui Target (2026-02-09)
Tests: 296 pass (generate-stack, +50 shadcn), 200 pass (cli), 0 failures
- New target:
shadcn — production-grade UI with shadcn/ui + React Hook Form + Zod resolvers + TanStack Table + Next.js App Router
gen-shadcn.ts: forms (RHF + shadcn FormField), columns (TanStack ColumnDef), list pages, detail pages, shared DataTable, form-utils, package-deps
- Reuses existing
types/*.ts (Zod schemas) and hooks/*.hooks.ts (TanStack Query) — no duplicate logic
'shadcn' added to GenerateTarget union, manifest, CLI --targets and full shorthand
Recommended Re-generation Command
go4 migrate --source ./hrms --additional-paths ./erpnext --app hrms --app-map HR=hrms,Setup=erpnext --targets full --foreign-keys --enum-mode
Output: extractions/hrms/v1/ (auto-versioned). All 9 targets. Scripts require DumpReader/HttpReader source.