@memberjunction/codegen-lib 5.51.0 → 6.1.0-edge.1
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/LICENSE +7 -0
- package/README.md +194 -0
- package/dist/Database/codeGenDatabaseProvider.d.ts +15 -0
- package/dist/Database/codeGenDatabaseProvider.d.ts.map +1 -1
- package/dist/Database/codeGenDatabaseProvider.js.map +1 -1
- package/dist/Database/manage-metadata.d.ts +22 -1
- package/dist/Database/manage-metadata.d.ts.map +1 -1
- package/dist/Database/manage-metadata.js +121 -5
- package/dist/Database/manage-metadata.js.map +1 -1
- package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.d.ts +37 -3
- package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.d.ts.map +1 -1
- package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.js +65 -4
- package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.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 +12 -1
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.js.map +1 -1
- package/dist/Database/providers/sqlserver/__tests__/LayeredBaseView.test.d.ts +2 -0
- package/dist/Database/providers/sqlserver/__tests__/LayeredBaseView.test.d.ts.map +1 -0
- package/dist/Database/providers/sqlserver/__tests__/LayeredBaseView.test.js +252 -0
- package/dist/Database/providers/sqlserver/__tests__/LayeredBaseView.test.js.map +1 -0
- package/dist/Database/search-guardrails.d.ts +146 -0
- package/dist/Database/search-guardrails.d.ts.map +1 -0
- package/dist/Database/search-guardrails.js +233 -0
- package/dist/Database/search-guardrails.js.map +1 -0
- package/dist/Database/sql.d.ts.map +1 -1
- package/dist/Database/sql.js +42 -10
- package/dist/Database/sql.js.map +1 -1
- package/dist/Database/sql_codegen.d.ts +40 -0
- package/dist/Database/sql_codegen.d.ts.map +1 -1
- package/dist/Database/sql_codegen.js +110 -21
- package/dist/Database/sql_codegen.js.map +1 -1
- package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts.map +1 -1
- package/dist/Manifest/GenerateClassRegistrationsManifest.js +25 -5
- package/dist/Manifest/GenerateClassRegistrationsManifest.js.map +1 -1
- package/dist/Misc/action_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/action_subclasses_codegen.js +13 -3
- package/dist/Misc/action_subclasses_codegen.js.map +1 -1
- package/dist/Misc/advanced_generation.d.ts.map +1 -1
- package/dist/Misc/advanced_generation.js +8 -1
- package/dist/Misc/advanced_generation.js.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.d.ts +81 -1
- package/dist/Misc/entity_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.js +234 -1
- package/dist/Misc/entity_subclasses_codegen.js.map +1 -1
- package/dist/Misc/graphql_server_codegen.d.ts.map +1 -1
- package/dist/Misc/graphql_server_codegen.js +13 -2
- package/dist/Misc/graphql_server_codegen.js.map +1 -1
- package/package.json +40 -35
|
@@ -12,6 +12,7 @@ import { SQLUtilityBase } from "./sql.js";
|
|
|
12
12
|
import { applyIncludeSchemaScope } from "./schema-scope.js";
|
|
13
13
|
import { AdvancedGeneration } from "../Misc/advanced_generation.js";
|
|
14
14
|
import { CodeGenReporter } from "../Misc/codegen-reporter.js";
|
|
15
|
+
import { applySearchableFieldsCap, entityLevelEnableBlockedReason, isNarrativeFieldName, normalizePredicate, normalizeSmartFieldResultShape, } from "./search-guardrails.js";
|
|
15
16
|
import { mapExternalNativeTypeToMJ } from "../Misc/externalTypeMapping.js";
|
|
16
17
|
import { SQLParser } from "@memberjunction/sql-parser";
|
|
17
18
|
import { createDisplayName, generatePluralName, MJGlobal, stripTrailingChars, UUIDsEqual } from "@memberjunction/global";
|
|
@@ -106,7 +107,7 @@ export class ManageMetadataBase {
|
|
|
106
107
|
}
|
|
107
108
|
/**
|
|
108
109
|
* Produces a schema-qualified object reference.
|
|
109
|
-
* SQL Server: [schema].[object], PostgreSQL: schema."object"
|
|
110
|
+
* SQL Server: [schema].[object], PostgreSQL: "schema"."object"
|
|
110
111
|
*/
|
|
111
112
|
qs(schema, object) {
|
|
112
113
|
return this.dialect.QuoteSchema(schema, object);
|
|
@@ -1297,6 +1298,7 @@ export class ManageMetadataBase {
|
|
|
1297
1298
|
await import('@memberjunction/external-data-source-sqlserver');
|
|
1298
1299
|
await import('@memberjunction/external-data-source-mysql');
|
|
1299
1300
|
await import('@memberjunction/external-data-source-oracle');
|
|
1301
|
+
await import('@memberjunction/external-data-source-databricks');
|
|
1300
1302
|
const router = MJGlobal.Instance.ClassFactory.CreateInstance(ExternalDataSourceReadRouter);
|
|
1301
1303
|
if (!router) {
|
|
1302
1304
|
logError(' Cannot sync external entity fields: no ExternalDataSourceReadRouter is registered. Ensure @memberjunction/external-data-sources (and the relevant driver) are loaded in the CodeGen process.');
|
|
@@ -4777,6 +4779,10 @@ export class ManageMetadataBase {
|
|
|
4777
4779
|
* Apply smart field identification results to entity fields and entity-level search configuration
|
|
4778
4780
|
*/
|
|
4779
4781
|
async applySmartFieldIdentification(pool, entity, fields, result) {
|
|
4782
|
+
// Run the guardrail pipeline once up front so every downstream applier
|
|
4783
|
+
// sees the same already-normalized result. See search-guardrails.ts for
|
|
4784
|
+
// the rules. Mutates result in place — see normalizeSearchFlagsInPlace.
|
|
4785
|
+
this.normalizeSearchFlagsInPlace(entity, fields, result);
|
|
4780
4786
|
const sqlStatements = [];
|
|
4781
4787
|
this.applyNameFieldUpdates(sqlStatements, fields, result);
|
|
4782
4788
|
this.applyDefaultInViewUpdates(sqlStatements, fields, result);
|
|
@@ -4796,6 +4802,100 @@ export class ManageMetadataBase {
|
|
|
4796
4802
|
}
|
|
4797
4803
|
}
|
|
4798
4804
|
}
|
|
4805
|
+
/**
|
|
4806
|
+
* Apply the code-level search guardrails to the LLM result, mutating
|
|
4807
|
+
* `result` in place so every downstream applier reads the normalized
|
|
4808
|
+
* version. The pure heuristics live in `search-guardrails.ts`; this
|
|
4809
|
+
* method wires them to the per-field metadata (Type, Length, IsPrimaryKey,
|
|
4810
|
+
* AutoUpdate flags) we have on `fields`.
|
|
4811
|
+
*
|
|
4812
|
+
* Order of operations:
|
|
4813
|
+
* 1. Shape cleanup (empty/contradictory states).
|
|
4814
|
+
* 2. Field-level eligibility — filter through `isFieldEligibleForUserSearch`
|
|
4815
|
+
* and the narrative-field-name blocklist.
|
|
4816
|
+
* 3. Per-entity cap.
|
|
4817
|
+
* 4. Predicate normalization — rewrite `Contains` to a default when the
|
|
4818
|
+
* field isn't FTS-backed, fill in defaults for missing entries.
|
|
4819
|
+
* 5. Telemetry — record proposed/accepted counts and any rewrites for
|
|
4820
|
+
* the end-of-run report.
|
|
4821
|
+
*/
|
|
4822
|
+
normalizeSearchFlagsInPlace(entity, fields, result) {
|
|
4823
|
+
const proposedSearchableCount = (result.searchableFields ?? []).length;
|
|
4824
|
+
const proposedAllowUserSearch = result.allowUserSearch === true;
|
|
4825
|
+
// 1. Shape cleanup (defensive — identifyFields() also runs this).
|
|
4826
|
+
const cleaned = normalizeSmartFieldResultShape(result);
|
|
4827
|
+
result.searchableFields = cleaned.searchableFields ?? [];
|
|
4828
|
+
result.searchPredicates = cleaned.searchPredicates ?? [];
|
|
4829
|
+
result.allowUserSearch = cleaned.allowUserSearch;
|
|
4830
|
+
// 2. Eligibility filter (PK / non-text / MAX-without-FTX / narrative).
|
|
4831
|
+
const ftxEnabled = !!entity.FullTextSearchEnabled;
|
|
4832
|
+
const fieldByName = new Map();
|
|
4833
|
+
for (const f of fields) {
|
|
4834
|
+
fieldByName.set(f.Name, f);
|
|
4835
|
+
}
|
|
4836
|
+
const eligible = [];
|
|
4837
|
+
let droppedNarrativeCount = 0;
|
|
4838
|
+
let droppedIneligibleCount = 0;
|
|
4839
|
+
for (const name of result.searchableFields) {
|
|
4840
|
+
const field = fieldByName.get(name);
|
|
4841
|
+
if (!field)
|
|
4842
|
+
continue; // already logged elsewhere
|
|
4843
|
+
if (!this.isFieldEligibleForUserSearch(field, ftxEnabled)) {
|
|
4844
|
+
droppedIneligibleCount += 1;
|
|
4845
|
+
continue;
|
|
4846
|
+
}
|
|
4847
|
+
if (isNarrativeFieldName(name) && !ftxEnabled) {
|
|
4848
|
+
droppedNarrativeCount += 1;
|
|
4849
|
+
continue;
|
|
4850
|
+
}
|
|
4851
|
+
eligible.push(name);
|
|
4852
|
+
}
|
|
4853
|
+
// 3. Per-entity cap.
|
|
4854
|
+
const { accepted, dropped: droppedByCap } = applySearchableFieldsCap(eligible);
|
|
4855
|
+
result.searchableFields = accepted;
|
|
4856
|
+
// 4. Predicate normalization. Build a fresh searchPredicates list keyed
|
|
4857
|
+
// on the accepted fields. The LLM's proposed predicates are the input
|
|
4858
|
+
// if present; missing entries get defaults.
|
|
4859
|
+
const proposedByName = new Map();
|
|
4860
|
+
for (const sp of result.searchPredicates ?? []) {
|
|
4861
|
+
if (sp && typeof sp.field === 'string') {
|
|
4862
|
+
proposedByName.set(sp.field, sp.predicate);
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
const ftsFieldSet = new Set(result.fullTextSearchFields ?? []);
|
|
4866
|
+
const normalizedPredicates = [];
|
|
4867
|
+
let predicatesRewrittenCount = 0;
|
|
4868
|
+
for (const fieldName of accepted) {
|
|
4869
|
+
const { predicate, rewritten } = normalizePredicate({
|
|
4870
|
+
fieldName,
|
|
4871
|
+
proposed: proposedByName.get(fieldName),
|
|
4872
|
+
isInFullTextSearchFields: ftsFieldSet.has(fieldName),
|
|
4873
|
+
entityFullTextSearchEnabled: ftxEnabled || result.enableFullTextSearch === true,
|
|
4874
|
+
});
|
|
4875
|
+
if (rewritten)
|
|
4876
|
+
predicatesRewrittenCount += 1;
|
|
4877
|
+
normalizedPredicates.push({ field: fieldName, predicate });
|
|
4878
|
+
}
|
|
4879
|
+
result.searchPredicates = normalizedPredicates;
|
|
4880
|
+
// If the cap or guardrails left us with no searchable fields, the
|
|
4881
|
+
// entity-level enable can't survive — re-run shape cleanup so
|
|
4882
|
+
// applyEntitySearchConfig sees a coherent result.
|
|
4883
|
+
if (accepted.length === 0 && result.allowUserSearch === true) {
|
|
4884
|
+
result.allowUserSearch = false;
|
|
4885
|
+
}
|
|
4886
|
+
// 5. Telemetry.
|
|
4887
|
+
const reporter = CodeGenReporter.Instance;
|
|
4888
|
+
reporter.counter('search.searchableFieldsProposed', proposedSearchableCount);
|
|
4889
|
+
reporter.counter('search.searchableFieldsAccepted', accepted.length);
|
|
4890
|
+
reporter.counter('search.searchableFieldsDroppedIneligible', droppedIneligibleCount);
|
|
4891
|
+
reporter.counter('search.searchableFieldsDroppedNarrative', droppedNarrativeCount);
|
|
4892
|
+
reporter.counter('search.searchableFieldsDroppedByCap', droppedByCap.length);
|
|
4893
|
+
reporter.counter('search.predicatesRewritten', predicatesRewrittenCount);
|
|
4894
|
+
if (proposedAllowUserSearch)
|
|
4895
|
+
reporter.counter('search.allowUserSearchProposed', 1);
|
|
4896
|
+
if (result.allowUserSearch === true)
|
|
4897
|
+
reporter.counter('search.allowUserSearchAcceptedSoFar', 1);
|
|
4898
|
+
}
|
|
4799
4899
|
/**
|
|
4800
4900
|
* Generate SQL UPDATEs for IsNameField — SINGLE-WINNER semantics.
|
|
4801
4901
|
*
|
|
@@ -5035,10 +5135,26 @@ export class ManageMetadataBase {
|
|
|
5035
5135
|
}
|
|
5036
5136
|
const newValue = result.allowUserSearch;
|
|
5037
5137
|
const currentValue = !!entity.AllowUserSearchAPI;
|
|
5038
|
-
//
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5138
|
+
// We never block a proposal to DISABLE search.
|
|
5139
|
+
if (newValue && !currentValue) {
|
|
5140
|
+
// Default-off semantics: a 0 → 1 flip requires the LLM to be highly
|
|
5141
|
+
// confident, requires at least one searchable field to have survived
|
|
5142
|
+
// the field-level guardrails, and refuses log/audit and detail/line-
|
|
5143
|
+
// item-shaped entities. Anything blocked here gets a one-line note in
|
|
5144
|
+
// the CodeGen run report so the team can audit proposals.
|
|
5145
|
+
const blocked = entityLevelEnableBlockedReason({
|
|
5146
|
+
entityName: entity.Name,
|
|
5147
|
+
confidence: result.confidence,
|
|
5148
|
+
acceptedSearchableFieldsCount: (result.searchableFields ?? []).length,
|
|
5149
|
+
});
|
|
5150
|
+
const reporter = CodeGenReporter.Instance;
|
|
5151
|
+
if (blocked) {
|
|
5152
|
+
reporter.counter('search.allowUserSearchEnableBlocked', 1);
|
|
5153
|
+
reporter.note(`[search] refused AllowUserSearchAPI 0→1 on '${entity.Name}': ${blocked}`);
|
|
5154
|
+
return;
|
|
5155
|
+
}
|
|
5156
|
+
reporter.counter('search.allowUserSearchEnableAccepted', 1);
|
|
5157
|
+
reporter.note(`[search] enabled AllowUserSearchAPI on '${entity.Name}': ${result.allowUserSearchReason ?? 'no reason given'}`);
|
|
5042
5158
|
}
|
|
5043
5159
|
if (newValue !== currentValue) {
|
|
5044
5160
|
sqlStatements.push(`
|