Core
Version 4.0.0
v4.0.0 is a major release that reworks providers and backends into a new plugin system. This will enable providers and backends to be installed directly from NuGet, independently of the CLI, and pin the versions in your CI.
Added
- Plugin Contract. A new set of interfaces in
NSchema.Pluginsthat will allow providers and backends to declare themselves. - BREAKING: Manual lock holds.
IStateLockHandleis no longer disposable; release is explicit, so a caller can take a lock that outlives the current process (e.g. one front-end command acquires, another releases) simply by never releasing it. - Lock time-to-live.
StateLockRequest.TimeToLiverecords an optional expiry on the resultingStateLockInfo.ExpiresUtc. Expiry is surfaced for visibility but never auto-enforced. - BREAKING: Caller-managed locking. The state lock is acquired by the caller through
app.Locks(IStateLockCoordinator.Acquire(operation, skipLock, …)) and held across the operations it guards, rather than each operation taking its own. - Schema-read seams on the application.
app.CurrentSchema(ICurrentSchemaProvider) reads the recorded (offline) or live (online) schema, andapp.PlanFile(IPlanFileWriter) reads and writes saved plan files — exposed as properties alongsideapp.Operations/app.Locks. - BREAKING: Inspect and release the lock via the coordinator.
IStateLockCoordinatornow manages the whole lock lifecycle throughapp.Locks:Peek(ct)reads the currentStateLockInfo?without acquiring it, andRelease(ct)force-releases whatever is held and returns the released lock’s details. - BREAKING: Operation surface. Every operation is reached through
app.Operations(theINSchemaOperationsfacade) with a uniformXArguments→Result<XResult>shape, and each result carries its outcome as data. - BREAKING: Result & diagnostic model. Operations no longer throw to signal expected outcomes or print their own output. They return
Result/Result<T>carrying success/failure plusDiagnostics, and narrate transient progress throughIProgress<OperationProgress>; the caller decides what to render. UseProgressReporter<TProgress>(). Registers theIProgress<OperationProgress>sink that receives an operation’s transient progress narration, replacing the default no-op reporter — a named builder method alongsideUseSqlGenerator.- Atomic file-state writes. The built-in file state store now writes to a temporary sibling file and atomically renames it into place, so a concurrent reader (e.g. a
planreading the recorded state while anapplycaptures new state) never observes a half-written snapshot. - Public diff reader.
DiffReaderis a public, stateless utility (with a shared.Defaultinstance) that reads aDatabaseDiffinto a renderer-neutralDiffDocument.
Changed
- BREAKING: operations live on
app.Operations, notNSchemaApplication.NSchemaApplicationis now a thin facade exposingServices,Operations(INSchemaOperations),Locks(IStateLockCoordinator),CurrentSchema(ICurrentSchemaProvider), andPlanFile(IPlanFileWriter); the per-operation methods (Plan,Apply,Refresh,Validate,Drift,Import,Doctor) moved ontoapp.Operations. - BREAKING:
IStateLockHandleis no longerIAsyncDisposable. Release is explicit viaValueTask Release(CancellationToken), and the handle now exposesStateLockInfo Info(replacingstring LockId). Operation call sites release in afinally. - BREAKING:
IStateLock.ForceUnlockrenamed toIStateLock.Release, now returningValueTask(wasTask<StateLockInfo?>) to matchIStateLockHandle.Release. Whether a release is “forced” is decided by the seam the caller reaches for —IStateLock.Releaseremoves whatever lock is held;IStateLockHandle.Releaseremoves only its own. StateLockInfogains an optionalExpiresUtc.
Removed
- BREAKING:
NoOpStateLockand the no-op lock fallback.IStateLockis now registered only when a state backend supplies one; an operation either takes a real lock or runs without one, rather than acquiring a placeholder. Operations resolveIStateLock?(optional). - BREAKING: The
ForceUnlockoperation.NSchemaApplication.ForceUnlock,IForceUnlockOperation, andForceUnlockArgumentsare gone; force-unlock is a thin caller ofIStateLock.Release()(the CLI does the peek + expected-id check + confirmation itself). - BREAKING: The
Showoperation.NSchemaApplication.Show,IShowOperation, andShowArgumentsare gone. Reading-and-rendering the recorded state, a saved plan, or (new) the live schema is a thin front-end concern, built on the public read seams above rather than a Core operation. - BREAKING: Renderer interfaces and
Use*Rendererbuilder methods.IDiffRenderer,ISchemaRenderer, andISqlPlanRendererare gone, along withUseDiffRenderer<T>(),UseTerraformRenderer(…),UseSchemaRenderer<T>(), andUseSqlPlanRenderer<T>(). The renderers were never consumed by Core and had no swap points; they are now public concrete utilities (see Added) rather than DI-registered services. A consumer wanting a different format writes its own renderer and calls it directly.