{"id":46806037,"url":"https://github.com/qninhdt/typespec-libraries","last_synced_at":"2026-04-02T12:01:57.388Z","repository":{"id":343387961,"uuid":"1177510366","full_name":"qninhdt/typespec-libraries","owner":"qninhdt","description":"TypeSpec emitters and decorators for generating GORM (Go) and SQLModel (Python) ORM code from a shared schema","archived":false,"fork":false,"pushed_at":"2026-03-24T01:27:18.000Z","size":468,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-24T05:01:30.864Z","etag":null,"topics":["dbml","go","gorm","javascript","postgresql","python","sql","sqlmodel","typescript","typespec","zod"],"latest_commit_sha":null,"homepage":"https://github.com/qninhdt/typespec-libraries","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/qninhdt.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-10T05:03:03.000Z","updated_at":"2026-03-23T15:40:23.000Z","dependencies_parsed_at":"2026-03-10T13:04:02.730Z","dependency_job_id":null,"html_url":"https://github.com/qninhdt/typespec-libraries","commit_stats":null,"previous_names":["qninhdt/typespec-libraries"],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/qninhdt/typespec-libraries","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qninhdt%2Ftypespec-libraries","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qninhdt%2Ftypespec-libraries/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qninhdt%2Ftypespec-libraries/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qninhdt%2Ftypespec-libraries/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/qninhdt","download_url":"https://codeload.github.com/qninhdt/typespec-libraries/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qninhdt%2Ftypespec-libraries/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31305971,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T09:48:21.550Z","status":"ssl_error","status_checked_at":"2026-04-02T09:48:19.196Z","response_time":89,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dbml","go","gorm","javascript","postgresql","python","sql","sqlmodel","typescript","typespec","zod"],"created_at":"2026-03-10T07:12:24.936Z","updated_at":"2026-04-02T12:01:57.382Z","avatar_url":"https://github.com/qninhdt.png","language":"TypeScript","readme":"# TypeSpec Libraries\n\nTypeSpec ORM and schema-generation tooling for teams that want one namespace-first source of truth and multiple generated targets.\n\nThis repository contains:\n\n- `@qninhdt/typespec-orm`: shared decorators, relation resolution, validation, normalization, and selector filtering\n- `@qninhdt/typespec-gorm`: Go + GORM emitter for `@table` and `@data`\n- `@qninhdt/typespec-sqlmodel`: Python + SQLModel emitter for `@table` and `@data`\n- `@qninhdt/typespec-zod`: Zod emitter for `@data`\n- `@qninhdt/typespec-dbml`: DBML emitter for `@table`\n\n## Why This Repo Exists\n\nThe goal of this repo is not just code generation. It is to make a TypeSpec schema behave like a real domain-model contract:\n\n- namespaces define output structure\n- relations are validated once in shared ORM logic\n- emitters fail loudly on unsupported persistence shapes\n- filtered generation is dependency-aware\n- examples are checked in and verified in CI\n\nThat means the same TypeSpec model can drive Go services, Python services, frontend form validation, and DBML documentation without every emitter inventing its own interpretation.\n\n## Current Design Principles\n\n### Namespace-first\n\nNamespaces are mandatory for ORM-managed declarations. They are not decorative. They control:\n\n- output folder layout\n- package structure\n- selection filters\n- import paths\n- namespace-split DBML output\n\nIf a team wants a folder like `models`, the namespace must include `Models`.\n\n### Shared ORM Core\n\nAll emitters consume the normalized ORM graph built by `@qninhdt/typespec-orm`. That graph resolves:\n\n- tables\n- data models\n- mixins\n- namespace segments and output paths\n- relation ownership\n- referenced-column foreign keys\n- filter selection and dependency validation\n- many-to-many shorthand associations\n\n### Strictness Over Silent Fallbacks\n\nUnsupported persistence mappings, invalid relation shapes, conflicting selectors, missing namespaces, and filtered dependencies are treated as diagnostics. The repository prefers explicit failure over degraded output.\n\n## Repository Layout\n\n```text\npackages/\n  typespec-orm/\n  typespec-gorm/\n  typespec-sqlmodel/\n  typespec-zod/\n  typespec-dbml/\nexamples/\noutputs/\ndocs/\n```\n\nImportant directories:\n\n- `examples/`: end-to-end schema used as the canonical example\n- `outputs/`: checked-in generated artifacts\n- `docs/feature-proposal.md`: phased roadmap and design notes\n\n## Package Roles\n\n| Package                      | Input                                  | Output                       | Primary Responsibility                                             |\n| ---------------------------- | -------------------------------------- | ---------------------------- | ------------------------------------------------------------------ |\n| `@qninhdt/typespec-orm`      | TypeSpec decorators and compiler state | none                         | Validation, normalization, relation resolution, selector filtering |\n| `@qninhdt/typespec-gorm`     | normalized ORM graph                   | Go packages for GORM         | Persisted models and DTOs for Go services                          |\n| `@qninhdt/typespec-sqlmodel` | normalized ORM graph                   | Python packages for SQLModel | Persisted models and DTOs for Python services                      |\n| `@qninhdt/typespec-zod`      | normalized ORM graph                   | Zod schemas + inferred types | Frontend and API validation for `@data` models                     |\n| `@qninhdt/typespec-dbml`     | normalized ORM graph                   | DBML files                   | Database review and architecture documentation                     |\n\n## Installation\n\nInstall only the packages you need, but most users start with the ORM core plus one or more emitters:\n\n```sh\npnpm add -D \\\n  @typespec/compiler \\\n  @qninhdt/typespec-orm \\\n  @qninhdt/typespec-gorm \\\n  @qninhdt/typespec-sqlmodel \\\n  @qninhdt/typespec-zod \\\n  @qninhdt/typespec-dbml\n```\n\nEmitter peer dependencies are documented in each package README.\n\n## Runtime Expectations By Target\n\nThe generator packages are TypeSpec build-time dependencies. The generated outputs have their own runtime expectations:\n\n| Target   | Typical runtime dependencies                                                                                 | Notes                                                                   |\n| -------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- |\n| GORM     | `gorm.io/gorm`, `github.com/google/uuid`, optionally `gorm.io/datatypes` and `github.com/shopspring/decimal` | standalone mode writes `go.mod`; non-standalone mode emits code only    |\n| SQLModel | `sqlmodel`, SQLAlchemy, Pydantic-compatible typing support                                                   | standalone mode writes `pyproject.toml` and package roots               |\n| Zod      | `zod`, TypeScript for package builds                                                                         | standalone mode writes `package.json`, `tsconfig.json`, and root barrel |\n| DBML     | none                                                                                                         | DBML is documentation output, not runtime code                          |\n\n## End-to-End Example\n\n### Schema\n\n```typescript\nimport \"@qninhdt/typespec-orm\";\n\nusing Qninhdt.Orm;\n\nnamespace Demo.Platform.Shared;\n\n@tableMixin\nmodel Timestamped {\n  @key id: uuid;\n  @autoCreateTime createdAt: utcDateTime;\n  @autoUpdateTime updatedAt?: utcDateTime;\n}\n\nnamespace Demo.Platform.Accounts;\n\n@table\nmodel User is Demo.Platform.Shared.Timestamped {\n  @unique\n  @maxLength(320)\n  @format(\"email\")\n  email: string;\n\n  @check(\"users_credits_non_negative\", \"credits \u003e= 0\")\n  credits: int32 = 0;\n\n  @manyToMany(\"user_badges\")\n  badges?: Badge[];\n}\n\n@table\nmodel Badge is Demo.Platform.Shared.Timestamped {\n  @unique\n  @maxLength(80)\n  code: string;\n\n  @manyToMany(\"user_badges\")\n  users?: User[];\n}\n\nnamespace Demo.Platform.Forms;\n\n@data(\"Create Invitation Form\")\nmodel CreateInvitationForm {\n  @title(\"Invitee Email\")\n  @placeholder(\"friend@example.com\")\n  inviteeEmail: Demo.Platform.Accounts.User.email;\n}\n```\n\n### Compiler Configuration\n\n```yaml\nemit:\n  - \"@qninhdt/typespec-gorm\"\n  - \"@qninhdt/typespec-sqlmodel\"\n  - \"@qninhdt/typespec-zod\"\n  - \"@qninhdt/typespec-dbml\"\n\noptions:\n  \"@qninhdt/typespec-gorm\":\n    output-dir: \"./outputs/gorm\"\n    standalone: true\n    library-name: \"github.com/acme/domain-models\"\n    collection-strategy: \"jsonb\"\n\n  \"@qninhdt/typespec-sqlmodel\":\n    output-dir: \"./outputs/sqlmodel\"\n    standalone: true\n    library-name: \"acme-models\"\n    collection-strategy: \"jsonb\"\n\n  \"@qninhdt/typespec-zod\":\n    output-dir: \"./outputs/zod\"\n    standalone: true\n    library-name: \"@acme/forms\"\n\n  \"@qninhdt/typespec-dbml\":\n    output-dir: \"./outputs/dbml\"\n    split-by-namespace: true\n```\n\n### Compile\n\n```sh\ntsp compile .\n```\n\n## Shared Concepts Across Packages\n\n### `@table`, `@data`, and `@tableMixin`\n\n- `@table` models participate in persistence emitters\n- `@data` models participate in form/DTO emitters\n- `@tableMixin` models are reusable ORM fragments that are validated but never emitted as standalone tables\n\n### Referenced-column foreign keys\n\nYou can point a relation at something other than `id`:\n\n```typescript\n@table\nmodel Organization {\n  @key\n  @unique\n  code: string;\n}\n\n@table\nmodel User {\n  organizationCode: string;\n\n  @foreignKey(\"organizationCode\", \"code\")\n  organization: Organization;\n}\n```\n\n### Named checks\n\n```typescript\n@check(\"users_credits_non_negative\", \"credits \u003e= 0\")\ncredits: int32 = 0;\n```\n\n### Many-to-many shorthand\n\n```typescript\n@manyToMany(\"user_badges\")\nbadges?: Badge[];\n```\n\nBoth sides must opt in with the same join table name.\n\n### Shared selectors\n\nEvery emitter supports the same selector model:\n\n```yaml\ninclude:\n  - \"Demo.Platform.Forms\"\nexclude:\n  - \"Demo.Platform.Audit\"\n```\n\nSelectors can target:\n\n- a namespace subtree such as `Demo.Platform.Forms`\n- a concrete declaration such as `Demo.Platform.Worlds.World`\n\nBehavior:\n\n- `exclude` wins over `include`\n- redundant selectors warn\n- selecting a model while excluding a required dependency is an error\n\n### Selector Reference\n\nSelectors are plain dotted declaration names. There is no wildcard syntax.\n\n| Selector                               | Meaning                                                                   |\n| -------------------------------------- | ------------------------------------------------------------------------- |\n| `Demo.GamePlatform`                    | everything under that namespace subtree                                   |\n| `Demo.GamePlatform.Forms`              | only the forms subtree                                                    |\n| `Demo.GamePlatform.Worlds.World`       | one concrete declaration plus anything nested below it                    |\n| `Demo.GamePlatform.Audit` in `exclude` | removes the audit subtree even if a broader parent namespace was included |\n\nPractical guidance:\n\n- use namespace selectors for bounded-context level output\n- use concrete declaration selectors sparingly, usually for targeted tests or partial package generation\n- if a selected model depends on an excluded enum, alias, mixin, or relation target, emission fails before files are written\n\n## Output Philosophy\n\n### GORM\n\n- namespace directories become Go package directories\n- standalone mode emits `go.mod` and a root `models.go`\n- `@manyToMany(...)` becomes GORM relationship tags\n- `@check(...)` becomes named check tags\n\n### SQLModel\n\n- namespace directories become Python packages\n- standalone mode emits `pyproject.toml`\n- package roots expose `metadata = SQLModel.metadata`\n- many-to-many shorthand generates `__associations__.py`\n\n### Zod\n\n- only `@data` models are emitted\n- standalone mode emits `src/...` plus a root `index.ts`\n- schemas, inferred types, and form metadata are emitted in a single render pass\n\n### DBML\n\n- can emit one file or split by namespace\n- preserves FK actions, lookup-derived columns, enum indexes, and many-to-many join tables\n\n## Feature Matrix\n\n| Feature                                | ORM Core | GORM                | SQLModel            | Zod             | DBML               |\n| -------------------------------------- | -------- | ------------------- | ------------------- | --------------- | ------------------ |\n| Namespace-first output                 | yes      | yes                 | yes                 | yes             | yes                |\n| Shared `include` / `exclude` selectors | yes      | yes                 | yes                 | yes             | yes                |\n| `@tableMixin`                          | yes      | yes                 | yes                 | n/a             | yes                |\n| Referenced-column foreign keys         | yes      | yes                 | yes                 | n/a             | yes                |\n| Collection persistence strategies      | yes      | `jsonb`, `postgres` | `jsonb`, `postgres` | n/a             | array rendering    |\n| Named `@check(...)` constraints        | yes      | yes                 | yes                 | n/a             | preserved as notes |\n| `@manyToMany(...)` shorthand           | yes      | yes                 | yes                 | n/a             | yes                |\n| Form metadata                          | yes      | form tags           | Pydantic metadata   | `*Meta` exports | n/a                |\n| Alembic helper                         | n/a      | n/a                 | yes                 | n/a             | n/a                |\n| Namespace-split DBML                   | n/a      | n/a                 | n/a                 | n/a             | yes                |\n\n## Example Project In This Repo\n\nThe checked-in example under [`examples/`](examples/) is deliberately more than a toy schema. It demonstrates:\n\n- nested namespaces across several bounded contexts\n- reusable mixins\n- lookup types across namespaces\n- named checks\n- many-to-many shorthand\n- collection persistence\n- Zod metadata\n- namespace-split DBML generation\n\nUseful commands:\n\n```sh\npnpm run compile-examples\npnpm run verify-generated\n```\n\nGenerated outputs are checked into:\n\n- [`outputs/gorm`](outputs/gorm)\n- [`outputs/sqlmodel`](outputs/sqlmodel)\n- [`outputs/zod`](outputs/zod)\n- [`outputs/dbml`](outputs/dbml)\n\n## Migration Notes\n\nIf you are coming from the pre-namespace versions of this repo, the main behavior changes are intentional hard breaks:\n\n- namespaces are now required for ORM-managed declarations\n- emitters no longer invent a flat `models/` folder\n- `library-name` replaced older emitter-specific package or module metadata options\n- Zod no longer supports a custom `filename`; output is namespace-derived per model plus a root barrel\n- filtering is shared across emitters and validated against dependencies instead of being loosely best-effort\n\nThe migration path is usually straightforward:\n\n1. move root-level models into explicit namespaces\n2. rename legacy emitter options to `library-name`\n3. update imports to use namespace-derived paths\n4. turn shared persisted base models into `@tableMixin` where appropriate\n5. regenerate outputs and fix downstream package imports\n\n## Development Workflow\n\nCommon commands:\n\n```sh\npnpm install\npnpm run build\npnpm run test\npnpm run typecheck\npnpm run lint\npnpm run format:check\npnpm run compile-examples\npnpm run verify-generated\n```\n\nCI verifies:\n\n- build\n- typecheck\n- lint\n- formatting\n- unit tests\n- example compilation\n- generated artifact drift\n- `go build` for generated GORM output\n- `python -m compileall` for generated SQLModel output\n- `tsc -p tsconfig.json` for generated Zod output\n\n## Package Documentation\n\n- [`packages/typespec-orm/README.md`](packages/typespec-orm/README.md)\n- [`packages/typespec-gorm/README.md`](packages/typespec-gorm/README.md)\n- [`packages/typespec-sqlmodel/README.md`](packages/typespec-sqlmodel/README.md)\n- [`packages/typespec-zod/README.md`](packages/typespec-zod/README.md)\n- [`packages/typespec-dbml/README.md`](packages/typespec-dbml/README.md)\n\n## Troubleshooting\n\nCommon errors and what they usually mean:\n\n- `namespace-required`\n  The model, mixin, or required dependency is declared at the global namespace. Move it under a real namespace.\n- `filtered-dependency`\n  Your `include` and `exclude` selectors selected a model but removed something it depends on. Expand the include set or stop excluding that dependency.\n- `mixin-field-conflict`\n  Two mixins, or a mixin plus the child model, define the same field name. Rename the field or model the overlap explicitly instead of relying on override behavior.\n- `many-to-many-conflicting-explicit-table`\n  A shorthand join table name collides with an explicit table model. Keep one approach only.\n- `standalone-requires-library-name`\n  The emitter is configured to write package metadata, but you did not provide the package/module name via `library-name`.\n\n## Known Boundaries\n\n- namespaces are required for ORM-managed declarations\n- root-level emitted models are unsupported\n- many-to-many shorthand is intended for simple join tables without payload columns\n- if a join table needs extra payload columns, use an explicit junction model\n- if you want a folder like `models`, put that in the namespace rather than expecting an emitter option\n\n---\n\nMade with heart by @qninhdt, with GPT-5.4 and Claude Opus 4.6.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqninhdt%2Ftypespec-libraries","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fqninhdt%2Ftypespec-libraries","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqninhdt%2Ftypespec-libraries/lists"}