Member Junction
    Preparing search index...
    Index

    Constructors

    Properties

    Accessors

    Methods

    addDefaultPermissionsForEntity addDefaultRolesForApplication addEntityToApplicationForSchema addMaterializedQueryEntityPermissions applicationExists applyAdvancedGeneration applyCategoryInfoSettings applyDefaultInViewUpdates applyEntityIcon applyEntityImportance applyEntitySearchConfig applyFieldCategories applyFormLayout applyFullTextSearchUpdates applyLLMFieldDescriptions applyLLMForeignKeys applyLLMPrimaryKeys applyNameFieldUpdates applySchemaPrefixesFromConfig applySearchableFieldUpdates applySearchPredicateUpdates applySmartFieldIdentification applySoftPKFKConfig applyTimeEntityFieldSequenceSQL applyValueListConfig applyVEFieldCategories assessQuerySourceRLSSafety boolLit buildEntityPairRelationshipSQL buildEntityPermissionInsertSQL buildExistingCategorySet buildExternalFieldRemoveSQL buildInsertRelationshipSQL buildISAEntityLookupSQL buildISAValidationSQL buildParentChainContext buildSourceEntityContext checkAndRemoveMetadataForDeletedTables checkDropSQLObject checkEntityFieldSequenceIntegrity classifyParameterizedQueryForMaterialization cleanupStaleEntityRelationships coalesce columnExistsInCoreSchema conditionalInsert createDefaultConstraintForSpecialDateField createExcludeTablesAndSchemasFilter createNewApplication createNewEntities createNewEntity createNewEntityDisplayName createNewEntityFieldsFromSchema createNewEntityInsertSQL createNewEntityName createNewUUID decorateSingleVirtualEntityWithLLM decorateVirtualEntitiesWithLLM deleteUnneededEntityFields deriveEntityNameFromView detectAndSetGeoCodingSupport detectMaterializationDrift dropAndCreateDefaultConstraintForSpecialDateField dropExistingDefaultConstraint dropIfExists ensureCreatedAtUpdatedAtFieldsExist ensureDeletedAtFieldsExist ensureSpecialDateFieldExistsAndHasCorrectDefaultValue entityHasExternalDataSourceColumn entityHasRowLevelRestriction entityHasRowLevelSecurity entityIdSubquery evaluateAndHoldDriftRow externalObjectIsSyncable extractEntitiesFromConfig extractFieldValueLists extractISARelationshipsFromConfig extractMaterializedBaseViewsFromConfig extractOrganicKeysFromConfig extractTablesFromConfig extractVirtualEntitiesFromConfig findEntitiesByBaseObject findFieldByName findFieldSourceParent gatherDriftFacts generateNewEntityDescriptions generateValidatorFunctionFromCheckConstraint getApplicationIDForSchema getEntityNamingOptions getMaterializedTableColumns getNewEntityNameRule getPendingEntityFieldINSERTSQL getPendingEntityFieldsSELECTSQL groupFieldsByEntityPair hasSoftPrimaryKeyInConfig iif isFieldEligibleForNameField isFieldEligibleForUserSearch isInheritedISAField isLikelyLogOrAuditEntity isNameFieldTypeSafe isSchemaNew loadAPIKeyRowFilterTargets loadGeneratedCode loadSchemaInfoRecords manageEntityFields manageEntityFieldValuesAndValidatorFunctions manageEntityRelationships manageExternalEntities manageExternalEntityRelationships manageManyToManyEntityRelationships manageMetadata manageOneToManyEntityRelationships manageParentEntityFields manageSingleEntityParentFields manageSingleExternalEntity manageSingleVirtualEntity manageSingleVirtualEntityField manageVirtualEntities markupEntityName materializedEntityFields materializedResultTableExists newEntityNameWithAdvancedGeneration normalizeSearchFlagsInPlace parentFieldNeedsUpdate parseCheckConstraintValues parseDefaultValue preferLiteralNameField primaryKeyFlagsChanged processBaseViewMaterializations processEntitiesBatched processEntityAdvancedGeneration processEntityConfigs processISARelationshipConfig processOrganicKeyConfig processQueryMaterializations processVirtualEntityConfig qi qs qsql queryHasExternalDataSourceColumn queryHasIsMaterializedColumn reconcileMaterializedQueryEntityReadGrants removeStaleOneToManyRelationships resolveAllowCachingForSchema resolveEntityNamesToIDs resolvePrimaryKeyFlags resolveUniqueEntityName revokeMaterializedEntityReadAccess revokeReadAndHoldMaterialization runQuery runQueryWithParams sanitizeCodeType selectNameFieldWinner selectTop setDefaultColumnWidthWhereNeeded shouldCreateNewEntity simpleNewEntityName snapshotEntityFields syncEntityFieldValues updateEntityFieldDisplayNameWhereNull updateEntityFieldRelatedEntityNameFieldMap updateExistingEntitiesFromSchema updateExistingEntityFieldsFromSchema updateSchemaInfoFromDatabase utcNow uuidLit validateExtendedType validateISARelationships AddEntityRequiringViewRegen addNewEntitiesToModifiedList clearFieldTracking EntityHasRoleReadRLS EntityHasRowLevelRestriction fieldChangeReasons getSoftPKFKConfig invalidateSoftPKFKConfigCache isDisplayNameReopened isFieldNew isTypeReopened registerFieldChange registerNewField

    Constructors

    Properties

    _sqlUtilityObject: SQLUtilityBase = ...
    apiKeyRowFilterTargets: "unknown" | ReadonlySet<string> = 'unknown'

    Per-run cache of the entity NAMES (trimmed + lowercased) that an API-KEY row filter (APIKeyScope.RowFilterID) or an API-APPLICATION row ceiling (APIApplicationScope.RowFilterID) binds to.

    'unknown' — the INITIAL value — means "not loaded, or could not be enumerated", and makes entityHasRowLevelRestriction treat EVERY entity as row-restricted. That is the fail-CLOSED direction on purpose: refusing to materialize costs nothing (the entity/query stays live-only), while materializing an entity whose API-key row filter we failed to see is the precise leak these gates exist to prevent. Loaded by loadAPIKeyRowFilterTargets at the top of every materialization entry point. protected (not private) so a unit-test seam can declare "this fixture configures no API-key row filters".

    Accessors

    • get dbProvider(): CodeGenDatabaseProvider

      Returns the CodeGenDatabaseProvider for the current database platform. Lazily initialized from dbPlatform() configuration.

      Lookup goes through MJGlobal.ClassFactory keyed by the platform string, which matches the @RegisterClass(CodeGenDatabaseProvider, '<platform>') decorators on the concrete providers ('sqlserver', 'postgresql'). Mismatched keys silently fall back to the abstract base class — fail loud with an explicit error so a misconfigured platform doesn't ship as a runtime breakage in dialect-specific methods.

      Returns CodeGenDatabaseProvider

    • get changedFieldReport(): readonly {
          entityName: string;
          fieldName: string;
          reasons: (
              | "AllowUpdateAPI"
              | "Type"
              | "AllowsNull"
              | "IsPrimaryKey"
              | "Description"
              | "Length"
              | "Precision"
              | "Scale"
              | "DefaultValue"
              | "AutoIncrement"
              | "IsVirtual"
              | "IsComputed"
              | "RelatedEntityID"
              | "RelatedEntityFieldName"
              | "IsUnique"
          )[];
      }[]

      Returns readonly {
          entityName: string;
          fieldName: string;
          reasons: (
              | "AllowUpdateAPI"
              | "Type"
              | "AllowsNull"
              | "IsPrimaryKey"
              | "Description"
              | "Length"
              | "Precision"
              | "Scale"
              | "DefaultValue"
              | "AutoIncrement"
              | "IsVirtual"
              | "IsComputed"
              | "RelatedEntityID"
              | "RelatedEntityFieldName"
              | "IsUnique"
          )[];
      }[]

    • get deletedEntitySchemaList(): string[]

      Schemas that lost an entity during this run.

      Deletion cannot be reported the way new/modified entities are. Those lists carry entity NAMES, which downstream code resolves to a schema by looking the entity up in live metadata — and a deleted entity is, by then, no longer there to look up. So the schema is captured here at deletion time instead.

      Without this the schema is never marked dirty, its per-schema file is never rebuilt, and the dead class keeps a live @RegisterClass registration on disk. The old monolith could not drift this way: it was rewritten in full every run.

      Returns string[]

    Methods

    • Adds default permissions for a newly created entity based on config settings. Shared by both table-backed entity creation and virtual entity creation.

      Parameters

      Returns Promise<void>

    • Adds default ApplicationRole records for a newly created application based on config settings. This grants configured roles access to the new application automatically.

      Parameters

      Returns Promise<void>

    • Adds a newly created entity to the application(s) that match its schema name. If no application exists for the schema and config allows it, creates one. Shared by both table-backed entity creation and virtual entity creation.

      Parameters

      Returns Promise<void>

    • Grants read permissions to a newly-minted materialized QUERY entity scoped to the INTERSECTION of source read access (C2). A precomputed snapshot has no per-row scoping, so a role may read it only if it can read EVERY source entity — otherwise a user who cannot read a source could read its rows through the snapshot. Starts from the same config-default role list as addDefaultPermissionsForEntity (never grants a role the defaults wouldn't) and drops any role that lacks explicit read on every source. Always read-only (the entity is a virtual entity with no CRUD sprocs). If the intersection is empty the entity receives no role-based read grants — the fail-closed direction (§10); a human can grant read after review.

      Parameters

      Returns Promise<void>

    • Generate SQL UPDATEs for DefaultInView on the identified default view fields

      Parameters

      • sqlStatements: string[]
      • fields: Record<string, unknown>[]
      • result: SmartFieldIdentificationResult
      • OptionalentityOrCtx:
            | { Name?: string }
            | {
                isNewEntity: boolean;
                newFieldNames: ReadonlySet<string>;
                typeReopenedNames: ReadonlySet<string>;
            }
      • OptionalmaybeCtx: {
            isNewEntity: boolean;
            newFieldNames: ReadonlySet<string>;
            typeReopenedNames: ReadonlySet<string>;
        }

      Returns void

    • Applies entity importance analysis to MJApplicationEntity records. Only called for NEW entities to set DefaultForNewUser.

      Parameters

      • pool: CodeGenConnection
      • entityId: string
      • importance: {
            confidence: string;
            defaultForNewUser: boolean;
            entityCategory: string;
            reasoning: string;
        }
      • OptionalentityName: string

      Returns Promise<void>

    • Generate SQL UPDATEs for entity-level AllowUserSearchAPI.

      Guardrail: even if the LLM proposes enabling AllowUserSearchAPI on an entity that looks like an audit/log/run-history table by name, refuse — those tables grow unboundedly, the LIKE fan-out across them dominates global-search latency, and they're virtually never the right target for a user-facing search box. The team can still enable search on such an entity by setting AutoUpdateAllowUserSearchAPI=0 and flipping the flag manually.

      Parameters

      • sqlStatements: string[]
      • entity: {
            AllowUserSearchAPI?: boolean;
            AutoUpdateAllowUserSearchAPI?: boolean;
            ID: string;
            Name?: string;
            SchemaName?: string;
        }
      • result: SmartFieldIdentificationResult

      Returns void

    • Applies category, display name, extended type, and code type to entity fields. Enforces stability rules: fields with existing categories cannot move to NEW categories. All SQL updates are batched into a single execution for performance.

      Parameters

      • pool: CodeGenConnection
      • entity: EntityInfo
      • fields: {
            AutoUpdateCategory: boolean;
            AutoUpdateDisplayName: boolean;
            AutoUpdateExtendedType: boolean;
            Category: string | null;
            CodeType: string | null;
            DisplayName: string;
            ExtendedType: string | null;
            GeneratedFormSection: string;
            ID: string;
            Name: string;
            ValueListType?: string | null;
        }[]
      • fieldCategories: {
            category: string;
            codeType?: string | null;
            displayName?: string;
            extendedType?: string | null;
            fieldName: string;
            reason?: string;
        }[]
      • existingCategories: Set<string>
      • ctx: { isNewEntity: boolean }

      Returns Promise<void>

    • Apply form layout generation results to set category on entity fields. Delegates to shared methods for category assignment, icon, and category info persistence.

      Parameters

      • pool: CodeGenConnection

        Database connection pool

      • entity: EntityInfo
      • fields: {
            AutoUpdateCategory: boolean;
            AutoUpdateDisplayName: boolean;
            AutoUpdateExtendedType: boolean;
            Category: string | null;
            CodeType: string;
            DisplayName: string;
            ExtendedType: string;
            GeneratedFormSection: string;
            ID: string;
            Name: string;
            ValueListType?: string | null;
        }[]

        Entity fields

      • result: FormLayoutResult

        Form layout result from LLM

      • isNewEntity: boolean = false

        If true, apply entityImportance; if false, skip it

      Returns Promise<void>

    • Generate SQL UPDATEs for entity-level and field-level FullTextSearch configuration

      Parameters

      • sqlStatements: string[]
      • entity: {
            AutoUpdateFullTextSearch?: boolean;
            FullTextSearchEnabled?: boolean;
            ID: string;
            Name?: string;
        }
      • fields: Record<string, unknown>[]
      • result: SmartFieldIdentificationResult
      • Optionalctx: {
            isNewEntity: boolean;
            newFieldNames: ReadonlySet<string>;
            typeReopenedNames: ReadonlySet<string>;
        }

      Returns void

    • Applies LLM-generated field descriptions to entity fields that lack descriptions. All SQL updates are batched into a single execution for performance.

      Parameters

      • pool: CodeGenConnection
      • entity: EntityInfo
      • fieldDescriptions: {
            category: string | null;
            codeType:
                | "SQL"
                | "HTML"
                | "Other"
                | "CSS"
                | "JavaScript"
                | "TypeScript"
                | null;
            description: string;
            displayName: string
            | null;
            extendedType:
                | "Email"
                | "Code"
                | "Color"
                | "FaceTime"
                | "Geo"
                | "GeoLatitude"
                | "GeoLongitude"
                | "GeoCountry"
                | "GeoStateProvince"
                | "GeoCity"
                | "GeoPostalCode"
                | "GeoAddress"
                | "HTML"
                | "Icon"
                | "Image"
                | "JSON"
                | "Markdown"
                | "MSTeams"
                | "Other"
                | "SIP"
                | "SMS"
                | "Skype"
                | "Tel"
                | "URL"
                | "WhatsApp"
                | "ZoomMtg"
                | null;
            fieldName: string;
        }[]
      • schema: string

      Returns Promise<boolean>

    • Applies LLM-identified foreign keys to entity fields. Sets RelatedEntityID, RelatedEntityFieldName, and IsSoftForeignKey=1. Only applies high and medium confidence FKs. All SQL updates are batched into a single execution for performance.

      Parameters

      • pool: CodeGenConnection
      • entity: EntityInfo
      • foreignKeys: {
            confidence: "high" | "medium" | "low";
            fieldName: string;
            relatedEntityName: string;
            relatedFieldName: string;
        }[]
      • schema: string

      Returns Promise<boolean>

    • Applies LLM-identified primary keys to entity fields. Sets IsPrimaryKey=1 and IsSoftPrimaryKey=1 for identified fields. First clears any default PK that was set by field-sync (field #1 fallback). All SQL updates are batched into a single execution for performance.

      Parameters

      Returns Promise<boolean>

    • Generate SQL UPDATEs for IsNameField — SINGLE-WINNER semantics.

      The LLM's result.nameFields is a RANKED candidate list (it may propose several fields that "together" name the record, e.g. FirstName + LastName), but every downstream consumer — EntityInfo.NameField, the base-view generator's FK-name virtual columns, RelatedEntityNameFieldMap resolution — assumes exactly ONE name field per entity. Historically this method flagged EVERY candidate and never cleared anything, so flags accumulated across runs (57 core entities ended up with 2–4 IsNameField=1 rows) and the FK-name pick silently DRIFTED between CodeGen runs as the first-by-sequence winner changed (observed: Conversation Details flipping MessageRole, reshaping every view that joins to it).

      Winner selection (deterministic, mirrors EntityInfo.NameField's runtime preference and the v5.41 metadata backfill):

      1. Stability — if exactly one currently-flagged field is still eligible, it stays the winner regardless of what the LLM proposes. An already-valid single name field never drifts.
      2. Repair — if several flagged fields are eligible, prefer the one literally named Name, else the first in field order (fields arrive ordered by Sequence from the metadata query).
      3. Fresh pick — if no flagged field is eligible, take the first eligible LLM candidate in ranked order.
      4. Preserve — if there is still no winner, keep an existing flag on a type-safe field rather than clearing it with no replacement (MJ #3551). Only a flag on an actively-wrong field (PK / non-text / unbounded) is cleared with nothing to replace it.

      Every NON-winner with IsNameField=1 and AutoUpdateIsNameField=1 is CLEARED, so historical accumulation self-heals on the next analysis pass. Fields pinned with AutoUpdateIsNameField=0 are never touched in either direction — the user owns them, and EntityInfo.NameField's literal-Name preference arbitrates at runtime.

      Parameters

      • sqlStatements: string[]
      • fields: Record<string, unknown>[]
      • result: SmartFieldIdentificationResult
      • Optionalentity: { Name?: string }

      Returns void

    • Applies schema-level entity name prefix/suffix rules from additionalSchemaInfo.json. The file may contain a top-level "Schemas" array with entries like:

      { "name": "CRM", "entityNamePrefix": "CRM: ", "entityNameSuffix": "" }
      

      These are merged into the cached SchemaInfo records so that getNewEntityNameRule() can resolve them during entity creation. Config-file rules (mj.config.cjs) still take priority over these — this just provides a fallback from DBAutoDoc output.

      Returns void

    • Generate SQL UPDATEs for IncludeInUserSearchAPI on the identified searchable fields.

      Guardrails: even if the SmartFieldIdentificationResult flags a field as searchable, we refuse to enable IncludeInUserSearchAPI when the field is a primary key, a non-text type (LIKE forces an implicit per-row CONVERT and never seeks an index), or an unbounded text type whose parent entity is not FTX-enabled (the LIKE path cannot seek MAX/ntext/text columns; FTX is the right tool there). These are always-wrong cases — they cause the data provider to emit unindexed scans on every search.

      Parameters

      • sqlStatements: string[]
      • fields: Record<string, unknown>[]
      • result: SmartFieldIdentificationResult
      • Optionalentity: { FullTextSearchEnabled?: boolean; Name?: string }
      • Optionalctx: {
            isNewEntity: boolean;
            newFieldNames: ReadonlySet<string>;
            typeReopenedNames: ReadonlySet<string>;
        }

      Returns void

    • Generate SQL UPDATEs for UserSearchPredicateAPI on each searchable field

      Parameters

      • sqlStatements: string[]
      • fields: Record<string, unknown>[]
      • result: SmartFieldIdentificationResult
      • OptionalentityOrCtx:
            | { Name?: string }
            | {
                isNewEntity: boolean;
                newFieldNames: ReadonlySet<string>;
                typeReopenedNames: ReadonlySet<string>;
            }
      • OptionalmaybeCtx: {
            isNewEntity: boolean;
            newFieldNames: ReadonlySet<string>;
            typeReopenedNames: ReadonlySet<string>;
        }

      Returns void

    • Apply smart field identification results to entity fields and entity-level search configuration

      Parameters

      • pool: CodeGenConnection
      • entity: {
            AllowUserSearchAPI?: boolean;
            AutoUpdateAllowUserSearchAPI?: boolean;
            AutoUpdateFullTextSearch?: boolean;
            FullTextSearchEnabled?: boolean;
            ID: string;
            Name?: string;
            SchemaName?: string;
        }
      • fields: Record<string, unknown>[]
      • result: SmartFieldIdentificationResult
      • Optionalctx: {
            isNewEntity: boolean;
            newFieldNames: ReadonlySet<string>;
            typeReopenedNames: ReadonlySet<string>;
        }

      Returns Promise<void>

    • Applies soft PK/FK configuration from a JSON file specified in mj.config.cjs (additionalSchemaInfo property). For soft PKs: Sets BOTH IsPrimaryKey=1 AND IsSoftPrimaryKey=1 (IsPrimaryKey is source of truth, IsSoftPrimaryKey protects from schema sync). For soft FKs: Sets RelatedEntityID/RelatedEntityFieldName + IsSoftForeignKey=1 (RelatedEntityID is source of truth, IsSoftForeignKey protects from schema sync). All UPDATE statements are logged to migration files via LogSQLAndExecute() for CI/CD traceability.

      Parameters

      Returns Promise<boolean>

    • The Sequence expression for every EntityField INSERT CodeGen emits: (SELECT COALESCE(MAX(Sequence), 0) + 1 FROM EntityField WHERE EntityID = '<id>').

      Evaluated at APPLY time, never a literal. The value is disposable — the run's own spUpdateExistingEntityFieldsFromSchema pass and R__RefreshMetadata renumber every field from the schema — but it must be unique on ANY database in ANY order, because the INSERT is appended verbatim to a migration and Flyway runs every versioned migration before the repeatable renumber. Each INSERT re-evaluates MAX after the one before it, so a batch that executes sequentially in emission order rises in that order (#3670, #4202).

      Parameters

      • entityID: string

      Returns string

    • Applies soft value-list configuration from Fields[] entries in additionalSchemaInfo.json. Runs AFTER manageEntityFieldValuesAndValidatorFunctions() so CHECK-constraint-derived values are already in the database. If EntityFieldValue rows already exist for a field (from CHECK constraints), the soft-enum pass skips that field — CHECK is authoritative.

      Parameters

      Returns Promise<boolean>

    • Applies category assignments from VE decoration results using the shared category methods. Loads field records from DB (needs ID, Name, Category, AutoUpdateCategory, AutoUpdateDisplayName) then delegates to the shared methods.

      Parameters

      Returns Promise<boolean>

    • RLS-safety assessment for a QUERY materialization, shared by the mint-time gate and the per-run drift re-check (plan §6.2 / §10). A materialized query entity does NOT inherit its source entities' row-level security, so it is only safe to serve an unscoped snapshot when we can PROVE no source is RLS-protected. Fails closed (returns { safe:false }) on three conditions, in order of decreasing severity of what we cannot prove:

      1. No provenance — zero linked source entities (raw SQL / un-analyzed query): we cannot inspect any source's RLS, so we cannot prove safety.
      2. Unresolvable link — a linked EntityID that doesn't resolve to a known EntityInfo: if it secretly carried a read RLS filter we'd never see it.
      3. Row-restricted source — any resolved source protected by role RLS or an API-key row filter (see entityHasRowLevelRestriction; the API-key layer matters because such an entity carries no ReadRLSFilterID, and the minted entity's NEW EntityID would not match the key's EntityID binding). Over-restriction here is harmless (the query stays live-only, or an existing materialization is held); the reverse — serving a protected source's rows unscoped — is the leak this guard exists to prevent.

      Parameters

      • md: Metadata
      • sourceEntityIds: string[]
      • Optionalsql: string

      Returns { reason?: string; safe: boolean }

    • Handles relationship management for an entire entity pair at once. Matches N FK fields to N existing relationship records using a 1:1 mapping:

      1. Exact matches (relationship.JoinField === fk.Name) — already correct, skip
      2. Unmatched FK fields assigned to unmatched relationships (update JoinField)
      3. Remaining FK fields with no available relationship — create new records

      Parameters

      • relationships: Record<string, unknown>[]
      • fkFields: Record<string, unknown>[]
      • md: Metadata
      • relationshipCountMap: Map<number, number>

      Returns string

    • INSERT for one EntityPermission row, skipped when a row already exists for that (EntityID, RoleID, Type).

      The guard is not defensive tidiness — EntityPermission carries a UNIQUE constraint on those three columns (UQ_EntityPermission_EntityID_RoleID_Type), so an unguarded INSERT is a failed CodeGen run rather than a duplicate row. All three call sites are "grant the configured default permissions", which is naturally re-entrant: an entity re-detected as new, or a second CodeGen pass over the same entity, reaches them again. Before the constraint existed this silently accumulated duplicates — a live database showed one (entity, role) pair with rows created three years apart, and pairs whose verb flags disagreed.

      Type is written explicitly rather than left to the column default so the row being inserted and the row being tested for are keyed identically; a default that changed later would otherwise put them out of step. Expressed as INSERT ... SELECT ... WHERE NOT EXISTS, which is valid on both SQL Server and PostgreSQL, so no provider branch is needed.

      Parameters

      • entityId: string
      • roleId: string
      • canRead: boolean
      • canCreate: boolean
      • canUpdate: boolean
      • canDelete: boolean

      Returns string

    • Builds a set of existing category names from entity fields. Used to enforce category stability (prevent renaming).

      Parameters

      • fields: { Category: string | null }[]

      Returns Set<string>

    • Builds the DELETE for EntityFields no longer present in the freshly-introspected remote object, or '' when nothing should be removed. Field-name matching is case-insensitive. Pure/testable (no DB access) — the data-loss-sensitive computation guarded by externalObjectIsSyncable.

      Parameters

      • schema: string
      • existingFields: { ID: string; Name: string }[]
      • introspectedFieldNames: string[]

      Returns string

    • Builds SQL to INSERT a new EntityRelationship record for a discovered FK field.

      Parameters

      • f: Record<string, unknown>
      • md: Metadata
      • relationshipCountMap: Map<number, number>

      Returns string

    • Builds the entity lookup behind processISARelationshipConfig: match on Name first, else on BaseTable, optionally constrained to a schema.

      The schema predicate is composed CONDITIONALLY rather than written as (@SchemaName IS NULL OR SchemaName = @SchemaName). That form is fatal on PostgreSQL: the parameter's only unambiguous use is $n IS NULL, which gives the planner no type to infer, so the whole statement fails to prepare with "could not determine data type of parameter $n". SQL Server infers the type from the other side of the OR and never saw the problem.

      The failure was silent in the worst way — processISARelationshipConfig catches per-relationship errors and logs them, so CodeGen ran to completion with a zero exit code while every declared IS-A relationship on PostgreSQL was quietly skipped, leaving Entity.ParentID NULL. Downstream that means no mirrored parent fields, no parent JOIN in the child base view, and a child whose Save() never writes the parent row.

      Emitting the predicate only when a schema was supplied keeps the parameter list free of type-ambiguous entries and is portable to both dialects without a cast.

      Parameters

      • schema: string
      • selectBody: string
      • nameParam: string
      • OptionalschemaName: string

      Returns { params: Record<string, unknown>; sql: string }

    • Builds the provider-neutral query behind validateISARelationships: one row per DECLARED IS-A child (Entity.ParentID IS NOT NULL) carrying everything the severity rules need. LEFT JOINs throughout so an unresolvable ParentID still returns a row (that is a hard error, not a missing row).

      Parameters

      • schema: string

      Returns string

    • Builds IS-A parent chain context for an entity, computing which parent each inherited field originates from. Used to provide the LLM with inheritance awareness during form layout generation.

      Returns an empty object for entities without parents, so it can be safely spread into the entity object passed to generateFormLayout().

      Parameters

      • entity: { ParentID?: string }
      • fields: {
            AllowUpdateAPI?: boolean;
            IsPrimaryKey?: boolean;
            IsVirtual?: boolean;
            Name: string;
        }[]

      Returns {
          IsChildEntity?: boolean;
          ParentChain?: { entityID: string; entityName: string }[];
      }

    • Parses a view definition SQL and resolves referenced tables to MJ entities. Returns enriched source entity context (all fields with descriptions and categories) for the LLM to use when decorating virtual entity fields.

      Parameters

      • viewDefinition: string

      Returns {
          Description: string;
          Fields: {
              Category: string | null;
              Description: string;
              IsForeignKey: boolean;
              IsPrimaryKey: boolean;
              Name: string;
              Type: string;
          }[];
          Name: string;
      }[]

    • This method will look for situations where entity metadata exist in the entities metadata table but the underlying table has been deleted. In this case, the metadata for the entity should be removed. This method is called as part of the manageMetadata method and is not intended to be called directly.

      Parameters

      Returns Promise<boolean>

    • INTEGRITY CHECK — in a well-formed entity every base (non-virtual) field sequences BEFORE the virtual/related fields, so the EntityField order matches the base view's SELECT [base].*, <joins> column output. The positional save-capture in the data providers (e.g. SQLServerDataProvider's @ResultTable) relies on that alignment. CodeGen inserts newly-discovered columns at an apply-time MAX(Sequence) + ordinal placeholder that updateExistingEntityFieldsFromSchema is supposed to renumber; if a base column is left sequenced AFTER a virtual field, the save-capture would mis-route values by position. The providers now compensate (saves stay correct), but the metadata is still wrong — so we scan for it after every metadata pass and log a prominent warning to drive the root cause out over time.

      Parameters

      Returns Promise<void>

    • Phase 2d — classifies a parameterized query's parameters (deterministic render-and-diff) and, when every parameter is a safe row filter, produces the BROAD source SQL the refresh engine materializes (the query with its row-filter WHERE predicates removed). Returns the persistence fields, or a precise refusal (per-parameter reasons) so non-qualifying queries are skipped loudly.

      Parameters

      Returns Promise<
          | {
              broadSQL: string;
              qualifies: true;
              readFilterSpec: ReadFilterSpecEntry[];
              rowFilterColumns: string[];
          }
          | { qualifies: false; reason: string },
      >

    • Public entry point for cleaning up stale EntityRelationship records. This must be called AFTER deleteUnneededEntityFields() has run, so that stale EntityField records (for dropped columns) are removed before we check which relationships are still valid. Called from sql_codegen.ts after the second manageEntityFields() pass.

      Parameters

      • pool: CodeGenConnection

        database connection

      • excludeSchemas: string[]

        schemas to exclude from FK field lookup

      Returns Promise<boolean>

    • Returns ISNULL/COALESCE expression. Both platforms support COALESCE, but SQL Server also has ISNULL.

      Parameters

      • expr: string
      • fallback: string

      Returns string

    • Generates SQL for conditional INSERT (IF NOT EXISTS pattern). Delegates to the database provider.

      Parameters

      • checkQuery: string
      • insertSQL: string

      Returns string

    • Creates the default constraint for a special date field. This method is called as part of the ensureSpecialDateFieldExistsAndHasCorrectDefaultValue method and is not intended to be called directly.

      Parameters

      Returns Promise<void>

    • Creates a new application using direct SQL INSERT to ensure it's captured in SQL logging. The Path field is auto-generated from Name using the same slug logic as MJApplicationEntityServer.

      Parameters

      • pool: CodeGenConnection

        SQL connection pool

      • appID: string

        Pre-generated UUID for the application

      • appName: string

        Name of the application

      • schemaName: string

        Schema name for SchemaAutoAddNewEntities

      • currentUser: UserInfo

        Current user for entity operations (unused but kept for signature compatibility)

      Returns Promise<string | null>

      The application ID if successful, null otherwise

    • Parameters

      • newEntityUUID: string
      • newEntityName: string
      • newEntity: any
      • newEntitySuffix: string
      • newEntityDisplayName: string | null

      Returns string

    • Applies LLM-assisted field decoration to a single virtual entity. Parses the view SQL to identify source entities, enriches the LLM prompt with their field metadata (descriptions, categories), then applies PKs, FKs, descriptions, and categories.

      Parameters

      • pool: CodeGenConnection
      • entity: EntityInfo
      • ag: AdvancedGeneration
      • currentUser: UserInfo
      • availableEntities: { BaseTable: string; Name: string; PrimaryKeyField: string; SchemaName: string }[]

      Returns Promise<{ decorated: boolean; skipped: boolean }>

      Whether the entity was decorated, skipped, or encountered an error.

    • Iterates over all virtual entities and applies LLM-assisted field decoration to identify primary keys, foreign keys, and field descriptions. Only runs if the VirtualEntityFieldDecoration advanced generation feature is enabled. Idempotent: skips entities that already have soft PK/FK annotations.

      Parameters

      Returns Promise<void>

    • Derives an entity name from a view name by removing common prefixes (vw, v_) and converting to a human-friendly format.

      Parameters

      • viewName: string

      Returns string

    • Phase 4 (§13/§17.2): scan active materializations and flag those whose source shape has drifted as DriftHold (stop refreshing, surface for review). Runs after the entity/field re-sync, so it compares each materialization's provenance against the CURRENT metadata. Flag-and-hold only — never auto-rebuilds. Already-held (DriftHold) and Disabled rows are skipped.

      Parameters

      Returns Promise<{ heldCount: number; success: boolean }>

    • Drops and recreates the default constraint for a special date field. This method is called as part of the ensureSpecialDateFieldExistsAndHasCorrectDefaultValue method and is not intended to be called directly.

      Parameters

      Returns Promise<void>

    • Generates a conditional existence check + DROP statement. Delegates to the database provider.

      Parameters

      • objectType: "VIEW" | "PROCEDURE" | "FUNCTION"
      • schema: string
      • name: string

      Returns string

    • This method ensures that the __mj_CreatedAt and __mj_UpdatedAt fields exist in each entity that has TrackRecordChanges set to true. If the fields do not exist, they are created. If the fields exist but have incorrect default values, the default values are updated. The default value that is to be used for these special fields is GETUTCDATE() which is the UTC date and time. This method is called as part of the manageEntityFields method and is not intended to be called directly.

      Parameters

      Returns Promise<boolean>

    • This method handles the validation of the existence of the specified special date field and if it does exist it makes sure the default value is set correctly, if it doesn't exist it makes sure that it is created. This method is called as part of the ensureCreatedAtUpdatedAtFieldsExist method and is not intended to be called directly.

      Parameters

      • pool: CodeGenConnection
      • entity: any
      • fieldName: string
      • currentFieldData: any
      • allowNull: boolean

      Returns Promise<boolean>

    • Whether the current database's Entity table has the ExternalDataSourceID column. External Data Sources ship as a SQL Server migration only, so on any database/schema that predates it (PostgreSQL today) the column is absent — and ANY raw SQL referencing it throws "column does not exist" and aborts the entire CodeGen run, even for a pure MJ-DB schema with no external entities. Callers gate EDS-specific queries on this so CodeGen stays green everywhere and auto-activates once the column lands on other platforms (mirrors the PG stored-proc guard in metadataSupportObjects.ts). Cross-platform via INFORMATION_SCHEMA; cached for the run (the schema cannot change mid-run).

      Parameters

      Returns Promise<boolean>

    • THE row-restriction gate for materialization. True when the entity is protected by ANY row-level restriction — role RLS or an API-key / API-application row filter — because a materialized snapshot reproduces neither. Fails closed when the API-key layer could not be enumerated (see apiKeyRowFilterTargets).

      Parameters

      Returns boolean

    • ROLE-RLS LAYER ONLY — true if any of the entity's role permissions carries a non-empty ReadRLSFilterID.

      This is deliberately NOT a gate on its own, and no materialization gate may call it directly. MJ enforces row restrictions in TWO layers (see EntityInfo.GetEffectiveRowFilterWhereClause, the sanctioned composer): role RLS and API-key row filters, AND-composed. An entity bound ONLY by an API-key row filter carries no ReadRLSFilterID at all, so a gate that stopped at this layer would judge it unprotected and fail OPEN — minting a materialized entity with a NEW EntityID, which the API-key binding (which binds by EntityID) no longer matches, handing a filtered principal a full unscoped snapshot of rows it cannot read live.

      EntityInfo exposes no user-free accessor for either layer (GetEffectiveRowFilterWhereClause needs a session UserInfo, and CodeGen has no session), so the composition is done explicitly here — by entityHasRowLevelRestriction, which is what every gate calls.

      Parameters

      Returns boolean

    • Returns a parenthesized subquery that resolves an entity's ID by its BaseTable + SchemaName. Used in logged SQL to keep INSERT statements portable across databases (avoids hardcoding runtime UUIDs that may differ on a fresh database).

      Parameters

      • tableName: string
      • schemaName: string

      Returns string

    • Processes ONE materialization row: the C1 RLS re-check, the C2 read-grant re-narrow, the external base-view leak guard, and generic shape/provenance drift. Returns true if the row was held. Extracted from detectMaterializationDrift so a throw on one row can be isolated by the caller's try/catch instead of aborting reconciliation for every remaining materialization.

      Parameters

      Returns Promise<boolean>

    • Whether an introspected remote object has columns we can safely sync. When false (object missing or zero columns), manageSingleExternalEntity skips the field sync rather than treating it as "the entity now has no fields" (which would DELETE every EntityField). Pure/testable.

      Parameters

      • obj: { Columns?: unknown[] } | null | undefined

      Returns boolean

    • Extracts the top-level "Entities" array from the additionalSchemaInfo config file. Each entry identifies an entity by BaseTable + SchemaName and declares arbitrary Entity-table attributes to set (e.g., AllowMultipleSubtypes, TrackRecordChanges).

      Parameters

      • config: Record<string, unknown>

      Returns EntityConfig[]

    • Extracts organic key configurations from the additionalSchemaInfo config file. Walks all schema-keyed table arrays and collects OrganicKeys arrays with their owning schema and table name.

      Parameters

      • config: Record<string, unknown>

      Returns { OrganicKeys: OrganicKeyConfig[]; SchemaName: string; TableName: string }[]

    • Extracts a flat array of table configs from the config file, handling both formats:

      1. Schema-as-key (template format): { "dbo": [{ "TableName": "Orders", ... }] }
      2. Flat tables array (legacy format): { "Tables": [{ "SchemaName": "dbo", "TableName": "Orders", ... }] } Returns a normalized array where each entry has SchemaName, TableName, PrimaryKey[], and ForeignKeys[].

      Parameters

      • config: Record<string, unknown>

      Returns SoftPKFKTableConfig[]

    • Maps a physical (schema, table/view) reference to the MJ entities backed by it — by BaseView or BaseTable, case/whitespace-insensitive. Returns ALL candidates so the P1 guard can fail closed on an AMBIGUOUS ref. Resolution: an explicit, non-default schema yields only that schema's exact matches; an empty schema OR the parser-default 'dbo' — which SQLParser.ExtractTableRefs assigns to every UNQUALIFIED ref, while MJ core/app entities live in __mj/app schemas (never dbo) — is treated as unqualified and yields every schema-agnostic name match. That way an unqualified reference to a __mj/app-schema entity is still caught, and a same-base-name collision across schemas surfaces every candidate rather than just the first (so the guard refuses if any of them is under-linked). Used by the P1 under-linking guard.

      Parameters

      • md: Metadata
      • schema: string | undefined
      • table: string

      Returns EntityInfo[]

    • Type Parameters

      • T extends { Name: string }

      Parameters

      • fields: T[]
      • name: string | null | undefined

      Returns T | undefined

    • For an inherited field, walks the parent chain to find which specific parent entity originally defines this field (by matching non-virtual fields on each parent).

      Parameters

      • fieldName: string
      • parentChain: { entityID: string; entityName: string }[]
      • allEntities: EntityInfo[]

      Returns { entityID: string; entityName: string } | null

    • Generates a TypeScript field validator function from the text of a SQL CHECK constraint.

      Parameters

      • data: any

        the data object containing the entity name, column name, and constraint definition

      • allEntityFields: any[]

        all of the entity fields in the system

      • currentUser: UserInfo

        the current user

      • generateNewCode: boolean

        a flag indicating whether or not to generate new code, this is set to false when we are just loading the generated code from the database.

      Returns Promise<ValidatorResult>

      a data structure with the function text, function name, function description, and a success flag

    • Resolves entity name prefix/suffix rules for a given schema. The resolution order is:

      1. Config file (mj.config.cjs NameRulesBySchema) - highest priority
      2. SchemaInfo database metadata (EntityNamePrefix/EntityNameSuffix columns) - fallback

      If both sources define rules for the same schema, the config file wins and a console warning is emitted to alert the user of the override.

      Parameters

      • schemaName: string

      Returns
          | {
              EntityNamePrefix: string;
              EntityNameSuffix: string;
              SchemaName: string;
          }
          | undefined

    • This method builds a SQL Statement that will insert a row into the EntityField table with information about a new field.

      Parameters

      • newEntityFieldUUID: string
      • n: any

        the new field

      Returns string

    • Creates a SQL statement to retrieve all of the pending entity fields that need to be created in the metadata. This method looks for fields that exist in the underlying database but are NOT in the metadata.

      IMPORTANT: The sequence calculation uses a dynamic offset based on the maximum existing sequence for each entity, plus 100,000, plus the column sequence. This ensures no collision with existing sequences while maintaining deterministic ordering. The spUpdateExistingEntityFieldsFromSchema stored procedure runs AFTER this method and will correct the sequences to ensure they are in the correct sequential order starting from 1. In a migration, the spUpdateExistingEntityFieldsFromSchema runs afterwards as well so this behavior ensures CodeGen works consistently.

      Parameters

      • OptionalentityIDs: string[]
      • OptionalexcludeSchemas: string[]

      Returns string

      • The SQL statement to retrieve pending entity fields.
    • Groups FK fields by their entity pair key (ParentEntityID|ChildEntityID). This ensures all FK fields for the same entity pair are processed together, preventing the ping-pong bug where multi-FK pairs swap JoinFields on each run.

      Parameters

      • entityFields: Record<string, unknown>[]

      Returns Map<string, Record<string, unknown>[]>

    • Checks if a table has a soft primary key defined in the additionalSchemaInfo JSON file (configured in mj.config.cjs)

      Parameters

      • schemaName: string
      • tableName: string

      Returns boolean

    • Returns an IIF/CASE expression. SQL Server: IIF(cond, t, f), PostgreSQL: CASE WHEN cond THEN t ELSE f END

      Parameters

      • condition: string
      • trueVal: string
      • falseVal: string

      Returns string

    • Returns true if the field is a sensible target for IsNameField (i.e. could serve as the entity's human-readable display name). A name field must be BOUNDED TEXT — so we reject primary keys, uniqueidentifiers, all non-text types, and unbounded (MAX) text. This stops Smart Field Identification from flagging a PK/uniqueidentifier as a name field, which would corrupt every related-entity name virtual field that joins to this entity (those resolve their SQL type from the related entity's NameField).

      VIRTUAL fields are rejected too — with ONE carve-out. A view-only name column forces the FK-name join to target the VIEW instead of the base table: the self-referencing case is unbuildable on PostgreSQL and SQL Server (see the self-FK skip in the view generator), and a name that is itself a BORROWED FK-name (_RelatedEntityNameFieldMap) resolves circularly. But an IS-A (Table-Per-Type) inherited field is neither of those: it is the child's mirror of a real parent COLUMN, materialized by joining the parent's base table (generateParentEntityFieldSelects), and it is the only sensible display value the child has. Rejecting it left IS-A children with ZERO name fields — selectNameFieldWinner returned null and the clear-loop then wiped the IsNameField=1 the deterministic pass had correctly set on Name (MJ issue #3551).

      Parameters

      • field: Record<string, unknown>

      Returns boolean

    • Returns true if the field is a sensible target for LIKE-based user search. Mirrors the runtime guard in GenericDatabaseProvider.isTextSearchableType / the Phase 1 hygiene migration so CodeGen stops re-introducing invalid flags.

      Parameters

      • field: Record<string, unknown>
      • ftxEnabled: boolean

      Returns boolean

    • Is this field an IS-A (Table-Per-Type) INHERITED field — the child entity's mirror of a column that physically lives on a parent table?

      Uses the same discriminator as the rest of this file (see syncISAParentFields and buildParentChainContext): IsVirtual=1 AND AllowUpdateAPI=1. That pair is unambiguous — every virtual column discovered from a base VIEW is inserted with AllowUpdateAPI=0 (the pending- fields SQL forces IIF(IsVirtual = 1, 0, …)), and the IS-A sync is the only thing that sets AllowUpdateAPI=1 back on a virtual field. So a borrowed FK-name column can never satisfy it, which is exactly the separation the name-field guardrail needs.

      Parameters

      • field: Record<string, unknown>

      Returns boolean

    • Heuristic: does the entity name match the shape of an audit / log / run-history / change-tracking table? These are the entities that drive the most LIKE-scan time and almost never belong in global search.

      Parameters

      • name: string | undefined
      • _schemaName: string | undefined

      Returns boolean

    • The TYPE half of isFieldEligibleForNameField, with no virtuality opinion: a primary key, a non-text type, or unbounded (MAX) text can never be a name field, because those are the values that actively CORRUPT the related-entity name virtual fields that resolve their SQL type from this entity's NameField.

      Split out so the two halves can be applied separately: virtuality only makes a field a worse choice than a base-table column, while failing this check makes it a wrong one. selectNameFieldWinner clears the wrong ones even with no replacement, but preserves a merely-worse one (see its step 4).

      Parameters

      • field: Record<string, unknown>

      Returns boolean

    • Loads apiKeyRowFilterTargets once per CodeGen run. Called at the top of every materialization entry point, BEFORE any gate runs, so the gates never evaluate against an unloaded cache in production.

      Resolution rules (all biased fail-closed):

      • The RowFilterID column absent on a scope table ⇒ that binding layer cannot exist on this database (pre-v6 schema / the PostgreSQL parallel world) ⇒ contributes nothing. This is CORRECT, not fail-open.
      • A filtered rule's ResourcePattern must name ONE exact entity (enforced at rule save). Mappability is decided by ResolveSingleEntityResourceTarget, shared verbatim with the runtime refresher's identical gate; a rule it cannot resolve collapses the whole set to 'unknown' — every entity is then treated as restricted.
      • Any error enumerating the rules ⇒ 'unknown' (never a silently-empty set). Permission type is deliberately NOT narrowed to Read: mapping a scope rule to a permission type requires the APIScope path taxonomy that lives outside CodeGen, and over-restriction here is harmless.

      Parameters

      Returns Promise<void>

    • Loads existing SchemaInfo records from the database into the cache so that entity name prefix/suffix rules are available before createNewEntities() runs. This is a read-only SELECT — it does NOT create or update any records.

      Parameters

      Returns Promise<boolean>

    • Manages the creation, updating and deletion of entity field records in the metadata based on the database schema.

      Parameters

      • pool: CodeGenConnection
      • excludeSchemas: string[]
      • skipCreatedAtUpdatedAtDeletedAtFieldValidation: boolean
      • skipEntityFieldValues: boolean
      • currentUser: UserInfo
      • skipAdvancedGeneration: boolean
      • skipDeleteUnneededFields: boolean = false
      • OptionalentityFilter: string[]

        Optional list of entity NAMES to scope the field-management work to. When provided, the three SP/inline-SQL passes (delete unneeded, create new from schema, update existing from schema) filter to those entities only. Other steps remain unscoped — they are cheap enough that scoping them adds complexity without measurable benefit. An empty array short-circuits the entire method as a no-op, which is the typical "no schema changes since last run" case in Pass 2. undefined (default) preserves prior full-scan behavior.

      Returns Promise<boolean>

    • This method creates and updates relationships in the metadata based on foreign key relationships in the database.

      Parameters

      • pool: CodeGenConnection
      • excludeSchemas: string[]

        specify any schemas to exclude here and any relationships to/from the specified schemas will be ignored

      • md: Metadata
      • batchItems: number = 5

      Returns Promise<boolean>

    • Baseline foreign-key consumption for an external entity: for each introspected single-column relationship whose referenced remote object is ALSO an imported external entity in the same data source, set the FK field's RelatedEntityID + RelatedEntityFieldName + IsSoftForeignKey=1. The standard manageEntityRelationships pass then materializes these into EntityRelationship records. Composite FKs and references to objects that aren't imported are skipped (logged) — those are the follow-up hardening. Returns true if any FK field was updated.

      Parameters

      Returns Promise<boolean>

    • Manages M->M relationships between entities in the metadata based on foreign key relationships in the database. NOT IMPLEMENTED IN CURRENT VERSION IN BASE CLASS. M->M relationships ARE supported fully, but they are not AUTO generated by this method, instead an administrator must manually create these relationships in the metadata.

      Parameters

      Returns Promise<boolean>

    • Manages 1->M relationships between entities in the metadata based on foreign key relationships in the database.

      Parameters

      • pool: CodeGenConnection
      • excludeSchemas: string[]

        specify any schemas to exclude here and any relationships to/from the specified schemas will be ignored

      • md: Metadata
      • batchItems: number = 5

      Returns Promise<boolean>

    • Manages virtual EntityField records for IS-A parent entity fields. For each entity with ParentID set (IS-A child), creates/updates virtual field records that mirror the parent entity's base table fields (excluding PKs, timestamps, and virtual fields). Runs collision detection to prevent child table columns from shadowing parent fields.

      Parameters

      Returns Promise<{ anyUpdates: boolean; success: boolean }>

    • Parameters

      • pool: CodeGenConnection
      • virtualEntity: any
      • veField: any
      • fieldSequence: number
      • makePrimaryKey: boolean
      • singleColumnPrimaryKey: boolean = true
      • reconcilePrimaryKey: boolean = false

      Returns Promise<{ newFieldID: string | null; success: boolean; updatedField: boolean }>

    • Applies entity name prefix/suffix rules to a given entity name. Rules are resolved from mj.config.cjs first, then from SchemaInfo database metadata as a fallback.

      Parameters

      • schemaName: string

        the database schema name

      • entityName: string

        the base entity name to apply prefix/suffix to

      Returns string

    • THE single rule for "which of an entity's fields get materialized" in a base-view materialization. Every site that computes a base-view column set — the MINT (physical table + wrapper view), the DRIFT comparison, and the runtime REFRESH mirror — must use this one predicate, or they judge each other's output as drift.

      Rule: for an EXTERNAL entity, virtual fields are MJ-computed and absent from the remote source, so the refresh mirror only materializes non-virtual columns and the mint must match. A LOCAL base view computes its virtual columns in the view itself, so SELECT * FROM <baseView> includes them and every field is kept. (MaterializationRefresher.rebuildFromExternalEntity applies !f.IsVirtual on the external path only — the same rule, expressed in a context where "external" is already established.)

      Parameters

      Returns EntityFieldInfo[]

    • Apply the code-level search guardrails to the LLM result, mutating result in place so every downstream applier reads the normalized version. The pure heuristics live in search-guardrails.ts; this method wires them to the per-field metadata (Type, Length, IsPrimaryKey, AutoUpdate flags) we have on fields.

      Order of operations:

      1. Shape cleanup (empty/contradictory states).
      2. Field-level eligibility — filter through isFieldEligibleForUserSearch and the narrative-field-name blocklist.
      3. Per-entity cap.
      4. Predicate normalization — rewrite Contains to a default when the field isn't FTS-backed, fill in defaults for missing entries.
      5. Telemetry — record proposed/accepted counts and any rewrites for the end-of-run report.

      Parameters

      • entity: { FullTextSearchEnabled?: boolean; Name?: string }
      • fields: Record<string, unknown>[]
      • result: SmartFieldIdentificationResult

      Returns void

    • Parameters

      • constraintDefinition: string
      • fieldName: string
      • entityName: string

      Returns string[] | null

    • This method takes the stored DEFAULT CONSTRAINT value from the database and parses it to retrieve the actual default value. This is necessary because the default value is sometimes wrapped in parentheses and sometimes wrapped in single quotes. This method removes the wrapping characters and returns the actual default value. Some common raw values that exist in SQL Server include 'getdate()', '(getdate())', 'N''SomeValue''', etc. and this method will remove those wrapping characters to get the actual underlying default value. NOTE: For future versions of MemberJunction where multiple back-end providers could be used, this method will be moved to the Provider architecture so that database-specific versions can be implemented, along with many other aspects of this current codebase.

      Parameters

      • sqlDefaultValue: string

      Returns string

    • Deterministic tie-break across several name-field candidates: the field literally called Name wins, else the first in field order (fields arrive ordered by Sequence from the metadata query). Mirrors EntityInfo.NameField's runtime preference so CodeGen and runtime never disagree about which one it is.

      Parameters

      • candidates: Record<string, unknown>[]

      Returns Record<string, unknown>

    • Whether a field's PK/Unique flags need to change. In RECONCILE mode (external entities, whose introspected PK set is authoritative across ALL fields) we sync in BOTH directions — setting the flags when a column becomes a PK AND clearing them when it stops being one (H5: otherwise a stale PK column keeps IsPrimaryKey=1 when the remote PK moves, yielding duplicate PKs). In NON-reconcile mode (virtual entities, where makePrimaryKey is a one-time first-column bootstrap, not authoritative) we only SET on acquisition and never clear — avoiding wiping a legitimately-configured PK, and avoiding a spurious UPDATE (+ __mj_UpdatedAt bump) every run when nothing changed. Pure function; unit-tested.

      Parameters

      • current: { isPrimaryKey: boolean; isUnique: boolean }
      • want: { wantPrimaryKey: boolean; wantUnique: boolean }
      • makePrimaryKey: boolean
      • reconcile: boolean

      Returns boolean

    • Processes base-view materialization declarations from additionalSchemaInfo (plan §4.1). Each declares a 1:1 snapshot of an existing entity's base view. Because the shape is identical to the source entity, NO new entity is minted — the existing entity is reused (its RLS applies unchanged, §6.1). For each declaration this:

      1. resolves the source entity,
      2. derives the column shape from the entity's base-view fields (SQLFullType),
      3. emits the physical table (materialized_) + wrapper view (materialized_vw) via the cross-engine DDL primitive (create-if-absent, so a migration-provided table is reused rather than clobbered — §12),
      4. upserts the "MJ: Materialized Results" row (SourceType='EntityBaseView') keyed on the source entity. Population is the refresh driver's job (a later phase); rows land in Status='Building'.

      Parameters

      Returns Promise<{ processedCount: number; success: boolean }>

    • Process entities in batches with parallel execution. Batch size is configurable via advancedGeneration.batchSize in mj.config.cjs (default: 5).

      Parameters

      Returns Promise<boolean>

    • Process advanced generation for a single entity

      Parameters

      • pool: CodeGenConnection

        Database connection pool

      • entity: EntityInfo

        Entity to process

      • fieldsByEntity: Map<string, any[]>

        Fields grouped by normalized EntityID (built once by the batch driver)

      • ag: AdvancedGeneration

        AdvancedGeneration instance

      • currentUser: UserInfo

        User context

      Returns Promise<void>

    • Processes Entity attribute configurations from the additionalSchemaInfo config. For each entry in the top-level "Entities" array, looks up the entity by BaseTable + SchemaName and applies any declared attribute updates to the Entity table. Reserved keys (BaseTable, SchemaName) are excluded from the UPDATE statement. Must run AFTER entities are created.

      Parameters

      Returns Promise<{ success: boolean; updatedCount: number }>

    • Processes IS-A relationship configurations from the additionalSchemaInfo config. For each configured relationship, looks up both entities by name (or by table name within the given schema) and sets Entity.ParentID on the child entity. Must run AFTER entities are created but BEFORE manageParentEntityFields().

      Parameters

      Returns Promise<{ success: boolean; updatedCount: number }>

    • Processes organic key configurations from additionalSchemaInfo. For each configured organic key:

      1. Creates transitive bridge views if TransitiveView is defined
      2. Upserts EntityOrganicKey records (matched on EntityID + Name)
      3. Upserts EntityOrganicKeyRelatedEntity records (matched on EntityOrganicKeyID + RelatedEntityID)

      All SQL is executed AND logged via LogSQLAndExecute for complete CI/CD traceability. Must run AFTER entities are created.

      Parameters

      Returns Promise<{ createdCount: number; success: boolean; updatedCount: number }>

    • Processes query materialization (CodeGen materialization phase, sub-step C — plan §4.2). Scans queries flagged IsMaterialized = 1 and, for each that qualifies (unparameterized, has declared output fields — see analyzeQueryForMaterialization, §9/§10):

      1. derives the materialized table's column shape + synthetic surrogate key,
      2. emits the physical table (materialized_) + wrapper view (materialized_vw) via the cross-engine DDL primitive,
      3. mints a read-only Virtual Entity over the wrapper view (idempotent — reuses an existing one), linked back to the source Query,
      4. upserts the "MJ: Materialized Results" row (SourceType='Query') and its MaterializedResultQuery join row linking it to the source Query (no direct SourceQueryID / MaterializedResultID FK columns — the join table breaks the cycle). Runs BEFORE manageVirtualEntities so the minted entity's fields get synced from the wrapper view in the same run. Population is the refresh driver's job (later phase); Status='Building'. Parameterized queries are skipped with a log (deferred to Phase 2).

      Parameters

      Returns Promise<{ mintedCount: number; processedCount: number; success: boolean }>

    • Processes virtual entity configurations from the additionalSchemaInfo config. For each configured virtual entity, checks if it already exists and creates it if not. Uses the spCreateVirtualEntity stored procedure. Must run BEFORE manageVirtualEntities() so newly created entities get field-synced.

      Parameters

      Returns Promise<{ createdCount: number; success: boolean }>

    • Produces a schema-qualified object reference. SQL Server: [schema].[object], PostgreSQL: "schema"."object"

      Parameters

      • schema: string
      • object: string

      Returns string

    • Quotes mixed-case identifiers in a SQL string for the current platform. Delegates to the database provider's quoteSQLForExecution method.

      Parameters

      • sql: string

      Returns string

    • Whether the current database's Query table has the ExternalDataSourceID column. Same rationale as entityHasExternalDataSourceColumn: External Data Sources ship as a SQL-Server-only migration, so on a DB without it (PostgreSQL today) the column is absent and any raw SQL referencing it aborts the CodeGen run. processQueryMaterializations gates its ExternalDataSourceID reference on this. Cached per run.

      Parameters

      Returns Promise<boolean>

    • True if the Query table has the IsMaterialized column (added by the materialization Foundation migration). processQueryMaterializations gates on this so codegen doesn't throw on a DB where that migration hasn't run yet (e.g. the PostgreSQL parallel world's object-availability lag) — same defensive pattern as queryHasExternalDataSourceColumn. Cached per run.

      Parameters

      Returns Promise<boolean>

    • Leak-2 (C2 ongoing): re-narrows a minted materialized QUERY entity's read grants to the CURRENT source-read intersection. The mint-time grant (addMaterializedQueryEntityPermissions) is computed once; if a role later LOSES plain CanRead on a source, its grant on the snapshot must be revoked too — otherwise it keeps reading snapshot rows it can no longer read live. Called from the drift pass on every codegen run. Only ever REVOKES (the safe direction); re-granting a role that regained access is a human decision after review. Returns the number of grants revoked.

      Parameters

      Returns Promise<number>

    • Removes stale One-To-Many EntityRelationship records whose RelatedEntityJoinField no longer corresponds to a valid FK field in the database. Only removes relationships where AutoUpdateFromSchema = true and Type = 'One To Many'.

      Parameters

      • pool: CodeGenConnection
      • allRelationships: Record<string, unknown>[]
      • entityFields: Record<string, unknown>[]

      Returns Promise<void>

    • Resolves the AllowCaching default for a new entity in the given schema. AllowCachingBySchema entries override the global AllowCaching default. The ${mj_core_schema} placeholder is expanded so core-schema rules apply regardless of how the core schema is named in this deployment.

      Parameters

      • schemaName: string

      Returns boolean

    • Resolves a list of entity names to their UUIDs via the live Metadata cache. Names that don't resolve are dropped silently — they may be entities that have been queued for creation but aren't yet in the metadata.

      Two safety properties:

      1. new Metadata() is constructed OUTSIDE the per-name try/catch so a metadata-load failure (e.g., refresh failed upstream) surfaces as an error instead of being silently treated as "no entities resolved" — which would misleadingly degrade Pass 2 to an unscoped full scan.
      2. The try/catch is narrowly scoped to EntityByName because that method throws on null/empty names (_newEntityList and _modifiedEntityList can pick those up from result-set rows whose EntityName column is null). The pre-filter on string type + trim covers the common case; the catch is a safety net for any other lookup failure (e.g., name shape we didn't anticipate).

      Parameters

      • entityNames: string[]

      Returns string[]

    • Desired PK/Unique flags for a virtual/external entity field. IsPrimaryKey mirrors makePrimaryKey; IsUnique is true ONLY for a SINGLE-column PK — a column of a COMPOSITE key is not unique on its own, so composite-key columns get IsUnique=false (M4). Pure function so the rule is unit-tested.

      Parameters

      • makePrimaryKey: boolean
      • singleColumnPrimaryKey: boolean

      Returns { wantPrimaryKey: boolean; wantUnique: boolean }

    • Pick an entity name that is actually free, disambiguating with the schema name and, if that is still taken, a counter.

      Entity names are generated from the table name with trailing discriminators stripped, so distinct tables routinely generate the SAME name — NetSuite's customlist72, customlist74, customlist160, customlist436, customlist534 and customlist873 all generate "Custom Lists".

      The previous logic appended the schema suffix ONCE and assumed the result was unique. It is not: the first table took "Custom Lists", and every table after it appended the same "__netsuite" and produced the identical "Custom Lists__netsuite". The second was a duplicate-key failure on UQ_Entity_Name and the rest were never created — a silent loss of 8 entities on one NetSuite tenant, reported only as repeated identical INSERT errors.

      Comparison is case-INSENSITIVE on both sides, matching UQ_Entity_Name's collation. The old in-run check used an exact === while the metadata check beside it lowercased, so two names differing only in case read as free and then collided on INSERT.

      Parameters

      • desiredName: string

        the generated name to place

      • schemaName: string

        schema, used for the first disambiguation step

      • takenNames: string[]

        every name already spoken for — existing metadata AND names claimed earlier in this run (which are not in metadata yet)

      Returns { name: string; suffix: string }

      the free name and the suffix used to reach it ('' when the name was already free)

    • Revokes read access on a minted materialized entity by setting CanRead=0 on all its EntityPermission rows (the __mj_UpdatedAt trigger stamps the timestamp). Used by the drift re-check when a query source gains RLS after minting: the snapshot can no longer be safely served, so it is made unreadable until a human authors protection. Non-destructive (rows are kept, just flipped) so the grant can be restored.

      Parameters

      Returns Promise<void>

    • The established fail-closed "stop serving this materialization" sequence, shared by every branch that must take a query materialization out of service.

      Revoke read FIRST, then flag DriftHold. The revoke is the security-critical action (it closes the leak — a snapshot serving unscoped source rows); DriftHold only stops FUTURE refreshes. Revoke-first means that if the second statement fails, the readable window is already closed (fail-safe) — whereas DriftHold-then-revoke would leave read OPEN if the revoke failed. A row with no minted entity has no grant row to flip; it is still held.

      Parameters

      Returns Promise<void>

    • Sanitizes an LLM-supplied codeType against the CK_EntityField_CodeType CHECK constraint. Uses MJEntityFieldSchema.shape.CodeType from @memberjunction/core-entities as the single source of truth — no hardcoded enum duplication. Values that fail Zod validation (e.g. 'Python', 'Markdown', 'javascript' wrong case) coerce to 'Other', preserving null/undefined. Logs any coercion so the underlying prompt drift is visible instead of silently failing the batch UPDATE at the DB.

      Parameters

      • codeType: string | null | undefined
      • fieldName: string
      • entityName: string

      Returns string | null | undefined

    • Picks the single IsNameField winner for an entity per the rules documented on applyNameFieldUpdates: stable current winner → deterministic repair (literal Name, then field order) → first eligible LLM candidate → PRESERVE an existing type-safe flag rather than clearing it with nothing to put in its place. Returns null only when every flagged field is actively WRONG (a PK, a non-text type, unbounded text) and the LLM named no usable replacement — there the entity genuinely ends with no name field, which beats pointing FK-name virtual columns at a uniqueidentifier or a blob.

      Parameters

      • fields: Record<string, unknown>[]
      • rankedCandidates: string[]

      Returns Record<string, unknown> | null

    • Wraps a SELECT query with a row limit. SQL Server: SELECT TOP N ... , PostgreSQL: SELECT ... LIMIT N

      Parameters

      • n: number
      • selectBody: string
      • fromAndWhere: string
      • OptionalorderBy: string

      Returns string

    • This method updates the DefaultColumnWidth field in the EntityField metadata. The default logic uses a stored procedure called spSetDefaultColumnWidthWhereNeeded which is part of the MJ Core Schema. You can override this method to implement custom logic for setting default column widths. It is NOT recommended to modify the stored procedure in the MJ Core Schema because your changes will be overriden during a future upgrade.

      Parameters

      Returns Promise<boolean>

    • This method is responsible for generating a Display Name for each field where a display name is not already set. The approach in the base class uses a simple algorithm that looks for case changes in the field name and inserts spaces at those points. It also strips the trailing 'ID' from the field name if it exists. Override this method in a sub-class if you would like to implement a different approach for generating display names.

      Parameters

      Returns Promise<boolean>

    • This method handles updating entity field related name field maps which is basically the process of finding the related entity field that is the "name" field for the related entity.

      Parameters

      Returns Promise<boolean>

    • Syncs SchemaInfo records from database schemas, capturing extended properties as descriptions. Creates new SchemaInfo records for schemas that don't exist yet and updates descriptions from schema extended properties for existing records.

      Parameters

      • pool: CodeGenConnection

        SQL connection pool

      • excludeSchemas: string[]

        Array of schema names to exclude from processing

      Returns Promise<boolean>

      Promise - true if successful, false otherwise

    • Returns an explicitly-typed UUID literal for the platform. SQL Server: CAST('...' AS uniqueidentifier), PostgreSQL: CAST('...' AS uuid)

      Needed wherever a UUID literal appears in the SELECT list of an INSERT ... SELECT. A bare quoted literal is fine in INSERT ... VALUES, where both platforms coerce it to the target column's type — but in the projection of a SELECT, PostgreSQL can resolve an untyped literal to text and then refuse to assign it to a uuid column. CAST(x AS y) is ANSI and settles it on both, with the type name coming from the dialect rather than a platform branch here.

      Parameters

      • value: string

      Returns string

    • Validates an LLM-suggested ExtendedType against the allowed values in EntityField. Returns the valid value (case-corrected) or null if invalid.

      Parameters

      • suggested: string

      Returns
          | "Email"
          | "Code"
          | "Color"
          | "FaceTime"
          | "Geo"
          | "GeoLatitude"
          | "GeoLongitude"
          | "GeoCountry"
          | "GeoStateProvince"
          | "GeoCity"
          | "GeoPostalCode"
          | "GeoAddress"
          | "HTML"
          | "Icon"
          | "Image"
          | "JSON"
          | "Markdown"
          | "MSTeams"
          | "Other"
          | "SIP"
          | "SMS"
          | "Skype"
          | "Tel"
          | "URL"
          | "WhatsApp"
          | "ZoomMtg"
          | null

    • FORWARD VALIDATION — verifies that every DECLARED IS-A relationship (any Entity with a non-null ParentID) actually satisfies what the IS-A runtime requires. Never mutates metadata.

      Channel-agnostic BY DESIGN: it validates the END STATE of Entity.ParentID, so it covers the additionalSchemaInfo "ISARelationships" config, an @lookup on ParentID in a metadata-sync file, and any future channel — one check instead of one per declaration mechanism.

      Reports ONLY provable-cannot-work defects (hard errors) — never inference. If a declared IS-A merely "looks off" but would still function, it passes silently: flagging it would misfire on correct declarations (see the note in the loop). The hard errors: - Child has a composite PK. The runtime routes ONE shared PK value between child and parent; it has no model for a multi-column subtype key. - Parent has a composite PK. Same reason, from the other side. - Child PK type <> parent PK type. Parent and child SHARE one PK value (Save() writes the child's PK into the parent's PK via ParentEntityFieldNames; loads match the child by the parent's PK value), so the value must be legal as BOTH PKs. Note a physical FK already guarantees matching types — this only ever fires on a soft/declared IS-A, which is exactly the case with no DB constraint to catch it. - ParentID does not resolve to an existing entity. DEFENSE-IN-DEPTH ONLY: the FK_Entity_ParentID constraint (Entity.ParentID -> Entity.ID) makes this state unstorable and vwEntities has no WHERE clause, so it is not reachable in a healthy database. It is kept because the parent JOIN must be a LEFT JOIN regardless, and without this branch an unresolved parent would silently SKIP the remaining checks (ParentPKType would be NULL) rather than fail. Covered by unit test, not live.

      Timing: runs AFTER every ParentID-writing pass (config + any previously-synced @lookup) and BEFORE the 2nd-pass manageParentEntityFields() materializes IS-A virtual fields + view JOINs, so a broken declaration fails before it produces generated code.

      Parameters

      Returns Promise<{ errorCount: number; success: boolean }>

    • Adds a list of entity names to the modified entity list if they're not already in there

      Parameters

      • entityNames: string[]

      Returns void

    • Pure form of the role-RLS layer (see entityHasRowLevelSecurity). IO-free so it can be reused verbatim by the runtime refresher's equivalent gate. Deliberately WIDER than the runtime's own reader (EntityInfo.GetUserRowLevelSecurityInfo collects a filter only from an Allow row whose CanRead is set, since #4358): a leftover filter beside a cleared flag still counts as "protected" here, which errs conservative for a leak gate.

      Parameters

      Returns boolean

    • Pure, IO-free core of entityHasRowLevelRestriction — the predicate any other layer (e.g. the runtime MaterializationRefresher) should mirror, supplying the API-key target set however it can obtain it. 'unknown' for the target set ⇒ true (fail closed).

      Parameters

      • entity: EntityInfo
      • apiKeyRowFilterTargets: "unknown" | ReadonlySet<string>

      Returns boolean

    • Parameters

      • entityID: string
      • name: string

      Returns ReadonlySet<
          | "AllowUpdateAPI"
          | "Type"
          | "AllowsNull"
          | "IsPrimaryKey"
          | "Description"
          | "Length"
          | "Precision"
          | "Scale"
          | "DefaultValue"
          | "AutoIncrement"
          | "IsVirtual"
          | "IsComputed"
          | "RelatedEntityID"
          | "RelatedEntityFieldName"
          | "IsUnique",
      >

    • Loads and caches the soft PK/FK configuration from the additionalSchemaInfo file. Cached per process to avoid repeated I/O within a CodeGen run; the in-process (RSU) path calls invalidateSoftPKFKConfigCache at the start of each run so a rewritten file is picked up.

      Returns Record<string, unknown> | null

    • Drops the cached soft PK/FK config so the next getSoftPKFKConfig call re-reads the additionalSchemaInfo file from disk. REQUIRED for the long-lived in-process (RSU) CodeGen path: the cache is process-static (load-once), which is correct for a one-shot mj codegen CLI run but STALE in a long-running MJAPI where RSU rewrites additionalSchemaInfo on every ApplyAll. Without invalidation, a connector's FIRST ApplyAll writes its soft PKs to the file but CodeGen returns the pre-write cached config → "No primary key found" → the entity is never created → no entity map → 0 rows sync until an MJAPI restart. RunInProcess calls this at the start of every run (which always follows RSU's WriteAdditionalSchemaInfo step), so each in-process run sees the freshly-written file. Deterministic + event-driven (no mtime/TOCTOU race). The CLI Run() path does not call this, so its load-once-per-process behavior is unchanged.

      Returns void

    • Parameters

      • entityID: string
      • name: string
      • reasons: Iterable<
            | "AllowUpdateAPI"
            | "Type"
            | "AllowsNull"
            | "IsPrimaryKey"
            | "Description"
            | "Length"
            | "Precision"
            | "Scale"
            | "DefaultValue"
            | "AutoIncrement"
            | "IsVirtual"
            | "IsComputed"
            | "RelatedEntityID"
            | "RelatedEntityFieldName"
            | "IsUnique",
        >

      Returns void