Skip to content

1.0.10

Choose a tag to compare

@dmealing dmealing released this 30 Sep 00:14
· 185 commits to main since this release

npm 1.0.10 (all 14 @metaobjectsdev/* packages in lockstep), PyPI 1.0.10, NuGet 1.0.10 and
Maven Central 8.0.10. metamodelVersion stays 1.0. Gated by a private 1.0.10-rc.5 build of the
release commit on the adopter estate (rc-gate.sh 7/7) and a full local CI run on that commit.

Added

  • Records in a store MetaObjects does not manage keep their wire contract, in every port.
    A sourceless entity is an object.entity with an identity.primary and no source.*:
    a record kept in MongoDB, Cassandra, Neo4j or behind an API. It still gets no table,
    queries, routes or migration (#248). It now gets the create and PATCH schemas and the filter
    allowlist that the adopter's own endpoint needs. Before, it was treated like a value
    object.

    • TypeScript: <E>UpdateSchema, <E>Patch, <E>FilterAllowlist, <E>SortAllowlist and
      <E>Filter beside the interface and <E>InsertSchema.
    • Python: <e>_filter_allowlist.py. <E>Create / <E>Patch were already emitted.
    • Java: <E>Patch and <E>FilterAllowlist beside <E>Dto.
    • Kotlin and C#: <E>FilterAllowlist.

    One predicate per port picks these objects: isSourcelessEntity (TS),
    is_sourceless_entity (Python), RestSurfaceGate.isSourcelessEntity (JVM) and
    InstanceArtifacts.IsSourcelessEntity (C#). Value objects are unchanged, since ADR-0028
    forbids them an identity. Upgrading: run meta gen (or the port's gen). A project
    with such an entity gets the new symbols and files. No existing symbol changes.

  • MySQL as a TypeScript codegen and runtime dialect. Set dialect: "mysql", and the
    generated tier targets drizzle-orm/mysql-core: mysqlTable entities, and queries and
    Fastify/Hono routes that read rows back after a write (MySQL has no RETURNING). The
    runtime's generated routes, and the ObjectManager over kyselyDriver (Kysely
    MysqlDialect) or drizzleDriver (drizzle-orm/mysql2), run on MySQL. MySQL-specific
    behaviour:

    • like stays case-sensitive (ADR-0049) through COLLATE utf8mb4_bin;
    • timestamps use Drizzle's date mode and store the UTC wall clock in DATETIME(3);
    • a field.time column's write schema refuses a UTC offset, which MySQL's TIME rejects
      with ERROR 1292 (a value object's field.time is JSON and keeps the offset);
    • identifiers are backtick-quoted;
    • constraint errors map to 409/400 as on the other dialects.

    MetaObjects does not own a MySQL schema. meta migrate --dialect mysql is refused with that
    explanation, meta gen tells you to update your DDL instead of pointing at migrate, and
    agent/schema.md is skipped. See docs/recipes/mysql.md. Covered against MySQL 8.4 by
    mysql-generated-app.test.ts and mysql-object-manager.test.ts, and the MySQL dialect
    joins the codegen-compile gate.

  • Recipe: MongoDB, Cassandra, Neo4j and other unmanaged stores
    (docs/recipes/document-graph-and-wide-column-stores.md). It shows how to model the records
    and what each port generates. It includes a MongoDB repository generator
    (docs/recipes/generators/typescript/mongo-repository.ts), run end to end against MongoDB 7
    and copied into a fresh project by the document-store-recipe integration test.

Fixed

  • CLI errors no longer print an empty reason. A refused Postgres connection on a dual-stack
    host rejects with an AggregateError whose own message is empty, so meta migrate apply-pending printed apply failed: and nothing else. Every CLI command now prints the
    inner errors instead (connect ECONNREFUSED 127.0.0.1:55440; …), or the error's code or name
    when it carries no message.

  • meta verify counts only requirement gaps a @disposition could settle. The "recorded
    gap(s) with no @disposition" line counted every partial / planned requirement without a
    disposition, including roll-up parents that are partial only because a descendant is. On one
    adopter ledger it read 19 where 7 were actionable. A requirement is now counted only when no
    descendant also carries outstanding work.

  • meta types <subtype> --detail printed every attribute twice. The subtype's block lists
    its @attrs with full descriptions, and each attr then printed again as its own (attr) block
    with the same text. It now prints once, which halves the output (field.string: 9.7KB to
    4.9KB). Asking for one attr (meta types "field.string @maxLength" --detail) still prints its
    block.

  • TypeScript ObjectManager: scalar array fields can be written. A
    field.string isArray (or any scalar array) was type-checked as a single string, so every
    create or update carrying one failed validation (tags: type) on every dialect. Each element
    is now checked, and an element error names its index (tags[1]). The Kysely driver binds a
    scalar array as JSON text on SQLite and MySQL, and a JSON-text array reads back as an array.
    Before, libsql could not bind the array at all and mysql2 expanded it into a value list.
    The value-object branch of the validator also reads the resolving isArray, not the
    own-only flag (ADR-0039).

  • Java: OMDB's MySQLDriver works against a MySQL server. It had never been run
    against one, and it failed on the first insert:

    • @generation: increment keys threw "This should not get called". They are now read
      back with LAST_INSERT_ID().
    • Timestamp and uuid values were bound with JDBC types Connector/J rejects
      ("Unsupported SQL type"). A timestamp is now stored in a DATETIME as the UTC wall
      clock of the instant, whatever the JVM zone. A uuid is stored as text.
    • Identifiers are backtick-quoted, so a column named after a reserved word (rank) works.

    MySqlObjectManagerTest covers insert, read, filtered and paged queries, update and
    delete against MySQL 8.4. MetaObjects still does not own a MySQL schema: write the DDL
    yourself.

Changed

  • Behaviour change in extract, every port: validator.array bounds are enforced. An
    array field's validator.array (@min/@max) now reaches the extract engine. A reply with
    more elements than @max keeps the first @max (a truncate coercion, like a numeric clamp)
    under the default tolerance and is MALFORMED under STRICT; a reply with fewer than @min
    is MALFORMED under every tolerance, so orThrow fails on a @required one and
    malformedRequired() names it. The TypeScript generated parse<Name> / safeParse<Name>
    Zod schema carries the same bounds (z.array(…).min(n).max(m)). Before, the bounds were
    declared and ignored, so code re-checked the count by hand. The extract-conformance corpus
    gains three cases (48 in all), a minItems/maxItems schema key and an optional per-case
    tolerance, run by every port.
  • SQLite: projection views that use origin.collect now open on SQLite 3.35. They used an
    in-aggregate ORDER BY (json_group_array(x ORDER BY …)), which needs 3.44, so a database
    holding one could not be opened by Ubuntu 22.04's SQLite 3.37 or Python 3.10's stdlib. A
    collect column is now built in two steps: the grouped query gathers each element with its
    sort keys, and an outer query sorts them. Element order and empty-set results are
    unchanged, and whole-object collects still return objects. The floor is now 3.35 for tables
    and views alike. Upgrading: the next meta migrate against a SQLite or D1 database with
    such a view replaces the view once.
  • SQLite: a timestamp column that defaults to "now" uses the ISO UTC form generated code
    writes.
    @autoSet and @default: "now" on a field.timestamp emit
    DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')) (2026-09-26T14:08:05.053Z) instead of
    DEFAULT CURRENT_TIMESTAMP (2026-09-26 14:08:05), so database-defaulted rows and
    application-written rows share one spelling that sorts and compares correctly. The generated
    Drizzle schema carries the same default. Upgrading: existing CURRENT_TIMESTAMP columns
    are not rebuilt for this: migrate treats the two as the same default. Rows already stored keep
    their old spelling. Regenerate with meta gen to pick up the Drizzle default.
  • SQLite: a function-call column default is wrapped in parentheses when emitted, as SQLite
    requires (DEFAULT (expr)). Before, an authored expression default such as
    datetime('now') produced a CREATE TABLE that did not parse.
  • meta init's config passes its own typecheck. extStyle was required by the config
    type but absent from the scaffolded metaobjects.config.ts, so
    npx tsc -p tsconfig.codegen.json --noEmit failed with Property 'extStyle' is missing
    on a fresh project. It is optional now and defaults to "js", which every generator
    already assumed.
  • dbImport is demanded only by a generator whose output imports db. The reference
    entity, routes and routes-hono generators (the copies meta eject hands over) copy
    the render context with an object spread, and the demand fired on that read, so
    meta eject entity alone failed with codegen config is missing dbImport in a project
    whose queries take db as a parameter. The runner now checks the emitted output instead,
    which also fixes copies already ejected.
  • A new database is pointed at meta migrate apply-pending. meta init's next steps
    name it, and meta migrate --db <url> --apply against an empty database with committed
    migrations now says to replay them rather than only asking for --slug.
  • meta verify names the prompt file it could not find. ERR_PARTIAL_UNRESOLVED now
    says which file to create (prompts/<ref>.mustache or .txt) and that --prompts moves
    the root, instead of only naming the directory it searched.
  • Agent context: an agent can work out any database, store or framework. The skills
    meta init installs now carry a playbook for a stack no reference generator targets
    (metaobjects-codegen/references/any-stack.md): MySQL is a dialect; MongoDB, DynamoDB,
    Cassandra, Neo4j or an API are sourceless entities plus a repository generator you own;
    other HTTP and UI frameworks are mounted, retargeted or generated. TypeScript projects also
    get the MySQL guide and the tested MongoDB repository generator. The always-on context and
    the meta gen --list header point at it. In cold runs, a fresh agent built a MongoDB app
    this way with no hints.
  • Agent context: the skills are split into a short SKILL.md plus topic files. The
    authoring skill went from 75KB to 40KB, codegen from 49KB to 23KB, and the TypeScript codegen
    reference from 30KB to 17KB. Each SKILL.md opens with a table of its references/ files and
    when to open each. A reference named <language>-<topic>.md installs only for that language.
    Run meta init --refresh-docs to take the new files.
  • Agent context: hand-built LLM prompts (#389). The always-on context installed by
    meta init gains a rule: once a project declares any template.prompt, every LLM prompt is
    one, replies are read through a response model, and changing a hand-built prompt means
    migrating it first. The metaobjects-prompts skill now triggers on code that builds text for
    an LLM or parses its reply, and has a migration procedure; the metaobjects-audit prompt
    census covers every module. Refresh with meta init --refresh-docs.