@memberjunction/codegen-lib 6.1.0-edge.4 → 6.1.0-edge.5
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/dist/Config/config.d.ts +172 -10
- package/dist/Config/config.d.ts.map +1 -1
- package/dist/Config/config.js +57 -1
- package/dist/Config/config.js.map +1 -1
- package/dist/Config/schema-output.d.ts +33 -0
- package/dist/Config/schema-output.d.ts.map +1 -0
- package/dist/Config/schema-output.js +52 -0
- package/dist/Config/schema-output.js.map +1 -0
- package/dist/Database/codeGenDatabaseProvider.d.ts +49 -0
- package/dist/Database/codeGenDatabaseProvider.d.ts.map +1 -1
- package/dist/Database/codeGenDatabaseProvider.js +105 -0
- package/dist/Database/codeGenDatabaseProvider.js.map +1 -1
- package/dist/Database/exclude-tables.d.ts +26 -0
- package/dist/Database/exclude-tables.d.ts.map +1 -0
- package/dist/Database/exclude-tables.js +38 -0
- package/dist/Database/exclude-tables.js.map +1 -0
- package/dist/Database/manage-metadata.d.ts +52 -2
- package/dist/Database/manage-metadata.d.ts.map +1 -1
- package/dist/Database/manage-metadata.js +95 -14
- package/dist/Database/manage-metadata.js.map +1 -1
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.d.ts +2 -0
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.d.ts.map +1 -1
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.js +10 -0
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.js.map +1 -1
- package/dist/Database/providers/sqlserver/__tests__/SQLServerCodeGenProvider.test.js +17 -0
- package/dist/Database/providers/sqlserver/__tests__/SQLServerCodeGenProvider.test.js.map +1 -1
- package/dist/Database/sql_codegen.d.ts +15 -0
- package/dist/Database/sql_codegen.d.ts.map +1 -1
- package/dist/Database/sql_codegen.js +55 -15
- package/dist/Database/sql_codegen.js.map +1 -1
- package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts +22 -0
- package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts.map +1 -1
- package/dist/Manifest/GenerateClassRegistrationsManifest.js +112 -5
- package/dist/Manifest/GenerateClassRegistrationsManifest.js.map +1 -1
- package/dist/Misc/codegen-reporter.d.ts +3 -0
- package/dist/Misc/codegen-reporter.d.ts.map +1 -1
- package/dist/Misc/codegen-reporter.js +3 -0
- package/dist/Misc/codegen-reporter.js.map +1 -1
- package/dist/Misc/emit-stats.d.ts +28 -0
- package/dist/Misc/emit-stats.d.ts.map +1 -0
- package/dist/Misc/emit-stats.js +43 -0
- package/dist/Misc/emit-stats.js.map +1 -0
- package/dist/Misc/entity_subclasses_codegen.d.ts +20 -2
- package/dist/Misc/entity_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.js +92 -33
- package/dist/Misc/entity_subclasses_codegen.js.map +1 -1
- package/dist/Misc/file-write.d.ts +15 -0
- package/dist/Misc/file-write.d.ts.map +1 -0
- package/dist/Misc/file-write.js +33 -0
- package/dist/Misc/file-write.js.map +1 -0
- package/dist/Misc/graphql_server_codegen.d.ts +12 -2
- package/dist/Misc/graphql_server_codegen.d.ts.map +1 -1
- package/dist/Misc/graphql_server_codegen.js +61 -12
- package/dist/Misc/graphql_server_codegen.js.map +1 -1
- package/dist/Misc/runCommand.d.ts +5 -0
- package/dist/Misc/runCommand.d.ts.map +1 -1
- package/dist/Misc/runCommand.js +43 -17
- package/dist/Misc/runCommand.js.map +1 -1
- package/dist/Misc/schema-emit.d.ts +123 -0
- package/dist/Misc/schema-emit.d.ts.map +1 -0
- package/dist/Misc/schema-emit.js +220 -0
- package/dist/Misc/schema-emit.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/runCodeGen.d.ts +20 -2
- package/dist/runCodeGen.d.ts.map +1 -1
- package/dist/runCodeGen.js +147 -21
- package/dist/runCodeGen.js.map +1 -1
- package/package.json +29 -29
|
@@ -283,6 +283,22 @@ export class ManageMetadataBase {
|
|
|
283
283
|
static get modifiedEntityList() {
|
|
284
284
|
return this._modifiedEntityList;
|
|
285
285
|
}
|
|
286
|
+
static { this._deletedEntitySchemaList = []; }
|
|
287
|
+
/**
|
|
288
|
+
* Schemas that lost an entity during this run.
|
|
289
|
+
*
|
|
290
|
+
* Deletion cannot be reported the way new/modified entities are. Those lists carry entity
|
|
291
|
+
* NAMES, which downstream code resolves to a schema by looking the entity up in live
|
|
292
|
+
* metadata — and a deleted entity is, by then, no longer there to look up. So the schema is
|
|
293
|
+
* captured here at deletion time instead.
|
|
294
|
+
*
|
|
295
|
+
* Without this the schema is never marked dirty, its per-schema file is never rebuilt, and
|
|
296
|
+
* the dead class keeps a live `@RegisterClass` registration on disk. The old monolith could
|
|
297
|
+
* not drift this way: it was rewritten in full every run.
|
|
298
|
+
*/
|
|
299
|
+
static get deletedEntitySchemaList() {
|
|
300
|
+
return this._deletedEntitySchemaList;
|
|
301
|
+
}
|
|
286
302
|
static { this._entitiesRequiringViewRegen = []; }
|
|
287
303
|
/**
|
|
288
304
|
* Entities that had late-phase changes requiring their base views to be
|
|
@@ -3260,6 +3276,11 @@ export class ManageMetadataBase {
|
|
|
3260
3276
|
const sqlDelete = this.dbProvider.callRoutineSQL(mj_core_schema(), 'spDeleteEntityWithCoreDependencies', [`'${e.ID}'`], ['EntityID'], true);
|
|
3261
3277
|
await this.LogSQLAndExecute(pool, sqlDelete, `SQL text to remove entity ${e.Name}`);
|
|
3262
3278
|
logStatus(` > Removed metadata for table ${e.SchemaName}.${e.BaseTable}`);
|
|
3279
|
+
// Recorded only after the delete succeeds, so a failed removal (logged below for
|
|
3280
|
+
// an admin to handle) does not mark a schema dirty for an entity still present.
|
|
3281
|
+
if (e.SchemaName && e.SchemaName.trim().length > 0) {
|
|
3282
|
+
ManageMetadataBase._deletedEntitySchemaList.push(e.SchemaName.trim());
|
|
3283
|
+
}
|
|
3263
3284
|
// next up we need to remove the spCreate, spDelete, spUpdate, BaseView, and FullTextSearchFunction, if provided.
|
|
3264
3285
|
// We only remoe these artifcacts when they are generated which is info we have in the BaseViewGenerated, spCreateGenerated, etc. fields
|
|
3265
3286
|
await this.checkDropSQLObject(pool, e.BaseViewGenerated, 'view', e.SchemaName, e.BaseView);
|
|
@@ -3897,14 +3918,20 @@ export class ManageMetadataBase {
|
|
|
3897
3918
|
if (ag.featureEnabled('EntityDescriptions')) {
|
|
3898
3919
|
// we have the feature enabled, so let's loop through the new entities and generate descriptions for them
|
|
3899
3920
|
for (let e of ManageMetadataBase.newEntityList) {
|
|
3900
|
-
|
|
3921
|
+
// Every literal in this function is doubled-quote escaped. The description below is
|
|
3922
|
+
// FREE TEXT FROM AN LLM — prose about business entities contains apostrophes as a
|
|
3923
|
+
// matter of course, and one unescaped quote aborts the whole CodeGen run with
|
|
3924
|
+
// "Unclosed quotation mark" (SQL Server) at the very end of an otherwise-complete
|
|
3925
|
+
// pass. '' doubling is correct on both supported dialects.
|
|
3926
|
+
const esc = (s) => s.replace(/'/g, "''");
|
|
3927
|
+
const dataResult = await this.runQuery(pool, `SELECT * FROM ${this.qs(mj_core_schema(), 'vwEntities')} WHERE Name = '${esc(e)}'`);
|
|
3901
3928
|
const data = dataResult.recordset;
|
|
3902
3929
|
const fieldsResult = await this.runQuery(pool, `SELECT * FROM ${this.qs(mj_core_schema(), 'vwEntityFields')} WHERE EntityID='${data[0].ID}'`);
|
|
3903
3930
|
const fields = fieldsResult.recordset;
|
|
3904
3931
|
// Use new API to generate entity description
|
|
3905
3932
|
const result = await ag.generateEntityDescription(e, data[0].BaseTable, fields.map((f) => ({ Name: f.Name, Type: f.Type, IsNullable: f.AllowsNull, Description: f.Description })), currentUser);
|
|
3906
3933
|
if (result?.entityDescription && result.entityDescription.length > 0) {
|
|
3907
|
-
const sSQL = `UPDATE ${this.qs(mj_core_schema(), 'Entity')} SET Description = '${result.entityDescription}' WHERE Name = '${e}'`;
|
|
3934
|
+
const sSQL = `UPDATE ${this.qs(mj_core_schema(), 'Entity')} SET Description = '${esc(result.entityDescription)}' WHERE Name = '${esc(e)}'`;
|
|
3908
3935
|
await this.LogSQLAndExecute(pool, sSQL, `SQL text to update entity description for entity ${e}`);
|
|
3909
3936
|
}
|
|
3910
3937
|
else {
|
|
@@ -4119,14 +4146,28 @@ export class ManageMetadataBase {
|
|
|
4119
4146
|
* following INSERT can use the real BaseView column_id without colliding on
|
|
4120
4147
|
* UQ_EntityField_EntityID_Sequence. The +100000 band is unique-safe; the
|
|
4121
4148
|
* subsequent spUpdateExistingEntityFieldsFromSchema rewrite brings every row
|
|
4122
|
-
* (parked and new) back to live catalog order.
|
|
4123
|
-
*
|
|
4149
|
+
* (parked and new) back to live catalog order.
|
|
4150
|
+
*
|
|
4151
|
+
* Parks ONLY when nothing on the entity is parked yet, which is what makes a second
|
|
4152
|
+
* emission for the same entity in the same run a no-op. `Sequence < 100000` alone does
|
|
4153
|
+
* not achieve that: after the first park the rows below the band are precisely the ones
|
|
4154
|
+
* the first pass just INSERTED at their catalog ordinals, so a second park lifts THOSE
|
|
4155
|
+
* into the band — onto the row the first park moved from the same ordinal, and the
|
|
4156
|
+
* migration dies on UQ_EntityField_EntityID_Sequence with a duplicate at 100000+ordinal.
|
|
4157
|
+
* Reachable whenever an entity gains fields in both CodeGen passes: pass 1 for the real
|
|
4158
|
+
* columns, pass 2 for the denormalized name column a new foreign key introduces.
|
|
4124
4159
|
*/
|
|
4125
4160
|
parkEntityFieldSequencesSQL(entityID) {
|
|
4126
|
-
|
|
4161
|
+
const table = this.qs(mj_core_schema(), 'EntityField');
|
|
4162
|
+
return `UPDATE ${table}
|
|
4127
4163
|
SET ${this.qi('Sequence')} = ${this.qi('Sequence')} + 100000
|
|
4128
4164
|
WHERE ${this.qi('EntityID')} = '${entityID}'
|
|
4129
|
-
AND ${this.qi('Sequence')} < 100000
|
|
4165
|
+
AND ${this.qi('Sequence')} < 100000
|
|
4166
|
+
AND NOT EXISTS (
|
|
4167
|
+
SELECT 1 FROM ${table}
|
|
4168
|
+
WHERE ${this.qi('EntityID')} = '${entityID}'
|
|
4169
|
+
AND ${this.qi('Sequence')} >= 100000
|
|
4170
|
+
);`;
|
|
4130
4171
|
}
|
|
4131
4172
|
parseDefaultValue(sqlDefaultValue) {
|
|
4132
4173
|
if (sqlDefaultValue === null || sqlDefaultValue === undefined) {
|
|
@@ -4998,15 +5039,12 @@ export class ManageMetadataBase {
|
|
|
4998
5039
|
// process a single new entity
|
|
4999
5040
|
let newEntityName = await this.createNewEntityName(newEntity, currentUser);
|
|
5000
5041
|
const newEntityDisplayName = this.createNewEntityDisplayName(newEntity, newEntityName);
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
if (existingEntity || existingEntityInNewEntityList) {
|
|
5005
|
-
// the generated name is already in place, so we need another name
|
|
5006
|
-
suffix = '__' + newEntity.SchemaName;
|
|
5007
|
-
newEntityName = newEntityName + suffix;
|
|
5008
|
-
LogError(` >>>> WARNING: Entity name already exists, so using ${newEntityName} instead. If you did not intend for this, please rename the ${newEntity.SchemaName}.${newEntity.TableName} table in the database.`);
|
|
5042
|
+
const { name: uniqueName, suffix: uniqueSuffix } = this.resolveUniqueEntityName(newEntityName, newEntity.SchemaName, md.Entities.map(e => e.Name));
|
|
5043
|
+
if (uniqueName !== newEntityName) {
|
|
5044
|
+
LogError(` >>>> WARNING: Entity name already exists, so using ${uniqueName} instead. If you did not intend for this, please rename the ${newEntity.SchemaName}.${newEntity.TableName} table in the database.`);
|
|
5009
5045
|
}
|
|
5046
|
+
newEntityName = uniqueName;
|
|
5047
|
+
const suffix = uniqueSuffix;
|
|
5010
5048
|
const isNewSchema = await this.isSchemaNew(pool, newEntity.SchemaName);
|
|
5011
5049
|
const newEntityID = this.createNewUUID();
|
|
5012
5050
|
const sSQLInsert = this.createNewEntityInsertSQL(newEntityID, newEntityName, newEntity, suffix, newEntityDisplayName);
|
|
@@ -5562,6 +5600,49 @@ export class ManageMetadataBase {
|
|
|
5562
5600
|
}
|
|
5563
5601
|
return revoked;
|
|
5564
5602
|
}
|
|
5603
|
+
/**
|
|
5604
|
+
* Pick an entity name that is actually free, disambiguating with the schema name and, if
|
|
5605
|
+
* that is still taken, a counter.
|
|
5606
|
+
*
|
|
5607
|
+
* Entity names are generated from the table name with trailing discriminators stripped, so
|
|
5608
|
+
* distinct tables routinely generate the SAME name — NetSuite's customlist72, customlist74,
|
|
5609
|
+
* customlist160, customlist436, customlist534 and customlist873 all generate "Custom Lists".
|
|
5610
|
+
*
|
|
5611
|
+
* The previous logic appended the schema suffix ONCE and assumed the result was unique. It
|
|
5612
|
+
* is not: the first table took "Custom Lists", and every table after it appended the same
|
|
5613
|
+
* "__netsuite" and produced the identical "Custom Lists__netsuite". The second was a
|
|
5614
|
+
* duplicate-key failure on UQ_Entity_Name and the rest were never created — a silent loss of
|
|
5615
|
+
* 8 entities on one NetSuite tenant, reported only as repeated identical INSERT errors.
|
|
5616
|
+
*
|
|
5617
|
+
* Comparison is case-INSENSITIVE on both sides, matching UQ_Entity_Name's collation. The old
|
|
5618
|
+
* in-run check used an exact `===` while the metadata check beside it lowercased, so two
|
|
5619
|
+
* names differing only in case read as free and then collided on INSERT.
|
|
5620
|
+
*
|
|
5621
|
+
* @param desiredName the generated name to place
|
|
5622
|
+
* @param schemaName schema, used for the first disambiguation step
|
|
5623
|
+
* @param takenNames every name already spoken for — existing metadata AND names claimed
|
|
5624
|
+
* earlier in this run (which are not in metadata yet)
|
|
5625
|
+
* @returns the free name and the suffix used to reach it ('' when the name was already free)
|
|
5626
|
+
*/
|
|
5627
|
+
resolveUniqueEntityName(desiredName, schemaName, takenNames) {
|
|
5628
|
+
const taken = new Set([...takenNames, ...ManageMetadataBase.newEntityList].map(n => n.toLowerCase()));
|
|
5629
|
+
const isTaken = (candidate) => taken.has(candidate.toLowerCase());
|
|
5630
|
+
if (!isTaken(desiredName)) {
|
|
5631
|
+
return { name: desiredName, suffix: '' };
|
|
5632
|
+
}
|
|
5633
|
+
let suffix = '__' + schemaName;
|
|
5634
|
+
let name = desiredName + suffix;
|
|
5635
|
+
// Bounded: thousands of tables collapsing to one name is a naming problem no suffix can
|
|
5636
|
+
// fix, and looping forever would hide it. Let the INSERT fail loudly instead.
|
|
5637
|
+
const maxAttempts = 1000;
|
|
5638
|
+
let attempt = 2;
|
|
5639
|
+
while (isTaken(name) && attempt <= maxAttempts) {
|
|
5640
|
+
suffix = `__${schemaName}_${attempt}`;
|
|
5641
|
+
name = desiredName + suffix;
|
|
5642
|
+
attempt++;
|
|
5643
|
+
}
|
|
5644
|
+
return { name, suffix };
|
|
5645
|
+
}
|
|
5565
5646
|
createNewEntityInsertSQL(newEntityUUID, newEntityName, newEntity, newEntitySuffix, newEntityDisplayName) {
|
|
5566
5647
|
const newEntityDefaults = configInfo.newEntityDefaults;
|
|
5567
5648
|
const newEntityDescriptionEscaped = newEntity.EntityDescription ? `'${newEntity.EntityDescription.replace(/'/g, "''")}'` : null;
|