Skip to content

v1.1.0

Choose a tag to compare

@dmealing dmealing released this 23 Oct 07:40
· 51 commits to main since this release

npm 1.1.0 (all 14 @metaobjectsdev/* packages in lockstep), PyPI 1.1.0, NuGet 1.1.0 and
Maven Central 8.1.0. metamodelVersion moves 1.0 to 1.1. The vocabulary is additive:
a model that uses none of the new names loads as it did under 1.0, with three exceptions: a bare
extends naming an object in another package is refused in TypeScript, Python and C#, a
fractional validator.numeric @min or @max is refused in TypeScript, C# and Python (both
under Breaking below), and the Java and Python parser fixes under Fixed bring those
ports into line with TypeScript. What it generates is NOT unchanged in every port: in C#, Java, Kotlin's runtime and
Python the default for column and table names flips from the field name to snake_case, so a
1.0 estate whose columns are named like its fields must add
"naming": { "columns": "literal" } (see Breaking below). Each entry says which models it
affects. This is a MINOR because the
metamodel moved and reporting is a new feature. It also removes commands and exports (the docs
commands and the types behind them, under Removed): that break rides this MINOR by the
maintainer's ruling in ADR-0034 Amendment 9. One page walks a 1.0.x project through every step:
upgrading-1.0-to-1.1.md.

Breaking, C#, Java, Kotlin's runtime and Python: the default physical names are snake_case.
Column and table names the metadata does not give now come from one naming setting in
.metaobjects/config.json, defaulting to snake_case in every port (it was the field name in
those four). A database whose columns are named exactly like the fields adds
"naming": { "columns": "literal" }. The per-port flags that used to carry the rule
(--column-naming, the columnNaming pom arg, ObjectManager(column_naming=),
columnNamingStrategy) are removed and refused with a message naming the setting. Nothing in the
build fails if you skip this: the first failure is a query against a column that does not exist.
Upgrade every port of an estate together: a 1.1 TypeScript tool reads naming, a 1.0.x C#, Java
or Python tool silently ignores the unknown key and keeps the old column names, so two ports of
one estate disagree about the schema without any error (only a 1.0.x TypeScript tool refuses the
key). Migration: physical-naming.md. The entry is under
Changed.

Breaking, in every port: documentation is generated the way code is. Every generator,
documentation included, is an entry in the project's generator list; one command runs the
list; nothing is generated that the list does not declare. The docs commands are removed:
meta docs and meta verify --docs (TypeScript), dotnet meta docs (C#), metaobjects docs
(Python) and the metaobjects:docs goal (Java and Kotlin, 8.1.0). Each prints where its work
went and exits 2 (the Maven goal fails the build, so mvn exits 1). A gen command given a 1.0 docs configuration fails before it writes anything
rather than running and writing no documentation, and in TypeScript a project with no
metaobjects.config.ts fails meta gen with a pointer instead of having its pages written
from defaults. There is no upgrade script: docs-are-generators.md
has a section per port, written for a coding agent to apply to the config by hand. Ruling:
ADR-0034 Amendment 9, which numbers the release 1.1.0 (Maven 8.1.0) by the maintainer's
explicit decision. The entries are under Removed and Changed.

Breaking, Java and Kotlin: commit .metaobjects/.gen-state/.hashes.json. mvn metaobjects:generate now records what it writes in the same committed hash manifest the other
ports use, and decides overwrites from it instead of from the GENERATED header token alone.
An edited generated file is refused by name (it was overwritten), and a file no generator emits
any more is deleted. The first run adopts your existing generated files and tells you to commit
the manifest; commit it in every module. Migration:
jvm-codegen-hash-manifest.md. The
entry is under Changed.

What 1.1.0 is. The reporting release (FR-044): declare dimensions, measures, segments and an
object.report, and get a SQL view, a served read-only list route in every port, a generated
TanStack list hook, and a Cube data model, with @spine for rows that have no facts and
@default for an empty measure. It also carries the first slice of requirements (the gate in
every port's verify), the derived-key parity fix across the ports, and the list-hook and
nested-average fixes found by the first adopter to map real report pages onto the feature.

How much smaller the code gets. The size reduction comes in two steps, and each is its own
number. The first landed on 1.0.x: 439 lines of handler code retired. The second is reporting,
and it is a SQL reduction only: in an adopter, hand-written SQL went from 75 lines to 15 (-80%).
Total hand-written lines did not fall, so do not read it as a total line reduction.

Breaking, TypeScript, Python and C#: a bare extends no longer binds an object in another
package.
It now fails with ERR_UNRESOLVED_SUPER, as Java already did and ADR-0042 requires. A
bare name binds the declaring file's own package, then the root. Qualify the reference:
extends: other::Base. The entry is under Changed (ADR-0059).

Breaking, TypeScript, C# and Python: attr.int and attr.long take whole numbers only, so a
fractional validator bound is refused.
A validator.numeric declares @min and @max as
integers, and a fraction such as @min: 0.5 on a decimal or double field is now
ERR_BAD_ATTR_VALUE at load ("'@min' must be of type 'int' but got the fraction 0.5"), as Java
already refused it, so the five ports agree. 1.0.x TypeScript accepted it. Fix: write an integer
bound (@min: 0). A decimal-valued bound (for example "at least 0.01") cannot be declared in
1.1.0: that needs new metamodel vocabulary, which is 1.2 work. A whole number written 1.0
still loads, as 1. The entry is under Changed and the FR-044 report entries.

Breaking, in every port: a field.decimal crosses the API as one canonical string
(ADR-0019 Amendment 1).
Every decimal a generated route sends, on an entity, a projection or a
report row (an avg, a ratio, a sum of a decimal), is a JSON string in plain notation with no
exponent and no trailing fraction zeros: 12.50 is sent "12.5", zero is "0". Before 1.1.0
each port sent its own form (TypeScript a string that kept trailing zeros, the others a number), so
a client that read a number from C#, Java, Kotlin or Python now reads a string, and TypeScript's
"12.50" is now "12.5". Display formatting, such as two digits
for an amount of money, is a view's job, never the wire's. On input a route takes a string in
JSON-number grammar or a JSON number, read exactly (in TypeScript a JSON number is exact to 15 significant
digits, so send a string for more); anything else is 400 validation, and so is
a value with more fraction digits than @scale or more integer digits than
@precision - @scale (nothing is rounded). A decimal filter operand uses the same grammar, is
compared exactly, and a bad one is 400 invalid_filter_value. Postgres and MySQL keep the
declared precision; SQLite and D1, which have no decimal type, keep 15 significant digits, and
meta migrate warns for a decimal there declared with @precision above 15. An ejected routes
generator keeps its old decimal handling until you resync it. The per-port entries are under
Changed.

Eleven diffs to expect when you upgrade.

  • Generated files no generator emits any more are deleted. The first full gen after the
    upgrade, in every port, deletes a file the committed hash manifest records writing, that no
    generator emitted in that run and that still hashes to what was recorded (a renamed value
    object's old file, an api-docs page that moved). An edited such file is refused by name and
    kept. Review the deletions in the diff. The entry is under Fixed.

  • An inherited reference means what it meant where it was written (ADR-0059 Decisions 3
    and 7).
    A reference an object inherits through extends resolves in the package of the
    node that declares it, not the inheriting object's package. A model whose base and subtype
    live in different packages and that relied on a bare name meaning the subtype package's
    object now binds the one in the base's package (or the root-level one). Qualify the
    reference if you meant the other.

  • References are written fully qualified (ADR-0059). meta export and canonical
    serialization write every reference as pkg::Name, a root-level target referenced from a
    package as ::Name, and a model that spans packages with a package on each top-level object.
    Exported JSON now loads back to the same model. The neutral template data's targetRef carries
    the same canonical value (rel product -> shop::Product), so a Mustache template that prints it
    changes. Documentation pages and error messages still quote a reference as you wrote it. meta fmt still writes references as you wrote them. Regenerate, then review the diff.

  • An ejected generator needs a resync (G11). An owned copy keeps the gate and the options it
    was copied with, so it does not receive the new report behaviour until you resync it. The
    entries under Added and Fixed name the command for each affected generator (meta eject hooks --force, meta eject routes --force, meta eject routes-hono --force). A generator you
    never ejected needs nothing.

  • An ejected Java Spring generator keeps its old rules until you re-eject it. A copy of
    routes, dto, repository or value-object made before 1.1.0 compiles after the upgrade
    wherever it compiled before, and applies the rules it was copied with: it calls its own sibling
    copies, and the four AutoSetSupport selectors it calls on the packaged class are still there,
    marked @Deprecated. A copy of routes, dto or value-object ejected alone did not compile
    before 1.1.0, and the upgrade does not change that. Run
    mvn metaobjects:eject -Dnames=<generator> -Dforce=true for each one you own to get a copy
    that compiles alone and reads the shared rules from SpringGeneratorSupport (the copy of it
    written beside them is yours too), and merge your own edits back from git diff. Code of your
    own that calls a static helper on the packaged SpringDtoGenerator or
    SpringRepositoryGenerator, or subclasses one of them, still compiles too: the 1.0.x helpers
    are still there, marked @Deprecated, and forward to SpringGeneratorSupport. A generator you
    never ejected needs nothing.

  • Decimal handling is generated. Every port's generated routes gain their decimal
    wire code (the canonical writer, the strict reader and the exact filter operand), and the
    allowlists gain the decimal field set. An ejected routes or allowlist generator needs a
    resync to get it; the Changed entries name the files per port.

  • C# array properties are IList<T>. A generated scalar or enum array property was
    ICollection<T>, which EF Core 8 cannot read on SQLite; code that assigns a HashSet<T> to
    one assigns a List<T>. The entry is under Changed.

  • A keyless projection regenerates differently (G12). meta gen rewrites the read-only
    surface of a keyless projection (no /:id route, no by-id function, and no write verbs in the
    API docs), and meta verify --codegen reports the affected pages as stale until they are
    regenerated. Run meta gen and review the diff; three-way merge preserves hand edits.

  • A multi-hop enum field references the shared enum. A field that reaches a
    package-level abstract enum through another field, with no @values or @intValueMap
    redeclared on the way, used to get an enum type of its own; it now references the shared one,
    in every port. In Kotlin its former <Object><Field> enum file is no longer written, and code
    that imported that type imports the shared enum instead. Java has two more diffs: a
    value-object field that reaches a shared or @provided enum, even in one hop, references that
    type instead of nesting a copy (TicketPayload.Priority becomes the top-level Priority),
    and an inline enum that redeclares @values or @intValueMap under a shared enum moves from
    the shared enum's name to <Owner><Field> (TaskDto.Priority becomes TaskDto.TaskPhase).
    Every other inline enum, in every port, keeps its previous name and bytes. Two build
    settings follow from it. Java: SpringDtoGenerator owns the shared enum files, so a pom
    that runs the value-object or output-parser generator WITHOUT it, on a model where one of
    their objects reaches a shared enum, must set emitSharedEnums=true (an <args> entry on the
    SpringValueObjectGenerator or SpringOutputParserGenerator <generator>) on one of them. Kotlin:
    every generator that types an enum now needs providedEnumNamespace /
    providedEnumPackages for a model with a @provided enum; set them in the plugin's
    <globals>. An owned (ejected) Kotlin generator keeps compiling and keeps its previous
    @provided answer until you re-eject it. An owned Java value-object or output-parser
    generator must be re-ejected, or merged with its new reference, when a response value object
    reaches a shared or @provided enum: an old copy of one beside the packaged other generates
    code that does not compile. The Fixed entry lists each port's before and after, and the
    re-eject commands.

  • CLI exit codes changed. meta gen on a model that does not load exits 1 (1.0.13 exited 2).
    meta init in an initialised project is a no-op that exits 0 (it exited 1; --force still
    re-scaffolds). The removed docs commands exit 2, except the Maven docs goal, which fails
    the build, so mvn exits 1. A script that tests for a specific code needs the new one.

  • Smaller changes an adopter can hit. Each has its own entry.

    • TypeScript: ObjectManager now enforces numeric bounds, array bounds, uri/inet format and
      an assigned primary key; RequirementTestArgs and RequirementView gained required
      fields (a compile break for code that builds them by hand).
    • Java: every regenerated class has a one-time header diff; entity no longer writes
      <Name>Extractor; decimal filter operands are BigDecimal, not String, so a hand-written
      repository that casts (String) p.value() breaks.
    • Kotlin: also emits MetaPortableColumnTypes.kt and <Report>Names; portableUuid binds
      text on MySQL and SQLite, which differs from an Exposed-created BINARY(16) column.
    • Python: refuses a project module that shadows the standard library; answers 400, not 422,
      for a malformed body.
    • The mermaid-er generator is removed.
    • C# and Python: gen and verify --codegen need a generator list (codegen/Generators.cs,
      written by dotnet meta init; metaobjects.config.yaml), and exit 2 without one. Python's
      gen <dir> --out form is gone.

Not in 1.1.0. Derived measures, the preview reporting library, the dbt exporter, the second
requirements slice and the advisory lint port, the JSON Schema and OpenAPI generators, the
generator catalog and the Atlas evaluation are 1.2 work.


The complete 1.1.0 entry (Added, Changed, Deprecated, Removed, Fixed) is in CHANGELOG.md; it is too long for a release body.