@zerotal/orm 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,24 @@ follows the Zerotal monorepo's unified versioning.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [1.6.0] — 2026-08-15
12
+
13
+ ### Fixed
14
+
15
+ - **Auto-`synchronize` was never hard-off in production.** The guard compared
16
+ `APP_ENV` against `"production"`, but `setAppEnv()` replaces that with the runtime
17
+ mode before the app boots — so it read `"web"` and never fired. The only thing standing
18
+ between a production database and boot-time schema sync was the config default in
19
+ `config/database.ts`. It reads `deployEnv()` now, and covers `staging` too.
20
+
21
+ - **`forceState()` did not refuse to run in production.** Its throw exists so a
22
+ state-machine escape hatch cannot be used on live data; the same `APP_ENV` comparison
23
+ meant it never triggered.
24
+
25
+ - **The N+1 detector's own environment gate never matched**, so the detector returned early
26
+ even in development. (The provider-level gate that installs it was fixed in 1.5.0; this is
27
+ the second gate inside the detector itself.)
28
+
11
29
  ## [1.5.0] — 2026-08-15
12
30
 
13
31
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerotal/orm",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "license": "MIT",
5
5
  "maturity": "stable",
6
6
  "private": false,
@@ -30,8 +30,8 @@
30
30
  "typecheck": "tsc --noEmit"
31
31
  },
32
32
  "dependencies": {
33
- "@zerotal/core": "1.5.1",
34
- "@zerotal/validator": "1.5.1"
33
+ "@zerotal/core": "1.6.0",
34
+ "@zerotal/validator": "1.6.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "typescript": "^5.8.0"
@@ -1,4 +1,5 @@
1
1
  import { ZerotalError, FrameworkEvents } from "@zerotal/core";
2
+ import { isProdLike, deployEnv } from "@zerotal/core";
2
3
  import { NPlusOneDetected } from "../events.ts";
3
4
 
4
5
  // ── N+1 Query Detector ────────────────────────────────────────────────────────
@@ -180,13 +181,12 @@ export function trackQuery(
180
181
 
181
182
  // Honour explicit opt-in or auto-enable in local/development only
182
183
  if (!_forced) {
183
- const env = (typeof Bun !== "undefined" ? Bun.env["APP_ENV"] : undefined) ?? "";
184
+ const env = deployEnv();
184
185
  if (env !== "local" && env !== "development" && env !== "dev") return;
185
186
  }
186
187
 
187
188
  // Production always off
188
- const env = (typeof Bun !== "undefined" ? Bun.env["APP_ENV"] : undefined) ?? "";
189
- if (env === "production" || env === "prod") return;
189
+ if (isProdLike(deployEnv())) return;
190
190
 
191
191
  // Only watch SELECTs — the N+1 problem is purely about reads
192
192
  if (!fingerprint.trimStart().toLowerCase().startsWith("select")) return;
@@ -19,6 +19,7 @@
19
19
  // @column() status!: keyof typeof States;
20
20
  // }
21
21
 
22
+ import { isProdLike, deployEnv } from "@zerotal/core";
22
23
  import { StateError } from "../errors/index.ts";
23
24
  import { currentOrmContext } from "./OrmContext.ts";
24
25
  import type { Constructor } from "./mixins.ts";
@@ -278,7 +279,7 @@ export function State<TBase extends Constructor>(Base: TBase) {
278
279
  * @throws {Error} when called with `APP_ENV=production`.
279
280
  */
280
281
  async forceState(state: string): Promise<this> {
281
- if (Bun.env["APP_ENV"] === "production") {
282
+ if (isProdLike(deployEnv())) {
282
283
  throw new Error("forceState() cannot be called in production (APP_ENV=production).");
283
284
  }
284
285
  const field = (this.constructor as unknown as StateModelClass).stateField;
@@ -1,4 +1,5 @@
1
1
  import type { ConcernDescriptor } from "@zerotal/core";
2
+ import { isProdLike, deployEnv } from "@zerotal/core";
2
3
  import type { ConfigManager } from "@zerotal/core/config";
3
4
  import { ModelInspector, columnDbName } from "./ModelInspector.ts";
4
5
  import { SchemaDiffer, type DiffResult } from "./SchemaDiffer.ts";
@@ -147,9 +148,12 @@ export const autoMigrateConcern: ConcernDescriptor = {
147
148
  name: "auto-migrate",
148
149
  order: 100,
149
150
  async run(ctx) {
150
- // Hard-off in production. `ctx.env` is the runtime mode (web/console/…), so the
151
- // deployment name is read from APP_ENV directly.
152
- if (Bun.env.APP_ENV === "production") return;
151
+ // Hard-off on any deployed environment. Both `ctx.env` AND `APP_ENV` hold the
152
+ // runtime mode by now the comment that used to sit here claimed APP_ENV still
153
+ // carried the deployment name, which is exactly the mistake. This guard never
154
+ // fired under `zt serve`, so the only thing between a production database and
155
+ // boot-time schema sync was the config default.
156
+ if (isProdLike(deployEnv())) return;
153
157
  const config = ctx.resolve<ConfigManager>("config");
154
158
  const { enabled, disruptive } = resolveSyncOptions(config?.get("database.synchronize"));
155
159
  if (!enabled) return;