Skip to content

1.0.9

Choose a tag to compare

@dmealing dmealing released this 26 Sep 23:45
· 207 commits to main since this release

npm 1.0.9 (all 14 @metaobjectsdev/* packages in lockstep), PyPI 1.0.9, NuGet 1.0.9 and
Maven Central 8.0.9. metamodelVersion stays 1.0. Gated by 1.0.9-rc.6 on the adopter estate:
rc-gate.sh 7/7, contract suite 43/43 in all five ports.

Found by a cold external review of 1.0.8: a reviewer ran the getting-started page, the drift
demo and the fit assessment from public sources only, then built a small app of their own.

Upgrading: run meta gen. Generated TypeScript changes: value-object interfaces,
date/time validators, typed create inputs, UTC timestamps and form input types (see Fixed).

Behaviour change in extract, every port: a required field that is present but unusable
now fails the strict extractor.
A model reply with an undeclared enum member or text where
a number belongs used to come back from extract<Name> / orThrow with null in a field
typed non-null. The written contract (the typed-enums and FR-010 designs) always said it must
fail; no port implemented it. Code that relied on those nulls now sees the failure, which names
the malformed fields (malformedRequired()). The tolerant extractor still never throws on a
bad reply.

Added

  • Write your own generator is the primary codegen path (ADR-0034 Amendment 4). The core
    (model, loader, verify, migrate, render, extract) is the product; the shipped generators are
    examples. meta generator new <name> [--scope entity|package|model] scaffolds a working
    generator into codegen/generators/ and wires it into metaobjects.config.ts. New
    model-walking helpers: TS objectRefTarget, enumValues, servedPath and the case helpers;
    Python metaobjects.codegen.model_walk; JVM ModelWalk and FileEmittingGenerator
    (codegen-base). A new guide, docs/recipes/write-your-own-generator.md, shows the 20-line
    shape in every port, with JSON Schema and OpenAPI 3.1 example generators that a test runs in
    each port. The skills, the agent context meta init installs and the docs now teach writing
    the generator you need first and ejecting a reference second, and no longer claim C# and
    Python cannot register a generator of your own.
  • Eject hands over the helper runtime, in every port. Ejecting a generator whose output
    calls helper runtime code now copies that code into your repository, and the ejected
    generator's output imports your copy, so you can fix a helper bug without waiting for a
    release. Projects that have not ejected are unchanged.
    • TypeScript: meta eject routes, routes-hono or entity copies the HTTP adapter source
      (mount helpers, filter parser, error envelopes, pagination, allowlist types) into
      codegen/runtime/; the routes references now carry the full route composition. Ejected
      output no longer needs @metaobjectsdev/runtime-ts; runtimeImport restores the package
      import.
    • C#: dotnet meta eject routes copies FilterParser, FilterParseResult, FilterPredicate,
      EfCoreFilterDispatch, ValueObjectValidator, ConstraintErrors and
      Iso8601TimestampConverter into codegen/runtime/ (namespace Codegen.Runtime).
    • Java: mvn metaobjects:eject of routes, dto or repository copies the helper classes
      their output imports into src/main/java/<groupId>.runtime/ (-DruntimePackage,
      -DruntimeDir). Kotlin output imports no helper runtime.
    • Python: metaobjects eject routes copies filter_parser and constraint_errors into
      codegen/runtime/; the generated package imports its own _runtime/ copy.
    • In every port, verify --codegen treats the copies as owned code, gen --list marks each
      one identical or DIFFERS against the installed version, and the docs give a recipe for
      pulling an upstream fix into your copy.
  • FilesystemProvider for TypeScript prompt rendering, on the Node-only
    @metaobjectsdev/render/providers subpath. lobby/welcome resolves to
    <root>/lobby/welcome.mustache, as in C#, Java and Python. The package's root entry stays
    browser-safe. The docs already imported it from that path; it did not exist.
  • meta eject covers the prompt generators: prompt-render, output-parser,
    extractor, output-prompt and render-helper. Each reference copy is held byte-identical
    to the built-in by a test.
  • meta verify advises on foreign keys with no covering index, naming each one and the
    index.lookup to declare. Advisory only: it never fails the build, and no index is generated
    automatically.
  • The fit assessment (assess.md) recommends an adoption scope instead of fit / not fit:
    not worth it, contract spine (declare only the shapes crossing between apps and services,
    generate the types, DTOs and validators each consumer compiles against, keep the ORM),
    partial, or full, and it names the seams where adoption would pay off. A quick pass answers
    in the conversation and writes nothing; the full assessment writes outside the target
    repository. Its capability claims are re-checked against 1.0.8 and cite published sources,
    so no clone of this repository is needed.

Fixed

  • SQLite / D1 CHECK constraints open on SQLite older than 3.39. The ones derived from
    requiredWhen / presentIff used IS DISTINCT FROM, and a database containing one would
    not open at all on older SQLite (Ubuntu 22.04's sqlite3, Python 3.10's stdlib):
    malformed database schema … near "DISTINCT". They now use SQLite's IS NOT / IS. An
    existing database is re-spelled once by the next meta migrate (a table rebuild that keeps
    the data, with no --allow needed). The SQLite version floor is now documented: 3.35 for
    tables, 3.44 for projection views that use origin.collect.

  • gen warns about attributes verify rejects, in TypeScript, Python and C#. A misspelt
    attribute (isAbstrakt: true) used to generate silently; gen now names the attribute,
    node and file. The exit code is unchanged.

  • The tolerant extractor skips // and /* */ comments in a JSON reply, in every port. A
    comment used to make every later field count as lost. The strict parser is unchanged.

  • meta gen --list marks owned generators WIRED correctly, and attributes an owned runtime
    copy only to the generators that were ejected, in text and JSON.

  • JVM template data reads inherited @required and @maxLength, so a field that inherits
    them through extends renders like one that declares them. Template output changes for those
    fields. Python no longer writes __init__.py into non-Python output folders, and C# names a
    generator that throws instead of reporting "metadata did not load cleanly".

  • meta migrate no longer steers a column rename toward data loss. A rename the diff
    cannot pair (title → summary) arrives as a blocked drop plus an add, and the hint said
    --allow drop-column, which deletes the column's data. Every hint on that shape now
    recommends --rename-column <table>.<old>=<new> first, with the real names, and shows
    --allow drop-column only as the option that deletes the data.

  • Generated TypeScript compiles under exactOptionalPropertyTypes, which recent
    tsc --init turns on. Value-object interfaces declared x?: T where Zod infers
    x?: T | undefined, so a response parser or a jsonb query over an optional field failed
    with TS2375. The codegen-compile gate now compiles with the option both off and on.

  • Generated Zod for field.date, field.time and string-mode field.timestamp rejects
    free text.
    "next tuesday" was a valid date. Every format the runtime writes still passes.

  • A list request with a bare field parameter answers 400. GET /issues?priority=low
    ignored priority and returned every row. The runtime-ts list routes now answer
    400 {"error": "filter.bare_field", "field": "priority", "expected": "filter[priority][eq]=low"}
    when the bare name is a filterable field; other unknown parameters are still ignored.
    TypeScript helpers only.

  • Form descriptors give field.timestamp a datetime-local input (and field.time a
    time input); a timestamp got a date picker.

  • <Entity>Filter types an enum's filter value as the member union, so a misspelt member
    fails to compile.

  • meta verify says which gates a partial run did not run, with the flag that selects
    each, so meta verify --codegen --db is not mistaken for a full run.

  • A template drift names its file and line: ERR_VAR_NOT_ON_PAYLOAD: question (prompts/…/brik-user.mustache:3). The cross-port diagnostic itself is unchanged.

  • meta init and meta --help no longer list commands that do not exist.

  • meta gen --list marks the generators with no reference copy as package-only, and
    meta eject names them as package-only instead of "unknown". ADR-0034 Amendment 3 carries a
    dated correction: those eight generators are not ejectable.

  • Generated TypeScript imports value-object types type-only, so a value object nesting
    another compiles under verbatimModuleSyntax (a fresh tsc --init; it failed with TS1484).
    The codegen-compile gate now builds under exactly the options tsc --init writes.

  • The response-format prompt fragment shows number and boolean placeholders unquoted, in
    every port
    ("averageRating": {averageRating}). A model following it answered numbers as
    strings, which the generated parser then rejected. Generated prompt text changes; the
    output-prompt-conformance corpus pins the new form.

  • An unexpected error on a runtime-ts route answers 500 {"error": "internal"} and logs the
    detail server-side. Fastify's default body echoed the failed SQL, its column names and bound
    parameters to the client.

  • A malformed JSON body answers 400 {"error": "invalid_json"} on the runtime-ts Fastify
    and Hono mounts, scoped to the routes they mount, instead of each framework's own shape.

  • A filter value that cannot be the field's type answers 400 invalid_filter_value with
    field, op and expected (filter[publishedOn][gte]=notadate returned []). The generated
    FilterAllowlist gains format and enumValues; run meta gen to pick them up.

  • meta migrate --rename-column carries a column's CHECK and FK constraints across instead of
    asking for --allow drop-check / --allow drop-fk: Postgres renames the constraint, SQLite
    and D1 rebuild the table with it under the new name. Blocked-change messages are plain text,
    never a JSON dump.

  • A child-side relationship silently overriding a parent-side one is reported. When both
    sides of a 1:N are declared with different referential actions (a parent composition =
    cascade, a child association = restrict), the child's wins, by design (ADR-0047). meta verify now names it (overridden-referential-action) and meta migrate warns, with the fix:
    @onDelete on the identity.reference. The DDL is unchanged.

  • meta gen --list describes output-parser and extractor correctly in every port: the
    parser is a strict parse plus a tolerant extractLenient, the extractor is tolerant recovery.
    The descriptions had them the other way round.

  • meta migrate --rollback no longer fails with "SQLITE_OK: not an error" when a down
    migration ends in comments. Comment-only statements are never sent to the database, on any
    dialect, and a down made only of comments is refused rather than recorded as reversed.

  • The down migration of a SQLite / D1 table rebuild restores the previous table: its
    columns, CHECKs, foreign keys and indexes, with the rows copied back under the old column
    names, and it names anything it cannot restore. It used to reverse only simple renames.

  • An index-name collision is reported as one, naming both declarations and the fix, as a
    normal CLI error. Index names are not prefixed with their table.

  • meta gen gives migrate advice in the project's own dialect (D1 included), and only when
    the change can affect the schema.

  • More silent query parameters answer 400 on the runtime-ts list routes: a bare parameter
    naming any column (filter.bare_field, with filterable: false and the filterable fields
    when it is not filterable), and a limit or offset that is not a non-negative integer
    (pagination.invalid_value). An opaque view refuses a limit over 1000 instead of clamping.
    invalid_sort names sort=<field>:asc|desc and the allowed fields.

  • Date validators reject impossible calendar days (2020-02-30, 2023-02-29) for
    field.date and the date part of timestamps. Generated output changes.

  • The response-format fragment shows an enum with no @example as its members
    ("low | medium | high"), in every port, instead of pre-filling the first member, which models
    copied.

  • The tolerant parser is documented as it is emitted: extractLenient<Name>WithLoader(root, text), which never throws on a bad reply and throws only when root lacks the response value
    object. The docs say which parser takes a raw model reply.

  • meta eject's install summary lists every package the ejected files import, and meta gen --list --probe counts the Fastify routes generator before dbImport is set.

  • Generated create<Entity> takes a typed input (<Entity>Create, the insert schema's
    input type), so a misspelt or renamed field is a compile error at the call site. It was
    data: unknown and failed only at runtime. The same applies to insertPreserving, TPH
    create<Sub> / update<Sub>ById and write-through create; a TPH subtype's create now names
    the discriminator in its input type, as its runtime schema already required.

  • Timestamps carrying an offset are stored and filtered in UTC ISO form (toISOString()), so
    SQLite and D1 text comparison orders them correctly. 2026-09-21T01:00:00+05:00 compared as text
    against a Z bound gave wrong results. Values without a zone, and @localTime fields, are
    stored as sent. Rows written earlier with an offset keep sorting wrongly until rewritten.

  • Date, time and timestamp validation errors carry a short message ("must be an ISO date (YYYY-MM-DD)"), and the TS route helpers drop Zod's pattern from validation responses; the
    error body used to carry a ~300-character regex.

  • meta verify --codegen lists every generated file that carries a hand edit, as a notice
    that does not change the exit code, and says how to see the edit. Hand edits are kept by the
    three-way merge by design; --forbid-hand-edits makes any of them fail the build, for teams
    that want generated code untouchable.

  • A bare meta verify says which gates it did not run, with the flags, instead of only
    "nothing to check".

  • meta gen --list --probe counts shared-model instead of reporting a failed probe.

  • Timestamps: a value already in UTC Z form is stored as sent; a zoneless value is read as
    UTC and a date alone as midnight UTC
    , on write and in filters (@localTime unchanged), per
    the api-contract rule that a timestamp is always UTC. Rewritten values use the canonical wire
    form (no .000). A zoneless or date-only value was stored verbatim and broke filters.

  • SQLite / D1 jsonb value-object columns are typed on read (.$type<VO>()); reading
    address.city from a generated finder failed with TS2339.

  • Generated create inputs are typed as z.input<typeof <Entity>InsertSchema>, so a project
    that ejected the queries generator before 1.0.9 keeps compiling.

  • An empty-body DELETE sent with content-type: application/json is accepted; fractional
    values on integer filters answer invalid_filter_value; an unencoded + offset in a
    timestamp filter gets a hint to encode it.

  • meta verify labels each advisory kind separately; unindexed foreign keys were reported as
    "hand-rolled".

Changed

  • The generated TypeScript file header no longer says DO NOT EDIT. Hand edits are kept by a
    three-way merge on regeneration, and the header now says so, and that meta verify --codegen
    checks only the generated parts. Regenerating rewrites line 1 of every generated file.
  • The fit assessment's maintainer notes moved to the end of the file, and the site copy strips
    them, so a reader of assess.md meets the prompt first.
  • Docs: PUT is an alias of PATCH in every port; an omitted limit returns every row in TS and
    C# and the first 50 in Java, Kotlin and Python; the migration reference lists the CHECK
    constraints meta migrate derives.
  • Docs: enums sort by their stored value, not by declared order (a stated known limit in
    docs/features/api-contract.md).