# Migration Guide: monSQLize v1/v2 → v3

This guide starts with the v2.0.6 to v3.0.0 upgrade path, then retains the
v1-to-v2 compatibility notes for older applications. Review every tightened
contract that intersects your application before changing the installed major.

## Upgrade from v3.0.0 to v3.1.0

monSQLize v3.1 consumes schema-dsl v3 without installing schema-dsl's legacy
String prototype extensions. Applications that need those extensions must opt
in through `schema-dsl/compat` or `schema-dsl/register-string`; monSQLize itself
continues to use the isolated `schema-dsl/runtime` entry and does not require
them.

Successful full-document Model validation now persists schema-dsl's normalized
`data` for `insertOne`, `insertMany`, `insertBatch`, `replaceOne`,
`findOneAndReplace`, and hydrated document `save()`. Coerced values and schema
defaults therefore reach MongoDB. The order is model defaults, before hook,
schema normalization, timestamps, version, then driver write. Replacement
writes do not apply model defaults, and `save()` has no new before-hook stage.

Validation failures still throw `VALIDATION_ERROR`, and public errors remain
`{ field, message }`; `field` is mapped from schema-dsl's canonical `path`.
Update operators and pipelines do not expose a complete final document and are
not included in normalized-data persistence. If an application relied on
schema-dsl coercion being discarded before v3.1, review stored value types in a
staging environment before upgrading.

## Upgrade from v2.0.6 to v3.0.0

### 1. Run the release checks before changing production

Use Node.js 18 or newer, install v3 in a staging branch, and run your own
Model, cache, sync, transaction, and data rollout tests. monSQLize v3 pins
`schema-dsl@2.1.6` and keeps the isolated `schema-dsl/runtime` integration.

### 2. Review bounded query and optimistic-locking behavior

- `find()` defaults to 500 documents. Configure an explicit safe limit when a
  service previously depended on an unbounded result.
- Versioned single-document Model writes use optimistic concurrency control and
  may throw `WRITE_CONFLICT` for stale state.
- Versioned `updateMany()` defaults to `counter`; select `strict` or `off` when
  that better matches the existing write contract.
- `updateBatch({ upsert: true })` is rejected. Use `upsertOne()` or MongoDB's
  native `updateMany(..., { upsert: true })` according to the required semantics.

### 3. Review sync, index, and production guards

- Resume-token persistence failures stop Change Stream sync unless a legacy
  best-effort option is selected explicitly.
- `autoIndex: true` now preflights with `listIndexes()`, creates only missing
  indexes, and reports conflicts. Keep production index rollout reviewed.
- `production`, `prod`, and `live` all activate production-like safety guards.

### 4. Use the new bounded data-task path for release data

For selected release data and declared indexes, use
`dataTasks.preview(job)` followed by `dataTasks.apply(job, { approval })`.
Production jobs require a durable affected-scope backup directory. This feature
does not replace schema migrations, full database export/import, or disaster
recovery. See the data-tasks and production-rollout guides before first use.

## Historical v1 to v2 notes

The sections below document intentional behavioural and contract changes between
monSQLize v1.x and v2.x. Most v2 type-level differences from v1 have been
softened (optional fields, alias keys, permissive callbacks) so that v1
fixtures and call sites continue to type-check unchanged. The items below
are the **deliberately tightened** semantics that v2 keeps for safety; v1
callers in these spots may need code changes.

## Tightened semantics retained in v2

### 1. `Model.findOne` returns `T | null` (not `T | undefined`)

- v1 returned `undefined` when no document matched.
- v2 returns `null`, matching the underlying MongoDB driver and removing
  the `undefined` vs `null` ambiguity.
- **Migration**: replace `result === undefined` with `result === null`,
  or use `!result` which works for both.

### 2. `ConnectionPoolManager#selectPool()` returns a typed handle

- v1 returned an opaque object whose shape was not declared.
- v2 returns a `PoolHandle` interface with documented fields
  (`name`, `client`, etc.).
- **Migration**: no source change is required if you only consume fields
  v2 declares. If your v1 code accessed undeclared fields, declare them
  through module augmentation or assert through `as unknown as ...`.

### 3. Cache event payloads use `unknown` rather than `any`

- The cache `publish()` / subscriber callback payload type is now
  `unknown` instead of `any`, forcing call sites to narrow before use.
- **Migration**: add a runtime check or a type assertion at the
  consumption point. The wire shape is unchanged.

## Type-level compatibility additions (no source change required)

For reference, v2 type files were extended with the following alias /
optional fields so that v1 fixtures and runtime payloads continue to
type-check; you do **not** need to migrate code for these:

- `BatchRetryRecord`: added optional `attempts` (alias of `attempt`) and
  `success?: boolean`. The runtime now also emits both fields on retry
  records to match v1.
- `FindPageOptions`: added top-level `comment?: string` shortcut that
  mirrors `options.comment`.
- `SagaContext.get<T = any>`: default generic relaxed from `unknown` to
  `any` to match v1 ergonomics.
- `SagaResult.sagaId` / `sagaName`: marked optional alongside
  `executionId`.
- `SagaStats`: aliased v2-only fields
  (`successfulExecutions` / `failedExecutions` / `compensatedExecutions`)
  are optional; `successCount` / `failureCount` / `compensationCount`
  remain the v1 primary fields.
- `MongoSession.transaction?.state`: re-exposed for v1 callers reading
  `session.transaction?.state`.
- `SyncConfig.transform`: accepts both v1 single-arg
  `(document) => ...` and v2 `(document, event) => ...` forms.
- `Lock.released`: marked `readonly` to match v1.
- `PoolStats`: counter fields are optional to accept v1 fixture shapes.

If a previously-undocumented field still does not type-check, please
file an issue with the v1 reproduction so the public types can be
extended.
