ProtectedconstructorOptional observer notified of every step and run boundary. Set once at process startup (see the RSU progress bridge in MJServer) — a single observer is sufficient because RSU runs are serialized by the pipeline lock, so events can never interleave between runs.
Never awaited, never allowed to fail the pipeline. See RSUPipelineObserver.
Static ReadonlyEXPECTED_The steps that run AFTER the pipeline restarts the API on itself, in order.
They belong to the same run — the setup journey the user is watching is not finished until the sync starts — but they execute in a DIFFERENT PROCESS, so nothing in this class ever runs them. They are declared here because this is where the run's shape is defined: they count towards ExpectedStepTotal, so the determinate progress bar does not reach 100% and then sit there while the connector is still not live, and they are named here so the post-restart consumer and this pipeline cannot drift apart on what the remaining steps are called.
Static ReadonlyOBSERVER_How long the pipeline will wait for an observer to make a restart.pending event durable.
Bounded on purpose. The wait exists because the process is seconds from being killed, but an observer that hangs must not wedge a schema migration — the invariant "progress reporting can never break a migration" outranks the checkpoint. Five seconds is orders of magnitude more than appending a few lines to a local file needs, and far less than the restart path's own timeouts.
The absolute additionalSchemaInfo path RSU writes, stages and hands to CodeGen. Callers that build RSU input (resolvers, SchemaBuilder) read it here so they merge into the same file. See ResolveRSUAdditionalSchemaInfoPath for the resolution order.
Whether RSU is enabled based on ALLOW_RUNTIME_SCHEMA_UPDATE=1.
Whether the local API is out of sync with the git repo.
Whether a pipeline is currently running (concurrency mutex).
When the last pipeline run completed.
Result of the last pipeline run.
When the out-of-sync state was first detected.
StaticInstanceClear the out-of-sync flag (called when CI/CD deployment lands).
Mark a pending work row Completed. Call ONLY after the work actually succeeded.
Optionalprovider: IMetadataProviderOptionalprovider: IMetadataProviderReturns the complete set of schema names this instance will block in DDL.
Always includes __mj (the MemberJunction system schema, never modifiable).
Also includes any additional schemas configured via the RSU_PROTECTED_SCHEMAS
environment variable (comma-separated list, e.g. RSU_PROTECTED_SCHEMAS=dbo,sys).
Callers such as the Database Designer Validator use this for early-exit UX
feedback before DDL is even generated — the same set that
ValidateMigrationSQL() enforces at the SQL level.
A readonly lowercase set of all protected schema names.
The Global Object Store is a place to store global objects that need to be shared across the application. Depending on the execution environment, this could be the window object in a browser, or the global object in a node environment, or something else in other contexts. The key here is that in some cases static variables are not truly shared because it is possible that a given class might have copies of its code in multiple paths in a deployed application. This approach ensures that no matter how many code copies might exist, there is only one instance of the object in question by using the Global Object Store.
Current status snapshot.
Mark the API as running code that is out-of-sync with the git repo.
Dry-run: validate the SQL and return what would happen.
Use ReadPendingWork instead, and mark each item terminal with CompletePendingWork / FailPendingWork once you have processed it.
Retained with its original signature so consumers taking this minor upgrade still
compile. It is NO LONGER FUNCTIONAL: pending work lives in MJ: RSU Pending Works
rather than in .rsu_pending files, and reading it requires a context user this
signature has no way to supply. Returns an empty array — the value that previously
meant "no pending work" — rather than pretending to have drained a queue.
Read every still-Pending work row. Never deletes or mutates — the caller marks each row Completed/Failed once it has actually finished processing it. Rows whose payload fails to parse are reported as Failed so they stop being retried forever.
OptionalstaleAfterMinutes: numberwhen set, rows created more than this many minutes ago are logged as stale so an operator can see work that a previous process abandoned.
Optionalprovider: IMetadataProviderRe-queues failed work for one more attempt, narrowed to what has NOT yet been done.
RSU is a long chain — migrations, CodeGen, a git commit, a compile, a restart — and a failure partway through it is frequently transient: the process was restarted mid-consumption, or a provider call failed once. Marking such an item Failed terminally means the objects it would have mapped are silently never mapped, and the only recovery is for someone to notice and re-apply the connector by hand.
The remainder matters as much as the retry. Re-running an item whole would redo the objects that already succeeded; carrying only the outstanding ones makes each attempt strictly smaller and keeps a single poison object from blocking its siblings forever.
Returns false when the budget is spent, in which case the caller should fail the item terminally — the message it writes is the operator's only signal, so it should name what was left undone.
Optionalprovider: IMetadataProviderExecute the RSU pipeline for a single input. Convenience wrapper around RunPipelineBatch — a batch of 1.
Execute the RSU pipeline for a batch of inputs.
Correctness guarantee: migrations and CodeGen never overlap. All migrations execute under one lock, then CodeGen runs once against the fully-committed schema.
Flow:
Batch equivalent of RunPipelineWithRetry, for the ApplyAll / ApplyAllBatch
install path.
Same transient-step whitelist, same backoff, same safety rule: a retry re-runs every input's migration, so it is only attempted when NOTHING was applied on the failed pass. Once even one migration has committed, the batch is returned as-is rather than replayed.
Run the RSU pipeline with automatic retry for transient failures. Only retries steps that are known to be transient (SQL execution, CodeGen, compilation, restart). Validation and git steps are not retried.
Pipeline input
Maximum number of retries (default: 2, so 3 total attempts)
Base delay for exponential backoff (default: 5000ms)
Set the additionalSchemaInfo file path that CodeGen reads (from mj.config.cjs
additionalSchemaInfo). RSU writes its soft PK/FK config to THIS path so the
subsequent CodeGen run actually finds it. Injected by the server at startup,
mirroring SetCodeGenOutputPaths.
Without injection, RSU falls back to RSU_ADDITIONAL_SCHEMA_INFO_PATH / 'additionalSchemaInfo.json' — a path that need not match CodeGen's configured one. When they diverge, RSU writes soft PKs to a file CodeGen never reads, and every integration table is skipped with "No primary key found". Keeping the two in lockstep is the whole point of this setter.
Set the CodeGen output directory paths (from mj.config.cjs output[].directory). Used by the git step to stage only CodeGen-produced source files.
Set the in-process CodeGen runner. Injected by the server at startup so RSU can run CodeGen without shelling out.
Set a dedicated provider for DDL operations (CREATE TABLE, CREATE SCHEMA, etc.). Should be configured with elevated credentials (e.g. MJ_CodeGen with db_owner). If not set, falls back to the default Metadata.Provider.
Register pending work durably in MJ: RSU Pending Works so it survives the
post-migration restart. Returns the new row's ID.
Unlike the .rsu_pending files this replaces, the row is NOT consumed on read —
it stays Pending until the consumer explicitly calls CompletePendingWork
or FailPendingWork, so a crash mid-consumption leaves visible, resumable work.
OptionalcontextUser: UserInfoOptionalprovider: IMetadataProviderStaticBuildMaps a finished batch onto its terminal run.end event.
result is undefined when a throw escaped the pipeline before a result existed — that case
must still produce a FAILED run.end, otherwise an observer's run would hang in-flight forever.
Public + static so the mapping is unit-testable without a live pipeline.
OptionalErrorMessage?: stringPresent when the run failed — the first failing step's message.
OptionalErrorStep?: stringPresent when the run failed — the name of the step that failed.
StaticCollectThe de-duplicated set of connections a batch touches, in input order.
Resolution order per input: the explicit RSUPipelineInput.CompanyIntegrationID first,
then the CompanyIntegrationID of any RSUPipelineInput.PendingWork entry. The fallback
is not a convenience — it is the compatibility path that gives EVERY production caller today an
identity without changing a single call site, because every one of them registers post-restart
work against a connection. An input with neither contributes nothing, and a batch where no
input has either yields an empty set: that run stays unreadable through the per-connection
endpoints, which is the conservative answer, not a bug.
Public and static so the identity rule is assertable on its own.
StaticExpectedThe full expected step sequence for a batch of itemCount migrations, in execution order —
including the two that run after the restart.
Public and static so the arithmetic is directly assertable without a live pipeline; the number it produces is the denominator every progress UI divides by.
StaticExpectedExpected total steps for a batch of itemCount migrations. See ExpectedSteps.
Protected StaticgetReturns the singleton instance of the class. If the instance does not exist, it is created and stored in the Global Object Store. If className is provided it will be used as part of the key in the Global Object Store, otherwise the actual class name will be used. NOTE: the class name used by default is the lowest level of the object hierarchy, so if you have a class that extends another class, the lowest level class name will be used.
OptionalclassName: string
Singleton pipeline orchestrator for runtime schema changes.
Usage: const rsm = RuntimeSchemaManager.Instance; if (!rsm.IsEnabled) throw new Error('RSU is disabled'); const result = await rsm.RunPipeline(input);