Skip to content

[BUG] PolicyPlugin detection fails under Next.js/Turbopack bundling #2662

@Albatrosso

Description

@Albatrosso

Suggested github repo issue text for the zenstack repo

Title

PolicyPlugin detection via constructor.name breaks under Next.js/Turbopack production bundling

Description

In @zenstackhq/orm@3.6.3, policy mode detection relies on:

plugin.constructor.name === "PolicyPlugin"

This is brittle under production bundling/minification. Bundlers are allowed to rewrite class declarations/exports and do not guarantee that constructor.name is preserved.

In a Next.js 16/Turbopack production bundle, PolicyPlugin is emitted as an anonymous class assigned to an export object, similar to:

exports.PolicyPlugin = class {
  // ...
}

For this pattern, JavaScript does not infer the class name from the property assignment, so:

new PolicyPlugin().constructor.name

can become an empty string, even though the plugin is still installed and active.

This creates an inconsistent state:

  • ORM layer thinks PolicyPlugin is not enabled.
  • PolicyPlugin is still present and intercepts Kysely queries.
  • For delete({ where }), ORM generates DELETE ... RETURNING * instead of policy-safe RETURNING id.
  • The active PolicyPlugin treats the full-returning mutation as needing read-back.
  • Since the row has already been deleted, post-delete read-back returns 0 rows.
  • The mutation fails with:
result is not allowed to be read back

and the transaction is rolled back.

Expected Behavior

PolicyPlugin detection should not rely on constructor.name, because class names are not stable under bundling/minification.

When PolicyPlugin is active, ORM should consistently use policy-safe mutation behavior. In particular, delete({ where }) should not end up as DELETE ... RETURNING * followed by post-delete read-back.

Actual Behavior

In Next.js/Turbopack production runtime:

DELETE ... RETURNING *

is generated, followed by a failed read-back and transaction rollback.

In a direct Node runtime:

DELETE ... RETURNING id

is generated, and the delete succeeds.

Environment

  • @zenstackhq/orm: 3.6.3
  • @zenstackhq/plugin-policy: 3.6.3
  • Next.js: 16.2.4
  • Node.js: 24.11.0
  • Package manager: pnpm
  • Runtime: Next.js production server with Turbopack bundling
  • Database: PostgreSQL

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions