Member Junction
    Preparing search index...

    This class is responsible for generating database level objects like views, stored procedures, permissions, etc. You can sub-class this class to create your own SQL generation logic or implement support for other databases. The base class implements support for SQL Server. In future versions of MJ, we will break out an abstract base class that has the skeleton of the logic and then the SQL Server version will be a sub-class of that abstract base class and other databases will be sub-classes of the abstract base class as well.

    Index

    Constructors

    Properties

    Accessors

    Methods

    analyzeCascadeDeleteDependencies applyLatePhaseFixups applyPermissions ApplyPermissions buildCascadeDeleteDependencies buildCustomBaseViewRefreshSQL buildPrimaryKeyComponents buildUpdateCursorParameters checkBaseViewChangedInDB checkBaseViewColumnsChangedPG CleanupStaleRelationships createCombinedEntitySQLFiles CreateCombinedEntitySQLFiles createEntityFieldsInsertString createEntityFieldsParamString createEntityFieldsUpdateString deleteGeneratedEntityFiles DeleteGeneratedEntityFiles detectRecursiveForeignKeys emitCustomBaseViewRefreshes executeEntityInPhases ExecuteEntityInPhases extractViewSelectBody forceLogForRoutine formatDefaultValue generateAllEntitiesSQLFileHeader GenerateAllEntitiesSQLFileHeader generateAllRootIDFunctions generateAlternateBaseViewsPermissionsSQL generateAndExecuteEntitySQLToSeparateFiles GenerateAndExecuteEntitySQLToSeparateFiles generateAndExecuteSingleEntitySQLToSeparateFiles GenerateAndExecuteSingleEntitySQLToSeparateFiles generateBaseView GenerateBaseView generateBaseViewJoins generateBaseViewPieces GenerateBaseViewPieces generateBaseViewRelatedFieldsString GenerateBaseViewRelatedFieldsString generateCascadeCursorOperation generateCascadeDeletes generateCustomBaseViewRefreshAndPermissions generateDefaultAlias generateEmbeddedGeoJoins generateEmbeddedGeoSelect generateEntityFullTextSearchSQL GenerateEntityFullTextSearchSQL generateEntitySQL generateFullTextSearchFunctionPermissions generateHierarchyFieldSelects generateHierarchyJoins generateIndexesForForeignKeys GenerateIndexesForForeignKeys generateIndexForSoftPrimaryKey GenerateIndexForSoftPrimaryKey generateNativeGeoFields generateParentEntityFieldSelects generateParentEntityJoins generateRecordGeoCodeFields generateRecursiveCTEJoins generateRootFieldSelects generateRootIDFunction generateRootIDJoins generateSingleCascadeOperation generateSingleEntitySQLFileHeader GenerateSingleEntitySQLFileHeader generateSingleEntitySQLToSeparateFiles GenerateSingleEntitySQLToSeparateFiles generateSPCreate generateSPDelete generateSPPermissions generateSPUpdate generateUpdatedAtTrigger generateViewPermissions getAlternateBaseViewRefreshStatements getCustomBaseViewEntitiesForLiveRefresh getEntitiesRequiringCascadeDeleteRegeneration getEntityPermissionFileNames GetEntityPermissionFileNames getEntityPrimaryKeyIndexName GetEntityPrimaryKeyIndexName getHierarchyFunctionsForField getIsNameFieldForSingleEntity getModifiedCustomBaseViewEntities getModifiedEntitiesWithUpdateAPI getOrderedAllAlternateBaseViews getOrderedAlternateBaseViews getSPName GetSPName hasAliasCollision hasNativeGeoFields hasValidAlternateViewIdentifiers isFieldInCompositeUniqueConstraint isRoutineDefinitionDrifted isRoutineMissing isRoutineSaveReturnModeChanged loadExistingRoutines logLayeredOuterViewRefresh logSQLForNewOrModifiedEntity manageSQLScriptsAndExecution ManageSQLScriptsAndExecution markEntitiesForCascadeDeleteRegeneration orderEntitiesByDependencies prepareFieldSecurityRunContext RefreshCustomBaseViewsLive reportDriftedRoutines reportEntityBatchProgress runCustomSQLScripts RunCustomSQLScripts shouldLogCustomBaseViewRefresh stripID validateAlternateBaseViews validateAlternateBaseViewsForEntities validateFieldExists WarnOnOutOfScopeISAChildDrift writeFileIfChanged

    Constructors

    Properties

    _dbProvider: CodeGenDatabaseProvider = ...

    The database-specific code generation provider, resolved from MJGlobal.ClassFactory against the configured dbPlatform. Each provider (SQLServerCodeGenProvider, PostgreSQLCodeGenProvider, …) registers itself with @RegisterClass(CodeGenDatabaseProvider, '<platform>') — the canonical DatabasePlatform value as its key — so this single line picks up the right one without dialect-specific branching. Subclasses can override via @RegisterClass(... priority) to plug in customized codegen behavior, same hook every other MJ class uses.

    _sqlUtilityObject: SQLUtilityBase = ...
    cascadeDeleteDependencies: Map<string, Set<string>> = ...

    Tracks cascade delete dependencies between entities. Key: Entity ID whose update/delete SP is called by other entities' delete SPs Value: Set of Entity IDs that have CascadeDeletes=true and call this entity's update/delete SP

    driftedRoutines: Set<string> = ...

    schema.routine keys of CRUD routines whose live definition differed from the body generated this run, so they were force-logged and re-executed. Reported once after generation.

    entitiesNeedingDeleteSPRegeneration: Set<string> = ...

    Tracks entities that need their delete stored procedures regenerated due to cascade dependencies

    entitiesQualifiedForForcedRegeneration: string[] = []

    Array of entity names that qualify for forced regeneration based on the whereClause filter

    existingRoutineDefinitions: Map<string, string> | null = null

    Map of schema.routine (lowercased) to its live OBJECT_DEFINITION text on SQL Server, used by isRoutineSaveReturnModeChanged to detect when Entity.Configuration.SaveReturnMode changed via mj sync push without a physical table/column change, and by isRoutineDefinitionDrifted to detect any other difference from the generated body.

    existingRoutines: Set<string> | null = null

    schema.routine keys (lowercased) present in the database at the start of this SQL-generation pass. Used to force-log CREATE PROC for routines that were dropped out-of-band (BigSchemaDemo recreate, a failed prior batch) while the entity itself is not in newEntityList/modifiedEntityList.

    filterEntitiesQualifiedForRegeneration: boolean = false

    Flag indicating whether to filter entities for forced regeneration based on entityWhereClause

    layeredEntitiesWithInlineRefresh: Set<string> = ...

    Names of LAYERED entities whose outer-view refresh was written right after their inner view (logLayeredOuterViewRefresh). STEP 2(f) does not log their refresh a second time.

    layeredEntitiesWithLoggedInnerView: Set<string> = ...

    Names of LAYERED entities whose generated inner view was written to the migration log this run. Their application-owned outer view must be refreshed in the same migration, even when the entity is on neither the new nor the modified list (the inner view changed on its own).

    orderedEntitiesForDeleteSPRegeneration: string[] = []

    Ordered list of entity IDs for delete SP regeneration (dependency order)

    Accessors

    Methods

    • Builds a complete map of cascade delete dependencies by analyzing all entities. This method populates the cascadeDeleteDependencies map without generating SQL.

      Parameters

      Returns Promise<void>

    • Builds a combined SQL string of sp_refreshview statements for the given entities.

      For a LAYERED entity the outer refresh is guarded on the application-owned BaseView existing, matching generateCustomBaseViewRefreshAndPermissions. On the first pass after layering is enabled the outer view cannot exist yet — it selects from the inner view that same pass creates — so an unguarded refresh here fails the documented setup procedure.

      The inner view is refreshed first, before the outer. That ordering is defensive rather than load-bearing: every entity reaching this method is in the modified/new list (see getModifiedCustomBaseViewEntities), and logSQLForNewOrModifiedEntity forces a modified entity's base view DDL into the same migration regardless of whether its text changed — so the inner view is already dropped and recreated in an earlier step, which resets its cached column list more thoroughly than a refresh would. The ordering is kept anyway so this path cannot become wrong if that coupling is ever loosened.

      Parameters

      Returns string

    • Parameters

      Returns {
          fetchInto: string;
          selectFields: string;
          spParams: string;
          varDeclarations: string;
      }

    • Parameters

      Returns {
          allParams: string;
          declarations: string;
          fetchInto: string;
          selectFields: string;
      }

    • Fetches the current base view definition from SQL Server and compares it against newly generated SQL. If the view body has changed (e.g., a virtual field was added to a joined table), the entity is logged and executed. Unlike adding to modifiedEntityList (which would cause all SPs, permissions, indexes, etc. to be logged too), this only logs the base view SQL itself.

      Comparison extracts the SELECT body from both the DB definition (CREATE VIEW ... AS ) and the generated SQL, normalizing whitespace for a fair comparison.

      Parameters

      Returns Promise<boolean>

      true if the base view changed, false otherwise

    • PostgreSQL base-view change detection by COLUMN SET rather than text.

      pg_get_viewdef() re-qualifies columns and normalizes whitespace/casing, so the SELECT-body text comparison used on SQL Server never byte-matches on PG and false-flags nearly every view. The base view exposes exactly the entity's fields, so comparing the view's live column set (information_schema.columns) against the entity's expected field set is deterministic, immune to that reformatting, and still catches the cases that matter:

      • a stale view missing a field that metadata now declares (the v5.46 OpenApp outage: a migration added the column + EntityField but never re-baked the view), and
      • a missing view entirely (empty column set → force-recreate; the documented self-heal for a view that a prior run dropped but failed to recreate). A pure Sequence renumber does not change the column set, so it correctly does NOT flag.

      NOT caught (by design — a tradeoff vs. SQL Server's text comparison): a view-BODY change that leaves the exposed column NAMES identical — e.g. a RelatedEntityNameFieldMap re-pointed to a different source column under the same output alias, or a codegen view-template change that rewrites the SELECT without renaming columns. Those alter the SELECT text but not the column set, so they must be forced through the metadata-driven modifiedEntityList (or an explicit EntitiesRequiringViewRegen entry), not this column-set check.

      Parameters

      Returns Promise<boolean>

      true if the view's column set differs from the entity's fields (or the view is missing)

    • Finds stale One To Many EntityRelationship rows among in-scope entities and, when the cleanupStaleRelationships config option is set, deletes them; otherwise each one is logged as a warning (#2071). Refreshes the global metadata only when rows were deleted, so the late-phase regeneration that follows sees the current relationships.

      Parameters

      Returns Promise<boolean>

      false when the cleanup failed

    • Detects self-referential foreign keys in an entity (e.g., ParentTaskID pointing back to Task table) Returns array of field info objects representing recursive relationships

      A column that IS the entity's own primary key is excluded. It can carry a self-referencing RelatedEntityID — FK auto-detection matches a <prefix>_key-style column to its own table and does not exclude that table's own key — but "my parent is me" is not a hierarchy. The generated recursive CTE then joins the key to itself and selects the same column three times, which the database rejects outright: 15 entities on one Postgres instance failed to compile with column reference "tea_key" is ambiguous. Nothing downstream can recover from that, and a real hierarchy always parents on a DIFFERENT column, so excluding the key loses nothing.

      Parameters

      Returns EntityFieldInfo[]

    • Emits sp_refreshview statements into the migration log file for modified custom base view entities (BaseViewGenerated === false). This ensures that when the migration runs on other environments, custom base views affected by schema changes get refreshed automatically. Only entities in the modified or new entity lists are included.

      Returns void

    • Extracts the SELECT body from a CREATE VIEW statement, normalizing whitespace for comparison. Handles both DB-stored definitions (CREATE VIEW ... AS SELECT ...) and generated SQL (which includes DROP VIEW, comment headers, GO, and permissions after the view body).

      Parameters

      • viewSQL: string

      Returns string

    • Whether a CRUD routine's SQL must be logged and executed regardless of the new/modified lists. generatedSQL, when given, is the body generated this run and enables the live-definition drift check (isRoutineDefinitionDrifted).

      Parameters

      • entity: EntityInfo
      • routineName: string
      • baseViewChanged: boolean
      • OptionalgeneratedSQL: string

      Returns boolean

    • Formats a default value for use in SQL, handling special cases like SQL functions

      Parameters

      • defaultValue: string

        The default value from the database metadata

      • needsQuotes: boolean

        Whether the field type typically needs quotes

      Returns string

      Properly formatted default value for SQL

    • Generates guarded permission statements for each registered alternate base view on entity:

      • Usable (Active / Deprecated) views receive GenerateViewPermissionsForTarget (REVOKE + GRANT SELECT + FLS DENY SELECT).
      • Disabled views receive GenerateDisabledViewRevokePermissionsForTarget (REVOKE SELECT) so disabling a view revokes database read grants.

      Parameters

      Returns string

    • Parameters

      • options: {
            batchSize?: number;
            directory: string;
            enableSQLLoggingForNewOrModifiedEntities?: boolean;
            entities: EntityInfo[];
            onlyPermissions: boolean;
            pool: CodeGenConnection;
            skipExecution: boolean;
            willRegenerate?: Set<string>;
            writeFiles: boolean;
        }
        • OptionalbatchSize?: number
        • directory: string
        • OptionalenableSQLLoggingForNewOrModifiedEntities?: boolean
        • entities: EntityInfo[]
        • onlyPermissions: boolean
        • pool: CodeGenConnection
        • skipExecution: boolean
        • OptionalwillRegenerate?: Set<string>

          Optional UNION of will-regenerate keys across ALL batches in the same codegen run. When provided, takes precedence over the per-batch set.

        • writeFiles: boolean

      Returns Promise<{ Files: string[]; Success: boolean }>

    • This function will handle the process of creating all of the generated objects like base view, stored procedures, etc. for all entities in the entities array. It will generate the SQL for each entity and then execute it.

      Parameters

      • options: {
            batchSize?: number;
            directory: string;
            enableSQLLoggingForNewOrModifiedEntities?: boolean;
            entities: EntityInfo[];
            onlyPermissions: boolean;
            pool: CodeGenConnection;
            progressLabel?: string;
            skipExecution: boolean;
            willRegenerate?: Set<string>;
            writeFiles: boolean;
        }
        • OptionalbatchSize?: number
        • directory: string
        • OptionalenableSQLLoggingForNewOrModifiedEntities?: boolean
        • entities: EntityInfo[]
        • onlyPermissions: boolean
        • pool: CodeGenConnection
        • OptionalprogressLabel?: string

          Phase name for the per-batch progress line, e.g. 'Generating SQL'. Defaults from onlyPermissions. See reportEntityBatchProgress for why the line exists.

        • skipExecution: boolean
        • OptionalwillRegenerate?: Set<string>

          Optional UNION of will-regenerate keys across ALL batches in the same codegen run. When provided, takes precedence over the per-batch set.

        • writeFiles: boolean

      Returns Promise<{ Files: string[]; Success: boolean }>

    • Parameters

      • options: {
            directory: string;
            enableSQLLoggingForNewOrModifiedEntities?: boolean;
            entity: EntityInfo;
            onlyPermissions: boolean;
            pool: CodeGenConnection;
            skipExecution: boolean;
            willRegenerate?: Set<string>;
            writeFiles: boolean;
        }
        • directory: string
        • OptionalenableSQLLoggingForNewOrModifiedEntities?: boolean
        • entity: EntityInfo
        • onlyPermissions: boolean
        • pool: CodeGenConnection
        • skipExecution: boolean
        • OptionalwillRegenerate?: Set<string>

          Names of other entities regenerating in this run — passed to the PG phased executor's willRegenerate so stale captured dependents that will be rebuilt anyway aren't restored.

        • writeFiles: boolean

      Returns Promise<{ Files: string[]; Success: boolean }>

    • Parameters

      • options: {
            directory: string;
            enableSQLLoggingForNewOrModifiedEntities?: boolean;
            entity: EntityInfo;
            onlyPermissions: boolean;
            pool: CodeGenConnection;
            skipExecution: boolean;
            willRegenerate?: Set<string>;
            writeFiles: boolean;
        }
        • directory: string
        • OptionalenableSQLLoggingForNewOrModifiedEntities?: boolean
        • entity: EntityInfo
        • onlyPermissions: boolean
        • pool: CodeGenConnection
        • skipExecution: boolean
        • OptionalwillRegenerate?: Set<string>

          Names of other entities regenerating in this run — passed to the PG phased executor's willRegenerate so stale captured dependents that will be rebuilt anyway aren't restored.

        • writeFiles: boolean

      Returns Promise<{ Files: string[]; Success: boolean }>

    • Same as generateBaseView, but returns the view DDL and the GRANT/permissions DDL as separate strings rather than concatenating. Used by phased per-entity execution paths (e.g. PG's phased executor) where the view must run under 42P16 fallback recovery but the permissions must run afterwards in a later phase — concatenating them would prevent the executor from detecting where a failure actually occurred.

      Parameters

      Returns Promise<{ viewPermSQL: string; viewSQL: string }>

    • Emits the refresh + permission statements for an entity whose BaseView CodeGen does NOT write — fully custom, or the application-owned outer half of a layered entity.

      The refresh runs before the grants so SQL Server picks up schema changes (new columns from migrations) before permissions are applied; developers no longer add it by hand.

      Two things are specific to LAYERED entities:

      • Inner before outer. The custom view selects g.* from the generated one, and a view caches its column list. Refreshing the outer against a stale inner re-caches the OLD columns, so a newly added column stays missing — indistinguishable from never having been added at all, which is the exact failure layering exists to eliminate.
      • The outer view may not exist yet. On the first pass after layering is enabled it cannot exist: it selects from the inner view that this very pass creates. Unguarded, the refresh and the GRANT both fail — and the step they fail on is the documented setup procedure, so there would be no way to adopt the feature at all. Guarding makes the bootstrap pass skip them and leaves every later pass identical to the unguarded form.

      Parameters

      • entity: EntityInfo
      • permissionsHeader: string
      • permissionsBody: string

      Returns string

    • Bubble lat/lng from EmbeddedRecord peers as __mj_Latitude_{FK}. Geocode lives on the Address (or other source); the parent only displays it.

      Parameters

      Returns { joins: string; select: string }

    • Generates the composite index covering the entity's SOFT primary key. Empty for every entity with a real PRIMARY KEY constraint, which is nearly all of them.

      Kept separate from generateIndexesForForeignKeys and gated by its own setting: a soft-PK index serves MJ's own per-record existence check on the create path, which is a different concern from indexing foreign keys for joins and filters, and an operator's opinion about one is not an opinion about the other.

      Parameters

      Returns string

    • Generate SELECT aliases for native lat/lng fields → __mj_Latitude / __mj_Longitude. Virtual fields are excluded — see hasNativeGeoFields for the rationale.

      Parameters

      • entityFields: EntityFieldInfo[]
      • classNameFirstChar: string
      • qi: (name: string) => string

      Returns string

    • Generates SELECT field expressions for IS-A parent entity columns. Walks the ParentID chain upward, joining to each parent's base table, and includes the columns physically on each parent table (InheritedParentColumns). Returns a string starting with ',\n' if there are parent fields, or '' if none.

      Parameters

      Returns string

    • Generates INNER JOIN clauses for IS-A parent entity base tables. Chains joins from child -> parent -> grandparent using PK-to-PK conditions. Each parent is joined via its base table (not view) to avoid view dependency ordering issues.

      Parameters

      Returns string

    • Generate SELECT aliases from the RecordGeoCode LEFT JOIN → __mj_Latitude / __mj_Longitude.

      Parameters

      • qi: (name: string) => string

      Returns string

    • Parameters

      • options: {
            directory: string;
            enableSQLLoggingForNewOrModifiedEntities?: boolean;
            entity: EntityInfo;
            onlyPermissions: boolean;
            pool: CodeGenConnection;
            skipExecution: boolean;
            writeFiles: boolean;
        }

      Returns Promise<{ files: string[]; permissionsSQL: string; sql: string }>

    • Parameters

      • options: {
            directory: string;
            enableSQLLoggingForNewOrModifiedEntities?: boolean;
            entity: EntityInfo;
            onlyPermissions: boolean;
            pool: CodeGenConnection;
            skipExecution: boolean;
            writeFiles: boolean;
        }

      Returns Promise<{ files: string[]; permissionsSQL: string; sql: string }>

    • Builds guarded refresh/rebind statements for each registered alternate base view on entity (including Disabled views so Active children of a Disabled parent and re-enabled views never hold stale column metadata) in topological ParentID order.

      Parameters

      Returns string[]

    • Identifies entities that need their delete stored procedures regenerated due to cascade dependencies on entities that had schema changes.

      Parameters

      Returns Promise<Set<string>>

    • Helper to get the canonical hierarchy function definitions and metadata for an entity and recursive FK field.

      Parameters

      Returns { description: string; fieldName: string; functionName: string; sql: string }[]

    • Parameters

      • entityName: string

      Returns { nameField: string; nameFieldIsComputed: boolean; nameFieldIsVirtual: boolean }

    • Gets entities that had schema changes from the ManageMetadataBase tracking. Returns a map of entity names to their IDs for entities that had update-affecting changes.

      Parameters

      Returns Promise<Map<string, string>>

    • Check if an entity has native latitude/longitude fields marked with GeoLatitude/GeoLongitude ExtendedType. If both exist, the view should alias them directly instead of joining to RecordGeoCode.

      MUST exclude virtual fields. The view-introspection pass creates virtual EntityField rows for the __mj_Latitude/__mj_Longitude SELECT aliases the JOIN path emits, then auto-assigns ExtendedType=GeoLatitude/Longitude by name-pattern. Without this filter the next view regen flips to the native path and tries to SELECT e.__mj_Longitude from the base table — the column doesn't exist on the table, only on the view itself, so view creation fails with "Invalid column name '__mj_Longitude'". This recurs on any geo-eligible entity whose RecordGeoCode-based view ran once.

      Parameters

      Returns boolean

    • Checks if a field is part of a composite (multi-column) unique constraint. Returns true if the field participates in a unique index with 2+ columns. This is used to determine if a nullable FK should use DELETE instead of UPDATE during cascade operations, since setting to NULL could violate uniqueness.

      Parameters

      Returns Promise<boolean>

    • Returns true when the live definition of the entity's spCreate / spUpdate / spDelete differs from generatedSQL, the body CodeGen generated for it this run, after both are normalized by the provider (comments, header, DROP batch, GO and whitespace removed).

      This catches drift that never reaches the new/modified entity lists: a column default or type synced by R__RefreshMetadata before CodeGen ran (#5636), or a cascade target whose column a migration dropped together with its EntityField row (#3343). Returns false when the live definitions were not loaded (PostgreSQL, or the catalog query failed), when the routine is missing (handled by isRoutineMissing), or when either side cannot be normalized.

      Parameters

      • entity: EntityInfo
      • routineName: string
      • generatedSQL: string

      Returns boolean

    • Returns true when spCreate or spUpdate in the database was generated under a different SaveReturnMode ('full' vs 'datestamps_only') than the entity's current metadata. This ensures that when Entity.Configuration.SaveReturnMode is updated via mj sync push, the subsequent mj codegen run automatically logs and executes the updated stored procedures.

      Parameters

      Returns boolean

    • Snapshot every CRUD routine currently in the database (one query) so generate-and-log can force-emit CREATE PROC for objects that vanished without the entity landing on newEntityList/modifiedEntityList, or whose return-row mode (SaveReturnMode) changed via mj sync push.

      Parameters

      Returns Promise<void>

    • Writes the layered entity's view refresh (buildCustomBaseViewRefreshSQL: inner view, then the guarded outer view, its rebind and alternate views) into the migration log AND the temp batch STEP 2(e) executes, right after the inner view and before the entity's CRUD routines (#4927). The application-owned outer view selects g.*, and SQL Server freezes that column list when the view is created; when the inner view's columns change, the routines that read the outer view fail to compile until it is refreshed. STEP 2(f) refreshes the outer view too, but only after the batch has run, which is too late for the routines in it.

      Called only when the inner view was logged, whether or not the entity is on the new/modified lists. No-op on platforms that rebind views themselves (PostgreSQL's phased executor).

      Parameters

      Returns void

    • Writes generated SQL to the migration log (and the temp batch executed on SQL Server) when the entity is new/modified, a force-regeneration rule matches, or forceLog is set. Returns whether the SQL was logged.

      Parameters

      • entity: EntityInfo
      • sql: string
      • description: string
      • logSql: boolean = false
      • forceLog: boolean = false

      Returns boolean

    • Orders entities by their cascade delete dependencies using topological sort. Ensures that if Entity A's delete SP calls Entity B's update/delete SP, then Entity B is regenerated before Entity A.

      Parameters

      • entities: EntityInfo[]

        All entities for name lookup

      • entityIdsToOrder: Set<string>

        Set of entity IDs that need ordering

      Returns string[]

      Array of entity IDs in dependency order

    • Builds the once-per-run FieldSecurityRunContext and hands it to the dialect provider (whose permission emitters are synchronous string builders and cannot query). Three inputs, all read here:

      1. Managed roles — MJ: Roles metadata rows with a non-blank SQLName, EXCLUDING the standard UI/Developer/Integration roles: by decision D1a, field-security DENYs target custom (DBA-created) roles only, so standard roles never enter RoleSQLNameByID and Deny rows against them stay app-tier-enforced.
      2. Service-protected roles — roles any protected principal is a member of (sys.database_role_members). Protected principals default to the known service logins (MJ_Connect, MJ_Connect_Dev) plus the configured CodeGen login; a DENY to such a role would strip the column from the service login itself.
      3. Catalog permission state — every sys.database_permissions entry (object- and column-level) granted to a managed role, keyed by <schema>.<object>. This powers the wipe-and-reassert reconciliation (decision D7) that finally makes permission REMOVAL propagate: the historical model was assert-only, so a deleted EntityPermission / Deny row left its GRANT/DENY in the database until the view happened to be regenerated.

      SQL Server only (PostgreSQL emits no DB-tier field security, decision D2). Any failure degrades to a null context — emitters fall back to the historical grants-only output — with a prominent warning, never a failed run.

      Parameters

      Returns Promise<void>

    • Executes the custom base view refresh (buildCustomBaseViewRefreshSQL: inner view, then the guarded outer view, then its rebind and alternate views) against the live database for EVERY included entity whose base view is application-owned, and writes the refresh for the entities whose views changed this run into the migration log.

      Runs right after STEP 2(e), so the outer views expose the columns the regenerated inner views now have before STEP 3 compares EntityField rows against them (#4336). Every entity is attempted; failures are logged together and make the step return false.

      Only the entities on the new/modified lists, plus layered entities whose inner view was logged this run, go into the migration log. Logging every custom view on every run would make a capture non-empty when nothing changed.

      Parameters

      Returns Promise<boolean>

    • Emits the one live progress line for the entity generation/permissions loop: which phase, how far through, which entity is being worked on, and how long the phase has been running.

      WHY. The spinner already re-renders an elapsed timer every 100 ms, so a long run is not silent — but the message it re-renders was set ONCE before the loop ("Generating SQL for 1,200 entities…") and never changed again. A run that has been sitting on entity 3 for four minutes and a run that is on entity 1,180 render identically, which makes a stall and normal progress indistinguishable. N/total is the whole difference.

      Called per BATCH rather than per entity: the batch is the unit of concurrency (its members run under one Promise.all), so a per-entity line would just interleave five entities' names at random. The named entity is the batch's first, which is what the run is demonstrably on.

      UpdateSpinner covers both output modes by construction — in verbose mode it routes to logStatus, so a verbose run gets the same information as a line per batch instead of a re-rendered spinner.

      Parameters

      • phaseLabel: string
      • completed: number
      • total: number
      • currentEntity: EntityInfo | undefined
      • phaseStartMs: number

      Returns void

    • Audits each usable alternate base view on entity for column parity with BaseView, catalog dependency on BaseView / ParentView (D10), and CachePolicy = 'Default' safety (clock functions or external relation dependencies). Logs warnings via LogWarning.

      Parameters

      Returns Promise<void>

    • Warns about IS-A children outside this run's scope whose live view lacks a column their in-scope parent's table now has (#4821). This run cannot regenerate those views: the child belongs to another app, which must run CodeGen or ship a migration. Until it does, the child's metadata (synced by that app's run) and its view disagree, and its saves fail. Never fails the run; a failed catalog read is logged and ignored.

      Parameters

      Returns Promise<OutOfScopeISAChildDrift[]>

    • Writes a generated SQL file to disk only if the content has changed from the existing file. Returns true if the file was written (content changed or file is new), false if unchanged. This avoids false timestamp updates and unnecessary I/O.

      Parameters

      • filePath: string
      • newContent: string

      Returns boolean