All notable changes to @metaobjectsdev/* TypeScript packages are documented
here. The format follows Keep a Changelog, and
this project adheres to Semantic Versioning
(pre-1.0; MINOR bumps may introduce breaking changes with notice).
Coordinated additive minor across all registries: npm 0.18.0 · NuGet 0.18.0 · PyPI 0.18.0 · Maven Central 7.10.0. No breaking changes.
Form controls — view-kind dispatch (TypeScript codegen). The generated <Entity>Form (@metaobjectsdev/codegen-ts-react formFile) now renders the right control for each field's declared view instead of a bare <input> for every scalar: a field.enum with no explicit view renders a <select> (dropdown), view.textarea renders a <textarea>, view.checkbox a checkbox, view.radio a radio fieldset; everything else keeps the existing typed <input>. Dispatched controls carry an aria-label (accessible name parity with the scalar path), and the submit button is wrapped in a styled metaobjects-form-actions / metaobjects-form-submit container. This is a generated-output change delivered default-on — regenerate to pick it up; three-way merge preserves hand-edits.
@formExclude registered as first-class vocabulary (cross-port). The form template already read @formExclude (to omit a field from a generated form), but core never registered it — so strict meta verify (ADR-0023 sealed registry) rejected it as ERR_UNKNOWN_ATTR. It is now registered as a boolean attribute on the field.* wildcard in spec/metamodel/ui.json (mirroring @filterable/@sortable) and flows to every port (TS / C# / Java / Kotlin / Python) via the data-driven UI provider — no per-port code. Cross-port registry conformance gates it.
Deferred (documented): configurable @rows on view.textarea — there is no clean cross-port home for an attribute on a TS-only view subtype today (ui.json's extends throws where view.textarea is deregistered; core view.json breaks the FR-033 "core owns zero view attrs" invariant); textareas render a fixed rows={4} until a proper design lands. The blank-optional-scalar submit fix is deferred as tristate-aware work (tracked in #223).
npm-only patch (0.17.0 → 0.17.1; PyPI / NuGet / Maven Central unchanged — a TypeScript-only fix). Fixes a real correctness bug in the generated REST surface for write-through entities (FR-024 §7 / #214): a write-through entity (writable table + @role:replica @kind:view replica + a derived origin.passthrough field) had its generated routes read/write only the base table, so GET / POST HTTP responses OMITTED the derived field. The #214 read-half had shipped in the query layer + the replica view, but the routes layer was left mounting vanilla table CRUD — and no write-through routes test existed, so nothing caught it.
runtime-tsmountCrudRoutes— write-through read-routing. A new optionalreadViewroutes the list/get reads AND the post-write re-read on create/update through the replica view, so read-your-writes returns the derived (origin.passthrough) columns the base table excludes (#213); writes still target the base table. Absent → byte-identical behaviour for vanilla / TPH entities (no regression).codegen-tsrenderRoutesFile— passreadViewfor write-through entities. The generated<Entity>.routes.tsnow imports the entity file's.existing()replica-view const and passes it asreadViewtomountCrudRoutes.reference/routes.ts(scaffold-and-own) delegates torenderRoutesFile, so it inherits the fix.
Gated by a new cross-port fixtures/api-contract-conformance/write-through/ corpus (POST create returns the derived field, GET reads it through the replica view) running on TS / C# / Kotlin — the ports whose generated artifact re-reads through the view — plus runtime-ts (mount-write-through) + codegen-ts (routes-file) unit tests.
Coordinated additive minor across all four registries: npm 0.17.0 · PyPI 0.17.0 · NuGet 0.17.0 · Maven Central 7.9.0 (Java/Kotlin). Bundles the accumulated projection/view + read-model + prompt work below, plus a full documentation + agent-context skills refresh (the seven meta init skills were accuracy-passed and Fable-reviewed, closing a class of stale-vocabulary and calibration defects; the runtime-ui skill gained its missing Python + C# language references). No breaking changes.
Two mutually-exclusive source.rdb attributes express who owns a DB object's DDL — the escape from "a projection's view is always synthesized from its origin.* children" (ADR-0043):
@sql— a hand-written view body the tool registers, fingerprints, and drift-checks but never authors or parses. The value is the body insideCREATE <kind> <name> AS …. It lets a genuinely-irreducible view (recursive CTE, window function, set operation) carry anextends-bound identity/fields for row identity and shape without the tool mis-synthesizing a wrong base-table passthroughSELECT— the suppression rule classifies DDL-ownership before the derivation decision, closing a silent-wrong-synthesis hole. The@sqlview rides the existing emit/fingerprint pipeline; a pre-existing unstamped view at its name isreplace-view-blocked pending the one-timemeta migrate --allow adopt-viewadoption ceremony.@sqlon a writable kind, combined with@unmanaged, empty, or combined withorigin.*/@filteris a load error. v1 migrate lowering is@kind: viewonly (matview/proc → an actionable hard error).@unmanaged: true— this DB object (a view or a table) is managed elsewhere (Flyway / a hand-migration owns its DDL).meta migratenever creates, drops, or drift-checks it (excluded from both the expected and the introspected-actual sides across the online, offline, D1, andverifypaths);meta verify --dbreports it as external (declared). An inbound FK from a managed table still resolves the external table's physical name.
Registration + the six fail-closed loader-validation rules (R1–R6) ship in all five ports (TS / C# / Java / Kotlin / Python), resolving MetaSource accessors following the @role/effectiveKind precedent (ADR-0039). The five error rules (R1–R5) are conformance-gated by shared fixtures/conformance/ error-fixtures — every port emits the same code per rule (ERR_SQL_BODY_WITH_UNMANAGED / ERR_SQL_BODY_ON_WRITABLE_KIND / ERR_BAD_ATTR_VALUE / ERR_ORIGIN_UNDER_SQL_BODY); the R6 WARN_ORIGIN_UNDER_UNMANAGED is per-port unit-tested. All migrate/verify lowering is TS-only (ADR-0015). An xhigh review before merge caught a real offline-migrate DROP-for-external-table bug (the offline planOffline path did not thread the unmanaged-name set), fixed + regression-tested. Deferred follow-ups: @dependsOn for @sql views, the matview managed path, and opaque-body column-name verification. Designed in docs/superpowers/specs/2026-07-17-issue-208-ddl-ownership-escape-valves-design.md.
Completes the read side of the FR-024 §7 entity read-view (#213 shipped the write/schema half — the write table excludes derived origin.* fields and the replica view is emitted + migrate-converged). A write-through entity (a writable table source + a read-only replica view source + derived fields) now generates a hybrid read/write surface in TypeScript:
- The entity file declares the
.existing()replica view alongside the write table, plus a Zod read schema whosez.inferis the read type<Entity>— carrying the derived fields the write table omits (z.infer, not DrizzleInferSelectModel/$inferSelect, because a Drizzle view is not a Table and SQLite views don't expose$inferSelect; the read schema's nullability mirrors the view columns, exactly like a projection). - Generated reads (
find<Entity>ById/list<Plural>/ reverse finders) SELECT from the view; writes (create/update/delete) target the table; a create/update re-reads the row through the view by primary key (keyed on the full PK) so the returned<Entity>carries the derived fields (read-your-writes). Insert/Update stay derived-free (#213). - The scaffold-and-own reference generators (
meta init) delegate the write-through variant to the engine composer (like the projection/TPH variants), so the default consumer path is not a silent no-op.
Shared renderExistingViewDecl + renderViewReadZodObject helpers now back both projections and write-through entities (projection output byte-identical). Gated by a tsc-compile test of the real generated output on both dialects + a real-Postgres re-read round-trip.
The read half is now complete in all five ports (schema migration stays TS-owned, ADR-0015; the codegen/runtime is per-port). Each port shares the metadata predicates MetaField.isDerived() (a field carrying an origin.* child) and MetaObject.isWriteThrough() (an object owning both a writable-kind and a read-only-kind source.rdb), and each write-through path preserves byte-identical output for vanilla entities and projections:
- Java (
codegen-spring, SQL-free): the read<Entity>Dtocarries the derived fields, the write<Entity>Patchexcludes them, and a write-through entity emits its controller / repository / filter-allowlist (order-independent). A derived field on a write-through entity carries no client-validation constraints, so a POST-create can omit the view-computed value. - Kotlin (
codegen-kotlin): a write-through entity emits two Exposed objects — a derived-free write<Short>Tableand a derived-carrying read-only<Short>View— and the repository/controller route reads to the view, writes to the table, re-reading by primary key. A derived field on a write-through entity is a nullable, default-null data-class property (the shared read/create body must be able to omit the view-computed value). Gated by a real-H2 read-your-writes test. - Python:
<Name>Create/<Name>Patchexclude the derived fields, and theObjectManagerroutes reads to the replica view while excluding derived fields from the INSERT/UPDATE column set and theRETURNINGset, re-reading the row through the view by primary key. - C# (EF Core cannot map one CLR type to both a table and a view): a write-through entity emits a derived-free table-mapped write entity plus a second view-mapped read model (
<Entity>View, sharing the write entity's per-field type converters) carrying the derived fields; reads route to the viewDbSet, writes to the tableDbSet, with a by-primary-key re-read after create/update. Gated by an EF-Core-8 compile test over a write-through entity carrying afield.uri+ a jsonb value-object column.
Each port's fan-out was adversarially reviewed before merge; the review caught a recurring cross-port class of bug (a derived field wrongly forced onto the shared create body) in the JVM ports and EF read-model registration gaps in C#, all fixed before merge. A flattened field.object on a write-through entity is not yet handled in the entity-host view SELECT (scalars, derived joins, and single-jsonb-column value objects are) — a tracked follow-up.
A projection (object.projection) can now declare a row-scope @filter — a portable attr.filter object (eq/ne/gt/gte/lt/lte/like/in/isNull with and/or, desugared to canonical { field: { op: value } } at parse time) selecting which rows the derived view returns. It lowers to an outer SQL WHERE, the metadata-managed way to express soft-delete / status / type views instead of a hand-written unmanaged view (which is drift, invisible to verify --db). Placement mirrors origin.aggregate @filter exactly: a filter-subtype attr on object.projection (the predicate is a LOGICAL derivation, so it lives on the object, not on source.rdb — it survives non-RDB lowerings).
- Registered in all five ports (TS / C# / Java / Python / Kotlin),
registry-conformance-gated (object.projectionnow carries the@filterattr in the byte-matched manifest), with afixtures/conformance/projection-filtercorpus fixture proving it loads + canonical-serializes identically everywhere. - Fail-closed validation (cross-port). A
@filterfield-ref must name one of the projection's own declared fields, and that field must be addressable in aWHERE. An aggregate-derived ref (origin.aggregate/origin.first/origin.collection) or a dangling ref (naming no declared field) is a fail-closed load error (ERR_BAD_ATTR_FILTER) — aWHEREruns before aggregation, so it cannot see an aggregate (post-aggregate filtering is a separate futureHAVING). This dangling/aggregate-derived check runs identically in all five ports (gated by shared negative conformance fixtures). TS additionally hardens the load (rejecting a non-arrayand/or, an empty op-object, and an op illegal for the field's subtype), so a malformed filter fails at load rather than silently dropping the predicate or crashing the view synthesizer. - Resolution + lowering (TS-owned, ADR-0015). The SQL
WHERElowering lives in TS (schema is TS-owned). Each field-ref resolves against itsSelectColumn: a passthrough (base OR joined) →sourceAlias.sourceColumn(soWHERE joined.status IS NULL OR joined.status = 1works across a join); a computed (origin.computed) field → its inlined expression. A field clause may carry multiple ops (a range{ gte, lte }), each AND-composed. TheWHERErenders after the joins and BEFORE anyGROUP BY, composing withorigin.firstcorrelated subqueries. - v1 scope: projections only. A write-through entity read-view (
isWriteThrough) is excluded by construction — the attr is registered onobject.projection, notobject.entity— because a filtered replica view breaks read-your-writes totality (write a row, read it back through the filter, it's gone). - Gated by golden extract+emit tests, the loader validation suite, and a real-Postgres round-trip (emit → apply → introspect → re-diff EMPTY, plus a filtered view that returns only the matching subset). Reuses the shipped
attr.filterdesugar + theViewFilterClauserenderer (extended with an inlined-expression comparison node for computed refs).
Projections can now express four common admin/monitoring read-model shapes as metadata-managed, drift-checked origins instead of hand-written unmanaged SQL — each defined as a semantic rows → value derivation (the RDB view lowering is one realization). Registered + validated + natively-typed in all five ports (TS / C# / Java / Python / Kotlin); the TS meta migrate view synthesizer lowers them for Postgres and SQLite (ADR-0015 — schema is TS-owned).
origin.aggregate @agg: any | all— a predicate quantifier over the related row-set ("did any related turn fail?" / "did every one succeed?").@filteris the quantified predicate (required),@ofis forbidden, the field isfield.boolean. Empty related set →any=false/all=true(vacuous truth). Lowered asbool_or/bool_and(Postgres) /MAX/MIN(CASE …)(SQLite), phantom-row-guarded +COALESCEd to the pinned empty-set value.origin.aggregate @agg: collect— an array rollup of@ofacross the related set (the field must beisArray:true, element type = the@ofcolumn).@distinct= set semantics;@orderBysets element order (mutually exclusive with@distinct, which orders by value). Empty set →[]. Lowered asarray_agg(Postgres) /json_group_array(SQLite).origin.computed @expr— a row-level value computed from the base entity's own fields via a newattr.expressionstructured expression tree (a closed node grammar: field/value refs, comparisons sharing the filter op vocabulary,isNull/isNotNull,and/or/not,coalesce). The tree's inferred type must equal the field's declared subType (ERR_COMPUTED_TYPE_MISMATCH); an unknown node is a fail-closed load error (ERR_UNKNOWN_EXPR_NODE). The flagship casepayload IS NOT NULLavoids shipping a heavy column. (Retires theorigin.expressionreservation — #159's future arithmetic becomes additive node kinds in this grammar.)origin.first— argmax-then-project: the single related row selected by@orderByalong@via, projecting its@ofcolumn ("the latest child's status"). The carrying field must not be@required(empty related set → null). Lowered as a correlated scalar subquery (ORDER BY … , child.pk ASC LIMIT 1) that composes with the view'sGROUP BY; the related PK ascending is always the deterministic tie-breaker;@orderBynulls sort last.
Shared new attrs @distinct (bool) + @orderBy (string array); the per-capability validation (conditional presence, field-shape, type-preservation, the closed expression grammar + type inference) and native projection-field typing (any/all/collect non-null; first nullable) hold identically across all five ports (each with a 20-case validation suite mirroring the reference). A load-time WARN fires when an inflation-sensitive aggregate (sum/avg/non-distinct collect) coexists with ≥2 to-many join branches. Cross-port conformance fixtures cover all five capabilities incl. the zero-related-rows determinism pins. Follow-ups: a collect native-array persistence roundtrip gate, the projection array-typing fix (collect → T[] in codegen-ts — #204), and the "not-migrate-managed" escape-valve FR (see spec/roadmap.md).
Value-object jsonb columns (field.object @objectRef @storage:jsonb, single AND @isArray) are now bound → nested-validated → written on POST and PATCH in all five ports (TS / Python / Java / Kotlin / C#), closing the deliberate FR-036 Day-1 simplification (Java/Kotlin/C# previously excluded VO columns from the patch set, so a single-port fix would have broken byte-identical api-contract parity). Nested VO constraints validate in full (a VO string member over @maxLength, an empty @required member, a present-null on a required member → 400 {"error":"validation"}), identically across ports. The FR-035 tristate holds for VO columns: absent → untouched; present-null clears a nullable column but 400s a @required one; present-[] writes an empty array (distinct from present-null → SQL NULL). Gated by fixtures/api-contract-conformance/jsonb/scenarios/jsonb-value-object-patch.yaml in both lanes (hand-rolled reference server + generated artifact over Testcontainers Postgres), all five ports. A latent TS runtime bug was fixed along the way: the ObjectManager validator did not recurse into field.object member constraints (nested violations wrote a 201). field.map (dict-of-VO), the Kotlin field.string @dbColumnType=jsonb open-bag PATCH, and TPH entities with VO columns remain tracked follow-ups.
Coordinated breaking release across all four registries: npm 0.16.0 · PyPI 0.16.0 · NuGet 0.16.0 · Maven Central 7.8.0 (full lockstep; the two angular packages stay on their 0.6.x line).
400 {"error":"validation"}.
The generated PATCH now distinguishes an ABSENT key from an explicit null, identically across all five ports (previously four different behaviors): an absent field is untouched; a present null on a nullable column CLEARS it; a present null on a @required field is 400 {"error":"validation"}; and a PATCH may omit @required fields entirely (no 400). Holds on both the vanilla and the TPH per-subtype update paths.
@requiredstring = NON-EMPTY (rejectnulland"", accept whitespace-only) — Java/Kotlin no longer reject whitespace (the auto-@NotBlankis retired for@NotNull+@Size(min=1)); C# emits[Required(AllowEmptyStrings=true)]+[MinLength(1)]; Python emitsmin_length=1; TS was already correct.validator.regex @pattern= FULL-MATCH (the whole value must match) — TS + Python now anchor the authored pattern as^(?:…)$; C#[RegularExpression]is anchored too (it was not a true full-match for ordered-alternation patterns).- C# and Python now enforce field constraints over HTTP (both were decorative at the wire tier — C# minimal-API never ran DataAnnotations, Python bound
dict[str,Any]). POST and PATCH now validate present values →400 {"error":"validation"}on all five ports, vanilla + TPH. @maxLength×validator.length @maxprecedence is strictest-wins (minof the two).- A missing
@requiredvalue-type field on POST now 400s on every port (previously C# accepted a garbage default); a@requiredfield with a server-side@default/@autoSet(or an auto-generated PK) is correctly OPTIONAL on POST (a POST omittingcreatedAt @default:now()is 201, not 400).
New cross-port conformance gates (validation-conformance + api-contract, both lanes) lock all of the above so it can't silently drift.
npm 0.15.21 (full lockstep across all 14 @metaobjectsdev/* publish candidates).
Coordinated release: npm 0.15.21 · PyPI 0.15.13 · Maven 7.7.11. NuGet is unchanged at 0.15.10 — the C# port needed no fix (it already derived the primary-key type correctly, and became the reference the other three ports were fixed against).
A bug-fix release, sourced from a downstream consumer's adoption report (TypeScript + Cloudflare D1 + uuid primary keys) and then widened by an adversarial review that hunted the same bug classes across the whole codebase. Several of these fail silently and unsafely — a wrong-row DELETE, a cross-schema DROP VIEW, a partial-unique index becoming fully unique — and several make meta migrate either destroy work or refuse to run at all. No metadata changes; no new vocabulary. Existing metadata generates byte-identical output except where it was previously wrong.
- Writable mounts performed a WRONG-ROW write/delete (
runtime-ts). Every writable mount coerced the:idpath param with a helper that numberifies any numeric-looking string. On a TEXT/uuid primary key that does not merely miss — it hits a different row: proven against real engines,DELETE /docs/0123deleted row'123'(bun:sqlite, via column affinity), while on libsql the row became permanently unfindable (404 on GET/PATCH/DELETE). All writable mounts (fastify, hono, ObjectManager, M:N) now resolve the id against the primary key's real column type. Also: a successful DELETE on bun:sqlite previously returned 404 after deleting the row. - Every incremental
meta migraterebuilt every uuid-PK table. A uuid primary key's physicalDEFAULTis synthesized at emit time and deliberately not modelled on the expected side, but introspection read it back as a real default — so the diff reported a falsechange-column-defaultfor every uuid-PK table, on every run. On SQLite/D1 (noALTER COLUMN) that recreate-and-copies the whole table, forever. Postgres emitted a bogusALTERinstead. drop-viewwas auto-allowed. An extension's view (e.g.pg_stat_statements) or any hand-written view got an un-gatedDROP VIEWemitted. Now gated behindallow.dropView, extension-owned views are filtered viapg_depend, and the recreate-pair exemption is keyed by (schema, name) — keyed on the bare name, rebuildingreporting.summaryun-gated a destructive drop of a hand-writtenpublic.summary.
@autoSetemittedDEFAULT now()on SQLite/D1 — invalid SQL, so any entity with the standardcreatedAt @autoSetproduced a migration that could not be applied at all.- Changing
field.enum @valuesnever migrated on SQLite. CHECK constraints were create-time-only and no change kind triggered the recreate path, someta migratereported "No schema changes" while inserts of the new member kept violating the stale CHECK in production. (--allow drop-checkwas also rejected by CLI arg validation, making Postgres CHECK evolution ungrantable.) @kind: storedProcprojections crashedmeta migrateoutright;@kind: materializedViewsilently created a plain view under the materialized view's name.- D1 introspection didn't exclude Cloudflare/wrangler tables.
_cf_METADATAappears after any write and D1's authorizer denies evenpragma_table_infoon it, aborting every second-and-latermeta migrate --dialect d1;d1_migrationsread as an undeclared table, so the diff proposed dropping wrangler's own bookkeeping. - Infra-table exclusions used
_as a literal when it is aLIKEwildcard — so'__new_%'also matched an ordinary table namedrenewals, hiding it from introspection and re-proposingCREATE TABLEforever.
- The SQLite emitter dropped index
@expr/@where/@orders. An expression index emittedCREATE INDEX x ON t ()(invalid SQL), and a partial UNIQUE index became a FULL UNIQUE constraint — silently rejecting inserts the model says are valid. - Boolean/numeric
@defaultliterals were quoted on SQLite (DEFAULT 'false'), which SQLite stores as TEXT in a numeric column, soWHERE flag = 0silently matched nothing. Literals containing a quote ("don't") or parentheses ("n/a (unknown)") never round-tripped either. - FK constraint names never converged on SQLite (the engine stores none), so a composite FK or
@constraintNameproduced a permanently blockeddrop-fkandmeta migrateexited 1 forever. @isArrayon any scalar but string/uuid generated a Drizzle.array()column against a migrated SCALAR column — the first insert failed, with no drift signal.
An entity declaring identity.primary @generation: uuid got broken generated output while its own DTO/model correctly used UUID. Not a metamodel gap — a missed reuse: each port already had the type mapper and was already using it a few lines away.
- Kotlin: the generated code did not compile.
@PathVariable id: Longagainst an ExposedColumn<UUID>is a type error, and FK columns hardcodedlong(...), so any relationship pointing at a uuid-PK entity broke the build. Also: the FR-009 filter coercer had no uuid arm (filter[id][eq]=<uuid>would have thrown at runtime), and TPHwritableFieldshardcoded the literal"id". - Java:
@PathVariable Long id→ Spring rejected a uuid path variable with 400, and the generated repository interface was un-implementable against a UUID-keyed entity. - Python: the generated FastAPI router typed every path id
int, so a real uuid was rejected with 422 by Pydantic and never reached the handler — the endpoint was simply unusable. - TypeScript: the TanStack hooks hardcoded
id: number(including the M:NsourceId), so consumer call sites failed to typecheck; and the generated grid hook failed undernoUncheckedIndexedAccess.
meta verify --templatesskipped@kind=emailtemplates — mustache↔payload drift in an email's subject/body was only caught later atmeta gen. (TypeScript and Python; the Java and C# CLIs still have this gap — tracked in #193.)
Every migrate fix is now gated by an emit → apply to a real engine → introspect → re-diff must be EMPTY round-trip, plus value-semantics probes (insert the defaults, ask the engine what it actually stored). The absence of that gate — nothing ever ran the pipeline twice against a real database — is what let this whole class survive a large test suite. Two goldens were found to be encoding the bugs they pinned and were corrected against real-engine evidence rather than regenerated.
npm 0.15.20 (full lockstep across all 14 @metaobjectsdev/* publish candidates).
Coordinated release: npm 0.15.20 · PyPI 0.15.12 · NuGet 0.15.10 · Maven 7.7.10. BREAKING — ADR-0042 bare-reference resolution; see Migration below. Shipped as a PATCH with the breaking notice in this entry (the pre-1.0 convention used by the 0.15.1 / 0.15.17 breaking releases), not signalled via the version number.
- BREAKING — ADR-0042: bare references are package-local. A bare metadata reference (no
::) now resolves in the referrer's package only, else a root-level (empty-package) object; every cross-package reference must be fully qualified. This retires ADR-0041's one-week-old "unique-anywhere" bare resolution (a bare ref silently binding across a package boundary), resolving the ADR-0032/ADR-0041 contradiction. The contract is uniform across every ref-bearing attribute —@objectRef(relationship /field.object/field.map),@references, the origin@from/@of/@viaheads and hops,@payloadRef/@responseRef/@parameterRef, and now@through(brought into the desugar + ref set);extendsis unchanged. Coordinated + conformance-gated across all five ports (TS / Python / Java / Kotlin / C#). Fixes #191.
ERR_AMBIGUOUS_REFis retired. With bare = package-local, cross-package ambiguity is unreachable; replace any handling of it with the per-attr unresolved codes (below).
ERR_UNRESOLVED_OBJECT_REF— a danglingfield.object/field.map@objectRef(present but resolving to no object) now fails closed at load, naming same-short-name objects in other packages so you can qualify it. Previously such a ref loaded clean and surfaced four layers downstream as a misleadingERR_VAR_NOT_ON_PAYLOAD(#191).
meta verify --templatesnow drift-checkstemplate.output @kind=emailand document-output bodies (#193). The check gated on@textRef, so email templates (which use@subjectRef/@htmlBodyRef/@textBodyRef) were skipped and a mustache{{field}}that drifted from its@payloadRefwas only caught later atmeta gen. Every renderable ref is now verified against the payload.
- Qualify every cross-package reference with its package (FQN). A bare reference that previously resolved to an object in another package now fails to resolve — the error hands you the exact FQN to write. YAML-authored models are unaffected (a bare ref already folds to the current package); this only affects hand-written canonical JSON that relied on unique-anywhere, or code handling the removed
ERR_AMBIGUOUS_REF.
Coordinated release: npm 0.15.19 · PyPI 0.15.11 · NuGet 0.15.9 · Maven 7.7.9. Additive, non-breaking.
origin.aggregate @filter— a scoping filter (anattr.filterobject) on an aggregate origin, restricting which related rows the aggregate spans; rendered as SQLFILTER (WHERE …)(Postgres) /CASE WHEN(SQLite). Registered across all five ports + registry-conformance, with a neworigin-aggregate-filteredconformance fixture. Closes the "projections can't express my scoped aggregate" gap (the attribute was previously codegen-local and failed strict load under ADR-0023).- Downstream metadata-decisions guide (
docs/features/downstream-metadata-decisions.md) — the judgment layer for extending the metamodel: exhaust existing vocab, converge-before-inventing (don't claim the charteredapi/operation/surface/bindingnames), the ADR-0037 ordered test, and the design rules that make downstream vocabulary age well (protocol/address-free nodes, names-only fail-closed config, reference-typed payloads, the register→extend→promote lifecycle).
- Projection / DB-view guidance made explicit across the agent-context skills + docs. A projection's
CREATE VIEWDDL is generated from itsorigin.*children by the Nodemeta migrate— hand-writing a view for a shape origins can express is drift, and because an unmodeled DB view is unmanaged it is invisible tometa verify --db. Bounded the "custom SQL views" hand-write exception (codegen skill +codegen-concepts.md) to genuinely-irreducible views (recursive CTEs, window functions, set ops). Themetaobjects-auditskill gains a view-necessity drift signature and a VOCAB CANDIDATE rule that flags where an app should register new vocabulary (a custom subtype/attr via a provider) instead of hand-coding a recurring pattern. Strengthenedmetaobjects-authoringwith the extend-decision lifecycle.
npm-only patch (14-package lockstep). PyPI / NuGet / Maven unchanged — these are TS-only fixes. Non-breaking; advances the 1.0 quiet period.
codegen-tspromptRenderemitted invalid TypeScript for FQN@objectRefpayload refs. Atemplate.promptpayload value-object nesting afield.object @objectRefto another value-object leaked the fully-qualified name (pkg::Name, FR-032/ADR-0041) into both the emitted field type and the generated interface name —::is not a valid TS identifier. Now stripped to the bare name (retaining the FQN only for resolution/recursion), matchingentityFile. Surfaced by dogfooding a package-declared consumer.- agent-context skills — four of six
metaobjects-*skills had invalid YAML front-matter (an unquoted:insidedescription:) so they never intent-triggered under a strict-YAML loader; the C# codegen reference documented non-existent flags; the reference-fragment install was reverted from deploy-all to stack-scoped; deprecated@metaobjectsdev/codegen-ts/generatorsimports and singular tanstack route paths were corrected; and the ADR-0040index.lookupvocabulary was added to the audit capability-checklist + verify migration doc. Also repaired a stale Kotlin@Serializable→Jackson reference fixture that had leftagent-context-conformancered onmain.
- Cross-port regression tests (Python + Kotlin) pinning that payload codegen strips an FQN
@objectRefto the bare name — the bug was TS-only (Java and C# already had equivalent coverage). (#190)
Coordinated release across all four registries: npm 0.15.17 (14-package lockstep) · PyPI metaobjects 0.15.10 · NuGet MetaObjects* 0.15.8 · Maven Central com.metaobjects:* 7.7.8. Three merged efforts: the breaking origin.passthrough type-preservation metamodel change (#185/#186), typed value-object jsonb columns across all ports (#187), and load-order-independent super-resolution (#188/#189).
⚠️ BREAKING (despite the patch version — pre-1.0):origin.passthroughis now type-preserving. Metadata where a passthrough field's declared type differs from its@fromsource (e.g. afield.uuidsource surfaced asfield.string) now fails to load withERR_PASSTHROUGH_TYPE_MISMATCH. The narrowERR_PARAMETER_REF_PASSTHROUGH_TYPE_MISMATCHis retired. Migration: declare the source's type (usually the fix), or add@convert: trueto acknowledge a deliberate type change. See the Changed entry below.Also fixed a latent build-portability bug (#188): a dotted
extends: Owner.membertargeting an inherited member could fail withERR_UNRESOLVED_SUPERunder one directory-scan order but not another (Node vs Bun) — resolution is now order-independent. If a corpus failed to load only under Bun/CI, this release fixes it.
@convertonorigin.passthrough— acknowledge a deliberate passthrough type change (#185). A new optional boolean attr. Absent/false (the default), a passthrough is type-preserving (see Changed, below);@convert: trueopts a field out of the type-equality check when its type intentionally differs from its@fromsource. It is an acknowledgement only — it does NOT generate a cast; the value flows through unchanged and the consumer owns any coercion. Real type-converting projections remainorigin.expression's job (#159). Registered onorigin.passthroughin all five ports (cross-port registry-conformance gated).- Typed value-object jsonb columns work end-to-end across all persistence ports (single + array-of-VO). A
field.object @storage:jsonbcolumn — a single value-object OR an@isArrayarray-of-VO — now round-trips through every port's runtime/ORM write+read codec (TS Kysely, C# EF Core, Java OMDB/Gson, Kotlin Exposed, Python pg8000). Gated by a new array-of-VO dimension in the persistence-conformanceAllTypesop: roundtripscenario: alabelscolumn (field.object @isArray @storage:jsonb) written as a 2-element, empty-[](≠null), and single-element array across three rows — the gap that had let a non-compiling / wrong array serializer ship in three ports. The single-VO jsonb path was already cross-port green; this closes the array-of-VO half.
- Load-order-independent super-resolution for dotted
extendsto an inherited member, all five ports (#188). Deferred super-resolution ran a single pre-order walk over the physical declaration tree, so a dotted ref to an INHERITED member (extends: Owner.memberwhereOwnerinheritsmembervia its OWNextends) only resolved when the owner's extends chain happened to be wired first — green under one directory-scan order,ERR_UNRESOLVED_SUPERunder another (Node vs Bunreaddir). Resolution is now ON DEMAND with memoization + cycle detection: before a dotted ref reads the owner's effectivechildren(), the owner's whole extends chain is resolved first, and the resolved target's chain is resolved too (multi-level inheritance). The result is a pure function of the source SET, independent of enumeration order. The TS SDK'slistMetadataFilesalso sorts its rawreaddirentries so every declaration-order-preserving artifact (serialization) is stable across runtimes — a deterministic-enumeration floor (the other ports' directory sources already sort). Tier-1 invariants are unchanged (ERR_UNRESOLVED_SUPER/ERR_EXTENDS_TARGET_MISMATCH, failure envelope, target-mismatch contract byte-identical). Gated by a newextends-dotted-inherited-member-load-orderconformance fixture (RED→GREEN in every port) + a TS shuffle-invariance test (six permutations → identical model) + a duplicate-failure regression test. - C# / Java / Python array-of-VO jsonb write+read codecs. C# EF Core model finalization threw
'ICollection must be a non-interface reference type'on an@isArrayfield.object @storage:jsonbcolumn —DbContextGeneratornow emits.OwnsMany(...).ToJson(...)whenfield.ResolvedIsArray()(theEntityGeneratoremitsICollection<VO>), with a coupled empty-[]-vs-nullnullability fix. Java OMDB threwExpected BEGIN_OBJECT but was BEGIN_ARRAY—GenericSQLDriver.deserializeJsonbnow branches onisArray(targetTypeToken.getParameterized(List.class, VO)viajsonbTargetType) and the read path stores the resultingListthroughsetObjectArray(not the scalarsetObject). Python's_coerce_write_valuenowjson.dumpss both dict and list jsonb-storagefield.object/field.mapvalues to a JSON text string — pg8000 binds a nativedictto jsonb fine, but adapts a nativelistas a Postgres ARRAY literal ({...,...}) which the JSONB column rejects with22P02.
- Kotlin codegen moved to Jackson for typed jsonb columns; entity/value/projection classes dropped
@Serializable.KotlinExposedTableGeneratornow emits a per-packageMetaJsonbMapper.kt— acom.fasterxml.jackson.databind.ObjectMapper(kotlinModule()+JavaTimeModule(),WRITE_DATES_AS_TIMESTAMPSdisabled) — that the generatedjsonb()column codecs read/write through (aTypeReference<List<VO>>captures the array-of-VO generic). Jackson (not kotlinx) is the codec precisely so generated entity/value/projection data classes carry NO@Serializableand need NOkotlin("plugin.serialization")compiler plugin: a kotlinxVO.serializer()would require the plugin, and once it is on, every VO carrying ajava.util.UUID/java.time.*/java.math.BigDecimal/java.net.*field fails to compile (kotlinx has no serializer for thosejava.*types).@Serializableis kept only on prompt payloads + enums (genuinely kotlinx-decoded by the FR-006 output parser). Consumers generating any typed jsonb/field.mapcolumn addjackson-databind+jackson-module-kotlin+jackson-datatype-jsr310(documented indocs/ports/kotlin.md+codegen-kotlin/KNOWN_GAPS.md); the open-bagfield.string @dbColumnType:jsonbcolumn stays on the kotlinxJsonElementlane. New gate: compile-WITHOUT-the-serialization-plugin + Testcontainers-PG roundtrip of a GENERATED typed-jsonb table (GeneratedTypedJsonbRoundTripTest). - BREAKING —
origin.passthroughis now type-preserving: a passthrough field must match its@fromsource's type (#185). A field carryingorigin.passthrough @from: "Entity.field"forwards another field's value unchanged, so its declaredfield.<subType>and array-ness must be identical to the resolved source field. A divergence now fails load withERR_PASSTHROUGH_TYPE_MISMATCH(e.g. afield.uuidsource surfaced by a projection asfield.string— the exact mismodeling that forces hand-writtenString↔UUIDbridging and defeats a UUID migration). The check compares subType and array-ness only — nullability is deliberately not judged (a view over an outer join legitimately widensNOT NULL→ nullable). Opt out of a deliberate type change with@convert: true(see Added). This generalizes and retires the narrow, stored-proc-parameter-ref-onlyERR_PARAMETER_REF_PASSTHROUGH_TYPE_MISMATCH(FR-015) into one host-agnostic invariant covering projections, entities, values, and parameter refs alike. Enforced in the loader/verify in all five ports (TS / Python / C# / Java / Kotlin), cross-port conformance-gated. Migration: if load newly fails, the error names both the declared type and the source type — declare the source type (usually the fix — the projection was wrong), or add@convert: trueif the divergence is intentional.
npm @metaobjectsdev/sdk + @metaobjectsdev/cli 0.15.15 (isolated patch; the other 12 packages stay 0.15.14). Ships updated agent-context skills — a docs/content change bundled into the SDK and delivered through the CLI (meta agent-docs / meta init). No runtime or generated-code change; PyPI / NuGet / Maven are unaffected.
- Agent-context skills now teach the correct direction for a brownfield migration/adoption (#183). The
metaobjects-*skills were framed greenfield-only ("metadata is the spine, generated code is disposable"), which licensed a backward loop: change metadata → regenerate → fix the resulting errors in existing code as if they were bugs. When adopting MetaObjects onto existing working code / a live database, the direction reverses — metadata FOLLOWS the code: author metadata + tune codegen to reproduce the existing native types, names, and nullability; minimize churn to code the generator isn't replacing; ask on ambiguity; default to the least existing-code change.metaobjects-authoringgains the adoption-direction section + a hardened UUID rule (field.string+@dbColumnType: uuidis a forbidden smell that generates aStringover a uuid column — usefield.uuid);metaobjects-auditpromotes UUID-as-string from non-failing advisory to a real correctness-adjacent finding (axis H2) with blast-radius counting;metaobjects-codegenadds "make codegen match the code, not the code match codegen";metaobjects-verifydocuments the semantic-mismodeling gap + a project-local CI ratchet lint; the always-on doc carries a one-line direction principle.
npm 0.15.14 (TypeScript, full lockstep). A codegen bug-fix patch. Java/Kotlin (Maven) carry no production change and stay on their current line; PyPI 0.15.9 and NuGet 0.15.7 ship the same fix on their own lines.
- Nested
@objectRefnow resolves FQN-exact across ports (ADR-0041). Theverify --templatesprompt-drift gate and the render-helper payload field-tree derivation resolved a nestedfield.object@objectRefby BARE short-name, so a fully-qualified ref (pkg::Name) bound the WRONG same-namedobject.valueon a cross-package short-name collision — emptying the element subtree and raising a spuriousERR_VAR_NOT_ON_PAYLOADon its inner{{fields}}. The CLI verify walk (payload-field-tree.ts) and the docs-source annotator (template-payload-tree.ts) now route through the sharedrefMatchesObjectresolver (FQN-exact when the ref contains::, else bare short-name first-wins); the render-helper resolver was already FQN-safe. The Python and C# render-tier walks also key their cycle guard by the fully-qualified name so a nested collision chain isn't falsely deduped. Gated by a new multi-packagexpkg-collisionsub-corpus underfixtures/template-output-render-conformance/that also drives the Python / C# / Kotlin render-helper conformance runners. (#182)
Maven Central 7.7.6 (Java + Kotlin, lockstep). A Kotlin-codegen-only patch — npm / PyPI / NuGet are unaffected (they carry no Kotlin) and stay on their current lines.
codegen-kotlinfolds TPH (table-per-hierarchy) discriminator subtypes into the base — no dead per-subtype artifacts (#180). A discriminator base already emitted the union table + data class + enum + polymorphic controller, but five other generation paths still emitted dead, partly non-compiling per-subtype artifacts (<Sub>Tablemapping the same physical table with a partial column set; a phantom per-subtype inverse FK frombuildGlobalFkMap; dead<Sub>data class / filter allowlist / validator registry entry / relations helper — the latter referencing the folded-away<Sub>Table). Every entity-iterating generator now skipsisTphSubtype(matching the controller), and the base union emits the enum class for any subtype-onlyfield.enumit folds in. Brings Kotlin in line with the Java (codegen-spring) port. Gated by an expanded snapshot fixture + a full-suite compile test (Exposed + Spring).codegen-kotlingenerated controller now filters@filterable field.enumcolumns (#179). The Exposed enum column is typedColumn<Enum>, but the controller emittedcol eq (p.value as <BareEnum>)— an unresolved un-prefixed enum type + aString→enum cast — so any filterable enum column produced a non-compiling controller. Enum columns are now filtered by their stored string viaCAST(col AS text)(castTo<String>(TextColumnType())), matching every other port's string-band enum-filter semantics (eq/ne/in/like/isNull). Gated by a compile-and-run test (eq/ne/in/like against Postgres-mode H2 over MockMvc). Non-enum controllers stay byte-identical.
npm 0.15.13 (full lockstep across all 14 @metaobjectsdev/* publish candidates).
meta docs --siterenders every remaining authored@attr. Building on theview.*attr rendering (0.15.12), the site now documents relationship@onDelete/@onUpdate, origin@of/@agg/@filter, a view source's@view, grid@layoutattrs, object-level@attrs(e.g. a consumer's@dataflow/@neo4j), field-level@attrs(@column/@storage/…), identity@constraintName, and non-standard template attrs on prompt + output pages. A genericotherAttrscatch-all consumes + renders whatever a bespoke renderer doesn't, so a consumer's own metamodel vocabulary is documented from a bare registration and new attrs never silently go un-rendered. On a real ~280-page model, coverage now reports zero "not rendered by any page" warnings.
npm 0.15.12 (full lockstep across all 14 @metaobjectsdev/* publish candidates).
meta docs --siterenders a field'sview.*subtypes + their attributes. Object pages now show each field's presentation view children (view.currency/view.badge/view.meter/view.duration/…) as a per-field sub-row —view.<subType>badges plus@attr=valuepairs (object-valued attrs like@variantMaprender as sortedk: vlists). The field builder consumes the view node + itsownAttrs, so they no longer surface as "not rendered by any page" in coverage. Attrs are read directly off the node, so a custom view subtype registered viaextraProvidersrenders its attrs from a bare registration (no per-attr schema needed in the consumer's provider).
npm 0.15.11 (full lockstep across all 14 @metaobjectsdev/* publish candidates).
meta docs --siteresolves cross-file overlays. The site loader fed metadata viafromDirectory, whoseDirectorySourcesorts by basename (a cross-port ordering contract). A base object in a top-level file plus anoverlay: trueextension in a subdir whose basename sorts earlier parsed the overlay before its base and failed withERR_OVERLAY_NO_TARGET— even though the sdk'sloadMemory(and thusmeta gen/migrate) loads the same model fine via files-before-subdirs. The site loader now collects files-before-subdirs and feedsMetaDataLoader.loaddirectly, leaving the cross-portDirectorySourceorder untouched.acmegolden byte-identical.
npm 0.15.10 (full lockstep across all 14 @metaobjectsdev/* publish candidates).
meta docs --sitenow honorsmetaobjects.config.tsproviders. The HTML site surface had its own loader (docs-site'sloadModel) with a fixed provider registry, so a model using consumer subtypes (customfield.*/view.*/object.*registered via a project'sproviders) failed on--sitealone withUnknown type "…" — not registered, even though the markdown surfaces (which load vialoadMemory) resolved them.docs-site'sloadModel/generateSitegain an additiveextraProvidersoption (composed after the built-in bundle, mirroringloadMemory'sproviders), and the CLI threads the config'sprovidersinto the site path. Additive — config-less callers are unchanged (theacmegolden is byte-identical).
Coordinated cross-language release: npm 0.15.9 (full lockstep across all @metaobjectsdev/* publish candidates) · PyPI 0.15.8 · NuGet 0.15.6 · Maven Central 7.7.5. A new cross-language reference-resolution contract (ADR-0041).
- Cross-package reference resolution contract (ADR-0041), all five ports. Every reference that names another object —
@objectRef(relationship +field.object),@references, the@from/@of/@viaheads oforigin.*,extends, a relationship's@through, and@payloadRef/@responseRef— now resolves under one shared, conformance-gated contract: a fully-qualified reference (pkg::Entity) resolves EXACTLY to its package (never a bare-tail fallback), and a bare reference prefers the referrer's own package, else a unique object of that name anywhere, elseERR_AMBIGUOUS_REF(new error). Newfixtures/conformance/xpkg-*/error-xpkg-*corpus covers every ref-bearing attribute cross-package plus the collision cases; all five ports serialize/err byte-identically.
- Java resolved an explicit FQN to the wrong same-named package.
SymbolTable.nameMatches/ValidationPhase.nameMatchesmatched the reference's bare tail before the exact-FQN check, so with same-named entities in different packages an explicitpkg::Entitysilently bound a different package's entity (wrong FK table). FQN references now resolve exactly. Kotlin bare-name collisions were arbitrary first-match; the shared loader fix corrects both. - Java M:N derivation/runtime resolvers had the same FQN-discard bug.
M2MFields(the FK-derivation SSOT) and the OMDB runtimeM2MResolverstripped an FQN@objectRef/@throughto its bare tail — mis-classifying a cross-package hetero M:N as a self-join / binding the wrong junction. They now resolve by exact FQN + resolved object identity.
Potentially breaking: a model that today relies on a bare reference silently binding an arbitrarily-chosen same-named entity in another package now fails with ERR_AMBIGUOUS_REF — previously undefined, port-divergent behavior. Fix by qualifying the reference with its package (FQN). No change for unique names or explicit FQNs. The remaining bare-collision same-package preference in codegen/runtime is tracked as a follow-up (#174).
npm 0.15.8 (full lockstep across all @metaobjectsdev/* publish candidates, now including the new docs-site package). A TypeScript-only release — NuGet stays at 0.15.5, Maven Central at 7.7.4, PyPI at 0.15.7.
@metaobjectsdev/docs-site— a browsable HTML documentation-site generator, wired asmeta docs --site. Generates a multi-page site from metadata alone (package nav, Cmd+K search, per-object/package/prompt/output pages, kind-aware ER diagrams that encode object kind by shape + domain by color); deterministic + link-checked.--siteis additive to the markdown--model/--apisurfaces (alone, it suppresses markdown). Relationship edges are sourced from the shared relationship IR, so the diagrams cover M:N-through-junction (the junction is kept as a node and a distinct M:N edge is drawn), directed + symmetric self-joins, belongs-to cardinality, and@onDelete.meta docs --scaffold-sitecopies the templates + CSS/JS intocodegen/docs-site/(ADR-0034 scaffold-and-own, write-only-if-absent) so a consumer owns its theme;meta docs --siteauto-detects the owned copies (bundled fallback).
- metadata —
deriveM2MFieldsresolves the M:N@throughjunction by FQN as well as bare name. The TS port looked the junction entity up by bare name only, so a fully-qualified@through(the codebase convention) threw and callers silently dropped the M:N relation — affecting both the new docs graph andcodegen-ts'sbuildRelationMap. It now falls back FQN → package-stripped, matching the Python and Java/Kotlin ports, which already handled both forms. - docs-site —
.jsextensions on relative imports (Node ESM compatibility). The package initially shipped extensionless relative imports (Bun-tolerant, Node-rejected), someta docs --site/--scaffold-sitecrashed from a real Node install even though every in-workspace bun test passed; all relative imports now carry the.jsextension every sibling package already uses.
npm 0.15.7 (full lockstep across all 13 @metaobjectsdev/* publish candidates). A TypeScript-only patch — NuGet stays at 0.15.5, Maven Central at 7.7.4, PyPI at 0.15.7.
- codegen-ts — generated Drizzle DAOs failed
tscunderverbatimModuleSyntax(#165). The defaultentityFile()output imported Drizzle's type-only symbols as value imports —InferSelectModel/InferInsertModel(drizzle-orm) andAnyPgColumn/AnySQLiteColumn(*-core, used only as a.references()return-type annotation). UnderverbatimModuleSyntax: true(a common default in modern Vite/TS app templates) tsc rejects each with TS1484, so a generated DAO failedtsc -bwith hundreds of errors even though it ran fine under a bundler. Those symbols now emit as type-only imports (import type/ inlinetypemodifier), fixing both the built-in generator and the ADR-0034 scaffold-and-own reference template. Gated by a real-tsccompile guard withverbatimModuleSyntaxon. - CLI —
meta init --refresh-docsre-scaffolded the project and regressed stack detection in a monorepo (#163).--refresh-docs --forcefell through to a full project re-scaffold (re-creatingmetaobjects/,metaobjects.config.ts,codegen/generators/, config, manifest — and scaffolding into sibling packages) because the refresh short-circuit was gated on!force; refresh now short-circuits regardless of--force, which instead means "overwrite hand-edited docs in place." And refresh re-detected the tech stack from a root-only probe — collapsing a monorepo'sjava, kotlin server, react, tanstack clienttojava server, no client(sibling-package client deps and Maven-built Kotlin are invisible at the root) — so it now reuses the stack persisted in.metaobjects/.agent-context.json(precedence: explicit--server/--client> persisted manifest > detection).
Coordinated cross-port patch: npm 0.15.6 (full lockstep across all 13 @metaobjectsdev/* publish candidates) · PyPI 0.15.7 (Python stays a patch ahead) · NuGet 0.15.5 · Maven Central 7.7.4. A loader-ordering bug-fix that hardens a latent fragility in every port.
- Loader — an overlay-only file could be merged before the base file that declares the entity it re-opens, breaking order-dependent super-resolution (all four loader ports). When a directory scan surfaced an
overlay: truefile ahead of the file declaring the base object (e.g. a top-levelmeta.a-presentation.jsonpresentation overlay sorting before a subdirmeta.z-model.jsonbase), the overlaid node preceded the entities it depends on — soobject.projectionre-opens (and any overlay reaching for a not-yet-loadedextends/origintarget) failed to resolve, producing spuriousERR_INVALID_ORIGIN/ERR_UNRESOLVED_SUPER/ERR_MISSING_REQUIRED_ATTR. This surfaced as a cross-port divergence — the TS loader tolerated one discovery order that the Python loader rejected — but the fragility was latent in all ports (a single-file projection-declared-before-its-base repro fails identically everywhere). Each loader now stable-partitions overlay-only sources/roots to merge last, deterministically, so base declarations are always present before any overlay re-opens them. Gated by a new shared cross-port conformance fixture (projection-overlay-abstract-identity, whose overlay basename deliberately sorts first) that all five ports serialize identically. (#160)
npm 0.15.5 (full lockstep across all 13 @metaobjectsdev/* publish candidates). NuGet unchanged at 0.15.4, PyPI 0.15.6 (the Python-only #158 fix ships there), Maven Central unchanged at 7.7.3. A TypeScript-only patch.
- Offline
meta migratenow threads consumer providers (#157).migrate baselineand the offlinemigrategenerate path calledloadMemorywithout theprovidersfrommetaobjects.config.ts— so a project registering a custom subtype via a config provider hitUnknown typeon offline migration, even thoughmeta genand the DB migrate paths loaded the same metadata fine. Both offline functions now load the config once up front and pass its providers to the loader (mirroring the DB path), and the offline generate path folds the latercolumnNamingStrategyread into that same load.
npm 0.15.4 (full lockstep across all 13 @metaobjectsdev/* publish candidates) · NuGet 0.15.4 · PyPI 0.15.5 (Python stays a patch ahead) · Maven Central unchanged at 7.7.3 (the Java loader was already correct). A coordinated loader bug-fix patch.
- Loader — root-level same-name nodes in different packages were wrongly merged (TS/C#/Python). Two files declaring the same (type, name) at root level under different packages collapsed into one node: identical twins merged silently, and twins differing in an
@attr(e.g. each package's@objectRefpointing at its own nested view) failed the load withERR_MERGE_CONFLICT. Root-level merge matching now compares the package-qualified identity (ownpackageelse the file-default package) — mirroring the Java parser, which was already correct. Nested children stay bare-name matched; same-package cross-file overlay merging unchanged. New cross-port conformance fixtureloader-same-name-distinct-packages(Java passes it unchanged). (#155)
npm 0.15.3 (full lockstep across all 13 @metaobjectsdev/* publish candidates).
npm 0.15.3 · NuGet 0.15.3 · Maven Central 7.7.3 · PyPI 0.15.4 (Python stays a patch ahead). A coordinated feature patch.
@viacan traverse anidentity.reference(reference-only FK). A projection'sorigin.*@viajoin path may now name anidentity.referenceas a to-one forward-FK hop (target =@references, cardinalityone), not just arelationship.*. The reference IS the FK —findReferenceBetweenalready derives every hop's join key from it, and a correlated relationship only adds a name + cardinality — so a FK-only / reverse-engineered model navigates it directly instead of authoring a redundantrelationship.associationthat just restates the target. Valid in apassthrough, rejected in anaggregate; single-hop-unique inference stays relationship-only. Applied across all four loader ports (TS also updates the codegen join-tree; Python/Java/C# are loader-only) + a shared cross-port conformance fixture (origin-via-reference-hop) all four loaders serialize identically. (#153)
npm 0.15.2 · NuGet 0.15.2 · Maven Central 7.7.2 · PyPI 0.15.3 (Python was a patch ahead). A coordinated bug-fix + hardening patch.
- codegen-ts —
isArrayfield with a@defaultemitted invalid Drizzle (.array().default("<string>")→tscTS2345). A regression from the0.15.0@dbColumnTypeslim (arrays became nativetext[]); the default-emitter now parses the string default into a JS array literal (.default([])/.default(["a","b"]),sql\...`fallback), so array-default output typechecks. Adds a real-tsc` compile-guard fixture. (#146) - Filter
in-list size cap unified cross-port. Java/Python/C# generated filter parsers + the Kotlin controller now enforce the same 100-element cap TS had — a>100-elementinlist is rejected with HTTP 400 (filter.in_too_large) so it can't be forced against the DB. Gated by a new shared api-contract conformance scenario. (#150, #32) - Java —
ERR_PROVIDER_ATTR_CONFLICTis now actually thrown with that code on a colliding attr child requirement (previously a bareIllegalArgumentException). (#148)
- Provider-composition conformance harness — five registry/provider error codes (
ERR_PROVIDER_DUPLICATE_ID/_MISSING_DEPENDENCY/_DEPENDENCY_CYCLE/_ATTR_CONFLICT,ERR_REGISTRY_SEALED) are now gated cross-port (all 5 ports) via a shared named-provider manifest corpus. (#148, #33)
- Java read-path cache wired into the resolving accessors (
getChildren/getMetaAttrs/isArrayType), frozen-only + behavior-neutral — matching the existing TS/Python/C# caches — plus a 100k-object throughput benchmark gate. (#149, FR-031)
- Cross-language version-drift guidance. Because the package-version lines differ by ecosystem (npm/PyPI/NuGet
0.xvs Maven7.x), a stale port is invisible in the numbers; themetaobjects-auditskill now enumerates every port and compares the sharedmetamodelVersion, and the always-on prompt tells agents to keep all ports on the same Metamodel version. (#147) - Documented the TS-only filter extensions (
search/filter[or]|[and]/ leading-wildcard / nesting) as not part of the cross-port contract. (#32)
PyPI 0.15.2 — Python-only patch (npm/NuGet stay 0.15.1, Maven Central stays 7.7.1).
- Python — the output-prompt spec emitter crashed
gen/verify --codegenon a nestedfield.objectpayload. Atemplate.outputwhose payload value-object had a nestedfield.objectchild emitted invalid Python: the nested-object branch appended an inline#comment to thePromptFieldliteral, and since the wholeOutputFormatSpec(...)is one line, the#swallowed the closing])— an unterminated list (SyntaxError) that hard-crashed codegen and the drift gate (not just a diff). Flat payloads were unaffected. Python-only (the other four ports use inline-safe/* */block comments). The nested field now emits a validFieldKind.OBJECTplaceholder (nested=None).
Maven Central 7.7.1 · PyPI 0.15.1 · NuGet 0.15.1 · npm 0.15.1.
⚠️ This "patch" carries a BREAKING metamodel change (versioned as a patch by request; treat it as breaking). Read the migration guide before upgrading:docs/features/migrations/identity-secondary-to-index-lookup.md.
- BREAKING —
identity.secondaryis now a unique alternate key;@uniqueis removed (ADR-0040). Uniqueness is encoded by the type, not a boolean — a legacy@uniqueonidentity.secondarynow fails load withERR_UNKNOWN_ATTR.
- New
indextype /index.lookupsubtype — a non-unique retrieval index. This is where a non-unique index now lives (previously mis-modeled asidentity.secondary @unique:false).@fieldsis required (single or composite). The physical RDB escapes@using/@expr/@where/@ordersare contributed by the db provider to bothindex.lookupandidentity.secondary, consumed only by RDB codegen.index.fulltext/index.vector/index.spatialare reserved on the axis but not registered. Cross-port conformance-gated (registry, metadata, persistence). Anindex.lookupproduces the sameCREATE INDEXa non-unique index always did — no schema/DDL churn for the migrated form (averify/migrate no-op).
identity.secondarywithunique: false→index.lookup(dropunique, keepname/fields/any physical escape).identity.secondarywithunique: trueor absent → staysidentity.secondary, drop the now-invalidunique. See the migration guide above.
Coordinated minor with breaking changes — Maven Central 7.7.0 · PyPI 0.15.0 · NuGet 0.15.0
· npm 0.15.0 (follows via RC). The metamodel-1.0 vocabulary program plus the ADR-0039
own-accessor correctness fix, cross-port conformance-gated across all five ports.
- 1.0 metamodel vocabulary program (ADR-0036/0037/0038).
field.uri(native URI, text column) andfield.inet(native IP, Postgresinetcolumn) field subtypes; a@stringFormatattribute ({email, hostname}) for validated-string content; reverse navigation via generated explicit FK finders (find<Source>By<FkField>(id)+ batched…In(ids)) instead of lazy collections; a closed-value-set conformance gate (allowedValuesin the registry manifest). A general decision framework (ADR-0037) now governs when a new concept becomes a subtype vs@kindvs an attribute.
- BREAKING —
field.timestampis instant-by-default (timestamptz/Instant/DateTimeOffset/ awaredatetime), with a boolean@localTimenaive opt-out. Retires@dbColumnType: timestamp_with_tz(now derived from the subtype). - BREAKING —
@dbColumnTypeslim-and-derive. Array-ness is derived (isArray) rather than spelled asuuid_array/text_array, and thetextdefault is derived;@kind: textand the*_arraycolumn types are dropped.@dbColumnTypenarrows to the genuinely-physical escapes (uuid,jsonb).
- ADR-0039 —
own*()accessors brokeextendsinheritance (all five ports).extendsis a super-reference, not a flatten: reading a field/node's effective property (isArray,subType,@maxLength,@precision,@default,@objectRef,@storage, …) or iterating its members via an own-only accessor silently droppedextends-inherited values, corrupting codegen and runtime. Resolving/effective accessors are now the default everywhere;own*()is reserved for its one legitimate use (codegen emitting a generated subclass's own members) plus the metamodel-internal serializer/overlay/origin.*/@dbColumnTypecases. A concrete field or entity thatextendsan abstract parent now correctly inherits its properties and members, guarded permanently by a sharedextends-abstract-field-inheritanceconformance fixture. Notable bugs it surfaced: an entity inheriting itssource.rdbviaextendsgenerated no table/controller; an M:N junction inheriting itsidentity.referencechildren was falsely rejected; a Python runtime path dropped an inherited M:N relationship.
npm 0.14.2 (full lockstep across all 13 @metaobjectsdev/* publish candidates).
- codegen-ts — required jsonb open-bag generated an uncompilable
z.unknown().min(1). The 0.14.0 jsonb open-bag change (field.string+@dbColumnType: jsonb→z.unknown()) left the string character-count validators attached: a required jsonb field still got the required-non-empty.min(1)chained onto itsz.unknown()base, emittingz.unknown().min(1)— a TS compile error (ZodUnknownhas no.min). The validator chain now skips the string.min/.max/.regexfor a jsonb open bag (a jsonb array still gets element-count bounds); "required" for an open bag means non-optional only. Surfaced by an adopter whose generated schema stopped compiling after 0.14.0.
npm 0.14.1 (full lockstep across all 13 @metaobjectsdev/* publish candidates).
- codegen-ts — package-qualified relationship
@objectRefdropped projection joins. When a projection's aggregate traversed a relationship whose@objectRefwas package-qualified (pkg::Entity) — the shape the directory loader produces for a same-package objectRef even when authored bare — the join resolver looked the target up by the raw qualified name whilefindObjectkeys on the bare name, so the join silently failed to resolve and every aggregate traversing it was dropped: the view degraded to PK + passthrough columns only.extract-view-specnowstripPackages the@objectRefbeforefindObject, matching the@via/@of/@frompaths. Surfaced by a directory-loaded consumer whosecount/filtered-maxaggregate columns vanished from a generated view while the string-loaded test fixture (bare objectRef) passed.
npm / PyPI / NuGet 0.14.0 · Maven Central 7.6.0. A coordinated minor with
breaking changes (verify strict-by-default + the jsonb open-bag contract).
-
BREAKING —
verifyis strict-by-default across all CLI ports (ADR-0023). An undeclared or typo'd own@attris nowERR_UNKNOWN_ATTRand the gate exits non-zero. This closes a real cross-port hole: the original assumption was "Java enforces strict, TS/Python are lax", but Java was in fact not enforcing either — its loader recordsERR_UNKNOWN_ATTR(record-not-throw) and the Maven mojo never drainedgetErrors(), sometaobjects:generate/:verifysilently passed. All four CLI ports now genuinely enforce strict and ship an escape:- TS
meta verify+ Pythonmetaobjects verify→--lax(#101) - C#
dotnet meta verify→--lax(#107) - Java/Kotlin Maven goals →
-Dmeta.lax=true, and the goals now fail the build on a recorded loader error instead of silently passing (#108)
Scope: only
verifydefaults strict on the Node/C# CLIs (gen/docs/agent-docsstay lax); the Java goals gate at generate-time too. Migration: if the gate now flags an attr you rely on, register it on a metadata provider, move arbitrary author-supplied properties into anattr.propertiesbag, or pass--lax/-Dmeta.lax=true. The failure message names all three exits. (#96) - TS
-
CHANGED — a jsonb open-bag is now a parsed JSON value at the API boundary (all five ports). A
field.string+@dbColumnType: jsonb(the sanctioned untyped-JSON escape hatch) was generated as a string in the validator/DTO while the column returns a parsed object — so a client could not POST/receive a real JSON object (it had to double-encode). Now the generated contract types it as a JSON value, wire form unchanged: TSz.unknown()(#97), PythonAny(#99), JavaObject(#103), Kotlinkotlinx JsonElementat every layer (#104), C#System.Text.Json.JsonDocument(#105). Adopters who hand-handled the field as a raw string may need to adjust. (#98)
- codegen-ts-react — nested value-object sub-forms in
formFile. Afield.objectwith an@objectRefto a value object now renders as a nested<fieldset>sub-form (react-hook-form nested paths; arrays viauseFieldArray) instead of a single text<input>bound to a JSON object. Recurses one+ levels with cycle/depth guards. (#95)
- sdk — Meta Forge descriptive layer is now strict-clean.
loadMemorybundles the Meta Forge descriptive types (decision/principle/…) and their@forge*provenance attrs so mixed prescriptive+descriptive content loads. Under the new strictverify, those were rejected (ERR_CHILD_NOT_ALLOWED/ERR_UNKNOWN_ATTR); the forge provider now admits its types undermetadata.rootand registers the@forge*attrs as common attrs, so a real memory record verifies clean. (#96)
npm 0.13.1 (full lockstep across all 13 @metaobjectsdev/* publish candidates).
- codegen-ts —
origin.aggregate@filterin projection view DDL. A scoped aggregate (e.g.max(version) where status='active') declared via the aggregate's optional@filtergenerated into the TS contract but was dropped from the generatedCREATE VIEW— the emitter rendered the aggregate with noFILTERclause, so it computed over all related rows.extract-view-specnow reads + desugars the filter andview-ddl-emitrenders postgresAGG(src) FILTER (WHERE …)(and the portableAGG(CASE WHEN … THEN src END)form on sqlite). (#90)
- codegen-ts — declarative Mustache template-codegen (SP-1a): a
templateGeneratorcan now take its walk declaratively viascope("perEntity" | "perPackage" | "perModel") +outputPatterninstead of a hand-writtenwalk(the two are mutually exclusive — provide exactly one). The generator derives a neutral, structural template data dict per unit (buildEntityTemplateData/buildPackageTemplateData/buildModelTemplateData, with typesFieldTemplateData/EntityTemplateData/IdentityTemplateData/RelationshipTemplateData/PackageTemplateData/ModelTemplateData) — raw structural facts only, distinct from the Markdown-flavoredEntityDocData, and byte-gated as a cross-port contract byfixtures/template-codegen-conformance/.outputPatternsupports{name}/{Name}/{package}(::→/; unknown placeholder throws), expandable via the exportedexpandOutputPattern. A JSON template-spec (parseTemplateSpec/templateSpecToGenerators, typesTemplateSpecEntry/TemplateSpecFile, JSON Schema beside the source) is the surface the C#/Python CLI ports will reuse. New package-scope engine helperperPackage(fn)joinsperEntity/perModel. All exported from the package main entry@metaobjectsdev/codegen-ts. - cli —
meta initscaffolds owned codegen generators (ADR-0034 scaffold-and-own, step 2):meta initnow copies the four codegen reference templates (step 1) into the consumer repo atcodegen/generators/{entity,queries,routes,barrel}.tsand scaffoldsmetaobjects.config.tsto import those local copies, someta genruns from generators the consumer owns and edits — not from the package. Each generator is written only if absent, so re-runningmeta init --forcenever clobbers a hand-edited generator (mirrors the existing config.ts preservation). codegen-ts gains a small reference-template reader the CLI uses to read the shipped assets (resolveReferenceRoot/readReferenceTemplate/REFERENCE_GENERATOR_NAMES, exported from@metaobjectsdev/codegen-ts). - codegen-ts — reference template library (ADR-0034 scaffold-and-own, step 1):
new in-repo, copyable reference generators under
src/reference/(entity/queries/routes/barrel) — self-contained starting points a consumer copies into their repo and owns, importing only the public engine (@metaobjectsdev/codegen-ts) plusts-poetand@metaobjectsdev/metadata. Each carries ause-when / emits / customize / composes-withheader. Purely additive — no existing generator or export was removed; the templates are scaffold assets excluded from the package build. To keep a copied generator on public imports only, the engine now also re-exports the assembly helpers those templates use:renderTphDiscriminatorUnion,hasWritableRdbSource,renderSharedEnumsFile/SHARED_ENUMS_BASENAME, and the queries CRUD-block renderers (renderFindByIdFn,renderListFn,renderCreateFn,renderUpdateFn,renderDeleteByIdFn,getPkInfo). (meta initscaffolding, generator-export deprecation, and the guidance rewrite are later steps.)
- codegen-ts —
oncePerRunscope helper (SP-1a): renamed toperModel— "run" is ambiguous under multi-target output (it reads as "per target"), whileperModelnames the data scope (the whole model).oncePerRunis kept as a soft-deprecated alias and still works. - codegen-ts —
@metaobjectsdev/codegen-ts/generatorsfactory re-exports (ADR-0034 scaffold-and-own, step 2): importingentityFile/queriesFile/routesFile/barrelfrom the package/generatorsexport is deprecated in favor of the owned local copiesmeta initscaffolds. The export still works (pre-GA latitude) but will be removed in a future major — own a copy instead.
- cli —
meta initgitignore hardening: the scaffolded.metaobjects/.gitignorepreviously ignored only.gen-state/, so a multi-target codegen config routing a target'soutDirunder.metaobjects/<target>/src/generated/let that regenerable generated shadow get committed by default. The scaffold now also ignores*/src/generated/and re-includes the tracked artifacts (!migrations/,!config.json,!package.meta.json) so they can never be swept up. - cli —
meta initmonorepo-subdir warning: scaffolding the agent-context.claude/skills/into a git subdirectory means a repo-root-launched Claude session won't discover the skills (discovery walks cwd + ancestors, never down into subdirs).meta initnow warns when run inside a subdir of a git repo and points atcd <repo-root> && meta init --docs-only --server <lang>. Scaffold warnings are also now surfaced on the normal init output path (previously dropped).
npm 0.12.5 (full lockstep across all 13 @metaobjectsdev/* publish candidates).
- codegen-ts — projection read-type nullability now mirrors the view column:
a non-
@requiredprojection field generates a nullable Drizzle view column but previously kept a non-null Zod read type, so the generated projection query returnedT | nullinto a non-null<Name>field and failed to compile under strict TS. The read field is now emitted as.nullable()whenever its view column is not.notNull(), so the read type matches the view's SELECT type.
npm 0.12.4 (full lockstep across all 13 @metaobjectsdev/* publish candidates).
- codegen-ts — projection codegen: an
object.projection(read-only, view-backed) now generates read-only query helpers (find…ById+list…selecting from the view) instead of table-style create/update that imported a nonexistent<Name>InsertSchema. This fixes aTS2724compile error that made a declared projection fail to build, forcing consumers to revert to hand-rolled aggregates. (Mirrors theisProjectionguard the routes generator already had.) - codegen-ts — generated SQLite
Dbtype is nowBaseSQLiteDatabase<"sync" | "async", unknown>, accepting both sync (better-sqlite3, the most common driver) and async (libsql/Turso/D1) Drizzle databases. The previous<"async">pin rejectedbetter-sqlite3with "is not assignable", forcingdb: anycasts. - codegen-ts — generated Postgres
Dbtype is now the basePgDatabase<PgQueryResultHKT, …>that every PG driver extends (node-postgres, postgres.js, Neon, Vercel, pglite), not justNodePgDatabase.
- cli — verify-as-teacher:
meta verifyandmeta genrun an advisory pass that flags hand-rolled aggregates, money-as-float, andCHECK (… IN …)enums and names the construct that models them. Warnings only — never changes the exit code. Opt out with--no-antipatternsorMETA_NO_ANTIPATTERNS=1(both honored on both commands). - agent-context skills: a model-first / generate-first operating principle in
the authoring skill, and a first-class "write your own generators" section in the
codegen skill (with the accurate
Generator/perEntityAPI).
npm 0.12.3 (full lockstep across all 13 @metaobjectsdev/* publish candidates).
- Agent-context: granular codegen control + projection consumption + the runtime→Fastify
mount API. The
metaobjects-codegenskill now teaches that codegen is à la carte (omitroutesFile()to generate the data layer + hand-write the routes, mix generated and hand-written, declare anobject.projectionand consume its generated query, copy/extend generators); themetaobjects-runtime-ui(TypeScript) reference documents the real@metaobjectsdev/runtime-ts/drizzle-fastifymount helpers (mountCrudRoutes({ expose }),mount<Verb>Route,mountReadOnlyCrudRoutes) so agents stop reverse-engineeringnode_modules(#78).
npm 0.12.2 (full lockstep across all 13 @metaobjectsdev/* publish candidates).
- Drizzle codegen annotates every FK
.references()callback with(): Any{Pg,SQLite}Column. Cross-module circular references (table A → B while B → A) went through the un-annotated branch and failedtsc --strictwith TS7022;codegen-tsnow emits the explicit return type unconditionally (Drizzle's documented fix for circular inference) (#76).
npm 0.12.1 (full lockstep across all 13 @metaobjectsdev/* publish candidates).
meta typesvocabulary search +whenToUsedecisional guidance. A newmeta types [query]command — apropos +kubectl explainover the live registry (--desc/--alldescription search,--kind/--typefilters, terse/--detail/--jsonoutput) — plus the canonicalwhenToUse"reach for this when…" guidance on the data-modeling constructs inspec/metamodel/*.json(flows to all five ports), so an agent finds and uses the right metadata construct instead of hand-writing data logic (#74).
npm 0.12.0 (full lockstep across all 13 @metaobjectsdev/* publish candidates).
- Agent-friendly
metaCLI. A--formatflag with TOON output (a compact, machine-readable format that becomes the default when stdout is piped to an agent/CI), structured errors and next-step hints emitted on stdout, package-manager detection, and deploy-all agent-context reference fragments (#71).
meta initagent-context scaffold no longer guesses the migration binding. The injectedAGENTS.md/CLAUDE.mdnow name the database schema and migrations as metadata-derived in the "never hand-write" principle ("change the metadata and regenerate, never hand-write SQL"), and the stack line dropped the guessed "migrations are TS" clause. This prevents an AI agent from hand-writing a rawALTER TABLEagainst a generated schema and silently reintroducing the driftmeta verifyexists to catch. The verify skill's JVM startup-validator note was also hedged to an opt-in (#1, #73).
npm 0.11.6 (full lockstep across all 13 @metaobjectsdev/* publish candidates).
- Typed projection (view-kind) read models. A projection's Drizzle
.existing()view declaration now emits a typed column map (honoring@dbColumnType, e.g. jsonb/timestamp) instead of an empty{}, sodb.select().from(view)is typed. - Projection passthroughs resolve value-object refs — a
field.objectpassthrough carries the value object's Zod schema +.$type<VO>()into the read schema/type, so the row is typed as the VO rather thanunknown. runtimeflag on output targets (TargetConfig.runtime, defaulttrue). A contract-only target (runtime: false) emits Zod schemas + inferred TS types and nothing else — nodrizzle-orm(table or view) and noruntime-tsallowlists — so a shared wire-contract package consumed by a UI client carries no DB dependency. The axis is the target's audience (server vs contract), applied uniformly to entities, value objects, and projections.
- Replaced the short-lived per-artifact
includeViewDeclgenerator option with the target-levelruntimeflag above.allowlistsremains as the finer Fastify-vs-Hono opt-out within a runtime target.
npm 0.11.5 (full lockstep across all 13 @metaobjectsdev/* publish candidates).
- All view DDL is unified onto one emitter + the single schema-diff path. The
parallel
computeProjectionMigrations/source-aware-diffview-migration stack is deleted;emitViewDdl(viabuildProjectionViews) is now the sole producer of everyCREATE VIEW, and the schema-diff produces all view changes (create / drop / replace) including a dependency-recreate pass that drops + recreates a view around a column-altering change to a table it reads. - Aggregate views now render as
LEFT JOIN + GROUP BY(withCOUNT(DISTINCT …)) instead of correlated subqueries. The two are data-equivalent for a single has-many join — pinned by theprojection-aggregatepersistence-conformance scenarios (populated rows + the empty-parentNULLcase).
- View JOIN columns now honor the column-naming strategy +
@columninstead of a hardcodedsnake_caseguess, and view-body identifiers are quoted when needed, soliteral/kebab-casecolumns (e.g.programId) survive Postgres case-folding. - SQLite/D1 view migrations are now emitted (previously Postgres-only);
drop-viewis staged before the recreate-and-copy so a dependent view can't error mid-recreate.introspectD1now reads view bodies, so D1 detects view-body drift.
- migrate-ts barrel exports for the deleted view-diff stack
(
classifyViewDiff,computeViewMigrations,emitPostgresViewMigration,emitSqliteViewMigration, and theViewShape/ViewDiffClass/ViewMigrationOpts/ViewMigration*types).
(0.11.3 was deprecated as a broken isolated patch; 0.11.4 — full lockstep view-DDL fix + native SQL array columns — shipped without a changelog entry.)
npm cli + migrate-ts 0.11.2 (isolated patch; other packages stay 0.11.1).
field.mapcolumns now generate ajsonbDDL type (was defaulting toTEXT).field.mapwas added to the metamodel andcodegen-ts(emittingjsonb+Record<string,V>), butmigrate-ts's expected-schema column-type switch had no case for it, so generated migrations set the column toTEXTwhile the ORM layer expectedjsonb.clirepins to the fixedmigrate-ts.
npm 0.11.1 · NuGet 0.11.1 · PyPI 0.11.1 · Maven Central 7.4.1.
field.mapsubtype — an open-keyed map (Record<string,V>/dict[str,V]/Map<String,V>/IDictionary<string,V>) stored in a singlejsonbcolumn. Keys are always strings; the value type is set by exactly one of@valueType(a scalar field subtype) or@objectRef(a value-object). Implemented across all five ports with cross-port registry-conformance and a loader rule enforcing the exactly-one-of value spec.
npm 0.11.0 · NuGet 0.11.0 · PyPI 0.11.0 · Maven Central 7.4.0.
- Semantic cross-field validators —
validator.comparison/requiredWhen/presentIff/atLeastOne: entity-scoped rules that reference sibling fields by name (a field compared to another, a field required when another equals a value, two fields mutually present/absent, at-least-one-of a set must be present). - Expression / functional indexes —
identity.secondarynow carries@expr(a functional index expression, e.g.lower(email)) and@using(the index method), plus physical index/constraint attributes, auto schema-scope, and DB-adoption fixes for migrations. - Metadata reference enforcement — a dangling cross-reference now fails the load instead of being silently ignored: an unresolved
relationship.@objectRefraisesERR_INVALID_RELATIONSHIPand an unresolvedidentity.reference.@referencesraisesERR_INVALID_REFERENCE, with a source envelope pinpointing the node (catches metadata drift immediately). - Validation derived from the type registry — each type's registration carries its cross-reference descriptors (and an optional validator), enforced by one registry-driven walk, so a downstream provider's new type validates itself with no core changes. (The config-driven, write-once-across-ports evolution is tracked in #51.)
- A load reports every validation error, not just the first — passes collect findings (deduped by code + source) and surface them together rather than aborting on the first.
- jsonb value-object typing (TS codegen) — typed jsonb VO columns, collection-name control, and a shared VO module resolver.
buildGrid()in@metaobjectsdev/runtime-web— metadata-driven grid columns at runtime.- C# entity inheritance codegen — TPH abstract intermediates + direct-parent chain (
DirectMappedParent) +@requiredCLR nullability, and the non-TPH inheritance chain.
- BREAKING — dangling metadata references now fail to load. Models that referenced a non-existent entity via
@objectRef/@referencespreviously loaded silently; they now error (ERR_INVALID_RELATIONSHIP/ERR_INVALID_REFERENCE). Fix the reference or remove the relationship/identity. - Config-driven default name for a name-less singleton
identity.primary— a name-less primary now loads named"primary"(referenceable asEntity.primary); a second primary on one entity isERR_TOO_MANY_OCCURRENCES.
- Inherited attributes now resolve via the effective accessor across all ports — codegen + validation were reading some attributes own-only, so a field/identity that inherited
@required/@maxLength/@objectRef/@fieldsviaextends(the BaseEntity / abstract-field pattern) was silently mis-generated: wrong column nullability (an inherited@requiredfield emitted as optional), wrongvarcharlength, a dropped FK, or a dropped primary key. Now correct in TS / Java / C# / Python / Kotlin, with cross-port regression gates. - Self-referential foreign keys — a FK whose target is the same entity (
parentId,managerId) is now emitted without a circular self-import (TS/DrizzleAnyPgColumn/AnySQLiteColumn) and round-trips through every port's runtime (gated by a new persistence-conformance scenario). - Cross-package FK resolution — a FK to a target in another package now resolves its target PK column correctly in the expected schema.
- Kotlin codegen — FK to a non-
idPK, reserved Exposed member names, PK-first column order, and cross-packageTableobject imports.
- The above ship across the relevant ports (TS / Java / C# / Python / Kotlin), gated by the shared conformance corpora.
- Released as npm
0.11.0· NuGet0.11.0· PyPI0.11.0· Maven Central7.4.0.
- FR-033 metamodel self-description +
meta docsmetamodel pages — every metadata type/subtype/attribute now carries declarative descriptions and per-subtype constraints (authored once inspec/metamodel/*.json, embedded per port), and the neutral docs engine renders tiered metamodel reference pages (index one-liners + per-provider detail) wired into the authoring skill. - FR-024
object.projectiontaxonomy — new first-classobject.projectionsubtype for derived read-only models, universal deep dottedEntity.childextends (e.g.Customer.priceCents.display),@viainference for single-hop relationships, and value-object purity rules (ADR-0028/ADR-0029). - FR-017 polymorphic (TPH) codegen across the TS stack — discriminated-union entity types + per-subtype Zod schemas, Drizzle single-table emission, polymorphic + per-subtype REST routes, TanStack hooks/grid, React forms, and per-subtype filter/sort allowlists for table-per-hierarchy inheritance.
- FR-018 M:N relationships — slim
@through/@sourceRefField/@symmetricvocabulary (FK fields derived from the junction'sidentity.reference), Drizzle m2m codegen, REST traversalGET /<source-plural>/{id}/<relation>, and a typed TanStack collection hookuse<Source><Relation>. - FR-019 shared &
@providedenums — reuse enum types across entities and bind a@providedenum to its declaring package;@providedis now first-class cross-port vocabulary. @metaobjectsdev/ai-runtimepackage + AI LLM-call trace persistence — typedrecord<Entity>/call<Entity>trace helpers,callLlmbridge, pluggable cost catalog,LlmClientseam, and Composite/Langfuse/OTel recorders;@responseRefontemplate.promptandtemplate.*children underobject.entityare now supported vocabulary.- Unified
meta docsdoor (ADR-0025) — one command and onedocs:config block emit both the model surface (entity + template pages) and the SDK/API reference surface (docs/api/, includingAGENT-API.md), cross-linked; supports per-languageapiSurfacesfor polyglot solutions. - SDK/API reference docs (api-docs) — runnable examples, per-symbol import paths, surfaced throws, and field shapes for model/create/update/REST/extractor payloads; covers relations, callable, prompt-render, and Hono.
- Linked, syntax-highlighted template source on template pages — fenced highlighted block + a Variables→field link table + a rich inline-linked HTML view, with per-field anchors and a link-integrity gate reusing
verify()'s variable→field resolver. - Neutral entity-doc improvements — per-entity 1-hop neighborhood mini-diagram (clickable, classed, value-object nodes), and a merged single Fields table (Storage + Constraints).
@embeddedColumnPrefixfor flattened owned-type columns, and@summarycommon documentation attribute.- Agent-context staleness nudge —
meta gen/verifyprompt to refresh adopter agent-context when it predates the installed CLI.
- BREAKING — FR-026 / ADR-0032: canonical refs are now fully-qualified. Relative ref navigation (
bare,::root,..::parent) is YAML-authoring-only; canonical JSON must carry absolutepackage::Namerefs. A relative ref in canonical JSON is rejected withERR_RELATIVE_REF_IN_CANONICAL. - BREAKING — FR-024 hard cutover. The pre-FR-024 spellings are gone: an
object.entitywhose primary source has a read-only@kind(view/materializedView/storedProc/tableFunction) is nowERR_ENTITY_PRIMARY_SOURCE_READONLY— derived read models must beobject.projection. Identity nodes now require a name. - BREAKING — strict per-subtype attribute placement. The loader rejects subtype-specific template attributes declared on the wrong subtype.
- BREAKING —
apiDocsFile()demoted from ameta gengenerator to themeta docsAPI-surface engine; it is deprecated formeta gen(the runner warns and skips it) and dropped from themeta initscaffold in favor of adocs:block. meta initscaffold defaultoutDiris nowsrc/generated(was./src/db); api-docs is on by default in the scaffold.@objectRefresolves to a bare class name in generated code, usingresolution_keyfor the header FQN.@metaobjectsdev/ai-runtimedescoped (ADR-0024) — bundled vendor LLM clients and the built-in cost rate table were removed; bring your own LLM caller library (theCostFn/LlmClientseams remain).
verify --templatesresolves@payloadRefby FQN short-segment.extractmaps a JSONnullliteral to an actual null (not the string"null") and inherits enum-coercion attrs throughextends.- Doc generation no longer silently overwrites pages on cross-package short-name collisions (hard-errors, with package-layout support);
meta docshonors projectoutputLayoutand surfaces a brokenmetaobjects.config.tsinstead of swallowing it. - Browser-safety fix — node-only registry-coverage re-exports removed from the browser-facing barrel.
- Repaired the workspace typecheck gate (cleared pre-existing
tscerrors) and added a pre-push typecheck gate to block type-broken pushes.
- The above metamodel, codegen, and docs features were fanned out across the Java/C#/Python/Kotlin ports (FR-017 TPH runtime + codegen, FR-018 M:N resolvers, FR-019/FR-024/FR-026/FR-033, AI trace recorders, native SDK/API-reference docs, and
agent-docsgoals/commands), all gated by the shared conformance corpora. - Released alongside NuGet
0.10.0and Maven Central7.3.0.
migrate-tsreference-snapshot engine — schema migrations now diff against a committed, per-dialectSchemaSnapshot(offline, deterministic) instead of a live DB: offline snapshot planner, metadata baseline, deterministic snapshot serializer withformatVersion2, andsnapshotChecksum/verifyReplayintegrity APIs exported from the package.- Migration runner — transactional
applyPending,rollbackTo(reverse-order down), append-only timestamped migrations on disk,PgExecutor/PgHistoryStorewith configurable schema/table (multi-tenant), Postgres session advisory lock, content-normalized checksums, and a_metaobjects_migrationsledger with baseline marker. - CLI migration + verify commands —
meta migrate --apply(postgres/sqlite, ledger-backed),meta migrate --rollback,meta verify --dbschema-drift gate (exit 1 on drift; DB-free default unchanged),meta migrate baseline(--from-metadata/--from-db), and default offline snapshot generation. - CHECK constraint codegen —
migrate-tsderives CHECK constraints fromfield.enum @values,validator.numeric @min/@max,validator.length @min, andvalidator.regex @pattern(Postgres), with add-check/drop-check change kinds, restore-on-drop, and PG-rewrite-tolerant expression comparison. - Runtime object model —
ValueObjectmap-backed base,MetaObjectAwareback-reference, self-registeringObjectClassRegistry(FQN→ctor), and a reflection-freenewInstancefactory in@metaobjectsdev/metadata(AOT-safe). extractcodegen + tolerant payload parsing — generated<Name>Extractorparses LLM/wire output into a strict typed payload (nested objects + arrays), delegating to the runtime object model; payload fields are now value-constrained typed unions forfield.enum.template.outputrender helper — per-template.outputcodegen emitsrender<Name>(payload, provider)for@kind=documentand anEmailDocument(@subjectRef/@htmlBodyRef/@textBodyRef) for@kind=email, with a build-time Mustache↔payload-VO drift gate that fails codegen on an unmatched{{field}}.- New metamodel vocabulary —
field.uuidlogical subtype,@dbColumnTypephysical-column-type attribute,field.decimal(precision/scale), FR-013 field-level@readOnly(excluded from Insert/Update schemas), FR-014 TPH discriminator metadata, FR-015@parameterRef+ callable-wrapper codegen (storedProc / tableFunction), FR-016source.rdbper-kind physical-name aliases, and FR-011@normalize/@coerceDefaultenum-coercion attrs onfield.enum. - Nested-object prompt expansion (FR-012) —
render()expands nested objects and arrays in prompt templates. - Plain-Fastify mount in
@metaobjectsdev/runtime-tsreaches contract parity with the Drizzle-Fastify mount (withCount,invalid_sort→ 400).
- Renamed
recover→extractacross the public surface (extractLenienttier,extract/module) — generatedrecover()and therecover-conformancecorpus are renamed accordingly; consumers calling the priorrecoverAPI must migrate toextract. - Runtime return types are now native in-process types (ADR-0019) —
ObjectManager/runtime queries return native types (field.decimal→ string in TS) with wire canonicalization applied only at the serialization boundary, not inside the query path. field.decimalnow maps tostringwith a fractional-ms read-path normalization in generated TS code.@maxCharsover-budget now throws (previously truncated in some ports), aligning render behavior across all ports.@readOnlyandorigin.*-derived fields are excluded from generatedInsertSchema/UpdateSchema.
migrate-tsSQL handling: quote/comment/dollar-quote-aware statement splitter for hand-authored migrations,normalizeCheckExprfolds PG= ANY(ARRAY[..])back toIN, cast-strip preserves::in regex patterns, and CHECK constraints emit as inline create-time only (no duplicate/non-idempotent diff).migrate-tsrunner: no client leak when advisory-lock acquire throws, correctapplied_atcast, view-body change detection, and down-from-snapshot restores index/FK shape changes plus the table's own indexes/FKs.validator.length @maxemits a length CHECK rather than a VARCHAR cap.- Enum payload mirror-string is cast to the typed union under the strict mapper (tsc-strict clean); extractor scalar-array mapping and required-ness predicate corrected.
@defaultonfield.enumis validated against declared members, and per-type@defaultcoercibility is validated at load (cross-port parity).
- Java / C# / Python / Kotlin reached parity on the runtime object model, metadata-driven
extract,<Name>Extractorcodegen,template.outputrender helper, typed-enum payloads, and the FR-011/013/014/015/016 + SP-A decimal/temporal-fidelity work, all gated by shared conformance corpora. - New cross-port conformance gates added: generated-API-over-HTTP fan-out for all five ports (SP-B/SP-F, found 10+ real deployment bugs), validator-parity corpus (SP-C), runtime return-type contract (SP-D), CLI parity (SP-E —
dotnet meta, Pythonmetaobjectsconsole-script, Javameta:verify), and the R13 output-prompt-fragment corpus.
codegen-ts: standalone read-only view-entities — a projection can now map a view's columns directly withoutextends-ing a writable entity, enabling views over non-entity-backed tables and views that expose a deliberately narrowed/safe column set (join-backed view-DDL generation still requiresextends; standalone views supply their own SQL).
- OMDB (Java runtime) correctness fixes not affecting the npm packages: standard ANSI
OFFSET/FETCHpaging for MSSQL/Oracle, app-side UUID primary-key minting, atomic bulk-create fallback under caller-managed transactions, and read/write codec unification.
- FR-010 tolerant output parsing & prompt rendering in
@metaobjectsdev/render— a forgivingrecover()engine (fence-stripping, root-span location, no-hang JSON/XML readers with truncated/unclosed-tag recovery, enum-alias and numeric-range coercion, returningRecoveryResult/RecoverMap) plus anOutputFormatRendereremittingguide/inline/exampleOnlyprompt fragments. - FR-010 codegen in
@metaobjectsdev/codegen-ts— per-template.outputgenerators emit<Template>.prompt.tswithrender<Name>Format()and a typed tolerantrecover()alongsideparse()for json/xml outputs. - FR-010 metamodel attributes accepted by the loader:
@promptStyle,@example,@instruction,@enumAlias,@enumDoc. emitAbstractShapesconfig knob (defaulttrue) onMetaobjectsGenConfig— whenfalse, abstract entities emit no file at all (cross-port parity).
- Abstract entities never emit instantiable artifacts.
@isAbstractis now honored universally across codegen — abstract entities render shape-only (type-only interface + Zod, never a Drizzle table), and write-form, CRUD hooks, and filter allowlists are skipped for both abstracts and projections. - R6 float/double wire fidelity —
field.floatnow emits SQLREAL(single precision), distinct fromfield.double(DOUBLE);migrate-tscollapsesreal4→realfor SQLite to avoid a phantom float diff, and both round-trip as wire-normalized strings. - Cross-port: conformance parity advanced across all five ports (TS/Java/Kotlin/C#/Python) for FR-010 recover/render and R6 float, plus a Spring Boot 3 OMDB autoconfiguration starter on the JVM side.
EntityGrid(@metaobjectsdev/tanstack) accepts id-less projection rows — relaxed the row-type bound from{ id?: number | string }toobjectso generated grids over composite-identity view models type-check.- Cross-package, cross-file
extendsresolution — a concrete-first entity extending a base declared in a different file-default package (e.g.acme::common::BaseTenantEntity) no longer fails super-resolution after the merge into the shared root. - CLI
ParseErrors are no longer masked, surfacing actionable loader errors to consumers.
-
Three-way merge overwrite policy.
decideAndWrite()switched from marker-based (clobber if@generatedis present, refuse otherwise — the rc.11-era strategy that silently lost hand-edits) to three-way merge against a canonical snapshot stored under.metaobjects/.gen-state/. Hand-edits in generated files now survive regen automatically (the spike 002 "HARD" case); same-line edits surface as standard git-conflict markers (the "CONFLICT" case). The@generatedmarker becomes informational, no longer load-bearing.Restated in adopter terms:
- Easy case (you add a comment): clean merge integrates it
- Hard case (you tweak a generated value): your edit survives
- Conflict case (both sides edit the same line): standard
<<<<<<</|||||||/=======/>>>>>>>markers — resolve like any git conflict; rerunmeta gento advance the snapshot - First-time-on-existing-file: write-if-different baseline (no merge,
no clobber).
meta gen --baseline=freshopts into "overwrite from fresh and re-baseline"
Add
.metaobjects/.gen-state/to your.gitignore.meta initscaffolding handles this automatically. Integrity is sha-256 hashed at.gen-state/.hashes.json; tampered snapshots fall back to first-time semantics with a warning.
templateGenerator()stock generator — a factory that walksMetaRoot→ renders shared Mustache templates via the existing@metaobjectsdev/renderengine → emits files in any format (Markdown / HTML / JSON / YAML / text). Establishes the framework line: code → hand-coded generators (ts-poet, idiomatic per-port); documents → templateGenerator (shared Mustache templates, port-agnostic).docsFile()refactored to usetemplateGenerator(). Markdown structure now lives incodegen-ts/templates/docs/entity-page.md.mustache; adopters can override by placing same-named templates in their project'stemplates/directory. Net: ~85 LOC + a template file replaces ~250 LOC of hand-coded string emit. Conformance fixturedocs-file-basic/expected/Author.mdstays byte-identical.EntityDocDataexported as a public-API contract. Template authors consuming the data dict get TypeScript type-checking. Versioning policy spelled out in the newdocs/features/codegen-data-shapes.md.
- The marker-based
decideAndWrite()path. The<!-- @generated -->HTML-comment marker that rc.11 added to docsFile output is retained as human-readable annotation, but the policy no longer checks for it.
docsFile()emits the@generatedmarker in an HTML comment ahead of the H1 so the overwrite-policy treats subsequentmeta genruns as refreshes rather than refusing to clobber. rc.10 emitted markdown without the marker, which meant a secondgenpass refused to overwrite the<Entity>.mdfiles. Comment-based markers stay invisible in rendered Markdown (GitHub / VS Code / mdBook all strip HTML comments on render) but are present in the raw source the policy inspects.
docsFile()stock generator — emits per-entity Markdown documentation (<Entity>.md) next to each generated entity file. Documents the storage schema, identity/relationships, validation, template cross-references, and generated-code surface for bothobject.entityandobject.value. Adopters can aggregate the per-entity files into docs sites, OpenAPI descriptions, or contributor guides; AI agents have a canonical entity-shape reference. Markdown output is port-agnostic; C# / Python / Java mirrors are tracked as follow-up cross-port work.
routesFileHono()stock generator — emits Hono route registration (register<Entity>Routes(app, { db })) for every writable entity, cross-port-API-contract-conformant with the existing FastifyroutesFile(). Lets Cloudflare-Workers / Hono-server consumers codegen the CRUD-5 endpoints they previously hand-wrote. New helperparseHonoFilterParamsships in@metaobjectsdev/runtime-ts/hono(parallel to the existing drizzle-fastify export).
- Java: generic required-attr enforcement. Pre-rc.8, Java required-attr
validation was per-subtype (an explicit block per subtype that wanted it).
rc.8 adds a generic pass mirroring TS / C# / Python: any node whose schema
declares
required: trueattrs that are absent on the loaded node firesERR_MISSING_REQUIRED_ATTR. The previously-explicit R1 (prompt) and R1b (toolcall) blocks in ValidationPhase collapse into the generic pass. Closes a latent contract gap surfaced during the rc.7 cross-porttemplate.toolcallrollout.
- Hardcoded type-count guards in TS / C# tests are now derived from
the schema constants. Previously
expect(allTypes).toHaveLength(70)(TS) /Core_provider_registers_exactly_70_types(C#) bumped manually on every new subtype; now they assert each base type's subtype list directly, catching drift only where it matters (in the relevant subtype family rather than a global integer).
template.toolcallreaches Java + C# + Python cores — the TS port shipped the subtype in rc.5/rc.6; this release brings the other three ports to parity per ADR-0011. Same vendor-agnostic attrs (@toolNamerequired,@payloadRefrequired, plus governance@owner/@since). Same "no@textRefrequirement" — toolcalls have no renderable body. Kotlin inherits the Java port. The provider-extension conformance fixtures (which moved totemplate.briefingin rc.5) continue to gate the provider-extension contract cross-port; the new core subtype gets its own coverage in each port's unit tests.registry.extend()on PythonTypeRegistry(@metaobjectsdev/metadataPython equivalent) — closes the cross-port parity gap surfaced during rc.3 implementation. Same signature semantics as the TS and C# versions: raisesERR_PROVIDER_ATTR_CONFLICTon duplicate attr;ERR_UNKNOWN_SUBTYPEif the target (type, subType) isn't registered.
- No TS source changes vs rc.6; the version bump keeps the rc.N marker aligned across the four-port release surface.
- rc.5 declared
@descriptionas a per-subtype attr ontemplate.toolcall, which conflicted with the@descriptioncommon-attr thatdocProvideradds to every type — surfacing as"Common attr 'description' conflicts with per-type attr on template.toolcall"at load time. rc.5 was therefore unusable for any consumer with template.toolcall metadata. rc.6 removes the duplicate declaration; tool descriptions surfaced to the LLM read the same@descriptioncommon attr that doc-gen uses. No consumer-facing API shift beyond the bug fix.
-
template.toolcallis now a core MO subtype (@metaobjectsdev/metadata) per ADR-0011. Three vendor-agnostic attrs:@toolName(required),@payloadRef(required, points at the output value-object),@description(optional, surfaced to the LLM for tool selection). Plus the governance attrs@owner/@since.Critically:
template.toolcalldoes NOT inheritgenericAttrsthe waytemplate.promptandtemplate.outputdo. No@textRefrequirement — a tool-call has no renderable text body; the body IS the structured output schema resolved via@payloadRef. This is the design rationale for toolcall being its own subtype rather thantemplate.output + @toolName.Vendor wire details (Anthropic's retry-with-reminder, OpenAI's function- calling envelope, MCP's tool definitions, etc.) are NOT in core. Consumers add vendor specifics via
registry.extend(TYPE_TEMPLATE, "toolcall", { attributes: [...] })— same patterndbProvideruses forsource.rdb.Cross-port rollout: TS ships in rc.5; Java / C# / Python in a follow-up. Kotlin inherits the Java port.
-
Conformance fixtures
provider-extension-new-subtype-successandprovider-extension-missing-provider-failsswap their test-only provider fromexample-template-toolcall(now meaningless — toolcall is core) toexample-template-briefing(a hypothetical briefing template, clearly fictional). The fixtures still demonstrateregistry.registerof a new subtype, just using a name that doesn't collide with the new core subtype. TS / C# / Python adapter providers and fixture inputs/expected files updated to match. -
template-constants.tsdesign comment refreshed to acknowledge three template subtypes (prompt / output / toolcall) and document each one's attr-schema basis. Internal-only — no consumer-facing change beyond the ADR + the new exports (TEMPLATE_SUBTYPE_TOOLCALL,TEMPLATE_ATTR_TOOL_NAME,TEMPLATE_ATTR_DESCRIPTION).
- rc.3 was packed with stale
dist/— the CLI'smeta gen/meta verify/meta migrate/meta prompt-snapshotcommands did not actually threadconfig.providersthrough toloadMemoryon npm, even though the source had the change. Same forloadMemory'sprovidersoption support in@metaobjectsdev/sdk. rc.4 ships with a fresh build so the providers API is actually live for consumers. - Side-effect of the fixture refactor investigation: the docs
extending-with-providers.md§ "When to add a subtype vs. an attr" gained two real-world escalation triggers (existing subtype's required attrs don't apply; load-time error detection requires subtype since@-attrsfollow open policy).
No API change vs. rc.3 — only the published artifacts now match the documented behavior.
-
Consumer-supplied providers via
loadMemory({ providers })(@metaobjectsdev/sdk,@metaobjectsdev/codegen-ts,@metaobjectsdev/cli) — the SDK'sloadMemory(repoRoot, opts?)now accepts aproviders?: readonly MetaDataTypeProvider[]option. Consumers (and the codegen config) can register additional metamodel subtypes/attrs without forking the loader.- Defaults stay back-compatible: the bundle composed is
[...coreProviders, forgeTypesProvider, ...(opts.providers ?? [])].forgeTypesProvideris now a first-classMetaDataTypeProvider(id"metaobjects-forge", depends on"metaobjects-core-types"); the legacyregisterForgeTypes()is a thin back-compat wrapper. - Advanced opt-out:
loadMemory(root, { providers: [...], replaceDefaults: true })skips the default bundle entirely; the caller owns the full provider set. - Codegen config:
MetaobjectsGenConfig.providers?lets a project'smetaobjects.config.tsdeclare its providers once. The CLI'sgen/verify/migrate/prompt-snapshotcommands all read the config and threadconfig.providersintoloadMemory— no silent skipping, no per-command divergence. - Stable error codes: composition surfaces
ERR_PROVIDER_DUPLICATE_ID,ERR_PROVIDER_MISSING_DEPENDENCY,ERR_PROVIDER_DEPENDENCY_CYCLEviacomposeRegistry. The contract is identical across Java, TS, C#, and Python.
- Defaults stay back-compatible: the bundle composed is
-
Cross-port parity (TS / C# / Python; Java deferred). Java already has SPI auto-discovery for type providers; a programmatic
compose()factory parallel to TScomposeRegistryis deferred to a follow-up.- C#: the runtime API entry is
MetaDataLoader.FromDirectory(dir, registry), which already takes a custom registry;Provider. ComposeRegistry(providers)is the supported composition surface. NewProviderExtensionTests(6 cases) assert the cross-port contract end-to-end. - Python:
MetaDataLoader.from_directory(dir, providers=...)already accepts a provider list; the conformance adapter now discoversproviders.jsonper fixture (parity with C#). Newtests/unit/test_provider_extension.py(5 cases) mirrors the TS test suite.
- C#: the runtime API entry is
-
5 conformance fixtures under
fixtures/conformance/exercising the contract cross-port:provider-extension-new-subtype-success(positive: a test-onlyexample-template-toolcallprovider registerstemplate.toolcall),provider-extension-missing-provider-fails(ERR_UNKNOWN_SUBTYPE),provider-extension-dependency-cycle(ERR_PROVIDER_DEPENDENCY_CYCLE),provider-extension-missing-dependency(ERR_PROVIDER_MISSING_DEPENDENCY), andprovider-extension-duplicate-id(ERR_PROVIDER_DUPLICATE_ID). Each fixture'sproviders.jsonis the public seam — explicitprovidersdeclarations bypass any ambient discovery, so the fixture's declared set is exactly the set the loader composes.
entityFile({ allowlists: false })opt-in flag (@metaobjectsdev/codegen-ts) — Worker/Lambda consumers can disable the Fastify-flavored<Entity>FilterAllowlist+<Entity>SortAllowlistemission. Generated entity files then carry no@metaobjectsdev/runtime-ts/drizzle-fastifyimports at all andruntime-tscan be omitted from the consumer's deps entirely. The client-side<Entity>Filtertype is still emitted (zero runtime-ts dependency). Default remainstruefor back-compat; consumers usingroutesFile()should leave the default. Closes the long-term recommendation from the 0.7.0-rc.1 Worker-consumer friction batch (commit bd0bcb8).- Loader error envelope + source-on-node (
@metaobjectsdev/metadata) — per ADR-0009, everyMetaDatanode now carries asource: ErrorSourceprovenance field ({ format: "json", files: [...], jsonPath: "..." }for loaded nodes;{ format: "code" }for programmatically constructed).ParseErrornow conforms to the cross-portLoaderErrorschema: requiredcode, requiredmessage, requiredsourceenvelope. NewLoadResult.warnings: LoaderWarning[]channel (legacy parser/validator strings are wrapped at the loader boundary asWARN_LEGACYenvelopes; future overlay-merge detection in FR5c will be the first feature to emit native envelope-shaped warnings). New public exports from@metaobjectsdev/metadata:ErrorSource,LoaderError,LoaderWarning,NodeContext,Contributortypes, plus thecodeSource()helper. Foundation for FR5b (YAML positions), FR5c (multi-file merge attribution), FR5d (reference-resolution errors), FR5e (database-source errors). outputParser()stock generator in@metaobjectsdev/codegen-ts/generators— for every declaredtemplate.output, emits a typed Zod parser file with a dual-API surface (parseXxx(text)throws,safeParseXxx(text)returns Result). Field-type → Zod-type mapping covers all scalars, arrays, and nestedfield.objectwith@objectRef. The emitted file is self-contained (no cross-file payload import) and exports a<TemplateName>Datatype-alias derived viaz.infer; consumers who also wirepromptRender()can use the payload-VO interface fromprompts.tsinterchangeably (structurally identical). Wire it intometaobjects.config.ts:generators: [..., outputParser()].meta verifyextension fortemplate.outputdrift — the build-time drift gate now checks both subtypes. Output diagnostics carry(output)prefix; prompt diagnostics gain(prompt)prefix for symmetry.- Conformance fixture
template-output-simple— shared cross-language corpus gainsinput/meta.npc.json,expected.json, andexpected/NpcResponseOutput.output.tsbyte-exact codegen artifact. TS conformance runner verifiesoutputParser()'s output matches. source.rdbdiscriminator filters entity-file emission (@metaobjectsdev/codegen-ts) — metaobjects without a writablesource.rdbchild now route through a streamlined value-only path emitting only the structural TS interface +<Name>InsertSchemaZod schema. The Drizzle table,InferSelectModel/InferInsertModelaliases,<Entity>FilterAllowlist/<Entity>SortAllowlist,<Entity>Filtertype, and$entity/$table/$pathconstants object are skipped entirely. Pure metadata-driven discriminator (type=source, subtype=rdb,MetaSource.isWritable()) — not anobject.valuevsobject.entitytype-ID gate, so the same filter also covers transient / in-memory shapes that declare no source. Closes the "dead generated tables" smell in consumers that model nested response payloads as value objects. Branch slots betweenisProjectionand the existing vanilla-entity path; both pre-existing paths are unchanged. New helperhasWritableRdbSource(entity)from@metaobjectsdev/codegen-ts/source-detect.meta verifylog line format adds(<subtype>)after the template name (e.g.,[npcTurn] (prompt) ERR_*). A pre-FR6 log scraper that matched on the bare[name]prefix needs to update its regex.- BREAKING (codegen-ts): Generated
<Entity>.queries.tsCRUD helpers now accept a Drizzledbinstance as the first parameter of every function (findUserById(db, id),listUsers(db, opts),createUser(db, data),updateUser(db, id, data),deleteUserById(db, id)). The module-levelimport { db } from "<dbImport>"line is no longer emitted; instead, every file declares a dialect-correcttype Db = ...alias at the top. Migration: bump, regen, search-and-replace call sites — see the new wiring-generated-queries.md recipe for the full guide. Background: ADR-0008. Enables Cloudflare Workers / edge consumers to drop their typecheck stubs; enables multi-tenant servers + test-isolateddbsetups.routesFile()is unchanged. - BREAKING (metadata):
ParseErrorconstructor signature changed. Wasnew ParseError(msg, { code?, source?: string, path? }); nownew ParseError(msg, { code, source: ErrorSource }). Direct construction outside the metadata package is rare (loader-internal API), but anyone catching + repackaging aParseErrorreads.sourceas the new envelope type, not a string. Legacyerror.pathis gone — readerror.source.jsonPathinstead. - BREAKING (metadata):
LoadResult.warningsretyped fromstring[]toLoaderWarning[]per ADR-0009. Consumers that inspected warning content viaresult.warnings[i].includes(...)should now readresult.warnings[i].message.includes(...). The publicExportResult.warnings(returned byloadAndExportJson()) keeps itsstring[]shape — extracted via.map((w) => w.message).
See ADR-0010 for the cross-port design.
@metaobjectsdev/clinow pulls@metaobjectsdev/runtime-tstransitively. Generated entity files emitimport type { FilterAllowlist, SortAllowlist } from "@metaobjectsdev/runtime-ts/drizzle-fastify"unconditionally; until now, consumers who installed onlycli(the recommended umbrella) hit unresolved-import errors on the firstmeta gen.clinow declaresruntime-tsas a runtime dependency at the same pinned workspace version. The imports are type-only, so the addition has no Worker/Lambda bundle impact. (Reported from a 0.7.0-rc.1 Worker consumer.) Long-term, an opt-in flag onentityFile({ allowlists: false })will let Workers consumers skip the imports entirely — that's a separate follow-up.meta migrate --dialect d1no longer fails against wrangler's local D1 sandbox.introspectD1was callingSELECT sqlite_version()to populateSnapshotMeta.sqliteVersion, but workerd blocks that function in the local D1 sandbox. The introspector now tries the call once and falls back to a static known-good version ("3.44.0"— matches Cloudflare D1's shipped SQLite) on failure. Remotewrangler d1 executepaths still answer the function and use the live value. (Reported from the same 0.7.0-rc.1 consumer.)field.enumcolumns emit Drizzletext({ enum: [...] as const })(@metaobjectsdev/codegen-ts) — CHECK-constrained enum columns now carry anenumoption on thetext()call, narrowing Drizzle's inferred select-model type from barestringto a literal union (e.g."supports" | "opposes" | ...). Theas constsuffix is what Drizzle's type signature requires to lift the values into the type position. Affects every non-arrayfield.enum; isArray enum columns remaintext({ mode: "json" })(Zod still validates element membership).field.object isArray:true objectRef:RefNameemitstext({ mode: "json" }).$type<RefName[]>()(@metaobjectsdev/codegen-ts) — SQLite JSON columns storing arrays of nested objects now carry a typed element annotation via ts-poetimp()cross-module hoisting (e.g.citations: text("citations", { mode: "json" }).$type<SourceLens[]>()). Sibling fix to the scalar.$type<E[]>()patch from 0.7.0-rc.1; closes the last row-type widening case that forced consumers toas unknown as z.ZodType<>cast the codegen'd<Name>InsertSchemaat the LLM-tool-use boundary.
- Cloudflare D1 dialect for
meta migrate—--dialect d1,meta init --d1,wrangler.tomlbinding resolution,introspectD1via shell-out,renderD1=renderSqlite+ D1-safety post-pass (strip explicit txns, rejectATTACH/VACUUM),writeMigrationD1(Wrangler<seq>_<slug>.sql+.down/sidecar), optional--applyhook. Seedocs/superpowers/specs/2026-05-24-meta-migrate-d1-dialect-design.md. - Projection (
source.dbView) migrations now emit DDL for D1 alongside Postgres/SQLite. - New
renderpackage added to the publish-candidate set (Tier 0); 12 packages now released in lockstep.
Dialectunion extended to include"d1"; existing"sqlite"/"postgres"paths unchanged.MigrateBlockin.metaobjects/config.jsongained an optionald1sub-block (binding,remote,autoApply,wranglerConfigPath).- Generated
deleteXById(...)helpers now use.returning()so the response shape is portable across D1, libsql/Turso, and Postgres (was previously libsql/Turso-specific).
- SQL injection in
introspectD1pragma calls via crafted SQLite identifier names; pragma queries now double-quote-escape identifiers (the Kysely-basedintrospectSqlitepath was already safe via Kysely's parameterization). - Removed dead
parseWranglerExecuteJsonexport fromcli/lib/wrangler.ts. codegen-ts/src/templates/jsdoc.tsnow satisfiesexactOptionalPropertyTypes.
- Pragma identifier injection patched in the D1 introspector; see Fixed.
First public release. 11 publish-candidate packages on latest; cli shipped
as 0.5.1 patch shortly after. Projects D–G shipped end-to-end (typed filter
syntax, source-aware entities + projections, currency, TanStack codegen).
See spec/roadmap.md for the full Projects D–G coverage.