@memberjunction/core-entities-server 5.48.0 → 5.50.0

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.
Files changed (41) hide show
  1. package/dist/custom/MJConversationDetailEntityServer.server.d.ts +20 -1
  2. package/dist/custom/MJConversationDetailEntityServer.server.d.ts.map +1 -1
  3. package/dist/custom/MJConversationDetailEntityServer.server.js +38 -9
  4. package/dist/custom/MJConversationDetailEntityServer.server.js.map +1 -1
  5. package/dist/custom/MJListDetailEntityServer.server.d.ts +31 -0
  6. package/dist/custom/MJListDetailEntityServer.server.d.ts.map +1 -0
  7. package/dist/custom/MJListDetailEntityServer.server.js +85 -0
  8. package/dist/custom/MJListDetailEntityServer.server.js.map +1 -0
  9. package/dist/custom/MJListEntityServer.server.d.ts +18 -0
  10. package/dist/custom/MJListEntityServer.server.d.ts.map +1 -0
  11. package/dist/custom/MJListEntityServer.server.js +42 -0
  12. package/dist/custom/MJListEntityServer.server.js.map +1 -0
  13. package/dist/custom/MJMLTrainingPipelineEntityServer.server.d.ts +62 -0
  14. package/dist/custom/MJMLTrainingPipelineEntityServer.server.d.ts.map +1 -0
  15. package/dist/custom/MJMLTrainingPipelineEntityServer.server.js +174 -0
  16. package/dist/custom/MJMLTrainingPipelineEntityServer.server.js.map +1 -0
  17. package/dist/custom/MJQueryEntityServer.server.d.ts +35 -0
  18. package/dist/custom/MJQueryEntityServer.server.d.ts.map +1 -1
  19. package/dist/custom/MJQueryEntityServer.server.js +69 -1
  20. package/dist/custom/MJQueryEntityServer.server.js.map +1 -1
  21. package/dist/custom/MJQuerySQLEntityServer.server.d.ts +32 -0
  22. package/dist/custom/MJQuerySQLEntityServer.server.d.ts.map +1 -0
  23. package/dist/custom/MJQuerySQLEntityServer.server.js +93 -0
  24. package/dist/custom/MJQuerySQLEntityServer.server.js.map +1 -0
  25. package/dist/custom/query-extraction/parse.d.ts +3 -3
  26. package/dist/custom/query-extraction/parse.d.ts.map +1 -1
  27. package/dist/custom/query-extraction/parse.js +4 -5
  28. package/dist/custom/query-extraction/parse.js.map +1 -1
  29. package/dist/custom/query-extraction/pipeline.js +2 -2
  30. package/dist/custom/query-extraction/pipeline.js.map +1 -1
  31. package/dist/custom/query-extraction/sync.d.ts +2 -2
  32. package/dist/custom/query-extraction/sync.d.ts.map +1 -1
  33. package/dist/custom/query-extraction/sync.js +26 -6
  34. package/dist/custom/query-extraction/sync.js.map +1 -1
  35. package/dist/custom/query-extraction/types.d.ts +5 -1
  36. package/dist/custom/query-extraction/types.d.ts.map +1 -1
  37. package/dist/index.d.ts +4 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +4 -0
  40. package/dist/index.js.map +1 -1
  41. package/package.json +26 -25
@@ -5,7 +5,13 @@ import { MJConversationDetailEntity } from "@memberjunction/core-entities";
5
5
  * when the original message content has been modified.
6
6
  *
7
7
  * When a user edits their message after initial creation, the OriginalMessageChanged
8
- * flag is set to true, allowing the UI to display an "(Edited)" indicator.
8
+ * flag is set to true, allowing the UI to display an "(Edited)" indicator. This is
9
+ * also the edit signal for cross-turn conversation compaction
10
+ * (plans/agent-conversation-compaction.md §6): a row edited after it was folded into
11
+ * a persisted summary keeps the flag, Record Changes already holds the diff, and the
12
+ * UI can derive "edited after summarization" from the flag plus the row's Sequence
13
+ * being below the current summary boundary — no summary regeneration required (the
14
+ * summary is explicitly lossy; agents page in exact rows via the retrieval tools).
9
15
  */
10
16
  export declare class MJConversationDetailEntityServer extends MJConversationDetailEntity {
11
17
  /**
@@ -13,5 +19,18 @@ export declare class MJConversationDetailEntityServer extends MJConversationDeta
13
19
  * This is done as pre-processing before calling super.Save() to ensure it's a single DB round trip.
14
20
  */
15
21
  Save(options?: EntitySaveOptions): Promise<boolean>;
22
+ /**
23
+ * True when this save is a genuine Message EDIT on an existing record — not one of
24
+ * the framework's own Message rewrites:
25
+ * - New records never qualify (also covers the agent-response placeholder INSERT).
26
+ * - Agent progress updates rewrite Message while the row's Status is 'In-Progress' — skipped.
27
+ * - The run's finalization save rewrites Message together with a Status transition — a
28
+ * dirty Status marks the save as lifecycle, not an edit — skipped.
29
+ *
30
+ * Note: the original implementation inverted the IsSaved check (`!this.IsSaved`
31
+ * selects NEW records), which combined with the OldValue guard meant the flag could
32
+ * never be set — fixed as part of the compaction edit-handling work.
33
+ */
34
+ ShouldFlagOriginalMessageChanged(): boolean;
16
35
  }
17
36
  //# sourceMappingURL=MJConversationDetailEntityServer.server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MJConversationDetailEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJConversationDetailEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAE3E;;;;;;GAMG;AACH,qBACa,gCAAiC,SAAQ,0BAA0B;IAC5E;;;OAGG;IACY,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;CAarE"}
1
+ {"version":3,"file":"MJConversationDetailEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJConversationDetailEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAE3E;;;;;;;;;;;;GAYG;AACH,qBACa,gCAAiC,SAAQ,0BAA0B;IAC5E;;;OAGG;IACY,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAQlE;;;;;;;;;;;OAWG;IACI,gCAAgC,IAAI,OAAO;CAiBrD"}
@@ -12,7 +12,13 @@ import { MJConversationDetailEntity } from "@memberjunction/core-entities";
12
12
  * when the original message content has been modified.
13
13
  *
14
14
  * When a user edits their message after initial creation, the OriginalMessageChanged
15
- * flag is set to true, allowing the UI to display an "(Edited)" indicator.
15
+ * flag is set to true, allowing the UI to display an "(Edited)" indicator. This is
16
+ * also the edit signal for cross-turn conversation compaction
17
+ * (plans/agent-conversation-compaction.md §6): a row edited after it was folded into
18
+ * a persisted summary keeps the flag, Record Changes already holds the diff, and the
19
+ * UI can derive "edited after summarization" from the flag plus the row's Sequence
20
+ * being below the current summary boundary — no summary regeneration required (the
21
+ * summary is explicitly lossy; agents page in exact rows via the retrieval tools).
16
22
  */
17
23
  let MJConversationDetailEntityServer = class MJConversationDetailEntityServer extends MJConversationDetailEntity {
18
24
  /**
@@ -20,17 +26,40 @@ let MJConversationDetailEntityServer = class MJConversationDetailEntityServer ex
20
26
  * This is done as pre-processing before calling super.Save() to ensure it's a single DB round trip.
21
27
  */
22
28
  async Save(options) {
23
- // Only check for message changes on existing records (not new records)
24
- if (!this.IsSaved) {
25
- // Check if the Message field is dirty (has been modified)
26
- const messageField = this.GetFieldByName('Message');
27
- if (messageField && messageField.Dirty && messageField.OldValue !== undefined) {
28
- // Message has been changed on an existing record - set the flag
29
- this.OriginalMessageChanged = true;
30
- }
29
+ if (this.ShouldFlagOriginalMessageChanged()) {
30
+ this.OriginalMessageChanged = true;
31
31
  }
32
32
  return super.Save(options);
33
33
  }
34
+ /**
35
+ * True when this save is a genuine Message EDIT on an existing record — not one of
36
+ * the framework's own Message rewrites:
37
+ * - New records never qualify (also covers the agent-response placeholder INSERT).
38
+ * - Agent progress updates rewrite Message while the row's Status is 'In-Progress' — skipped.
39
+ * - The run's finalization save rewrites Message together with a Status transition — a
40
+ * dirty Status marks the save as lifecycle, not an edit — skipped.
41
+ *
42
+ * Note: the original implementation inverted the IsSaved check (`!this.IsSaved`
43
+ * selects NEW records), which combined with the OldValue guard meant the flag could
44
+ * never be set — fixed as part of the compaction edit-handling work.
45
+ */
46
+ ShouldFlagOriginalMessageChanged() {
47
+ if (!this.IsSaved) {
48
+ return false;
49
+ }
50
+ const messageField = this.GetFieldByName('Message');
51
+ if (!messageField || !messageField.Dirty || messageField.OldValue === undefined) {
52
+ return false;
53
+ }
54
+ if (this.Status === 'In-Progress') {
55
+ return false;
56
+ }
57
+ const statusField = this.GetFieldByName('Status');
58
+ if (statusField?.Dirty) {
59
+ return false;
60
+ }
61
+ return true;
62
+ }
34
63
  };
35
64
  MJConversationDetailEntityServer = __decorate([
36
65
  RegisterClass(BaseEntity, "MJ: Conversation Details")
@@ -1 +1 @@
1
- {"version":3,"file":"MJConversationDetailEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJConversationDetailEntityServer.server.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAqB,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAE3E;;;;;;GAMG;AAEI,IAAM,gCAAgC,GAAtC,MAAM,gCAAiC,SAAQ,0BAA0B;IAC5E;;;OAGG;IACM,KAAK,CAAC,IAAI,CAAC,OAA2B;QAC3C,uEAAuE;QACvE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAChB,0DAA0D;YAC1D,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;YACpD,IAAI,YAAY,IAAI,YAAY,CAAC,KAAK,IAAI,YAAY,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC5E,gEAAgE;gBAChE,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC;YACvC,CAAC;QACL,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;CACJ,CAAA;AAlBY,gCAAgC;IAD5C,aAAa,CAAC,UAAU,EAAE,0BAA0B,CAAC;GACzC,gCAAgC,CAkB5C"}
1
+ {"version":3,"file":"MJConversationDetailEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJConversationDetailEntityServer.server.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAqB,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAE3E;;;;;;;;;;;;GAYG;AAEI,IAAM,gCAAgC,GAAtC,MAAM,gCAAiC,SAAQ,0BAA0B;IAC5E;;;OAGG;IACM,KAAK,CAAC,IAAI,CAAC,OAA2B;QAC3C,IAAI,IAAI,CAAC,gCAAgC,EAAE,EAAE,CAAC;YAC1C,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC;QACvC,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;;;;;;;OAWG;IACI,gCAAgC;QACnC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAChB,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QACpD,IAAI,CAAC,YAAY,IAAI,CAAC,YAAY,CAAC,KAAK,IAAI,YAAY,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC9E,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;YAChC,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,WAAW,EAAE,KAAK,EAAE,CAAC;YACrB,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAA;AA1CY,gCAAgC;IAD5C,aAAa,CAAC,UAAU,EAAE,0BAA0B,CAAC;GACzC,gCAAgC,CA0C5C"}
@@ -0,0 +1,31 @@
1
+ import { EntityDeleteOptions } from '@memberjunction/core';
2
+ import { MJListDetailEntityExtended } from '@memberjunction/core-entities';
3
+ /**
4
+ * Server-side List Details entity. Enforces the SAME row-level DELETE authorization as
5
+ * {@link MJListEntityServer}, but scoped through the parent List's owner: a user may delete a
6
+ * List Detail only if they own the List it belongs to, UNLESS they hold the Developer or Integration
7
+ * role (who may delete any). The rule itself lives once in {@link MJListEntityExtended.UserCanDelete}
8
+ * and is shared with the Lists entity and the UI — a single source of truth for "who can delete".
9
+ *
10
+ * Why this is needed: the `EntityPermission` migration grants the UI role entity-wide Delete on BOTH
11
+ * Lists AND List Details (so users can manage their own lists). Lists get owner-scoping in
12
+ * `MJListEntityServer`; without this parallel override a UI user could delete the membership rows of
13
+ * lists they DON'T own, because a List Detail carries no owner of its own — only a `ListID`.
14
+ *
15
+ * The owner is resolved by looking up the parent List's `UserID`. If the parent List can't be found
16
+ * (an orphaned detail, or the parent already deleted during list teardown — `spDeleteList` does not
17
+ * cascade, so details are removed while the parent still exists in the normal path) the rule
18
+ * fails OPEN: there is no owner to protect and the coarse entity permission still applies. Enforcement
19
+ * only bites when a parent List genuinely exists and is owned by someone else.
20
+ */
21
+ export declare class MJListDetailEntityServer extends MJListDetailEntityExtended {
22
+ Delete(options?: EntityDeleteOptions): Promise<boolean>;
23
+ /**
24
+ * Looks up the `UserID` (owner) of this detail's parent List. Returns `null` when it can't be
25
+ * positively determined (no `ListID`, parent not found, or the lookup failed) so the caller can
26
+ * fail open rather than block a legitimate/cascade delete. A read-only `simple` RunView limited to
27
+ * the single `UserID` field keeps this cheap.
28
+ */
29
+ private resolveParentListOwnerUserID;
30
+ }
31
+ //# sourceMappingURL=MJListDetailEntityServer.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MJListDetailEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJListDetailEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,mBAAmB,EAAY,MAAM,sBAAsB,CAAC;AAEnG,OAAO,EAAE,0BAA0B,EAAwB,MAAM,+BAA+B,CAAC;AAEjG;;;;;;;;;;;;;;;;;GAiBG;AACH,qBACa,wBAAyB,SAAQ,0BAA0B;IAC9C,MAAM,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;IAoB7E;;;;;OAKG;YACW,4BAA4B;CA2B7C"}
@@ -0,0 +1,85 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { BaseEntity, BaseEntityResult, LogError } from '@memberjunction/core';
8
+ import { RegisterClass } from '@memberjunction/global';
9
+ import { MJListDetailEntityExtended, MJListEntityExtended } from '@memberjunction/core-entities';
10
+ /**
11
+ * Server-side List Details entity. Enforces the SAME row-level DELETE authorization as
12
+ * {@link MJListEntityServer}, but scoped through the parent List's owner: a user may delete a
13
+ * List Detail only if they own the List it belongs to, UNLESS they hold the Developer or Integration
14
+ * role (who may delete any). The rule itself lives once in {@link MJListEntityExtended.UserCanDelete}
15
+ * and is shared with the Lists entity and the UI — a single source of truth for "who can delete".
16
+ *
17
+ * Why this is needed: the `EntityPermission` migration grants the UI role entity-wide Delete on BOTH
18
+ * Lists AND List Details (so users can manage their own lists). Lists get owner-scoping in
19
+ * `MJListEntityServer`; without this parallel override a UI user could delete the membership rows of
20
+ * lists they DON'T own, because a List Detail carries no owner of its own — only a `ListID`.
21
+ *
22
+ * The owner is resolved by looking up the parent List's `UserID`. If the parent List can't be found
23
+ * (an orphaned detail, or the parent already deleted during list teardown — `spDeleteList` does not
24
+ * cascade, so details are removed while the parent still exists in the normal path) the rule
25
+ * fails OPEN: there is no owner to protect and the coarse entity permission still applies. Enforcement
26
+ * only bites when a parent List genuinely exists and is owned by someone else.
27
+ */
28
+ let MJListDetailEntityServer = class MJListDetailEntityServer extends MJListDetailEntityExtended {
29
+ async Delete(options) {
30
+ const ownerUserID = await this.resolveParentListOwnerUserID();
31
+ // Only enforce when we could positively identify the owning user. A null owner means the
32
+ // parent List wasn't found (orphan / already-deleted parent) — nothing to protect, fall open.
33
+ if (ownerUserID != null &&
34
+ !MJListEntityExtended.UserCanDelete(ownerUserID, this.ContextCurrentUser)) {
35
+ const result = new BaseEntityResult();
36
+ result.StartedAt = new Date();
37
+ result.Success = false;
38
+ result.Type = 'delete';
39
+ result.Message = 'You do not have permission to delete this List item. Only the owner of ' +
40
+ 'the parent List, or a user in the Developer or Integration role, may delete it.';
41
+ result.EndedAt = new Date();
42
+ this.RegisterResultHistoryEntry(result);
43
+ return false;
44
+ }
45
+ return super.Delete(options);
46
+ }
47
+ /**
48
+ * Looks up the `UserID` (owner) of this detail's parent List. Returns `null` when it can't be
49
+ * positively determined (no `ListID`, parent not found, or the lookup failed) so the caller can
50
+ * fail open rather than block a legitimate/cascade delete. A read-only `simple` RunView limited to
51
+ * the single `UserID` field keeps this cheap.
52
+ */
53
+ async resolveParentListOwnerUserID() {
54
+ if (!this.ListID) {
55
+ return null;
56
+ }
57
+ try {
58
+ // Use the provider bound to THIS entity instance (not the global default) so the lookup
59
+ // targets the correct server/tenant in multi-provider setups — mirrors the sibling
60
+ // MJListDetailEntityExtended.Save() and CLAUDE.md's per-provider rule.
61
+ const rv = this.RunViewProviderToUse;
62
+ const result = await rv.RunView({
63
+ EntityName: 'MJ: Lists',
64
+ ExtraFilter: `ID = '${this.ListID}'`,
65
+ Fields: ['ID', 'UserID'],
66
+ MaxRows: 1,
67
+ ResultType: 'simple'
68
+ }, this.ContextCurrentUser);
69
+ if (result.Success && result.Results && result.Results.length > 0) {
70
+ return result.Results[0].UserID ?? null;
71
+ }
72
+ }
73
+ catch (e) {
74
+ // Fail open on a lookup error — don't block deletes on a transient read failure. The
75
+ // security-relevant case (parent exists, owned by someone else) resolves normally.
76
+ LogError(`MJListDetailEntityServer: failed to resolve parent List owner for ListID '${this.ListID}': ${e}`);
77
+ }
78
+ return null;
79
+ }
80
+ };
81
+ MJListDetailEntityServer = __decorate([
82
+ RegisterClass(BaseEntity, 'MJ: List Details')
83
+ ], MJListDetailEntityServer);
84
+ export { MJListDetailEntityServer };
85
+ //# sourceMappingURL=MJListDetailEntityServer.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MJListDetailEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJListDetailEntityServer.server.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAuB,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACnG,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAEjG;;;;;;;;;;;;;;;;;GAiBG;AAEI,IAAM,wBAAwB,GAA9B,MAAM,wBAAyB,SAAQ,0BAA0B;IACpD,KAAK,CAAC,MAAM,CAAC,OAA6B;QACtD,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,4BAA4B,EAAE,CAAC;QAE9D,yFAAyF;QACzF,8FAA8F;QAC9F,IAAI,WAAW,IAAI,IAAI;YACnB,CAAC,oBAAoB,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,kBAAkB,CAAC,EAAE,CAAC;YAC5E,MAAM,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;YACtC,MAAM,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;YAC9B,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC;YACvB,MAAM,CAAC,IAAI,GAAG,QAAQ,CAAC;YACvB,MAAM,CAAC,OAAO,GAAG,yEAAyE;gBACtF,iFAAiF,CAAC;YACtF,MAAM,CAAC,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,0BAA0B,CAAC,MAAM,CAAC,CAAC;YACxC,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,4BAA4B;QACtC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACf,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,IAAI,CAAC;YACD,wFAAwF;YACxF,mFAAmF;YACnF,uEAAuE;YACvE,MAAM,EAAE,GAAG,IAAI,CAAC,oBAAoB,CAAC;YACrC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAiC;gBAC5D,UAAU,EAAE,WAAW;gBACvB,WAAW,EAAE,SAAS,IAAI,CAAC,MAAM,GAAG;gBACpC,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC;gBACxB,OAAO,EAAE,CAAC;gBACV,UAAU,EAAE,QAAQ;aACvB,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;YAE5B,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAChE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC;YAC5C,CAAC;QACL,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,qFAAqF;YACrF,mFAAmF;YACnF,QAAQ,CAAC,6EAA6E,IAAI,CAAC,MAAM,MAAM,CAAC,EAAE,CAAC,CAAC;QAChH,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAA;AAtDY,wBAAwB;IADpC,aAAa,CAAC,UAAU,EAAE,kBAAkB,CAAC;GACjC,wBAAwB,CAsDpC"}
@@ -0,0 +1,18 @@
1
+ import { EntityDeleteOptions } from '@memberjunction/core';
2
+ import { MJListEntityExtended } from '@memberjunction/core-entities';
3
+ /**
4
+ * Server-side Lists entity. Enforces row-level DELETE authorization: a user may delete a List only
5
+ * if they own it, UNLESS they hold the Developer or Integration role (who may delete any List). The
6
+ * rule itself lives once in {@link MJListEntityExtended.UserCanDelete} and is shared with the UI.
7
+ *
8
+ * This is the security-critical enforcement point. The `EntityPermission` migration grants the UI
9
+ * role entity-wide Delete permission on Lists (so standard users can delete their own); this override
10
+ * narrows that back down to owner-or-privileged at the application layer, since coarse entity
11
+ * permissions can't express row-level ownership. On denial it returns `false` and records a
12
+ * `BaseEntityResult` so callers get `LatestResult.CompleteMessage` (Delete does not throw on a
13
+ * logical rejection — see the CLAUDE.md Save/Delete error-handling contract).
14
+ */
15
+ export declare class MJListEntityServer extends MJListEntityExtended {
16
+ Delete(options?: EntityDeleteOptions): Promise<boolean>;
17
+ }
18
+ //# sourceMappingURL=MJListEntityServer.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MJListEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJListEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEzF,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE;;;;;;;;;;;GAWG;AACH,qBACa,kBAAmB,SAAQ,oBAAoB;IAClC,MAAM,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;CAchF"}
@@ -0,0 +1,42 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { BaseEntity, BaseEntityResult } from '@memberjunction/core';
8
+ import { RegisterClass } from '@memberjunction/global';
9
+ import { MJListEntityExtended } from '@memberjunction/core-entities';
10
+ /**
11
+ * Server-side Lists entity. Enforces row-level DELETE authorization: a user may delete a List only
12
+ * if they own it, UNLESS they hold the Developer or Integration role (who may delete any List). The
13
+ * rule itself lives once in {@link MJListEntityExtended.UserCanDelete} and is shared with the UI.
14
+ *
15
+ * This is the security-critical enforcement point. The `EntityPermission` migration grants the UI
16
+ * role entity-wide Delete permission on Lists (so standard users can delete their own); this override
17
+ * narrows that back down to owner-or-privileged at the application layer, since coarse entity
18
+ * permissions can't express row-level ownership. On denial it returns `false` and records a
19
+ * `BaseEntityResult` so callers get `LatestResult.CompleteMessage` (Delete does not throw on a
20
+ * logical rejection — see the CLAUDE.md Save/Delete error-handling contract).
21
+ */
22
+ let MJListEntityServer = class MJListEntityServer extends MJListEntityExtended {
23
+ async Delete(options) {
24
+ if (!MJListEntityExtended.UserCanDelete(this.UserID, this.ContextCurrentUser)) {
25
+ const result = new BaseEntityResult();
26
+ result.StartedAt = new Date();
27
+ result.Success = false;
28
+ result.Type = 'delete';
29
+ result.Message = 'You do not have permission to delete this List. Only the List owner, ' +
30
+ 'or a user in the Developer or Integration role, may delete it.';
31
+ result.EndedAt = new Date();
32
+ this.RegisterResultHistoryEntry(result);
33
+ return false;
34
+ }
35
+ return super.Delete(options);
36
+ }
37
+ };
38
+ MJListEntityServer = __decorate([
39
+ RegisterClass(BaseEntity, 'MJ: Lists')
40
+ ], MJListEntityServer);
41
+ export { MJListEntityServer };
42
+ //# sourceMappingURL=MJListEntityServer.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MJListEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJListEntityServer.server.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAuB,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE;;;;;;;;;;;GAWG;AAEI,IAAM,kBAAkB,GAAxB,MAAM,kBAAmB,SAAQ,oBAAoB;IACxC,KAAK,CAAC,MAAM,CAAC,OAA6B;QACtD,IAAI,CAAC,oBAAoB,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,kBAAkB,CAAC,EAAE,CAAC;YAC5E,MAAM,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;YACtC,MAAM,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;YAC9B,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC;YACvB,MAAM,CAAC,IAAI,GAAG,QAAQ,CAAC;YACvB,MAAM,CAAC,OAAO,GAAG,uEAAuE;gBACpF,gEAAgE,CAAC;YACrE,MAAM,CAAC,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,0BAA0B,CAAC,MAAM,CAAC,CAAC;YACxC,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;CACJ,CAAA;AAfY,kBAAkB;IAD9B,aAAa,CAAC,UAAU,EAAE,WAAW,CAAC;GAC1B,kBAAkB,CAe9B"}
@@ -0,0 +1,62 @@
1
+ import { ValidationResult } from '@memberjunction/core';
2
+ import { MJMLTrainingPipelineEntity } from '@memberjunction/core-entities';
3
+ /**
4
+ * Server-side ML Training Pipeline entity. Its job is to make the leakage guard
5
+ * impossible to disarm by accident.
6
+ *
7
+ * The guard's `DenyFields` list keeps target-leaking columns out of the feature
8
+ * matrix. Its worst failure mode is silent: a deny entry that matches no column
9
+ * looks exactly like a deny entry that is armed, so training "succeeds" on leaked
10
+ * features and the model looks brilliant. That is precisely what happened when a
11
+ * user pasted a bracketed list into the editor and got
12
+ * `DenyFields: ["[CheckInTime", ..., "Status]"]` — the two most dangerous columns
13
+ * were left completely unguarded, and the save was accepted.
14
+ *
15
+ * So this is the authoritative gate: nothing malformed, and nothing that matches
16
+ * no real column, reaches the database. The rules themselves live in
17
+ * `@memberjunction/predictive-studio-core` so the editor can apply the identical
18
+ * checks at input time without the two ever drifting apart.
19
+ */
20
+ export declare class MJMLTrainingPipelineEntityServer extends MJMLTrainingPipelineEntity {
21
+ get DefaultSkipAsyncValidation(): boolean;
22
+ ValidateAsync(): Promise<ValidationResult>;
23
+ /**
24
+ * Whether the leakage guard needs re-checking on this save. It does on insert,
25
+ * and whenever the guard or the source bindings it is validated against change.
26
+ */
27
+ private needsLeakageGuardCheck;
28
+ /**
29
+ * Parse the `LeakageGuard` JSON column. A null/blank guard is legitimate (no
30
+ * deny-list configured) and yields an empty guard rather than an error;
31
+ * malformed JSON is a hard failure, because a guard we cannot read is a guard
32
+ * we cannot enforce.
33
+ *
34
+ * @returns the parsed guard, or `null` when a parse error was recorded on `result`
35
+ */
36
+ private parseLeakageGuard;
37
+ /**
38
+ * Collect the columns a deny entry could legitimately name, across the target
39
+ * entity and every bound source.
40
+ *
41
+ * `ColumnsFullyResolved` is the important part. Only `Entity` sources expose an
42
+ * enumerable column list; a Query, external entity, vector set, or upstream
43
+ * feature pipeline does not. If any such source is bound we cannot prove a deny
44
+ * entry matches nothing, so the semantic check must stand down — otherwise a
45
+ * perfectly valid entry naming a column on that source would be wrongly rejected.
46
+ * The structural check still runs either way, and that is what catches the
47
+ * pasted-list bug.
48
+ */
49
+ private buildValidationContext;
50
+ /** Field names of an entity, which are the column names a deny entry may name. */
51
+ private columnsOf;
52
+ /** Parse the `SourceBindings` JSON column, tolerating null/blank/garbage. */
53
+ private parseSourceBindings;
54
+ /** Fold guard issues into the entity's ValidationResult. */
55
+ private applyIssues;
56
+ }
57
+ /**
58
+ * Tree-shaking guard — dynamic ClassFactory instantiation is invisible to the
59
+ * bundler, so this keeps the registration alive.
60
+ */
61
+ export declare function LoadMJMLTrainingPipelineEntityServer(): void;
62
+ //# sourceMappingURL=MJMLTrainingPipelineEntityServer.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MJMLTrainingPipelineEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJMLTrainingPipelineEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuF,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7I,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAU3E;;;;;;;;;;;;;;;;GAgBG;AACH,qBACa,gCAAiC,SAAQ,0BAA0B;IAE5E,IAAoB,0BAA0B,IAAI,OAAO,CAExD;IAEqB,aAAa,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAkBhE;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAQ9B;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IA4BzB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,sBAAsB;IAkC9B,kFAAkF;IAClF,OAAO,CAAC,SAAS;IAIjB,6EAA6E;IAC7E,OAAO,CAAC,mBAAmB;IAe3B,4DAA4D;IAC5D,OAAO,CAAC,WAAW;CAStB;AAED;;;GAGG;AACH,wBAAgB,oCAAoC,IAAI,IAAI,CAE3D"}
@@ -0,0 +1,174 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { BaseEntity, ValidationErrorInfo, ValidationErrorType } from '@memberjunction/core';
8
+ import { RegisterClass } from '@memberjunction/global';
9
+ import { MJMLTrainingPipelineEntity } from '@memberjunction/core-entities';
10
+ import { validateLeakageGuard, DOMINANCE_THRESHOLD_DEFAULT, } from '@memberjunction/predictive-studio-core';
11
+ /**
12
+ * Server-side ML Training Pipeline entity. Its job is to make the leakage guard
13
+ * impossible to disarm by accident.
14
+ *
15
+ * The guard's `DenyFields` list keeps target-leaking columns out of the feature
16
+ * matrix. Its worst failure mode is silent: a deny entry that matches no column
17
+ * looks exactly like a deny entry that is armed, so training "succeeds" on leaked
18
+ * features and the model looks brilliant. That is precisely what happened when a
19
+ * user pasted a bracketed list into the editor and got
20
+ * `DenyFields: ["[CheckInTime", ..., "Status]"]` — the two most dangerous columns
21
+ * were left completely unguarded, and the save was accepted.
22
+ *
23
+ * So this is the authoritative gate: nothing malformed, and nothing that matches
24
+ * no real column, reaches the database. The rules themselves live in
25
+ * `@memberjunction/predictive-studio-core` so the editor can apply the identical
26
+ * checks at input time without the two ever drifting apart.
27
+ */
28
+ let MJMLTrainingPipelineEntityServer = class MJMLTrainingPipelineEntityServer extends MJMLTrainingPipelineEntity {
29
+ get DefaultSkipAsyncValidation() {
30
+ return false;
31
+ }
32
+ async ValidateAsync() {
33
+ const result = await super.ValidateAsync();
34
+ if (!result.Success)
35
+ return result;
36
+ // Fast-path: the guard can only become newly unsound when the guard itself,
37
+ // or the sources its entries are checked against, are being changed.
38
+ if (!this.needsLeakageGuardCheck())
39
+ return result;
40
+ const guard = this.parseLeakageGuard(result);
41
+ if (guard === null)
42
+ return result; // parse failure already recorded
43
+ const context = this.buildValidationContext();
44
+ const issues = validateLeakageGuard(guard, context);
45
+ this.applyIssues(result, issues);
46
+ return result;
47
+ }
48
+ /**
49
+ * Whether the leakage guard needs re-checking on this save. It does on insert,
50
+ * and whenever the guard or the source bindings it is validated against change.
51
+ */
52
+ needsLeakageGuardCheck() {
53
+ if (!this.IsSaved)
54
+ return true;
55
+ const guardDirty = this.GetFieldByName('LeakageGuard')?.Dirty ?? false;
56
+ const bindingsDirty = this.GetFieldByName('SourceBindings')?.Dirty ?? false;
57
+ const targetDirty = this.GetFieldByName('TargetEntityID')?.Dirty ?? false;
58
+ return guardDirty || bindingsDirty || targetDirty;
59
+ }
60
+ /**
61
+ * Parse the `LeakageGuard` JSON column. A null/blank guard is legitimate (no
62
+ * deny-list configured) and yields an empty guard rather than an error;
63
+ * malformed JSON is a hard failure, because a guard we cannot read is a guard
64
+ * we cannot enforce.
65
+ *
66
+ * @returns the parsed guard, or `null` when a parse error was recorded on `result`
67
+ */
68
+ parseLeakageGuard(result) {
69
+ const raw = this.LeakageGuard;
70
+ if (raw == null || raw.trim().length === 0) {
71
+ return { DenyFields: [], SingleFeatureDominanceThreshold: DOMINANCE_THRESHOLD_DEFAULT };
72
+ }
73
+ try {
74
+ const parsed = JSON.parse(raw);
75
+ return {
76
+ DenyFields: parsed.DenyFields ?? [],
77
+ DenySources: parsed.DenySources,
78
+ SingleFeatureDominanceThreshold: parsed.SingleFeatureDominanceThreshold ?? DOMINANCE_THRESHOLD_DEFAULT,
79
+ };
80
+ }
81
+ catch (e) {
82
+ const message = e instanceof Error ? e.message : String(e);
83
+ result.Errors.push(new ValidationErrorInfo('LeakageGuard', `LeakageGuard is not valid JSON (${message}). A guard that cannot be read cannot be enforced, ` +
84
+ `so the pipeline would train with no leakage protection at all.`, raw, ValidationErrorType.Failure));
85
+ result.Success = false;
86
+ return null;
87
+ }
88
+ }
89
+ /**
90
+ * Collect the columns a deny entry could legitimately name, across the target
91
+ * entity and every bound source.
92
+ *
93
+ * `ColumnsFullyResolved` is the important part. Only `Entity` sources expose an
94
+ * enumerable column list; a Query, external entity, vector set, or upstream
95
+ * feature pipeline does not. If any such source is bound we cannot prove a deny
96
+ * entry matches nothing, so the semantic check must stand down — otherwise a
97
+ * perfectly valid entry naming a column on that source would be wrongly rejected.
98
+ * The structural check still runs either way, and that is what catches the
99
+ * pasted-list bug.
100
+ */
101
+ buildValidationContext() {
102
+ const bindings = this.parseSourceBindings();
103
+ const provider = this.ProviderToUse;
104
+ const columns = [];
105
+ let fullyResolved = true;
106
+ const targetEntity = this.TargetEntityID ? provider?.EntityByID(this.TargetEntityID) : undefined;
107
+ if (targetEntity) {
108
+ columns.push(...this.columnsOf(targetEntity));
109
+ }
110
+ else if (this.TargetEntityID) {
111
+ fullyResolved = false; // target entity is set but unresolvable
112
+ }
113
+ for (const binding of bindings) {
114
+ if (binding.Kind !== 'Entity') {
115
+ fullyResolved = false; // columns of non-entity sources aren't enumerable here
116
+ continue;
117
+ }
118
+ const entity = provider?.EntityByName(binding.Ref);
119
+ if (!entity) {
120
+ fullyResolved = false;
121
+ continue;
122
+ }
123
+ columns.push(...this.columnsOf(entity));
124
+ }
125
+ return {
126
+ KnownColumns: columns,
127
+ KnownSources: bindings.map((b) => b.Ref),
128
+ ColumnsFullyResolved: fullyResolved,
129
+ };
130
+ }
131
+ /** Field names of an entity, which are the column names a deny entry may name. */
132
+ columnsOf(entity) {
133
+ return entity.Fields.map((f) => f.Name);
134
+ }
135
+ /** Parse the `SourceBindings` JSON column, tolerating null/blank/garbage. */
136
+ parseSourceBindings() {
137
+ const raw = this.SourceBindings;
138
+ if (raw == null || raw.trim().length === 0)
139
+ return [];
140
+ try {
141
+ const parsed = JSON.parse(raw);
142
+ return Array.isArray(parsed) ? parsed : [];
143
+ }
144
+ catch {
145
+ // A malformed SourceBindings column is not this validator's problem to
146
+ // report, but it does mean we cannot enumerate columns — treat as none,
147
+ // which makes the context not-fully-resolved and stands the semantic
148
+ // check down rather than producing bogus "matches no column" errors.
149
+ return [];
150
+ }
151
+ }
152
+ /** Fold guard issues into the entity's ValidationResult. */
153
+ applyIssues(result, issues) {
154
+ for (const issue of issues) {
155
+ const type = issue.Severity === 'Failure' ? ValidationErrorType.Failure : ValidationErrorType.Warning;
156
+ result.Errors.push(new ValidationErrorInfo('LeakageGuard', issue.Message, issue.Value, type));
157
+ if (issue.Severity === 'Failure') {
158
+ result.Success = false;
159
+ }
160
+ }
161
+ }
162
+ };
163
+ MJMLTrainingPipelineEntityServer = __decorate([
164
+ RegisterClass(BaseEntity, 'MJ: ML Training Pipelines')
165
+ ], MJMLTrainingPipelineEntityServer);
166
+ export { MJMLTrainingPipelineEntityServer };
167
+ /**
168
+ * Tree-shaking guard — dynamic ClassFactory instantiation is invisible to the
169
+ * bundler, so this keeps the registration alive.
170
+ */
171
+ export function LoadMJMLTrainingPipelineEntityServer() {
172
+ // no-op
173
+ }
174
+ //# sourceMappingURL=MJMLTrainingPipelineEntityServer.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MJMLTrainingPipelineEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJMLTrainingPipelineEntityServer.server.ts"],"names":[],"mappings":"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"}
@@ -22,10 +22,45 @@ export declare class MJQueryEntityServer extends MJQueryEntityExtended {
22
22
  protected GenerateCompositeEmbedding(): Promise<void>;
23
23
  Save(options?: EntitySaveOptions): Promise<boolean>;
24
24
  Delete(options?: EntitySaveOptions): Promise<boolean>;
25
+ /**
26
+ * Returns the database platform of the connected database.
27
+ * Uses the DB_PLATFORM env var (same source as MJServer's provider selection).
28
+ */
29
+ private get CurrentPlatform();
30
+ /**
31
+ * Resolves the SQL to use for extraction based on the current database platform.
32
+ *
33
+ * Resolution order:
34
+ * 1. MJ: Query SQLs record matching the current platform's dialect → use that SQL
35
+ * 2. Base Query.SQL field → fall back if no dialect variant exists
36
+ *
37
+ * After resolution, validates the SQL is compatible with the current platform's
38
+ * dialect by attempting a parse. Logs a warning if the SQL appears to be for a
39
+ * different dialect (e.g., T-SQL bracket identifiers on a PostgreSQL database).
40
+ */
41
+ private resolveExtractionSQL;
42
+ /**
43
+ * Best-effort dialect validation: attempts to parse the SQL with the current platform's
44
+ * dialect. If parsing throws, logs a warning — the SQL may be for a different dialect.
45
+ *
46
+ * This is a heuristic, not a guarantee: node-sql-parser is permissive on many
47
+ * dialect-specific functions (ISNULL, GETDATE pass in both dialects), so a passing
48
+ * parse does not prove the SQL is correct for this dialect. But a failing parse is a
49
+ * strong signal of incompatibility.
50
+ */
51
+ private validateDialectCompatibility;
25
52
  /**
26
53
  * Builds the QuerySyncContext from this entity instance for use by the extraction pipeline.
54
+ * Resolves the SQL to the appropriate dialect variant for the connected database.
27
55
  */
28
56
  private buildSyncContext;
57
+ /**
58
+ * Re-runs the extraction pipeline on this query. Called by MJQuerySQLEntityServer
59
+ * when a dialect variant matching the current platform is saved — this handles
60
+ * the timing issue where the parent Query is saved before its QuerySQL children
61
+ * exist in the cache.
62
+ */
63
+ RerunExtraction(): Promise<void>;
29
64
  /**
30
65
  * Runs the extraction pipeline and saves the UsesTemplate flag.
31
66
  * On error, sets UsesTemplate=false and attempts to save so the query remains usable.
@@ -1 +1 @@
1
- {"version":3,"file":"MJQueryEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJQueryEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,iBAAiB,EAGjB,qBAAqB,EACxB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,qBAAqB,EAAmC,MAAM,+BAA+B,CAAC;AAUzH;;;;GAIG;AACH,qBACa,mBAAoB,SAAQ,qBAAqB;IAC1D;;;yEAGqE;IAC9D,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAI5C;;OAEG;cACsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAI5F;;;OAGG;cACa,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC;IAuB5C,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAwCnD,MAAM,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBpE;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAaxB;;;OAGG;YACW,uBAAuB;IAsBrC;;OAEG;YACW,sBAAsB;IAmBpC;;;OAGG;YACW,wBAAwB;IAiCtC;;OAEG;YACW,sBAAsB;IA4BpC;;OAEG;YACW,oBAAoB;IA4BlC;;OAEG;YACW,6BAA6B;CAgB9C"}
1
+ {"version":3,"file":"MJQueryEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJQueryEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,iBAAiB,EAGjB,qBAAqB,EAExB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,qBAAqB,EAAmC,MAAM,+BAA+B,CAAC;AAazH;;;;GAIG;AACH,qBACa,mBAAoB,SAAQ,qBAAqB;IAC1D;;;yEAGqE;IAC9D,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAI5C;;OAEG;cACsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAI5F;;;OAGG;cACa,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC;IAuB5C,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAwCnD,MAAM,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBpE;;;OAGG;IACH,OAAO,KAAK,eAAe,GAE1B;IAED;;;;;;;;;;OAUG;IACH,OAAO,CAAC,oBAAoB;IAmB5B;;;;;;;;OAQG;IACH,OAAO,CAAC,4BAA4B;IAgBpC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAcxB;;;;;OAKG;IACU,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7C;;;OAGG;YACW,uBAAuB;IAsBrC;;OAEG;YACW,sBAAsB;IAmBpC;;;OAGG;YACW,wBAAwB;IAiCtC;;OAEG;YACW,sBAAsB;IA4BpC;;OAEG;YACW,oBAAoB;IA4BlC;;OAEG;YACW,6BAA6B;CAgB9C"}