Skip to content

v0.0.2-alpha.42

Pre-release
Pre-release

Choose a tag to compare

@mobeenabdullah mobeenabdullah released this 28 Jul 09:30
4fde146

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

What's changed

Patch Changes

  • #332 80febb5 Thanks @mobeenabdullah! - Block props now go through the field system: a block declares its editable props with the same field types a collection uses, and their values are validated by the same server-side pass entries get. Binding a data field to a block prop is derived from the prop type, so every compatible prop offers it without the block opting in.

  • #343 a614d3d Thanks @mobeenabdullah! - Collections and singles can now hold a page built from blocks. Add a field with blocks({ name: "content" }), optionally naming which registered blocks it accepts, and the whole page document is stored in one column and typed for you when you generate types.

  • #346 4f39297 Thanks @mobeenabdullah! - Field-level read rules now reach related rows inside components. A component's relationship fields copy whole rows out of the collection they point at, and neither the parent entity's field list nor the component's describes that collection's fields, so a field you protected there was returned inside the populated component to any caller that could read the parent. Reading a collection entry or a Single now judges those related rows by the rules of the collection they come from, for the caller making the request.

    This completes the read side of the redaction added for direct relationships. Write-side callers that assemble a payload without a caller are unchanged, so a mutation response still returns what it did before.

  • #347 81204af Thanks @mobeenabdullah! - Outbox event recording is now endpoint-gated. A content write records a webhook event only when the install has at least one enabled webhook endpoint, or when the new webhooks.audit option is turned on. Installs with no webhooks configured no longer pay an event-table insert and a full-document serialization on every write.

    Recording resumes immediately when an endpoint is created in the same process, and within about 30 seconds for one created in another process. A few events may still be recorded just after the last endpoint is removed; retention prunes them.

  • #344 2dec172 Thanks @mobeenabdullah! - The scaffolded blog template now uses tag-based ISR: publishing or editing content in the admin refreshes the affected pages on the next request, with no rebuild and no 60-second timer. Content-template scaffolds (blog) also install nextly and @nextlyhq/* from the alpha dist-tag so they always get the nextly/runtime cache helpers the pages use.

  • #339 9ab5f19 Thanks @mobeenabdullah! - Content pages can now use tag-based ISR: cache a read with cachedFind and tag it with nextlyTags from nextly/runtime, and every content change (create, update, publish, unpublish, delete, or slug rename) busts exactly those tags so the page regenerates on the next visit — no rebuild, no force-dynamic. Revalidation turns on automatically wherever you mount the admin route (createDynamicHandlers). A per-operation disableRevalidate flag lets a bulk import, seed, or CLI write skip it. See the new "ISR and caching" guide, including the rule for keying a per-user read so it cannot leak across callers.

  • #337 6f19e60 Thanks @mobeenabdullah! - Collections and singles created in the Schema Builder now carry their cache-revalidation setting. A new "Cache revalidation" switch on the Advanced tab (on by default) lets you opt a collection or single out of busting cache tags on write, and the setting round-trips through boot, HMR, db:sync, and migrate:create the same way code-first revalidate config does. Existing databases pick up the new registry column when you run nextly migrate (boot warns until it is run).

  • #336 d0a45d5 Thanks @mobeenabdullah! - Collections and singles can now opt out of webhook recording with webhooks: false (or { record: false }). Form submissions opt out by default, so visitor IP address, user agent, and submission content are no longer recorded to the webhook outbox or delivered to endpoints subscribed to entry.created or *. Existing installs: submission events recorded before this release remain in the outbox and can be pruned manually; no data is deleted automatically.

  • #351 b44b1a3 Thanks @mobeenabdullah! - @nextlyhq/plugin-seo now generates a sitemap of your published content and serves it at a public HTTP route under Nextly's dynamic handler (in a scaffolded app, /admin/api/plugins/@nextlyhq/plugin-seo/sitemap.xml). It lists one URL per published entry across the collections you configure, reflects publishes and edits on the next request, and leaves out drafts and any page marked noindex. Configure the site origin with baseUrl and per-entry paths with urlFor, or disable the route with sitemap: false.

  • #348 c0b3796 Thanks @mobeenabdullah! - Read rules that narrow by a filter are now applied in full. A stored read rule can return a filter describing which rows the caller may see, and only part of it was being applied: the first field's equals value. A rule naming two fields filtered by one of them, a rule using any other operator applied nothing at all, and a rule whose value was legitimately falsy — 0, false, an empty string — also applied nothing. In each of those cases the read returned rows the rule was written to exclude, and the matching count reported them too.

    Filters now go through the same translation your own where clauses use, so every field and every supported operator binds. Owner-only rules are unaffected: a single non-empty owner id was the one shape the old path handled correctly, which is why this went unnoticed.

    A filter is applied only if all of it can be applied, and access filters are held to a narrower shape than the where clauses you write yourself. A filter may name columns on the collection (or its localized fields) and compare them with any supported operator, including the shorthand { field: value } form. Logical and/or groups, dotted paths like author.name, and empty in/not_in lists are refused rather than approximated, because each of those translates to something narrower than the rule states — or, in the dotted case, to a comparison against a different column.

    A refused filter is reported as forbidden, and the matching count refuses identically. If you need a shape that is currently refused, the read fails closed instead of quietly returning more than the rule allows.

  • #335 8512d5d Thanks @mobeenabdullah! - Field-level read rules now apply to related rows. Populating a relationship copies the whole related row into the parent entry, and a field's access.read was only ever evaluated against the collection being read, never against the collection on the other end of the relationship. A field you protected on one collection was therefore returned in full to anyone who reached it through a relationship from another, at any depth. Passwords and system columns were already stripped there; this closes the same gap for the rules you write yourself.

    Each related row is now judged by its own collection's rules, for the caller making the request, so a relationship cannot return more than a direct read of that row would. Trusted server-side reads that pass overrideAccess are unaffected, and secrets are still stripped for every caller regardless.

    If you relied on reading a protected field indirectly through a relationship, that field will now be absent: read it as the collection that owns it, with a caller its rule admits.

  • #333 ba3e8f4 Thanks @mobeenabdullah! - Collection read rules now apply over the REST API. Listing, fetching and counting entries previously ignored who was asking, so a collection configured with an owner-only or role-based read rule still returned every row to any caller who could reach the endpoint — the rule only ever held on writes and inside the Direct API. Reads now evaluate the caller against the collection's stored rules, with owner-only scoping applied in the database query so pagination and totals stay correct, and a count can no longer describe rows the caller is not allowed to see.

    Role-based read rules are evaluated against the caller's resolved roles, and a super-admin keeps the bypass they already have everywhere else. A scoped API key is judged on its own read grant rather than on the permissions of the account that issued it, so a read-only key issued by an administrator is no longer treated as that administrator's full session.

    If you configured a read rule expecting it to be enforced, this closes that gap. If instead something in your app depended on reads returning unfiltered data, it will now see only the rows its rule allows: check any integration that reads with a user session or API key against a collection whose read rule is not public.

  • #356 a24c17e Thanks @mobeenabdullah! - REST reads now default to published-only. A list, get, or count request to a Draft/Published collection or single with no ?status= returns only published entries; pass ?status=all or ?status=draft to include drafts (subject to your read access rules). Previously these reads defaulted to returning every status, which could expose drafts to any caller.

    An invalid ?status= value (for example a typo like ?status=pubished) is now rejected with a 400 instead of being silently treated as "all", so a malformed filter can never widen a read. Trusted server-side Direct API calls are unchanged (they still see every status). The admin panel already requests every status, so editors continue to see their drafts.

  • #338 1687ff1 Thanks @mobeenabdullah! - Read rules on Singles now apply over the REST API. A Single's stored read rule was enforced on every update and inside the Direct API, but reading the document over HTTP skipped it entirely, so a Single you restricted to a role was still returned in full to any caller who could reach the endpoint. Reads now evaluate the caller against the rule you configured, and a Single's related rows are redacted by the field rules of the collection they come from. Relationships reached through an embedded component are not yet covered.

    A scoped API key is judged on its own read grant rather than on the permissions of the account that issued it, and super-admins keep the bypass they have everywhere else.

    An owner-only read is judged against the document itself, since a Single has no list query to fold an ownership filter into.

    custom read rules on Singles are not enforced by this change. A custom function may return a query constraint, which a list read compiles into SQL; applying that to a single document would mean re-implementing the filter grammar, so it is left as it behaves today rather than partly applied. public, authenticated, role-based and owner-only read rules are all enforced. That rule reports "allowed" for any authenticated caller and hands back the predicate a list query would have filtered by, which a Single has no list to apply, so the predicate is checked against the row instead.

    The standalone nextly/api/singles-detail GET route is deliberately public and does not authenticate. A Single with no read rule stays publicly readable there, exactly as before. A Single you restrict is no longer served by that route at all, including to callers the rule would admit, because the route has no caller to evaluate. Read restricted Singles through the authenticated API instead.

    If you configured a read rule on a Single expecting it to be enforced, this closes that gap. If something in your app read a restricted Single over HTTP and depended on getting it, that call will now be denied: give the caller a role the rule admits, or read it through the Direct API, which is trusted by default.

  • #353 48f82a8 Thanks @mobeenabdullah! - nextly now exports buildMetadata from nextly/runtime: it maps a content entry's SEO field group (from @nextlyhq/plugin-seo) to a Next.js Metadata object, so a page's generateMetadata becomes a single call instead of a hand-written mapping. It sets the title, description, canonical, OpenGraph, Twitter card, robots (from noindex), and hreflang alternates, with per-call fallbacks for blank fields. The next dependency is type-only, so importing it never forces next at load.

  • #349 9cab18c Thanks @mobeenabdullah! - Add the first-party @nextlyhq/plugin-seo package. Register it in your config to add an SEO field group (title, description, OG image, canonical, noindex) to the collections you name. It is opt-in and framework-agnostic (no Next.js dependency), so it is safe in headless and admin-only projects.

    The plugin SDK now also re-exports the field-authoring factories (text, textarea, checkbox, upload, group) and the FieldConfig type, so plugin authors get the whole authoring surface from @nextlyhq/plugin-sdk.

  • #340 3d48019 Thanks @mobeenabdullah! - Fixed a dev-mode gap where setting a collection or single to webhooks: false did not take effect if the same config reload also hit a schema error (for example a transient database blip during introspection, or a change awaiting confirmation). The recording opt-out is now applied up front, so a newly private entity stops recording immediately even when the rest of the reload is deferred; re-enabling recording still waits for a clean schema sync.

  • #341 a98cdcf Thanks @mobeenabdullah! - Fixed webhook-outbox retention not running after a Single update that opts out of both recording (webhooks: false) and cache revalidation (revalidate: { disable: true }) when a post-commit hook then fails. The Single write result now carries an explicit committed-write signal, matching the collection path, so the write-path cleanup runs for every durable write on installs without a scheduled webhook drain.

  • #342 f0b4fc3 Thanks @mobeenabdullah! - A Single that opts out of webhook recording (webhooks: false) no longer assembles its webhook payload on update. Previously the previous/next event documents were built (reading every component subtree) before the opt-out was checked, so a scalar update to an opted-out Single still performed webhook-only component reads and could fail on a missing or stale component table. The opt-out is now resolved before any payload assembly.

  • #345 38d50d0 Thanks @mobeenabdullah! - Collection status webhook events now fire. Publishing an entry delivers entry.published (and the generic entry.status_changed); unpublishing delivers entry.unpublished (and entry.status_changed); any other status change delivers entry.status_changed. A create-as-published delivers entry.created + entry.published. Per-locale status changes on a localized collection are tagged with their locale. Every status event carries an explicit statusChange: { from, to }. Only Draft/Published collections emit these, and collections that opt out of recording (webhooks: false) emit none. Previously these event types were subscribable in the admin UI but never fired.

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