@omnifyjp/ts
v5.8.37
Published
TypeScript model type generator from Omnify schemas.json
Readme
@omnifyjp/ts
TypeScript / Zod / form-metadata / payload-builder generator for Omnify
schemas. Reads a schemas.json produced by omnify generate and emits a
single, type-safe data layer that frontend / consumer projects import
directly.
What it generates
Per model (in <output>/base/<Model>.ts):
- TypeScript interface for the row shape
- Zod schemas for create / update with validation rules
- Per-locale Zod sub-objects for
translatable: truefields (issue #53) <modelName>Metadataconstant — single source of truth for form runners (field type taxonomy, validation, label + description + placeholder dictionaries, enum values, relation pointers, aggregations) — issues #54 + #59<Model>CreateFormState/<Model>UpdateFormStateinterfaces andempty<Model>CreateForm()/build<Model>CreatePayload()/build<Model>UpdatePayload()helpers (issue #55)
Per model with options.service or options.api (in <output>/base/):
<Model>Service.ts— service factory with typed CRUD methods, auto-generated<Model>Filtersinterface from filterable fields (issue #58)<Model>QueryKeys.ts— TanStack Query key factory per model<Model>Hooks.ts— React Query hooks (useList,useDetail,useCreate,useUpdate,useDelete,useRestore) with auto-invalidation and toast notifications viasonner
Plus shared infrastructure at the codegen output root:
common.ts—LocaleMap,Locale,DateTimeString,OmnifyFilepayload-helpers.ts—buildI18nPayload,emptyLocaleMap,SUPPORTED_LOCALES,DEFAULT_LOCALEi18n.ts— validation messages + locale helpersenum/<Enum>.ts— every enum + helpersindex.ts— barrel re-exports<Model>.ts— editable wrapper (created once, never overwritten)
Two ways to invoke
Backend mode (auto)
When the backend's omnify.yaml sets codegen.typescript.enable: true,
the Go binary omnify generate shells out to omnify-ts automatically.
You don't run anything yourself.
Consumer mode (frontend repo, no Go binary)
Install:
npm install --save-dev @omnifyjp/tsCreate omnify.yaml at the consumer project root:
# yaml-language-server: $schema=https://cdn.jsdelivr.net/npm/@omnifyjp/omnify@latest/omnify-config-schema.json
# Top-level `input` is the consumer-mode shortcut. Three forms accepted:
# 1. Local path → ../backend/.omnify/schemas.json
# 2. http(s):// URL → https://raw.githubusercontent.com/org/repo/v1.2.3/.omnify/schemas.json
# 3. @org/pkg/file path → @famgia/dxs-product-schemas/schemas.json
input: ../backend/.omnify/schemas.json
codegen:
typescript:
enable: true
output: src/types/models # `output` is preferred; `modelsPath` still acceptedAdd to package.json scripts:
{
"scripts": {
"codegen": "omnify-ts",
"codegen:check": "omnify-ts && git diff --exit-code src/types/models/base src/types/models/enum"
}
}Run:
npm run codegenCLI flags
omnify-ts read omnify.yaml in cwd
omnify-ts -c, --config <path> specify a different config file
omnify-ts -i, --input <spec> override input (path / URL / npm spec)
omnify-ts -o, --output <dir> override output directory
omnify-ts --force regenerate auto files
omnify-ts --update force re-fetch remote, refresh lockfile
omnify-ts --frozen-lockfile CI mode: fail on upstream drift<spec> (the value of input: or --input) is sniffed by prefix:
| Prefix | Resolved as |
|---|---|
| http:// / https:// | HTTP fetch + cache + lockfile pin |
| @<scope>/<pkg>... | Node module resolution from cwd |
| anything else | Local file path (relative to omnify.yaml) |
Three input strategies
| Strategy | Use when | Example |
|---|---|---|
| Local path | Monorepo, sibling repos, git submodules — frontend can read backend over the filesystem | input: ../backend/.omnify/schemas.json |
| HTTP(S) URL | Backend and frontend are independent repos. Pin a tag/commit SHA in the URL for reproducible builds | input: https://raw.githubusercontent.com/famgia/dxs-product/v3.13.0/.omnify/schemas.json |
| NPM package | Backend publishes a tiny @org/<project>-schemas package containing only schemas.json. The frontend pins via package-lock.json | input: "@famgia/dxs-product-schemas/schemas.json" |
Same omnify.yaml shape for all three. Switching strategies is a one-line
change to the input: field — never a config restructure.
Reproducibility & lockfile (HTTP only)
For HTTP-sourced inputs, omnify-ts maintains:
.omnify/cache/schemas.json— most recently fetched copy.omnify/input.lock.json— pins the input URL + sha256
Commit .omnify/input.lock.json so CI runs omnify-ts --frozen-lockfile
and refuses to silently fetch a different hash. Local dev runs without the
flag and gets a warning when upstream changes; the lockfile is auto-updated
and the dev commits it alongside the regenerated types.
| Mode | Behavior on upstream drift |
|---|---|
| Default (local dev) | Re-fetch + warn + auto-update lockfile |
| --frozen-lockfile (CI) | Fail loudly; refuse to overwrite the lockfile |
| --update | Force re-fetch even if cache + lockfile match |
Local files and npm packages don't need a separate omnify lockfile —
local files are read every run with no fetch step, and npm packages are
already version-pinned by package-lock.json / pnpm-lock.yaml.
Idiomatic CRUD form (with the generated builders)
import { useState } from 'react';
import {
emptyProductCreateForm,
buildProductCreatePayload,
type ProductCreateFormState,
} from '@/types/models/Product';
export function ProductCreateForm() {
const [form, setForm] = useState<ProductCreateFormState>(emptyProductCreateForm());
async function submit() {
await api.create(buildProductCreatePayload(form));
}
return (
<form onSubmit={submit}>
<Input translatable value={form.name} onChange={(v) => setForm({ ...form, name: v })} />
<Input value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} />
{/* … */}
</form>
);
}The builder handles trimming, locale-mirroring (top-level field set to default-locale value), nested per-locale sub-object construction, and dropping empty optional fields. None of that boilerplate lives in the form anymore.
Versioning
@omnifyjp/ts ships in lockstep with the omnify Go binary in the
@omnifyjp/omnify npm package. Bump both together; the version is the
single source of truth for the schemas.json shape they exchange.
In a sibling-repo / submodule layout the lockfiles take care of this
automatically. In the published-package layout (HTTP URL or @org/pkg),
pin the upstream version explicitly in the input: URL or npm dep.
