Skip to content

v0.0.2-alpha.48

Pre-release
Pre-release

Choose a tag to compare

@mobeenabdullah mobeenabdullah released this 30 Jul 21:15
1cebbda

Released 17 packages at 0.0.2-alpha.48 in lockstep. Every package below ships at this version.

What's changed

Patch Changes

  • #417 1f81cf3 Thanks @mobeenabdullah! - Read every related row through one code path, so a capability added to relationship population applies everywhere a relationship is populated instead of at whichever call sites were remembered.

  • #423 2f05141 Thanks @mobeenabdullah! - Fixes PostgreSQL index introspection reading indexes from the wrong table. Table names are unique per schema rather than per database, so a table with the same name in another schema had its indexes merged into the one being inspected. That could hide an index that needed creating, or report one that was never there.

    Refuses to run a schema sync while a field group storage migration is in flight. Mid-run some tables carry their old names and some their new ones, and the registry rows pointing at them move one step at a time, so a sync during that window could delete storage it could not account for.

    Also further groundwork for that migration: it can now execute its rename steps and check its own work. A table, its localization companion and the registry row pointing at them move as one step, and on PostgreSQL and SQLite they commit together. MySQL applies a schema change as soon as it is issued, so there the halves land in sequence and a resume completes whatever did not; a reader in that window sees a table as missing rather than reading anything wrong. Every step verifies against the database rather than trusting that it ran, and index survival is checked by name, so an index dropped and replaced by another is caught rather than passing on an unchanged count. Nothing calls the migration itself yet.

  • #424 0538f4f Thanks @mobeenabdullah! - Let a form be updated without resending its fields. Changing a form's name or settings failed with "Form must have at least one field" because an absent fields in the patch was treated as an empty one.

  • #422 a4dad07 Thanks @mobeenabdullah! - Emit a form.submission.created webhook when a form submission is created. The event type was already subscribable in the admin UI but had no producer, so an operator could subscribe to an event that never fired.

    Form submissions carry visitor-entered answers plus ipAddress/userAgent, so the submissions collection suppresses the PII-bearing entry.* events. It now instead emits a curated, metadata-only form.submission.created carrying only which form, when, and the status — never the answers, IP, or user agent. The event is recorded in the same transaction as the submission, so it commits atomically and is never delivered for a rolled-back write.

    This is driven by a new declarative webhooks.emit collection option ({ event, fields }): any PII-bearing collection can replace its default entry.* events with a safe curated one that ships only an allowlisted set of fields (default-deny). The resource kind is derived from the event name.

  • #421 83ed5c9 Thanks @mobeenabdullah! - A string stored in a JSON field no longer fails the write on PostgreSQL and MySQL.

    A field backed by a JSON column accepts any JSON document, a plain string included. A string that is not itself encoded JSON was passed through to the driver as bare text, which PostgreSQL and MySQL reject as invalid JSON, so storing "hello" in a json field failed the write outright. On SQLite, where the column is plain text, it was stored in a form no read could recover as what was written. Such a value is now encoded, so it round-trips as the string it was.

    A string that already parses as JSON is still passed through untouched, so content a previous write encoded is not wrapped a second time.

  • #415 0e18a97 Thanks @mobeenabdullah! - Apply a target collection's read rule when it filters on one of that collection's localized fields, so populating a relationship returns the rows the rule permits instead of withholding every one of them.

  • #405 e1467e8 Thanks @mobeenabdullah! - Plugin-contributed field types are now first-class in generated output, in the manifest, and in the validation a plugin can reuse.

    nextly build emitted nothing at all for a custom field type: the generators test membership of the built-in list, so the field was skipped while its value was still stored, leaving apps with no generated type and no schema entry for it. A type now states its own rendering through PluginFieldType.codegen, receiving the field as declared so a type whose options narrow what it stores can narrow what it generates.

    A type's options can now be held in a pluginOptions container core never reads, so an option may use a name the field schema already declares — options, fields, admin, label — which was previously judged against the core meaning and refused. Options written directly on a field are still read, and a type is handed one flat view of both, so where an option was stored is not something a plugin author tracks.

    A user field whose type a plugin contributed can now be declared from code with pluginUserField(), which was previously impossible without a cast: UserFieldConfig admits only the built-in shapes, and widening it to accept an unknown type token would have made a malformed built-in declaration pass too.

    validateFieldValues is now available from the plugin SDK, marked experimental until a first-party plugin depends on it, so a plugin storing structured content of its own applies the same rules a write does rather than reimplementing required, the per-type checks, and every plugin field type's validate.

    Several correctness fixes ride along, most of them about a value reaching a column its type cannot hold.

    A JSON column stores a JSON document, and true or 42 is a document as much as {} is; only objects were encoded, so a scalar reached the driver as its own type and could not round-trip through a SQLite text column. The four write paths that each carried their own copy of that encoding now share one.

    A value written to a custom user field was never checked against the column its type stores in, and a failed user_ext write is read as the extension table being absent — so the user was created without the value, with extensions disabled for the rest of the process, rather than the write being refused. Such a value is now refused with the field named. A required single field backed by a plugin type was seeded with the wrong kind of value for the same reason, which could stop the single being created at all.

    nextly build now generates types for a project made only of singles, field groups or user fields, where it previously wrote nothing or left a stale file, and narrows PermissionSlug and EventName as generate:types does — a deployment build no longer widens types a development run had narrowed. db:sync --watch now keeps watching such a project too, instead of exiting its watch loop and never re-syncing.

    A key named __proto__ was silently dropped when rebuilding an object from data nobody validates, which lost it from a delivered webhook envelope, from a stored version diff, and from the declaration a plugin validator judges. And db:sync --watch could classify one config's columns with another config's field types, because a reload replaces the process-wide registry while the previous sync is still running; work now resolves against the config it started from, and a reload whose watcher was replaced mid-flight no longer applies its result or leaves its registrations behind.

  • #418 21bb5b3 Thanks @mobeenabdullah! - Stop serving unpublished rows through relationships. A related row is now filtered by Draft/Published exactly as a direct read of it is, so a published document linking to a draft one no longer discloses that draft's contents.

  • #414 45faba9 Thanks @mobeenabdullah! - Emit user.created and user.deleted webhook events. Both were already advertised as subscribable in the admin UI but had no emit sites, so an operator could subscribe to events that never fired. They now record into the transactional outbox atomically with the account change, through a new Drizzle-transaction recorder (recordEventInTx) that lets services running on BaseService.withTransaction — like the auth service — participate in the outbox without the adapter's positional transaction context. Each event is attributed to the authenticated caller and, like the content write paths, offers the fast-path drain and a bounded retention prune after commit — including on self-registration — so delivery and outbox pruning do not wait for the scheduled drain. The delete event reads the removed account's identity inside the delete transaction, so a concurrent update cannot make it report a stale address. The payload is PII-safe: identity only (id, email, name), never the password hash, a token, or role assignments.

Packages

  • @nextlyhq/adapter-drizzle
  • @nextlyhq/adapter-mysql
  • @nextlyhq/adapter-postgres
  • @nextlyhq/adapter-sqlite
  • @nextlyhq/admin
  • @nextlyhq/admin-css
  • @nextlyhq/blocks-engine
  • @nextlyhq/plugin-form-builder
  • @nextlyhq/plugin-page-builder
  • @nextlyhq/plugin-sdk
  • @nextlyhq/plugin-seo
  • @nextlyhq/storage-s3
  • @nextlyhq/storage-uploadthing
  • @nextlyhq/storage-vercel-blob
  • @nextlyhq/ui
  • create-nextly-app
  • nextly