Skip to content

v1.1.2

Choose a tag to compare

@dmealing dmealing released this 24 Oct 19:23
· 47 commits to main since this release

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.50 was 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 setting wire in .metaobjects/config.json, read by
    every port's codegen the way naming is (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 as null with 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 a Decimal as 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. nullMembers also
    takes "emit", which writes every declared member of a value object, null when unset,
    including members the stored value lacks (Python and TypeScript; in C#, Java and Kotlin
    emit is what native already writes). Input is unchanged in every mode: a decimal is
    accepted as a string or a number, and garbage or a value past @scale or @precision is
    400 validation, never rounded. An unknown key or value is refused when the config is read.
    Every port's codegen drift gate says which wire block 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.cs and MetaNetValidation.g.cs.
    When the model's packages bound to more than one namespace, each namespace's copy of a helper
    was named DecimalWire.g.cs, and gen refused 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's naming
    , as the JVM fromDirectory does. A host that composed its own registry
    and loaded with FromDirectory(dir, registry, strict: true) got PhysicalNaming.Default
    (snake_case) whatever .metaobjects/config.json said, 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 own GenConfig still sets ColumnNamingStrategy from the loaded
    root: ColumnNamingStrategy = load.Root.PhysicalNaming.Columns.

Added

  • C#: Include on the packaged generators. EntityGenerator, DbContextGenerator,
    RoutesGenerator, FilterAllowlistGenerator, NamesGenerator and CallableGenerator take an
    optional Func<MetaObject, bool>? Include init property that scopes the entry to some objects
    (new EntityGenerator { Include = o => … }), and GenContext.Narrow(predicate) hands any
    other generator a narrowed context. C# had no equivalent of the TypeScript filter, 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 is Include, not
    Filter, because PerEntityGenerator.Filter(MetaObject) is the generators' own applicability
    method and stays the subclass seam. Nothing changes for an entry that sets none.
  • meta upgrade prints 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 the metaobjects-* 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.md gains "Changes to generated text (for subclassed or post-processed
    generators)"
    : the C# emit-shape changes since 1.0.5, each with the protected virtual seam that
    replaces rewriting the text.
  • The codegen skill says how to narrow a generator in each language's own idiom (TypeScript
    filter, C# Include, Python filter(), the JVM's args or eject), and references/csharp.md
    gains "Scoping a generator to some objects", which names dropping files by name as the
    anti-pattern. Its claim that every port reads naming now says what a hosted runner must do.