@memberjunction/codegen-lib 6.1.0-edge.3 → 6.1.0-edge.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -21
- package/dist/Config/config.d.ts +118 -1
- package/dist/Config/config.d.ts.map +1 -1
- package/dist/Config/config.js +202 -8
- package/dist/Config/config.js.map +1 -1
- package/dist/Database/__tests__/exclude-schema-permissions.test.d.ts +2 -0
- package/dist/Database/__tests__/exclude-schema-permissions.test.d.ts.map +1 -0
- package/dist/Database/__tests__/exclude-schema-permissions.test.js +32 -0
- package/dist/Database/__tests__/exclude-schema-permissions.test.js.map +1 -0
- package/dist/Database/codeGenDatabaseProvider.d.ts +77 -2
- package/dist/Database/codeGenDatabaseProvider.d.ts.map +1 -1
- package/dist/Database/codeGenDatabaseProvider.js +98 -0
- package/dist/Database/codeGenDatabaseProvider.js.map +1 -1
- package/dist/Database/heal-schema-params.d.ts +33 -0
- package/dist/Database/heal-schema-params.d.ts.map +1 -0
- package/dist/Database/heal-schema-params.js +51 -0
- package/dist/Database/heal-schema-params.js.map +1 -0
- package/dist/Database/manage-metadata.d.ts +9 -0
- package/dist/Database/manage-metadata.d.ts.map +1 -1
- package/dist/Database/manage-metadata.js +66 -51
- package/dist/Database/manage-metadata.js.map +1 -1
- package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.d.ts +25 -35
- package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.d.ts.map +1 -1
- package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.js +97 -51
- package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.js.map +1 -1
- package/dist/Database/providers/postgresql/__tests__/SoftPrimaryKeyIndex.test.d.ts +2 -0
- package/dist/Database/providers/postgresql/__tests__/SoftPrimaryKeyIndex.test.d.ts.map +1 -0
- package/dist/Database/providers/postgresql/__tests__/SoftPrimaryKeyIndex.test.js +110 -0
- package/dist/Database/providers/postgresql/__tests__/SoftPrimaryKeyIndex.test.js.map +1 -0
- package/dist/Database/providers/postgresql/metadataSupportObjects.js +78 -10
- package/dist/Database/providers/postgresql/metadataSupportObjects.js.map +1 -1
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.d.ts +12 -0
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.d.ts.map +1 -1
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.js +28 -0
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.js.map +1 -1
- package/dist/Database/providers/sqlserver/__tests__/SoftPrimaryKeyIndex.test.d.ts +2 -0
- package/dist/Database/providers/sqlserver/__tests__/SoftPrimaryKeyIndex.test.d.ts.map +1 -0
- package/dist/Database/providers/sqlserver/__tests__/SoftPrimaryKeyIndex.test.js +195 -0
- package/dist/Database/providers/sqlserver/__tests__/SoftPrimaryKeyIndex.test.js.map +1 -0
- package/dist/Database/schema-filters.d.ts +23 -0
- package/dist/Database/schema-filters.d.ts.map +1 -0
- package/dist/Database/schema-filters.js +42 -0
- package/dist/Database/schema-filters.js.map +1 -0
- package/dist/Database/schema-scope.d.ts.map +1 -1
- package/dist/Database/schema-scope.js +4 -0
- package/dist/Database/schema-scope.js.map +1 -1
- package/dist/Database/sql_codegen.d.ts +18 -0
- package/dist/Database/sql_codegen.d.ts.map +1 -1
- package/dist/Database/sql_codegen.js +92 -41
- package/dist/Database/sql_codegen.js.map +1 -1
- package/dist/EntityNameScanner/EntityNameScanner.d.ts.map +1 -1
- package/dist/EntityNameScanner/EntityNameScanner.js +33 -2
- package/dist/EntityNameScanner/EntityNameScanner.js.map +1 -1
- package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts +8 -4
- package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts.map +1 -1
- package/dist/Manifest/GenerateClassRegistrationsManifest.js +63 -20
- package/dist/Manifest/GenerateClassRegistrationsManifest.js.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.d.ts +22 -5
- package/dist/Misc/entity_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.js +96 -30
- package/dist/Misc/entity_subclasses_codegen.js.map +1 -1
- package/dist/Misc/system_integrity.d.ts.map +1 -1
- package/dist/Misc/system_integrity.js +5 -0
- package/dist/Misc/system_integrity.js.map +1 -1
- package/package.json +29 -29
|
@@ -4,7 +4,8 @@ import * as fs from 'fs';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { SQLUtilityBase } from './sql.js';
|
|
6
6
|
import { CodeGenDatabaseProvider } from './codeGenDatabaseProvider.js';
|
|
7
|
-
import { autoIndexForeignKeys, configInfo, customSqlScripts, mjCoreSchema } from '../Config/config.js';
|
|
7
|
+
import { autoIndexForeignKeys, autoIndexSoftPrimaryKeys, configInfo, customSqlScripts, mjCoreSchema } from '../Config/config.js';
|
|
8
|
+
import { entityInCustomBaseViewRefreshScope, shouldEmitCascadeForRelatedEntity } from './schema-filters.js';
|
|
8
9
|
import { ManageMetadataBase } from './manage-metadata.js';
|
|
9
10
|
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
10
11
|
import { combineFiles, logIf } from '../Misc/util.js';
|
|
@@ -16,6 +17,15 @@ export const SPType = {
|
|
|
16
17
|
Update: 'Update',
|
|
17
18
|
Delete: 'Delete',
|
|
18
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* Entities whose SchemaName is not in excludeSchemas (case-insensitive).
|
|
22
|
+
* This is the CodeGen run's in-scope set: SQL objects and GRANTs are
|
|
23
|
+
* generated and executed only for these. excludeSchemas means "not this run".
|
|
24
|
+
*/
|
|
25
|
+
export function entitiesNotInExcludedSchemas(entities, excludeSchemas) {
|
|
26
|
+
const exclude = new Set(excludeSchemas.map((s) => s.toLowerCase()));
|
|
27
|
+
return entities.filter((e) => !exclude.has(e.SchemaName.toLowerCase()));
|
|
28
|
+
}
|
|
19
29
|
/**
|
|
20
30
|
* 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
|
|
21
31
|
* 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
|
|
@@ -108,9 +118,7 @@ export class SQLCodeGenBase {
|
|
|
108
118
|
// Entities are already sorted by name in PostProcessEntityMetadata (see providerBase.ts)
|
|
109
119
|
const baselineEntities = entities.filter(e => e.IncludeInAPI);
|
|
110
120
|
// OPTIMIZATION: Use a Set for O(1) lookups instead of O(n) array finds to improve performance
|
|
111
|
-
const
|
|
112
|
-
const includedEntities = baselineEntities.filter(e => !excludeSchemasSet.has(e.SchemaName.toLowerCase())); //only include entities that are NOT in the excludeSchemas list
|
|
113
|
-
const excludedEntities = baselineEntities.filter(e => excludeSchemasSet.has(e.SchemaName.toLowerCase())); //only include entities that ARE in the excludeSchemas list in this array
|
|
121
|
+
const includedEntities = entitiesNotInExcludedSchemas(baselineEntities, configInfo.excludeSchemas);
|
|
114
122
|
// Initialize temp batch files for each schema
|
|
115
123
|
// These will be populated as SQL is generated and will be used for actual execution
|
|
116
124
|
const schemas = Array.from(new Set(baselineEntities.map(e => e.SchemaName))).sort();
|
|
@@ -140,17 +148,17 @@ export class SQLCodeGenBase {
|
|
|
140
148
|
.filter(e => e !== undefined);
|
|
141
149
|
// Track per-batch success without short-circuiting. Historically,
|
|
142
150
|
// any single batch failure here returned `false` immediately, which
|
|
143
|
-
// skipped the cascade-regen and
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
151
|
+
// skipped the cascade-regen batch and left those entities with no
|
|
152
|
+
// SQL emitted at all. The post-run CRUD validator (in runCodeGen.ts)
|
|
153
|
+
// is the authoritative ship-gate now, so we want every in-scope
|
|
154
|
+
// batch to attempt its work and surface the complete picture of
|
|
155
|
+
// what's missing — partial generation is strictly more useful than
|
|
156
|
+
// nothing.
|
|
149
157
|
let entityGenSuccess = true;
|
|
150
158
|
// Build a UNION will-regenerate set across every batch we're about
|
|
151
|
-
// to launch (main + cascade-regen
|
|
152
|
-
//
|
|
153
|
-
//
|
|
159
|
+
// to launch (main + cascade-regen). The PG view fallback's
|
|
160
|
+
// restoreDependents path uses this to skip restoring captured
|
|
161
|
+
// dependents that codegen will recreate later in the run.
|
|
154
162
|
// Without the union, batch 1's fallback only sees batch 1's
|
|
155
163
|
// entities — so a dependent like vwAIAgentRuns (which lives in
|
|
156
164
|
// batch 2 cascade-regen) is "missing from willRegenerate", the
|
|
@@ -162,7 +170,6 @@ export class SQLCodeGenBase {
|
|
|
162
170
|
const allBatchEntities = [
|
|
163
171
|
...entitiesWithoutCascadeRegeneration,
|
|
164
172
|
...entitiesForCascadeRegeneration,
|
|
165
|
-
...excludedEntities,
|
|
166
173
|
];
|
|
167
174
|
const globalWillRegenerate = new Set(allBatchEntities
|
|
168
175
|
.filter(e => !!e.BaseView)
|
|
@@ -196,7 +203,7 @@ export class SQLCodeGenBase {
|
|
|
196
203
|
willRegenerate: globalWillRegenerate
|
|
197
204
|
}); // enable sql logging for NEW entities....
|
|
198
205
|
if (!genResult.Success) {
|
|
199
|
-
logError('Main entity SQL generation batch had failures — continuing with cascade-regen
|
|
206
|
+
logError('Main entity SQL generation batch had failures — continuing with cascade-regen; validator will report any missing routines.');
|
|
200
207
|
entityGenSuccess = false;
|
|
201
208
|
}
|
|
202
209
|
// Generate SQL for cascade delete regenerations in dependency order (sequentially)
|
|
@@ -219,23 +226,19 @@ export class SQLCodeGenBase {
|
|
|
219
226
|
}
|
|
220
227
|
genResult.Files.push(...cascadeGenResult.Files);
|
|
221
228
|
}
|
|
222
|
-
// STEP 2(c)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (!genResult2.Success) {
|
|
236
|
-
logError('Excluded-entities permissions batch had failures — continuing; validator will report any missing routines.');
|
|
237
|
-
entityGenSuccess = false;
|
|
238
|
-
}
|
|
229
|
+
// STEP 2(c) used to generate+apply GRANT files for excludeSchemas entities
|
|
230
|
+
// ("we don't want to generate SQL, we do want to generate the permissions").
|
|
231
|
+
// That is wrong once excludeSchemas means "not this CodeGen run":
|
|
232
|
+
//
|
|
233
|
+
// - Open Apps exclude sibling schemas (__mj_BizAppsCommon, …) and __mj.
|
|
234
|
+
// GRANT SELECT ON [__mj_BizAppsCommon].[vwContactMethods] then runs from
|
|
235
|
+
// the orders (or other) repo, pollutes SQL Scripts/generated/<sibling>/,
|
|
236
|
+
// and fails the whole run (exit 1) when that object is missing or lives
|
|
237
|
+
// in a schema the executing login does not resolve.
|
|
238
|
+
// - Core (__mj) permissions already ship in MJ migrations. Refreshing them
|
|
239
|
+
// from an app CodeGen is not this app's job.
|
|
240
|
+
//
|
|
241
|
+
// Permissions are applied in STEP 4 for includedEntities only.
|
|
239
242
|
if (entityGenSuccess) {
|
|
240
243
|
succeedSpinner(`Entity generation completed (${(new Date().getTime() - step2StartTime.getTime()) / 1000}s)`);
|
|
241
244
|
}
|
|
@@ -244,15 +247,15 @@ export class SQLCodeGenBase {
|
|
|
244
247
|
}
|
|
245
248
|
// STEP 2(d) now that we've generated the SQL, let's create a combined file in each schema sub-directory for convenience for a DBA
|
|
246
249
|
startSpinner('Creating combined SQL files...');
|
|
247
|
-
const allEntityFiles = this.createCombinedEntitySQLFiles(directory,
|
|
248
|
-
succeedSpinner(`Created combined SQL files for ${allEntityFiles.length}
|
|
250
|
+
const allEntityFiles = this.createCombinedEntitySQLFiles(directory, includedEntities);
|
|
251
|
+
succeedSpinner(`Created combined SQL files for ${allEntityFiles.length} file(s)`);
|
|
249
252
|
// STEP 2(e) ---- FINALLY, we execute SQL in proper dependency order
|
|
250
253
|
// Use temp batch files (which maintain CodeGen log order) if available, otherwise fall back to combined files
|
|
251
254
|
startSpinner('Executing entity SQL files...');
|
|
252
255
|
const step2eStartTime = new Date();
|
|
253
256
|
let executionSuccess = false;
|
|
254
257
|
if (useProviderPhasedExecution) {
|
|
255
|
-
// Per-entity phased execution already ran during
|
|
258
|
+
// Per-entity phased execution already ran during step 2(b).
|
|
256
259
|
// Skip the bulk file re-execution — replaying the same SQL here
|
|
257
260
|
// would re-trigger 42P16 without the phased recovery wrapper and
|
|
258
261
|
// undo the work the phased executor just did.
|
|
@@ -363,10 +366,13 @@ export class SQLCodeGenBase {
|
|
|
363
366
|
succeedSpinner('No valid entities to regenerate');
|
|
364
367
|
}
|
|
365
368
|
}
|
|
366
|
-
// STEP 4- Apply permissions
|
|
369
|
+
// STEP 4- Apply permissions for THIS run's schemas only. Excluded-schema
|
|
370
|
+
// GRANT files are not generated (see STEP 2(c) note) and must not be
|
|
371
|
+
// executed: a missing sibling object would fail CodeGen with
|
|
372
|
+
// "Cannot find the object 'vw…'" even though this app's work succeeded.
|
|
367
373
|
startSpinner('Applying permissions...');
|
|
368
374
|
const step4StartTime = new Date();
|
|
369
|
-
if (!await this.applyPermissions(pool, directory,
|
|
375
|
+
if (!await this.applyPermissions(pool, directory, includedEntities)) {
|
|
370
376
|
failSpinner('Failed to apply permissions');
|
|
371
377
|
overallSuccess = false;
|
|
372
378
|
}
|
|
@@ -925,7 +931,8 @@ export class SQLCodeGenBase {
|
|
|
925
931
|
return md.Entities.filter(e => modifiedOrNewNames.includes(e.Name) &&
|
|
926
932
|
!e.BaseViewGenerated &&
|
|
927
933
|
e.IncludeInAPI &&
|
|
928
|
-
!e.VirtualEntity
|
|
934
|
+
!e.VirtualEntity &&
|
|
935
|
+
entityInCustomBaseViewRefreshScope(e.SchemaName, configInfo.excludeSchemas ?? [], configInfo.includeSchemas));
|
|
929
936
|
}
|
|
930
937
|
/**
|
|
931
938
|
* Builds a combined SQL string of sp_refreshview statements for the given entities.
|
|
@@ -951,7 +958,14 @@ export class SQLCodeGenBase {
|
|
|
951
958
|
return outer; // fully custom — one view, and it is a standing prerequisite
|
|
952
959
|
}
|
|
953
960
|
const inner = this._dbProvider.generateViewRefreshSQL(e.SchemaName, e.GeneratedViewName);
|
|
954
|
-
|
|
961
|
+
const rebind = this._dbProvider.generateLayeredOuterRebindSQL(e);
|
|
962
|
+
const outerSql = outer.trim().length > 0
|
|
963
|
+
? this.guardOnApplicationOwnedView(e, outer)
|
|
964
|
+
: '';
|
|
965
|
+
const rebindSql = rebind.trim().length > 0
|
|
966
|
+
? this.guardOnApplicationOwnedView(e, rebind.trim())
|
|
967
|
+
: '';
|
|
968
|
+
return [inner, outerSql, rebindSql].filter((s) => s && s.trim().length > 0).join('\n');
|
|
955
969
|
})
|
|
956
970
|
.join('\n');
|
|
957
971
|
}
|
|
@@ -981,9 +995,21 @@ export class SQLCodeGenBase {
|
|
|
981
995
|
let baseViewChanged = false;
|
|
982
996
|
// Indexes for Fkeys for the table (skip for virtual entities — views can't have indexes)
|
|
983
997
|
if (!options.onlyPermissions && !options.entity.VirtualEntity) {
|
|
984
|
-
const
|
|
998
|
+
const forceIndexes = !!(configInfo.forceRegeneration?.enabled && configInfo.forceRegeneration?.indexes);
|
|
999
|
+
const shouldGenerateIndexes = autoIndexForeignKeys() || forceIndexes;
|
|
985
1000
|
const indexSQL = shouldGenerateIndexes ? this.generateIndexesForForeignKeys(options.pool, options.entity) : ''; // generate indexes if auto-indexing is on OR force regeneration is enabled
|
|
986
|
-
|
|
1001
|
+
// The soft-PK index rides in the same generated file (and therefore the same
|
|
1002
|
+
// migration log), but on its own setting — see generateIndexForSoftPrimaryKey.
|
|
1003
|
+
// Appended rather than prepended so the file's existing content stays
|
|
1004
|
+
// byte-identical for every entity that has no soft PK, which is nearly all of
|
|
1005
|
+
// them: writeFileIfChanged compares content, and reordering would rewrite the
|
|
1006
|
+
// whole tree for no functional gain.
|
|
1007
|
+
const softPKSQL = (autoIndexSoftPrimaryKeys() || forceIndexes)
|
|
1008
|
+
? this.generateIndexForSoftPrimaryKey(options.entity)
|
|
1009
|
+
: '';
|
|
1010
|
+
const s = this.generateSingleEntitySQLFileHeader(options.entity, 'Index for Foreign Keys')
|
|
1011
|
+
+ indexSQL
|
|
1012
|
+
+ (softPKSQL ? (indexSQL ? '\n\n' : '') + softPKSQL : '');
|
|
987
1013
|
if (options.writeFiles) {
|
|
988
1014
|
const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('index', options.entity.SchemaName, options.entity.BaseTable, false, true));
|
|
989
1015
|
this.writeFileIfChanged(filePath, s);
|
|
@@ -1324,6 +1350,19 @@ export class SQLCodeGenBase {
|
|
|
1324
1350
|
const indexStatements = this._dbProvider.generateForeignKeyIndexes(entity);
|
|
1325
1351
|
return indexStatements.join('\n\n');
|
|
1326
1352
|
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Generates the composite index covering the entity's SOFT primary key. Empty for every
|
|
1355
|
+
* entity with a real `PRIMARY KEY` constraint, which is nearly all of them.
|
|
1356
|
+
*
|
|
1357
|
+
* Kept separate from {@link generateIndexesForForeignKeys} and gated by its own setting: a
|
|
1358
|
+
* soft-PK index serves MJ's own per-record existence check on the create path, which is a
|
|
1359
|
+
* different concern from indexing foreign keys for joins and filters, and an operator's
|
|
1360
|
+
* opinion about one is not an opinion about the other.
|
|
1361
|
+
*/
|
|
1362
|
+
generateIndexForSoftPrimaryKey(entity) {
|
|
1363
|
+
const indexStatements = this._dbProvider.generateSoftPrimaryKeyIndex(entity);
|
|
1364
|
+
return indexStatements.join('\n\n');
|
|
1365
|
+
}
|
|
1327
1366
|
/**
|
|
1328
1367
|
* Detects self-referential foreign keys in an entity (e.g., ParentTaskID pointing back to Task table)
|
|
1329
1368
|
* Returns array of field info objects representing recursive relationships
|
|
@@ -1597,6 +1636,12 @@ export class SQLCodeGenBase {
|
|
|
1597
1636
|
const refreshSQL = this._dbProvider.generateViewRefreshSQL(entity.SchemaName, entity.BaseView);
|
|
1598
1637
|
sOutput += this.guardOnApplicationOwnedView(entity, refreshSQL) + separator;
|
|
1599
1638
|
}
|
|
1639
|
+
if (entity.HasLayeredBaseView && !entity.VirtualEntity) {
|
|
1640
|
+
const rebindSQL = this._dbProvider.generateLayeredOuterRebindSQL(entity);
|
|
1641
|
+
if (rebindSQL.trim().length > 0) {
|
|
1642
|
+
sOutput += this.guardOnApplicationOwnedView(entity, rebindSQL.trim()) + separator;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1600
1645
|
return sOutput + permissionsHeader + this.guardOnApplicationOwnedView(entity, permissionsBody) + separator;
|
|
1601
1646
|
}
|
|
1602
1647
|
/**
|
|
@@ -1949,6 +1994,9 @@ export class SQLCodeGenBase {
|
|
|
1949
1994
|
const md = new Metadata(); // global-provider-ok: codegen runs offline against a single provider
|
|
1950
1995
|
// Find all fields in other entities that are foreign keys to this entity
|
|
1951
1996
|
for (const e of md.Entities) {
|
|
1997
|
+
if (!shouldEmitCascadeForRelatedEntity(entity.SchemaName, e.SchemaName, configInfo.allowCrossSchemaCascadeDeletes === true)) {
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
1952
2000
|
for (const ef of e.Fields) {
|
|
1953
2001
|
if (UUIDsEqual(ef.RelatedEntityID, entity.ID) && ef.IsVirtual === false) {
|
|
1954
2002
|
const cascadeSql = await this.generateSingleCascadeOperation(entity, e, ef, pool);
|
|
@@ -2059,6 +2107,9 @@ export class SQLCodeGenBase {
|
|
|
2059
2107
|
const md = new Metadata(); // global-provider-ok: codegen runs offline against a single provider
|
|
2060
2108
|
// Find all fields in other entities that are foreign keys to this entity
|
|
2061
2109
|
for (const e of md.Entities) {
|
|
2110
|
+
if (!shouldEmitCascadeForRelatedEntity(entity.SchemaName, e.SchemaName, configInfo.allowCrossSchemaCascadeDeletes === true)) {
|
|
2111
|
+
continue;
|
|
2112
|
+
}
|
|
2062
2113
|
for (const ef of e.Fields) {
|
|
2063
2114
|
if (UUIDsEqual(ef.RelatedEntityID, entity.ID) && ef.IsVirtual === false) {
|
|
2064
2115
|
// Skip self-referential foreign keys (e.g., ParentID pointing to same entity)
|