@dforge-core/metadata
Canonical dForge authoring registries, module-file types, and JSON schemas — the single source of truth shared by the web app, the SDK, the diagram library, the CLI, the MCP server, and the VS Code extension.
- Runtime-dependency-free. Safe to import anywhere — editors, bundled web apps, Node CLIs, MCP servers.
- One source of truth. The registries mirror the platform metadata seed
(
server/database/system-modules/metadata/seed-data/) and the structural types mirror the JSON schemas underdocs/schemas/. Parity tests fail the build if either drifts.
Install
pnpm add @dforge-core/metadata
What's inside
| Surface | Exports |
|---|---|
| Field types | fieldTypes, fieldTypeCds, isFieldTypeCd, getFieldType, fieldTypesByColumnType |
| Base datatypes | baseDatatypes, baseToDbDatatype |
| Column types | columnTypes, getColumnType |
| Traits | traits, getTrait, expandTrait, expandTraits |
| Derivation | deriveDbDatatype, deriveBaseDatatype, defaultParams, NUMERIC_TOTAL |
| Aggregation | AggType, AGG_TYPE_LIST |
| Display flags | flagDefs (V Visible · I Identity · E Editable · M Mandatory · H Hidden) |
| View / report kinds | dataViewKinds, vizTypes, chartTypes |
| Scalar enums | FieldTypeCd, BaseDatatypeCd, ColumnTypeCd, FlagCd, AlignType |
| Module-file types | ManifestDef, EntityDef, DataViewsFile, ReportsFile, MenusFile, FoldersFile, RolesFile, SettingsFile, JobsFile, TriggersFile, WebhooksFile, PrintTemplatesFile, SeedDataFile, ActionsFile, … |
| Filter expression | Filter, FilterCondition, FilterGroup, SortClause |
| DSL analyzer | ./dsl — checkDsl, parseDsl, tokenize, BUILTINS (see Action DSL) |
| JSON schemas | ./schemas/<name>.schema.json (see Schemas) |
Registries & derivation
An author picks a friendly fieldTypeCd; the helpers derive the physical
metadata so no one types a raw SQL type by hand.
import {
getFieldType,
isFieldTypeCd,
deriveDbDatatype,
deriveBaseDatatype,
defaultParams,
} from "@dforge-core/metadata";
isFieldTypeCd("currency"); // true
isFieldTypeCd("reference"); // false — not a real code (it's `lookup`)
getFieldType("currency");
// { cd: "currency", columnType: "D", baseDatatypeCd: "number", precision: 2, ... }
deriveDbDatatype("currency"); // "numeric(18,2)"
deriveDbDatatype("text"); // "varchar"
deriveDbDatatype("textarea"); // "text"
deriveDbDatatype("lookup"); // null — relationship-backed, no own column
deriveBaseDatatype("color"); // "string"
defaultParams("flags"); // { style: "buttons" }
defaultParams("number"); // undefined — numeric types carry no defaults
Traits
import { expandTrait, expandTraits } from "@dforge-core/metadata";
// Expand a trait into the FieldDef map it contributes to an entity.
expandTrait("audit", "invoice");
expandTraits(["identity", "audit"], "invoice");
Module-file types
The structural types describe the authoring shape of every file in a .dforge
module package — the same shapes the JSON schemas validate.
import type { ManifestDef, EntityDef, DataViewsFile } from "@dforge-core/metadata";
const entity: EntityDef = {
description: "Customer",
dbObject: "customer",
fields: {
/* … */
},
};
Note: these are the authoring shapes. The resolved runtime model returned by the API (
metadata.getModel) is a different contract — see@dforge/sdk'sModel*types, which reuse the scalar enums from this package.
Action DSL
@dforge-core/metadata/dsl is a separate entry point holding the lexer, parser
and static rules for an action body (logic/actions/*.dsl). One implementation
serves both hosts that check these files: the language server, which turns
issues into squiggles, and the module validator, which gates a pack.
import { checkDsl } from "@dforge-core/metadata/dsl";
const issues = checkDsl(source, {
moduleCode: "wms",
action: { code: "transfer_stock", executionMode: "single" },
currentEntity: { qualified: "wms.stock_movement", columns: entityColumns },
});
// → [{ rule: "dsl/unknown-column", severity: "error", message, start, end, line, column }]
What a rule knows about the module arrives through that context — a module code,
the action, and a column lookup (any Set or Map will do) — rather than
through a module loader, which is what lets the same rule run in an editor and
in a file walker. Every field is optional and every rule that reads one fails
open: with an empty context the text-only rules still run and the rest stand
down silently. A false "unknown column" underlines working code and blocks a
pack; a missed one costs an install round trip.
One obligation comes with currentEntity: columns must be trait-expanded,
built-in traits and the module's own traits.json alike, because the identity
PK and [status] live in traits rather than in fields. An unexpanded set
turns the rule into a wall of false errors. When the entity can't be resolved —
a bridge module acting on another module's entity — pass null rather than the
fragment you do have.
Issues carry both byte offsets and a 1-indexed line/column, so a
range-based host and a file.dsl:12 reporter each get what they need.
The blocks, the [field] lookbehind and the case rules follow
ActionDslCompiler rather than the prose docs: five block kinds including
schema:, headers anchored at column 0 (an indented one is body text, and is
reported), and execute: running to end-of-file.
It is not re-exported from the package root: importing @dforge-core/metadata
in a browser bundle must not pull a tokenizer in behind it.
JSON schemas
The 14 canonical schemas are shipped alongside the types and stay byte-identical
to docs/schemas/ (a parity test enforces it; prepack re-syncs before
publish). Import them directly or point a validator / editor at the files.
import entitySchema from "@dforge-core/metadata/schemas/entity.schema.json" with { type: "json" };
// VS Code settings.json — validate module files against the shipped schemas
"json.schemas": [
{
"fileMatch": ["entities/*.json"],
"url": "./node_modules/@dforge-core/metadata/schemas/entity.schema.json"
}
]
Available: manifest, entity, data_views, folders, menus, roles,
settings, reports, jobs, triggers, webhooks, print_templates,
traits, seed_data, demo_vars.
Source of truth & parity
This package never edits the canonical sources — it mirrors them and guards the mirror:
- Registries mirror the platform metadata seed;
test/registries.test.tsfails if a code drifts fromfield_types.json(and a compile-time coverage guard fails if theFieldTypeCdunion and thefieldTypesarray diverge). - Schemas are copied from
docs/schemas/bypnpm sync-schemas;test/schemas.test.tsfails if the shipped copy drifts.prepackre-syncs.
When consumed standalone (no monorepo around the package) the parity checks are skipped — the shipped data is authoritative on its own.
Scripts
pnpm build # tsc → dist/
pnpm test # vitest (registry + schema parity)
pnpm sync-schemas # refresh schemas/ from docs/schemas/
License
MIT