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:
is generated, followed by a failed read-back and transaction rollback.
In a direct Node runtime:
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
Suggested github repo issue text for the zenstack repo
Title
PolicyPlugin detection via
constructor.namebreaks under Next.js/Turbopack production bundlingDescription
In
@zenstackhq/orm@3.6.3, policy mode detection relies on:This is brittle under production bundling/minification. Bundlers are allowed to rewrite class declarations/exports and do not guarantee that
constructor.nameis preserved.In a Next.js 16/Turbopack production bundle,
PolicyPluginis emitted as an anonymous class assigned to an export object, similar to:For this pattern, JavaScript does not infer the class name from the property assignment, so:
can become an empty string, even though the plugin is still installed and active.
This creates an inconsistent state:
PolicyPluginis not enabled.PolicyPluginis still present and intercepts Kysely queries.delete({ where }), ORM generatesDELETE ... RETURNING *instead of policy-safeRETURNING id.PolicyPlugintreats the full-returning mutation as needing read-back.0 rows.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
PolicyPluginis active, ORM should consistently use policy-safe mutation behavior. In particular,delete({ where })should not end up asDELETE ... RETURNING *followed by post-delete read-back.Actual Behavior
In Next.js/Turbopack production runtime:
is generated, followed by a failed read-back and transaction rollback.
In a direct Node runtime:
DELETE ... RETURNING idis generated, and the delete succeeds.
Environment
@zenstackhq/orm:3.6.3@zenstackhq/plugin-policy:3.6.316.2.4