@memberjunction/core-entities-server 6.2.0-edge.6 → 6.2.0-edge.8

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 (179) hide show
  1. package/README.md +155 -100
  2. package/dist/custom/AnchoredConversationReadSecurityHook.d.ts +22 -0
  3. package/dist/custom/AnchoredConversationReadSecurityHook.d.ts.map +1 -0
  4. package/dist/custom/AnchoredConversationReadSecurityHook.js +293 -0
  5. package/dist/custom/AnchoredConversationReadSecurityHook.js.map +1 -0
  6. package/dist/custom/MJAIAgentExampleEntityServer.server.d.ts +2 -0
  7. package/dist/custom/MJAIAgentExampleEntityServer.server.d.ts.map +1 -1
  8. package/dist/custom/MJAIAgentExampleEntityServer.server.js +8 -5
  9. package/dist/custom/MJAIAgentExampleEntityServer.server.js.map +1 -1
  10. package/dist/custom/MJAIAgentNoteEntityServer.server.d.ts +2 -0
  11. package/dist/custom/MJAIAgentNoteEntityServer.server.d.ts.map +1 -1
  12. package/dist/custom/MJAIAgentNoteEntityServer.server.js +8 -5
  13. package/dist/custom/MJAIAgentNoteEntityServer.server.js.map +1 -1
  14. package/dist/custom/MJAIAgentRelationshipEntityServer.server.d.ts +17 -0
  15. package/dist/custom/MJAIAgentRelationshipEntityServer.server.d.ts.map +1 -0
  16. package/dist/custom/MJAIAgentRelationshipEntityServer.server.js +71 -0
  17. package/dist/custom/MJAIAgentRelationshipEntityServer.server.js.map +1 -0
  18. package/dist/custom/MJAIAgentSessionEntityServer.server.d.ts +8 -22
  19. package/dist/custom/MJAIAgentSessionEntityServer.server.d.ts.map +1 -1
  20. package/dist/custom/MJAIAgentSessionEntityServer.server.js +24 -1
  21. package/dist/custom/MJAIAgentSessionEntityServer.server.js.map +1 -1
  22. package/dist/custom/MJAIAgentUserPolicyEntityServer.server.d.ts +23 -0
  23. package/dist/custom/MJAIAgentUserPolicyEntityServer.server.d.ts.map +1 -0
  24. package/dist/custom/MJAIAgentUserPolicyEntityServer.server.js +53 -0
  25. package/dist/custom/MJAIAgentUserPolicyEntityServer.server.js.map +1 -0
  26. package/dist/custom/MJAIPromptEntityServer.server.d.ts.map +1 -1
  27. package/dist/custom/MJAIPromptEntityServer.server.js +2 -1
  28. package/dist/custom/MJAIPromptEntityServer.server.js.map +1 -1
  29. package/dist/custom/MJBrandKitEntityServer.server.d.ts +14 -0
  30. package/dist/custom/MJBrandKitEntityServer.server.d.ts.map +1 -0
  31. package/dist/custom/MJBrandKitEntityServer.server.js +75 -0
  32. package/dist/custom/MJBrandKitEntityServer.server.js.map +1 -0
  33. package/dist/custom/MJCompanyIntegrationEntityServer.server.d.ts.map +1 -1
  34. package/dist/custom/MJCompanyIntegrationEntityServer.server.js +1 -0
  35. package/dist/custom/MJCompanyIntegrationEntityServer.server.js.map +1 -1
  36. package/dist/custom/MJComponentEntityServer.server.d.ts +2 -0
  37. package/dist/custom/MJComponentEntityServer.server.d.ts.map +1 -1
  38. package/dist/custom/MJComponentEntityServer.server.js +5 -1
  39. package/dist/custom/MJComponentEntityServer.server.js.map +1 -1
  40. package/dist/custom/MJConversationDetailEntityServer.server.d.ts +20 -30
  41. package/dist/custom/MJConversationDetailEntityServer.server.d.ts.map +1 -1
  42. package/dist/custom/MJConversationDetailEntityServer.server.js +89 -35
  43. package/dist/custom/MJConversationDetailEntityServer.server.js.map +1 -1
  44. package/dist/custom/MJConversationEntityServer.server.d.ts +19 -4
  45. package/dist/custom/MJConversationEntityServer.server.d.ts.map +1 -1
  46. package/dist/custom/MJConversationEntityServer.server.js +169 -11
  47. package/dist/custom/MJConversationEntityServer.server.js.map +1 -1
  48. package/dist/custom/MJEntityDocumentEntityServer.server.d.ts.map +1 -1
  49. package/dist/custom/MJEntityDocumentEntityServer.server.js +1 -0
  50. package/dist/custom/MJEntityDocumentEntityServer.server.js.map +1 -1
  51. package/dist/custom/MJListDetailEntityServer.server.d.ts.map +1 -1
  52. package/dist/custom/MJListDetailEntityServer.server.js +1 -0
  53. package/dist/custom/MJListDetailEntityServer.server.js.map +1 -1
  54. package/dist/custom/MJProjectEntityServer.server.d.ts +21 -0
  55. package/dist/custom/MJProjectEntityServer.server.d.ts.map +1 -0
  56. package/dist/custom/MJProjectEntityServer.server.js +106 -0
  57. package/dist/custom/MJProjectEntityServer.server.js.map +1 -0
  58. package/dist/custom/MJQueryEntityServer.server.d.ts +177 -47
  59. package/dist/custom/MJQueryEntityServer.server.d.ts.map +1 -1
  60. package/dist/custom/MJQueryEntityServer.server.js +479 -164
  61. package/dist/custom/MJQueryEntityServer.server.js.map +1 -1
  62. package/dist/custom/MJQuerySQLEntityServer.server.d.ts +15 -18
  63. package/dist/custom/MJQuerySQLEntityServer.server.d.ts.map +1 -1
  64. package/dist/custom/MJQuerySQLEntityServer.server.js +34 -42
  65. package/dist/custom/MJQuerySQLEntityServer.server.js.map +1 -1
  66. package/dist/custom/MJTagEntityServer.server.d.ts +2 -0
  67. package/dist/custom/MJTagEntityServer.server.d.ts.map +1 -1
  68. package/dist/custom/MJTagEntityServer.server.js +13 -7
  69. package/dist/custom/MJTagEntityServer.server.js.map +1 -1
  70. package/dist/custom/MJUserRoutineEntityServer.server.d.ts.map +1 -1
  71. package/dist/custom/MJUserRoutineEntityServer.server.js +8 -1
  72. package/dist/custom/MJUserRoutineEntityServer.server.js.map +1 -1
  73. package/dist/custom/RestrictedFieldCommentClassifier.d.ts +73 -0
  74. package/dist/custom/RestrictedFieldCommentClassifier.d.ts.map +1 -0
  75. package/dist/custom/RestrictedFieldCommentClassifier.js +240 -0
  76. package/dist/custom/RestrictedFieldCommentClassifier.js.map +1 -0
  77. package/dist/custom/query-extraction/childRowWriter.d.ts +77 -0
  78. package/dist/custom/query-extraction/childRowWriter.d.ts.map +1 -0
  79. package/dist/custom/query-extraction/childRowWriter.js +277 -0
  80. package/dist/custom/query-extraction/childRowWriter.js.map +1 -0
  81. package/dist/custom/query-extraction/compositionTargets.d.ts +40 -0
  82. package/dist/custom/query-extraction/compositionTargets.d.ts.map +1 -0
  83. package/dist/custom/query-extraction/compositionTargets.js +99 -0
  84. package/dist/custom/query-extraction/compositionTargets.js.map +1 -0
  85. package/dist/custom/query-extraction/declared.d.ts +79 -0
  86. package/dist/custom/query-extraction/declared.d.ts.map +1 -0
  87. package/dist/custom/query-extraction/declared.js +91 -0
  88. package/dist/custom/query-extraction/declared.js.map +1 -0
  89. package/dist/custom/query-extraction/describeFacts.d.ts +40 -0
  90. package/dist/custom/query-extraction/describeFacts.d.ts.map +1 -0
  91. package/dist/custom/query-extraction/describeFacts.js +225 -0
  92. package/dist/custom/query-extraction/describeFacts.js.map +1 -0
  93. package/dist/custom/query-extraction/descriptionPass.d.ts +23 -0
  94. package/dist/custom/query-extraction/descriptionPass.d.ts.map +1 -0
  95. package/dist/custom/query-extraction/descriptionPass.js +95 -0
  96. package/dist/custom/query-extraction/descriptionPass.js.map +1 -0
  97. package/dist/custom/query-extraction/extractQuery.d.ts +25 -0
  98. package/dist/custom/query-extraction/extractQuery.d.ts.map +1 -0
  99. package/dist/custom/query-extraction/extractQuery.js +146 -0
  100. package/dist/custom/query-extraction/extractQuery.js.map +1 -0
  101. package/dist/custom/query-extraction/extractionTypes.d.ts +133 -0
  102. package/dist/custom/query-extraction/extractionTypes.d.ts.map +1 -0
  103. package/dist/custom/query-extraction/extractionTypes.js +2 -0
  104. package/dist/custom/query-extraction/extractionTypes.js.map +1 -0
  105. package/dist/custom/query-extraction/index.d.ts +16 -0
  106. package/dist/custom/query-extraction/index.d.ts.map +1 -1
  107. package/dist/custom/query-extraction/index.js +11 -1
  108. package/dist/custom/query-extraction/index.js.map +1 -1
  109. package/dist/custom/query-extraction/pipeline.d.ts +6 -2
  110. package/dist/custom/query-extraction/pipeline.d.ts.map +1 -1
  111. package/dist/custom/query-extraction/pipeline.js +21 -14
  112. package/dist/custom/query-extraction/pipeline.js.map +1 -1
  113. package/dist/custom/query-extraction/resolve.d.ts +7 -2
  114. package/dist/custom/query-extraction/resolve.d.ts.map +1 -1
  115. package/dist/custom/query-extraction/resolve.js +17 -14
  116. package/dist/custom/query-extraction/resolve.js.map +1 -1
  117. package/dist/custom/query-extraction/sourceBinding.d.ts +57 -0
  118. package/dist/custom/query-extraction/sourceBinding.d.ts.map +1 -0
  119. package/dist/custom/query-extraction/sourceBinding.js +104 -0
  120. package/dist/custom/query-extraction/sourceBinding.js.map +1 -0
  121. package/dist/custom/query-extraction/sync.d.ts +27 -3
  122. package/dist/custom/query-extraction/sync.d.ts.map +1 -1
  123. package/dist/custom/query-extraction/sync.js +69 -44
  124. package/dist/custom/query-extraction/sync.js.map +1 -1
  125. package/dist/custom/query-extraction/templateFacts.d.ts +62 -0
  126. package/dist/custom/query-extraction/templateFacts.d.ts.map +1 -0
  127. package/dist/custom/query-extraction/templateFacts.js +229 -0
  128. package/dist/custom/query-extraction/templateFacts.js.map +1 -0
  129. package/dist/custom/query-extraction/types.d.ts +4 -0
  130. package/dist/custom/query-extraction/types.d.ts.map +1 -1
  131. package/dist/custom/query-refresh/QueryExtractionService.d.ts +54 -0
  132. package/dist/custom/query-refresh/QueryExtractionService.d.ts.map +1 -0
  133. package/dist/custom/query-refresh/QueryExtractionService.js +207 -0
  134. package/dist/custom/query-refresh/QueryExtractionService.js.map +1 -0
  135. package/dist/custom/query-refresh/QueryRefreshExtractionServerOperation.d.ts +28 -0
  136. package/dist/custom/query-refresh/QueryRefreshExtractionServerOperation.d.ts.map +1 -0
  137. package/dist/custom/query-refresh/QueryRefreshExtractionServerOperation.js +106 -0
  138. package/dist/custom/query-refresh/QueryRefreshExtractionServerOperation.js.map +1 -0
  139. package/dist/custom/query-refresh/index.d.ts +14 -0
  140. package/dist/custom/query-refresh/index.d.ts.map +1 -0
  141. package/dist/custom/query-refresh/index.js +10 -0
  142. package/dist/custom/query-refresh/index.js.map +1 -0
  143. package/dist/custom/query-refresh/queryRebind.d.ts +33 -0
  144. package/dist/custom/query-refresh/queryRebind.d.ts.map +1 -0
  145. package/dist/custom/query-refresh/queryRebind.js +85 -0
  146. package/dist/custom/query-refresh/queryRebind.js.map +1 -0
  147. package/dist/custom/query-refresh/refreshAuthorization.d.ts +29 -0
  148. package/dist/custom/query-refresh/refreshAuthorization.d.ts.map +1 -0
  149. package/dist/custom/query-refresh/refreshAuthorization.js +66 -0
  150. package/dist/custom/query-refresh/refreshAuthorization.js.map +1 -0
  151. package/dist/custom/query-refresh/refreshFormat.d.ts +16 -0
  152. package/dist/custom/query-refresh/refreshFormat.d.ts.map +1 -0
  153. package/dist/custom/query-refresh/refreshFormat.js +67 -0
  154. package/dist/custom/query-refresh/refreshFormat.js.map +1 -0
  155. package/dist/custom/query-refresh/refreshOrder.d.ts +25 -0
  156. package/dist/custom/query-refresh/refreshOrder.d.ts.map +1 -0
  157. package/dist/custom/query-refresh/refreshOrder.js +95 -0
  158. package/dist/custom/query-refresh/refreshOrder.js.map +1 -0
  159. package/dist/custom/query-refresh/refreshSelection.d.ts +15 -0
  160. package/dist/custom/query-refresh/refreshSelection.d.ts.map +1 -0
  161. package/dist/custom/query-refresh/refreshSelection.js +99 -0
  162. package/dist/custom/query-refresh/refreshSelection.js.map +1 -0
  163. package/dist/custom/query-refresh/refreshTypes.d.ts +81 -0
  164. package/dist/custom/query-refresh/refreshTypes.d.ts.map +1 -0
  165. package/dist/custom/query-refresh/refreshTypes.js +5 -0
  166. package/dist/custom/query-refresh/refreshTypes.js.map +1 -0
  167. package/dist/custom/util.d.ts +8 -0
  168. package/dist/custom/util.d.ts.map +1 -1
  169. package/dist/custom/util.js +18 -1
  170. package/dist/custom/util.js.map +1 -1
  171. package/dist/engines/QueryEngineServer.d.ts +8 -5
  172. package/dist/engines/QueryEngineServer.d.ts.map +1 -1
  173. package/dist/engines/QueryEngineServer.js +16 -23
  174. package/dist/engines/QueryEngineServer.js.map +1 -1
  175. package/dist/index.d.ts +7 -0
  176. package/dist/index.d.ts.map +1 -1
  177. package/dist/index.js +7 -0
  178. package/dist/index.js.map +1 -1
  179. package/package.json +30 -30
package/README.md CHANGED
@@ -9,14 +9,14 @@ This package contains server-side implementations of MemberJunction entity subcl
9
9
  ```mermaid
10
10
  graph TD
11
11
  A["Core Entities Server"] --> B["AIPromptEntityExtended"]
12
- A --> C["QueryEntityExtended"]
12
+ A --> C["MJQueryEntityServer"]
13
13
  A --> D["ComponentEntityExtended"]
14
14
  A --> E["MJActionEntityExtended"]
15
15
  A --> F["ApplicationEntityExtended"]
16
16
  A --> G["Other Server Entities"]
17
17
 
18
18
  B -->|manages| H["Templates<br/>Template Contents"]
19
- C -->|manages| H
19
+ C -->|extracts| L["Query Fields<br/>Parameters<br/>Entities<br/>Dependencies"]
20
20
  D -->|generates| I["Vector Embeddings"]
21
21
 
22
22
  J["MJ Class Factory"] -->|returns server subclass| A
@@ -32,6 +32,7 @@ graph TD
32
32
  style I fill:#b8762f,stroke:#8a5722,color:#fff
33
33
  style J fill:#2d6a9f,stroke:#1a4971,color:#fff
34
34
  style K fill:#b8762f,stroke:#8a5722,color:#fff
35
+ style L fill:#2d8659,stroke:#1a5c3a,color:#fff
35
36
  ```
36
37
 
37
38
  ### Key Differences from Core Entities
@@ -49,14 +50,13 @@ npm install @memberjunction/core-entities-server
49
50
 
50
51
  ### Basic Setup
51
52
 
52
- ```typescript
53
- import { LoadCoreEntitiesServerSubClasses } from '@memberjunction/core-entities-server';
53
+ Each server-side subclass registers itself with `@RegisterClass` when the package is imported, so there is no setup function to call:
54
54
 
55
- // Load all server-side entity subclasses at application startup
56
- LoadCoreEntitiesServerSubClasses();
55
+ ```typescript
56
+ import '@memberjunction/core-entities-server';
57
57
  ```
58
58
 
59
- **Important**: Call `LoadCoreEntitiesServerSubClasses()` early in your application initialization to ensure the server-side subclasses are registered with the MemberJunction metadata system before any entities are instantiated.
59
+ **Important**: The package must be loaded before any entities are instantiated. An MJAPI server built on `@memberjunction/server-bootstrap` loads it through the bootstrap's class-registration manifest, which also keeps a bundler from removing the classes as unused. Other server applications import the package at startup.
60
60
 
61
61
  ### Example: AI Prompt Entity
62
62
 
@@ -97,13 +97,16 @@ Extends the Component entity with automatic vector embedding generation:
97
97
  - **Smart Updates**: Only regenerates embeddings when source text changes
98
98
  - **Parallel Processing**: Generates multiple embeddings concurrently for performance
99
99
 
100
- ### QueryEntityExtendedServer
100
+ ### MJQueryEntityServer
101
101
 
102
- Extends the Query entity with automatic Template management similar to AIPromptEntity:
102
+ The server-side `MJ: Queries` entity. It extends `MJQueryEntityExtended` from `@memberjunction/core-entities`:
103
103
 
104
- - **Virtual Property**: `TemplateText` - Manages the SQL query template content
105
- - **Automatic Template Management**: Creates and updates Template records when saving
106
- - **Dynamic SQL Support**: Supports parameterized queries with Nunjucks templates
104
+ - **Query Extraction**: Saving a new query, or a query whose SQL changed, works out the query's output fields, parameters, the entities it reads and its dependencies, and writes them as child rows. See [Query Extraction](#query-extraction).
105
+ - **Save-Time Rules**: A query whose template breaks a rule, or whose output repeats a column name, is refused with the reasons in the save's result.
106
+ - **Declared Child Rows**: A query and the `Parameters`, `Fields` and `Entities` rows it declares save together as one graph.
107
+ - **Embeddings**: Computes a vector from the query's name, user question and description, unless `SkipEmbeddings` is set.
108
+ - **Dialect Conversion**: Keeps the query's SQL variants for other database platforms in step with its SQL.
109
+ - **On-Demand Refresh**: `QueryExtractionService` re-runs extraction for saved queries without changing their SQL. See [Query Refresh](#query-refresh).
107
110
 
108
111
  ### MJAIAgentNoteEntityServer (v5.30.x+)
109
112
 
@@ -129,8 +132,8 @@ The **AI-from-Description** half of the [Remote Operations](../../guides/REMOTE_
129
132
 
130
133
  The package uses MemberJunction's entity subclassing system:
131
134
 
132
- 1. At startup, `LoadCoreEntitiesServerSubClasses()` is called
133
- 2. This registers all server-side entity subclasses with the metadata system
135
+ 1. At startup, the package is imported (directly, or through the server bootstrap's class-registration manifest)
136
+ 2. Each subclass's `@RegisterClass` decorator registers it with the metadata system
134
137
  3. When code requests an entity (e.g., "AI Prompts"), the metadata system returns the server-side subclass
135
138
  4. The server-side subclass extends the base entity with additional functionality
136
139
 
@@ -305,130 +308,182 @@ export class OrderEntityServer extends OrderEntity {
305
308
  }
306
309
  ```
307
310
 
308
- ## SQL Parser
311
+ ## Query Extraction
309
312
 
310
- The `SQLParser` utility class extracts table/view references and column references from SQL statements. It uses `node-sql-parser` for AST-based parsing with automatic regex fallback when the AST parser fails. This class has **no dependency on MJ Metadata** — it returns raw parse results that callers cross-reference against entities themselves.
313
+ When a query is saved and it is new, or its SQL changed, `MJQueryEntityServer` works out what the query reads and returns, and writes it to the query's child entities. Nothing has to be filled in by hand for a query to be usable.
311
314
 
312
- ### When to Use
315
+ ### What a Save Does
313
316
 
314
- - **Virtual entity decoration**: Parse a view's SQL to identify which source entities it pulls from
315
- - **Query entity metadata**: Parse SQL queries to find referenced tables and columns
316
- - **Any server-side SQL analysis**: Extract structural information from SQL without executing it
317
+ 1. **Reads the template.** MJ's own template parser reads the SQL: its parameters (with composition pass-throughs), the queries it composes (`{{query:"Category/Name(...)"}}`), and the tables and views it reads. Whether the query uses a template at all (`UsesTemplate`) comes from the lexer, so a tag inside a comment does not count.
318
+ 2. **Asks the database server.** The query is rendered with sample parameter values (a caller can supply tested values in `ParameterHints`, keyed by parameter name), once for the base render and once for each `{% if %}` branch, and the database server is asked to describe each render without running it. The server reports each output column's exact type, nullability, and the table column it comes from, when there is one.
319
+ 3. **Applies the save-time rules.** A rule error in the template, or an output that repeats a column name, refuses the save. Nothing is written, and the reasons are in the save's result (`LatestResult`).
320
+ 4. **Writes the child rows** once the query row is saved:
317
321
 
318
- ### Basic Usage — Plain SQL
322
+ | Child entity | What is written |
323
+ |---|---|
324
+ | `MJ: Query Fields` | Each output column: name, order, SQL type, nullability, whether it is computed, and its source (`SourceEntityFieldID` when the column maps to an entity field, otherwise `SourceObjectName`, the object the server reported) |
325
+ | `MJ: Query Parameters` | Each template parameter: name, inferred type, whether it is required, its default |
326
+ | `MJ: Query Entities` | Each entity whose base view, base table or alternate base view the query reads |
327
+ | `MJ: Query Dependencies` | Each query it composes, with the alias and parameter mapping |
319
328
 
320
- ```typescript
321
- import { SQLParser } from '@memberjunction/core-entities-server';
329
+ 5. **Records the run** on the query row (see [Extraction Status](#extraction-status)).
330
+ 6. **Fills empty descriptions** with an LLM, after the save's transaction commits. Facts never come from the LLM. Pass `SkipEntityAIActions: true` in the save options to skip this step.
322
331
 
323
- const sql = `
324
- SELECT c.Name, c.Email, o.OrderDate, o.Total
325
- FROM [dbo].[Customer] c
326
- INNER JOIN [dbo].[Order] o ON o.CustomerID = c.ID
327
- WHERE o.Status = 'Active'
328
- `;
332
+ ### Facts and Annotations
329
333
 
330
- const result = SQLParser.Parse(sql);
334
+ Extraction separates what the SQL determines from what a person wrote:
331
335
 
332
- // Tables found in the query
333
- for (const table of result.Tables) {
334
- console.log(`${table.SchemaName}.${table.TableName} (alias: ${table.Alias})`);
335
- }
336
- // Output:
337
- // dbo.Customer (alias: c)
338
- // dbo.Order (alias: o)
336
+ - **Facts** are corrected on every run: names, order, types, nullability, sources and the computed flag. A fact a person edited is set back to what the SQL and the server say.
337
+ - **Annotations** are kept: descriptions, and for a parameter a person wrote (`DetectionMethod` `Manual`), its type, required flag, default, sample value and validation filters. Doc comments in the SQL (`{# @param ... #}`, `{# @field ... #}`, and a trailing `--:` comment on a select-list line) count as authored.
338
+ - **A parameter a person wrote that the SQL no longer uses** is kept, and flagged in `ExtractionMessage`.
339
+ - **A row extraction added that the SQL no longer produces** is removed.
339
340
 
340
- // Columns referenced in the query
341
- for (const col of result.Columns) {
342
- console.log(`${col.TableQualifier ? col.TableQualifier + '.' : ''}${col.ColumnName}`);
343
- }
344
- // Output:
345
- // c.Name, c.Email, o.OrderDate, o.Total, o.CustomerID, c.ID, o.Status
341
+ ### DetectionMethod Values
346
342
 
347
- // Whether AST parsing succeeded (true) or regex fallback was used (false)
348
- console.log(`Used AST: ${result.UsedASTParsing}`);
349
- ```
343
+ Each child row records who wrote it:
344
+
345
+ | Value | Used on | Meaning |
346
+ |---|---|---|
347
+ | `Database` | Query Fields | The facts came from the database server describing the query's output |
348
+ | `Parser` | Query Fields, Parameters, Entities | The facts came from MJ's analysis of the template and SQL structure |
349
+ | `Manual` | Query Fields, Parameters, Entities, Dependencies | A person wrote the row. A `Manual` parameter's annotations are never overwritten, and it is kept when the SQL stops using it |
350
+ | `AI` | Query Fields, Parameters, Entities | On a field, an LLM wrote its description (an LLM never sets types or sources). On a parameter or entity link, the row was written by the earlier LLM-based extraction |
351
+ | `Auto` | Query Dependencies | The dependency was found in the SQL |
350
352
 
351
- ### Nunjucks Template SQL
353
+ ### Extraction Status
352
354
 
353
- For SQL that contains Nunjucks template syntax (e.g., MemberJunction query templates), use `ParseWithTemplatePreprocessing`. This replaces template expressions with safe placeholder values before parsing:
355
+ Each run is recorded on the `MJ: Queries` row:
356
+
357
+ | Column | Meaning |
358
+ |---|---|
359
+ | `ExtractionStatus` | `OK`: every fact was extracted. `Warning`: extraction succeeded with findings. `Error`: the database server could not describe the query, so the stored child rows were kept. `Skipped`: the facts could not be checked, for example for a query on an external data source. `NULL`: never extracted. |
360
+ | `ExtractionMessage` | The run's findings, one per line: flagged parameters, rule warnings, the server's error |
361
+ | `ExtractedSQLHash` | A hash of the SQL and dialect the child rows were extracted from. A value that differs from the current SQL's hash means the rows may be stale |
362
+
363
+ ### Controlling Extraction per Save
364
+
365
+ `MJQueryEntityServer.ExtractionOverride` sets what the next save does. It goes back to `Auto` after every save.
366
+
367
+ | Value | Behaviour |
368
+ |---|---|
369
+ | `Auto` (default) | Extract when the query is new or its SQL changed |
370
+ | `Skip` | Do not extract, even when the SQL changed. The save-time rules are not applied, and the child rows, the extraction columns and `UsesTemplate` stay as they are. For bulk loaders and fixtures that write those rows themselves |
371
+ | `Force` | Extract even when the SQL did not change. On a query whose SQL did not change, rule errors are recorded instead of refusing the save; a save that changes the SQL is still held to the save-time rules |
354
372
 
355
373
  ```typescript
356
- import { SQLParser } from '@memberjunction/core-entities-server';
357
-
358
- const templateSQL = `
359
- SELECT * FROM [dbo].[Customer]
360
- WHERE Status = {{ status | sqlString }}
361
- {% if regionId %}
362
- AND RegionID = {{ regionId | sqlNumber }}
363
- {% endif %}
364
- `;
365
-
366
- // Nunjucks syntax is replaced with placeholders, then parsed normally
367
- const result = SQLParser.ParseWithTemplatePreprocessing(templateSQL);
368
- console.log(result.Tables); // [{ TableName: 'Customer', SchemaName: 'dbo', Alias: 'Customer' }]
374
+ import { Metadata, UserInfo } from '@memberjunction/core';
375
+ import { MJQueryEntityServer } from '@memberjunction/core-entities-server';
376
+
377
+ async function saveQuery(contextUser: UserInfo, queryID: string, sql: string): Promise<boolean> {
378
+ const md = new Metadata();
379
+ const query = await md.GetEntityObject<MJQueryEntityServer>('MJ: Queries', contextUser);
380
+ if (!(await query.Load(queryID))) return false;
381
+
382
+ query.SQL = sql;
383
+ query.ExtractionOverride = 'Auto'; // the default; 'Skip' or 'Force' change this one save
384
+ const saved = await query.Save({ SkipEntityAIActions: true }); // no LLM description pass
385
+ if (!saved) {
386
+ console.error(query.LatestResult?.CompleteMessage); // the save-time rule findings
387
+ }
388
+ return saved;
389
+ }
369
390
  ```
370
391
 
371
- ### Cross-Referencing with Entity Metadata
392
+ ### Other Behaviour
372
393
 
373
- The parser returns raw table/column references. To resolve them to MemberJunction entities:
394
+ - **Declared child rows win by name.** A save can declare child rows through the query's `Parameters`, `Fields` and `Entities` collections, and the query and those rows save as one graph. Extraction does not add a second row with a declared row's name or delete a declared row. A declared field still gets its facts from the describe, so only its annotations are the caller's.
395
+ - **Written as the system user.** The child rows a save derives mirror SQL the caller was allowed to change, so they are written as the system user, inside the caller's transaction when there is one. A failed child-row write is recorded on the query as an `Error` rather than failing a save whose row is already written.
396
+ - **Tables named by a parameter.** A query that reads a table or view named by a template parameter is not described, because its columns depend on the arguments. Its declared fields are kept, and `ExtractionMessage` says why.
397
+ - **Network providers.** A query saved through a network (client) provider leaves extraction to the server it saves to.
374
398
 
375
- ```typescript
376
- import { Metadata } from '@memberjunction/core';
377
- import { SQLParser } from '@memberjunction/core-entities-server';
399
+ ### Legacy Pipeline
378
400
 
379
- const parseResult = SQLParser.Parse(viewDefinitionSQL);
380
- const md = new Metadata();
401
+ `RunExtractionPipeline` in `src/custom/query-extraction/pipeline.ts`, the earlier LLM-assisted extraction, is deprecated. It is not used by saves and is not exported from the package root. Saves use `ComputeQueryExtraction` with `WriteExtractedChildRows`. The pipeline is kept until 7.0 for code that imports it directly.
402
+
403
+ ## Query Refresh
404
+
405
+ `QueryExtractionService` re-runs extraction for saved queries on demand, without changing their SQL. It is the same extraction a save runs, under the refresh rules: a rule error is recorded on the query, never a refusal. Use it after a schema change, after an upgrade, or when a query's child rows look stale.
406
+
407
+ - **`RefreshQuery(queryID, options)`** refreshes one query and returns a `QueryRefreshReport`.
408
+ - **`RefreshQueries(filter, options)`** refreshes a selection and returns a `QueryRefreshRunReport`. The `QueryRefreshFilter` selectors combine as a union: `QueryIDs`, `QueryNames` (a bare name or a full path such as `Category/Sub/Name`), `CategoryPath` (that category and every category below it), `EntityNames` or `EntityIDs` (the queries that read those entities), or `All`. A filter with no selector refreshes nothing.
409
+ - **`QueryRefreshOptions`**: `DryRun` works out and reports the changes without writing anything; `WithAI` queues the LLM description pass for rows still without a description (off by default); `OnProgress` is called after each query in a bulk run.
381
410
 
382
- for (const tableRef of parseResult.Tables) {
383
- const entity = md.Entities.find(e =>
384
- (e.BaseTable.toLowerCase() === tableRef.TableName.toLowerCase() ||
385
- e.BaseView.toLowerCase() === tableRef.TableName.toLowerCase()) &&
386
- e.SchemaName.toLowerCase() === tableRef.SchemaName.toLowerCase()
387
- );
411
+ Each query is refreshed in its own transaction on the caller's provider, so a failure rolls back that query only. A bulk run refreshes the queries a composition depends on before the queries that compose them; queries that compose each other in a cycle are refreshed once, after the rest, and listed in `Cycles`.
388
412
 
389
- if (entity) {
390
- console.log(`Table ${tableRef.TableName} → Entity "${entity.Name}"`);
391
- // Access entity.Fields for full field metadata
413
+ Each query's report gives:
414
+
415
+ - `Outcome`: `Refreshed` (rows changed, or would change in a dry run), `Unchanged`, `NotPermitted` (a bulk run skipped it), `Unavailable` (does not exist, or the caller may not refresh it), or `Failed` (nothing was written)
416
+ - `Added`, `Updated` and `Removed`: the child rows changed, with the changed columns for an update
417
+ - `Flagged`: authored parameters the SQL no longer uses
418
+ - `ExtractionStatus`, `QueryColumns` (the query-row columns that change) and `Diagnostics`
419
+
420
+ ```typescript
421
+ import { IMetadataProvider, UserInfo } from '@memberjunction/core';
422
+ import {
423
+ FormatQueryRefreshRun,
424
+ IsQueryRefreshProvider,
425
+ QueryExtractionService,
426
+ QueryRefreshFilter,
427
+ } from '@memberjunction/core-entities-server';
428
+
429
+ async function refreshSalesQueries(provider: IMetadataProvider, contextUser: UserInfo): Promise<void> {
430
+ if (!IsQueryRefreshProvider(provider)) {
431
+ throw new Error('A query refresh needs a provider that can run views');
432
+ }
433
+ const service = new QueryExtractionService({ Provider: provider, User: contextUser });
434
+ const filter: QueryRefreshFilter = { CategoryPath: 'Reports/Sales' };
435
+
436
+ // Review first: a dry run reports what would change and writes nothing
437
+ const preview = await service.RefreshQueries(filter, {
438
+ DryRun: true,
439
+ OnProgress: p => console.log(`${p.Processed}/${p.Total} ${p.Report.Outcome}: ${p.Report.QueryName}`),
440
+ });
441
+ for (const line of FormatQueryRefreshRun(preview, true)) console.log(line);
442
+
443
+ // Then apply
444
+ const run = await service.RefreshQueries(filter);
445
+ for (const report of run.Reports) {
446
+ if (report.Outcome === 'Failed') console.error(report.QueryName, report.Diagnostics);
392
447
  }
393
448
  }
394
449
  ```
395
450
 
396
- ### Return Types
451
+ ### Entry Points
397
452
 
398
- ```typescript
399
- interface SQLParseResult {
400
- Tables: SQLTableReference[]; // All table/view references (FROM, JOIN, subqueries, CTEs)
401
- Columns: SQLColumnReference[]; // All column references (SELECT, WHERE, JOIN ON, etc.)
402
- UsedASTParsing: boolean; // true = AST parsed, false = regex fallback
403
- }
453
+ Every entry point goes through `QueryExtractionService`, so the same authorization applies to all of them:
404
454
 
405
- interface SQLTableReference {
406
- TableName: string; // Table or view name
407
- SchemaName: string; // Schema name (defaults to 'dbo')
408
- Alias: string; // Query alias, or table name if no alias
409
- }
455
+ - **`Query.RefreshExtraction` remote operation** (`QueryRefreshExtractionServerOperation`): a long-running operation that reports each query as a progress event. An API key needs the `query:refresh` scope.
456
+ - **CLI**: `mj query refresh [--id|--name|--category|--entity|--all] [--dry-run] [--with-ai]`. After an upgrade, run `mj query refresh --all --dry-run` to review, then `mj query refresh --all`.
457
+ - **CodeGen**: `RebindQueriesForChangedEntities` refreshes the queries that read an entity whose fields were added, dropped, or changed in type or nullability, with no description pass. The SQL is never edited: a query the change broke is recorded as an `Error` with the server's message. A query authorized by its role list (`MJ: Query Permissions`) that gains a column from a field with field permissions is recorded as a `Warning`.
410
458
 
411
- interface SQLColumnReference {
412
- ColumnName: string; // Column name
413
- TableQualifier: string | null; // Table alias prefix (e.g., "c" in "c.Name")
414
- }
415
- ```
459
+ ### Refresh Authorization
460
+
461
+ - The caller needs Update on `MJ: Queries` and Create, Update and Delete on `MJ: Query Fields`, `MJ: Query Parameters`, `MJ: Query Entities` and `MJ: Query Dependencies`. A dry run needs the same, since its report shows the same metadata. Run permissions on the query play no part: refreshing is an authoring act.
462
+ - The caller must be able to read every entity the query reads, with no field denied on any of them, because the report and the written rows expose those entities' columns.
463
+ - A refused single query reads exactly like one that does not exist (`Unavailable`). A bulk run reports a refused query as `NotPermitted`, with no reason.
464
+ - A refresh writes the child rows as the caller, not as the system user, so each write's own permission check also applies.
416
465
 
417
- ### How It Works
466
+ `UserCanRefreshQueries(user, entities)` and `UserCanSeeQuerySources(user, entityIDs, entities)` expose these checks for callers that want to test them up front.
467
+
468
+ ## SQL Parser
418
469
 
419
- 1. **AST Parsing** (preferred): Uses `node-sql-parser` with `TransactSQL` dialect to build an AST, then recursively walks it to extract table references from `FROM`/`JOIN` clauses, subqueries, and CTEs, plus column references from `SELECT`, `WHERE`, `GROUP BY`, `ORDER BY`, and `ON` conditions.
470
+ This package does not contain a SQL parser. SQL and template parsing live in [`@memberjunction/sql-parser`](../SQLParser/README.md), which query extraction uses. New code reads SQL with `SqlDocument.Parse(sql, dialect)` (table references, column references, output items, CTEs) and templates with `QueryTemplate.Parse(sql, dialect)` (parameters, compositions, `{% if %}` branches).
420
471
 
421
- 2. **Regex Fallback**: If AST parsing fails (complex SQL, dialect edge cases), a regex-based extractor finds `FROM`/`JOIN` table references. Column references are not extracted in regex mode (the `Columns` array will be empty).
472
+ The legacy `SQLParser` class in `@memberjunction/sql-parser` is deprecated but kept. Its `Parse` and extraction methods now run on the new parser, which changes some results from earlier releases:
422
473
 
423
- 3. **Nunjucks Preprocessing**: `ParseWithTemplatePreprocessing` first renders the SQL through a Nunjucks environment with placeholder filters (`sqlString` → `'placeholder'`, `sqlNumber` → `0`, etc.), stripping template blocks while preserving the SQL structure. If Nunjucks rendering fails, it falls back to regex-based template removal.
474
+ - A CTE name is not reported as a table, and neither are table-valued functions or derived tables.
475
+ - Names inside comments and string literals are never read as tables or columns.
476
+ - A template is read branch by branch, and the result is the union of every branch's references; a `{{query:"..."}}` composition is not a table.
477
+ - An unqualified table gets the dialect's default schema (`dbo` on SQL Server, `public` on PostgreSQL). The dialect defaults to SQL Server when none is given.
478
+ - `ParseWithTemplatePreprocessing` is the same as `Parse`. `UsedASTParsing` is true when every reading of the SQL parsed completely, and `Columns` is empty when it could not be read completely.
424
479
 
425
480
  ## Troubleshooting
426
481
 
427
482
  ### Entity subclass not being used
428
- Ensure `LoadCoreEntitiesServerSubClasses()` is called before any entity instantiation.
483
+ Ensure the package is imported before any entity is instantiated. In a bundled server, check that it is in the class-registration manifest (`mj codegen manifest`), so the bundler does not drop the classes.
429
484
 
430
485
  ### Module loading errors
431
- This package uses CommonJS. Ensure your Node.js application is configured to handle CommonJS modules.
486
+ This package is a native ES module (`"type": "module"`). Import it from ES module code (`import`), not with `require()`.
432
487
 
433
488
  ### Virtual properties not persisting
434
489
  Virtual properties are not saved to the database. Use them as convenient accessors that manage real database fields behind the scenes.
@@ -0,0 +1,22 @@
1
+ import { PostRunViewHook } from '@memberjunction/core';
2
+ /**
3
+ * Server-side `PostRunView` data hook enforcing Row-Level Security (RLS) and Field-Level Security (FLS)
4
+ * on anchored conversations (`MJ: Conversations` where `AnchorType IS NOT NULL`) and their comment
5
+ * messages (`MJ: Conversation Details`).
6
+ *
7
+ * Guarantees that even a raw `RunView` call over GraphQL, REST, or server code cannot return:
8
+ * - Anchored conversation threads (`MJ: Conversations`) attached to a linked record that `contextUser`
9
+ * cannot read (entity permissions + RLS), or anchored/stamped with restricted fields (`AnchorKey` /
10
+ * `AnchorMetadata.restrictedFields`) that `contextUser` is denied Read access to (FLS).
11
+ * - Comment messages (`MJ: Conversation Details`) belonging to any such anchored conversation thread.
12
+ *
13
+ * Never mutates `results.Results` in place (safe with frozen cache entries); returns a replacement
14
+ * `RunViewResult` whenever rows are filtered out.
15
+ */
16
+ export declare const AnchoredConversationPostRunViewHook: PostRunViewHook;
17
+ /**
18
+ * Ensures {@link AnchoredConversationPostRunViewHook} is registered in the global `PostRunView`
19
+ * data hooks store. Safe to call multiple times (idempotent).
20
+ */
21
+ export declare function EnsureAnchoredConversationReadHookRegistered(): void;
22
+ //# sourceMappingURL=AnchoredConversationReadSecurityHook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnchoredConversationReadSecurityHook.d.ts","sourceRoot":"","sources":["../../src/custom/AnchoredConversationReadSecurityHook.ts"],"names":[],"mappings":"AAAA,OAAO,EAMH,eAAe,EAOlB,MAAM,sBAAsB,CAAC;AAmE9B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mCAAmC,EAAE,eA0CjD,CAAC;AA4SF;;;GAGG;AACH,wBAAgB,4CAA4C,IAAI,IAAI,CAKnE"}