Repository navigation
v1.1.2
npm 1.1.2 (full lockstep across all 14 @metaobjectsdev/* publish candidates), PyPI 1.1.2, NuGet 1.1.2 and Maven Central 8.1.2. Fixes #454 and #455. A bug-fix release: no vocabulary or metamodelVersion change.
Fixed
-
Each port writes its language's standard JSON again by default, and the cross-port form is
a setting (#454). 1.1.0 changed what every generated API writes, with no way back. Decimals
became canonical strings (12.50was sent as"12.5"), and C#, Java and Kotlin stopped
writing a value object's null members. On an upgrade within 1.x, that broke clients of live
APIs. The output form is now the project settingwirein.metaobjects/config.json, read by
every port's codegen the waynamingis (ADR-0019 Amendment 2):{ "schema_version": 1, "wire": { "decimals": "native", "nullMembers": "native" } }Both keys default to
native, meaning what the language's standard serializer writes. C#,
Java and Kotlin write a decimal as a JSON number again (12.50), and a value object's null
member asnullwith its key present: the 1.0.x output. Python and TypeScript write a
decimal as their standard string, which keeps its scale ("12.50"). That is a change from
1.1.1's"12.5". Python adopters coming from 1.0.x: 1.0.x routers returned plain dicts,
and FastAPI's encoder wrote aDecimalas a float (12.5), losing digits past a double's
precision. From 1.1.2 a decimal is Pydantic's string,str(Decimal)("12.50"), so a client
that read a number now reads a string. There is no float option, by decision: a float that
loses precision is not the Python-normal form, and Pydantic's string is.
To keep 1.1.0's output in every port, declare
"wire": { "decimals": "string", "nullMembers": "omit" }and regenerate.nullMembersalso
takes"emit", which writes every declared member of a value object,nullwhen unset,
including members the stored value lacks (Python and TypeScript; in C#, Java and Kotlin
emitis whatnativealready writes). Input is unchanged in every mode: a decimal is
accepted as a string or a number, and garbage or a value past@scaleor@precisionis
400 validation, never rounded. An unknown key or value is refused when the config is read.
Every port's codegen drift gate says whichwireblock reproduces committed output generated
under a different setting, instead of telling you to regenerate. The conformance
corpora pin the portable form in their own config. The per-port table is in
docs/features/api-contract.md, "The wire setting".What to expect when you regenerate with no setting. C# entity and value-object properties
carry[DecimalWire.Number]in place of the string converters, and lose
[JsonIgnore(WhenWritingNull)]. A non-required array member of a value object starts as an
empty list again. Java DTOs and records, and Kotlin data classes, lose the decimal serializer
annotation and@JsonInclude(NON_NULL). Python and TypeScript routes stop canonicalizing
decimals on output. -
C#: two namespaces no longer collide on
DecimalWire.g.csandMetaNetValidation.g.cs.
When the model's packages bound to more than one namespace, each namespace's copy of a helper
was namedDecimalWire.g.cs, andgenrefused the duplicate output path (emitted by both "entity" and "entity"). With more than one namespace, each copy is now named after its
namespace (DecimalWire.Acme.Billing.g.cs). A single-namespace project keeps the plain name. -
C#:
MetaDataLoader.FromDirectory(every overload, the registry overload included) now applies
the project'snaming, as the JVMfromDirectorydoes. A host that composed its own registry
and loaded withFromDirectory(dir, registry, strict: true)gotPhysicalNaming.Default
(snake_case) whatever.metaobjects/config.jsonsaid, and every[Column]and[Table]
changed without a word. The config is found the way the CLI finds it (the nearest ancestor
.metaobjects/config.json, no further than the repository root); a malformed one throws. A
hosted runner that builds its ownGenConfigstill setsColumnNamingStrategyfrom the loaded
root:ColumnNamingStrategy = load.Root.PhysicalNaming.Columns.
Added
- C#:
Includeon the packaged generators.EntityGenerator,DbContextGenerator,
RoutesGenerator,FilterAllowlistGenerator,NamesGeneratorandCallableGeneratortake an
optionalFunc<MetaObject, bool>? Includeinit property that scopes the entry to some objects
(new EntityGenerator { Include = o => … }), andGenContext.Narrow(predicate)hands any
other generator a narrowed context. C# had no equivalent of the TypeScriptfilter, so an
adopter whose value objects belong to another port (ADR-0056 emits a POCO for every concrete
object.value) dropped emitted files by name afterwards. The property isInclude, not
Filter, becausePerEntityGenerator.Filter(MetaObject)is the generators' own applicability
method and stays the subclass seam. Nothing changes for an entry that sets none. meta upgradeprints the agent-context refresh as its first line.
Documentation
- Every upgrade guide starts with the agent-context refresh, and says why: the agent doing the
upgrade reads themetaobjects-*skills, and refreshing them last (as the 1.0-to-1.1 guide did,
at step 9) meant the whole upgrade ran on the old version's skill. upgrading-1.0-to-1.1.mdgains "Changes to generated text (for subclassed or post-processed
generators)": the C# emit-shape changes since 1.0.5, each with theprotected virtualseam that
replaces rewriting the text.- The codegen skill says how to narrow a generator in each language's own idiom (TypeScript
filter, C#Include, Pythonfilter(), the JVM's args or eject), andreferences/csharp.md
gains "Scoping a generator to some objects", which names dropping files by name as the
anti-pattern. Its claim that every port readsnamingnow says what a hosted runner must do.