Repository navigation
1.0.9
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 intocodegen/generators/and wires it intometaobjects.config.ts. New
model-walking helpers: TSobjectRefTarget,enumValues,servedPathand the case helpers;
Pythonmetaobjects.codegen.model_walk; JVMModelWalkandFileEmittingGenerator
(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 contextmeta initinstalls 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-honoorentitycopies 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;runtimeImportrestores the package
import. - C#:
dotnet meta eject routescopiesFilterParser,FilterParseResult,FilterPredicate,
EfCoreFilterDispatch,ValueObjectValidator,ConstraintErrorsand
Iso8601TimestampConverterintocodegen/runtime/(namespaceCodegen.Runtime). - Java:
mvn metaobjects:ejectofroutes,dtoorrepositorycopies the helper classes
their output imports intosrc/main/java/<groupId>.runtime/(-DruntimePackage,
-DruntimeDir). Kotlin output imports no helper runtime. - Python:
metaobjects eject routescopiesfilter_parserandconstraint_errorsinto
codegen/runtime/; the generated package imports its own_runtime/copy. - In every port,
verify --codegentreats the copies as owned code,gen --listmarks each
one identical or DIFFERS against the installed version, and the docs give a recipe for
pulling an upstream fix into your copy.
- TypeScript:
FilesystemProviderfor TypeScript prompt rendering, on the Node-only
@metaobjectsdev/render/providerssubpath.lobby/welcomeresolves 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 ejectcovers the prompt generators:prompt-render,output-parser,
extractor,output-promptandrender-helper. Each reference copy is held byte-identical
to the built-in by a test.meta verifyadvises on foreign keys with no covering index, naming each one and the
index.lookupto 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/presentIffusedIS DISTINCT FROM, and a database containing one would
not open at all on older SQLite (Ubuntu 22.04'ssqlite3, Python 3.10's stdlib):
malformed database schema … near "DISTINCT". They now use SQLite'sIS NOT/IS. An
existing database is re-spelled once by the nextmeta migrate(a table rebuild that keeps
the data, with no--allowneeded). The SQLite version floor is now documented: 3.35 for
tables, 3.44 for projection views that useorigin.collect. -
genwarns about attributesverifyrejects, in TypeScript, Python and C#. A misspelt
attribute (isAbstrakt: true) used to generate silently;gennow 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 --listmarks 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
@requiredand@maxLength, so a field that inherits
them throughextendsrenders like one that declares them. Template output changes for those
fields. Python no longer writes__init__.pyinto non-Python output folders, and C# names a
generator that throws instead of reporting "metadata did not load cleanly". -
meta migrateno 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-columnonly as the option that deletes the data. -
Generated TypeScript compiles under
exactOptionalPropertyTypes, which recent
tsc --initturns on. Value-object interfaces declaredx?: Twhere 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.timeand string-modefield.timestamprejects
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
ignoredpriorityand 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.timestampadatetime-localinput (andfield.timea
timeinput); a timestamp got a date picker. -
<Entity>Filtertypes an enum's filter value as the member union, so a misspelt member
fails to compile. -
meta verifysays which gates a partial run did not run, with the flag that selects
each, someta verify --codegen --dbis 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 initandmeta --helpno longer list commands that do not exist. -
meta gen --listmarks the generators with no reference copy aspackage-only, and
meta ejectnames 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 underverbatimModuleSyntax(a freshtsc --init; it failed with TS1484).
The codegen-compile gate now builds under exactly the optionstsc --initwrites. -
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-conformancecorpus pins the new form. -
An unexpected error on a
runtime-tsroute answers500 {"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 theruntime-tsFastify
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_valuewith
field,opandexpected(filter[publishedOn][gte]=notadatereturned[]). The generated
FilterAllowlistgainsformatandenumValues; runmeta gento pick them up. -
meta migrate --rename-columncarries 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 parentcomposition=
cascade, a childassociation= restrict), the child's wins, by design (ADR-0047).meta verifynow names it (overridden-referential-action) andmeta migratewarns, with the fix:
@onDeleteon theidentity.reference. The DDL is unchanged. -
meta gen --listdescribesoutput-parserandextractorcorrectly in every port: the
parser is a strict parse plus a tolerantextractLenient, the extractor is tolerant recovery.
The descriptions had them the other way round. -
meta migrate --rollbackno 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 gengives 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-tslist routes: a bare parameter
naming any column (filter.bare_field, withfilterable: falseand the filterable fields
when it is not filterable), and alimitoroffsetthat is not a non-negative integer
(pagination.invalid_value). An opaque view refuses alimitover 1000 instead of clamping.
invalid_sortnamessort=<field>:asc|descand the allowed fields. -
Date validators reject impossible calendar days (
2020-02-30,2023-02-29) for
field.dateand the date part of timestamps. Generated output changes. -
The response-format fragment shows an enum with no
@exampleas 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 whenrootlacks 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, andmeta gen --list --probecounts the Fastifyroutesgenerator beforedbImportis 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: unknownand failed only at runtime. The same applies toinsertPreserving, TPH
create<Sub>/update<Sub>ByIdand 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:00compared as text
against aZbound gave wrong results. Values without a zone, and@localTimefields, 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'spatternfrom validation responses; the
error body used to carry a ~300-character regex. -
meta verify --codegenlists 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-editsmakes any of them fail the build, for teams
that want generated code untouchable. -
A bare
meta verifysays which gates it did not run, with the flags, instead of only
"nothing to check". -
meta gen --list --probecountsshared-modelinstead of reporting a failed probe. -
Timestamps: a value already in UTC
Zform is stored as sent; a zoneless value is read as
UTC and a date alone as midnight UTC, on write and in filters (@localTimeunchanged), 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.cityfrom 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/jsonis accepted; fractional
values on integer filters answerinvalid_filter_value; an unencoded+offset in a
timestamp filter gets a hint to encode it. -
meta verifylabels 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 thatmeta 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 ofassess.mdmeets the prompt first. - Docs: PUT is an alias of PATCH in every port; an omitted
limitreturns every row in TS and
C# and the first 50 in Java, Kotlin and Python; the migration reference lists the CHECK
constraintsmeta migratederives. - Docs: enums sort by their stored value, not by declared order (a stated known limit in
docs/features/api-contract.md).