@memberjunction/core 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.
Files changed (73) hide show
  1. package/LICENSE +7 -0
  2. package/dist/generic/baseEngine.d.ts.map +1 -1
  3. package/dist/generic/baseEngine.js +13 -2
  4. package/dist/generic/baseEngine.js.map +1 -1
  5. package/dist/generic/baseEngineRegistry.d.ts +14 -0
  6. package/dist/generic/baseEngineRegistry.d.ts.map +1 -1
  7. package/dist/generic/baseEngineRegistry.js +32 -0
  8. package/dist/generic/baseEngineRegistry.js.map +1 -1
  9. package/dist/generic/baseEntity.d.ts +329 -26
  10. package/dist/generic/baseEntity.d.ts.map +1 -1
  11. package/dist/generic/baseEntity.js +788 -79
  12. package/dist/generic/baseEntity.js.map +1 -1
  13. package/dist/generic/databaseProviderBase.d.ts +54 -17
  14. package/dist/generic/databaseProviderBase.d.ts.map +1 -1
  15. package/dist/generic/databaseProviderBase.js +133 -52
  16. package/dist/generic/databaseProviderBase.js.map +1 -1
  17. package/dist/generic/entityCompanion.d.ts +218 -0
  18. package/dist/generic/entityCompanion.d.ts.map +1 -0
  19. package/dist/generic/entityCompanion.js +170 -0
  20. package/dist/generic/entityCompanion.js.map +1 -0
  21. package/dist/generic/entityInfo.d.ts +146 -0
  22. package/dist/generic/entityInfo.d.ts.map +1 -1
  23. package/dist/generic/entityInfo.js +188 -0
  24. package/dist/generic/entityInfo.js.map +1 -1
  25. package/dist/generic/entitySavePlan.d.ts +199 -0
  26. package/dist/generic/entitySavePlan.d.ts.map +1 -0
  27. package/dist/generic/entitySavePlan.js +213 -0
  28. package/dist/generic/entitySavePlan.js.map +1 -0
  29. package/dist/generic/entityTransactionScope.d.ts +125 -0
  30. package/dist/generic/entityTransactionScope.d.ts.map +1 -0
  31. package/dist/generic/entityTransactionScope.js +115 -0
  32. package/dist/generic/entityTransactionScope.js.map +1 -0
  33. package/dist/generic/interfaces.d.ts +93 -35
  34. package/dist/generic/interfaces.d.ts.map +1 -1
  35. package/dist/generic/interfaces.js +27 -0
  36. package/dist/generic/interfaces.js.map +1 -1
  37. package/dist/generic/providerBase.d.ts +13 -0
  38. package/dist/generic/providerBase.d.ts.map +1 -1
  39. package/dist/generic/providerBase.js +64 -5
  40. package/dist/generic/providerBase.js.map +1 -1
  41. package/dist/generic/relatedRecordBatchLoader.d.ts +39 -0
  42. package/dist/generic/relatedRecordBatchLoader.d.ts.map +1 -0
  43. package/dist/generic/relatedRecordBatchLoader.js +154 -0
  44. package/dist/generic/relatedRecordBatchLoader.js.map +1 -0
  45. package/dist/generic/relatedRecordCollection.d.ts +578 -0
  46. package/dist/generic/relatedRecordCollection.d.ts.map +1 -0
  47. package/dist/generic/relatedRecordCollection.js +1004 -0
  48. package/dist/generic/relatedRecordCollection.js.map +1 -0
  49. package/dist/generic/saveEntityGraphOperation.d.ts +148 -0
  50. package/dist/generic/saveEntityGraphOperation.d.ts.map +1 -0
  51. package/dist/generic/saveEntityGraphOperation.js +157 -0
  52. package/dist/generic/saveEntityGraphOperation.js.map +1 -0
  53. package/dist/generic/securityInfo.d.ts +99 -1
  54. package/dist/generic/securityInfo.d.ts.map +1 -1
  55. package/dist/generic/securityInfo.js +88 -6
  56. package/dist/generic/securityInfo.js.map +1 -1
  57. package/dist/generic/telemetryManager.d.ts +21 -1
  58. package/dist/generic/telemetryManager.d.ts.map +1 -1
  59. package/dist/generic/telemetryManager.js +21 -6
  60. package/dist/generic/telemetryManager.js.map +1 -1
  61. package/dist/index.d.ts +6 -1
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +6 -3
  64. package/dist/index.js.map +1 -1
  65. package/dist/views/runView.d.ts +31 -0
  66. package/dist/views/runView.d.ts.map +1 -1
  67. package/dist/views/runView.js.map +1 -1
  68. package/package.json +13 -13
  69. package/readme.md +159 -1
  70. package/dist/generic/runReport.d.ts +0 -25
  71. package/dist/generic/runReport.d.ts.map +0 -1
  72. package/dist/generic/runReport.js +0 -38
  73. package/dist/generic/runReport.js.map +0 -1
package/readme.md CHANGED
@@ -130,7 +130,13 @@ flowchart LR
130
130
  | `securityInfo.ts` | Security classes: `UserInfo`, `RoleInfo`, `AuthorizationInfo`, `AuditLogTypeInfo` |
131
131
  | `interfaces.ts` | Core interfaces: `IMetadataProvider`, `IEntityDataProvider`, `IRunViewProvider`, etc. |
132
132
  | `compositeKey.ts` | `CompositeKey` and `KeyValuePair` for multi-field primary key support |
133
- | `transactionGroup.ts` | `TransactionGroupBase` for atomic multi-entity operations |
133
+ | `transactionGroup.ts` | `TransactionGroupBase` — an *arbitrary batch* facility for shipping unrelated records in one atomic round trip (saves are deferred; not for parent/children) |
134
+ | `entityTransactionScope.ts` | `EntityTransactionScope` + `RunInEntityTransaction()` — the one provider-arbitrated transaction primitive, shared by IS-A, composites and application cascades |
135
+ | `entityCompanion.ts` | `EntityCompanion` — named, serialisable state attached to a record (the "bag") |
136
+ | `relatedRecordCollection.ts` | `RelatedRecordCollection<T>` — the typed parent/children companion |
137
+ | `relatedRecordBatchLoader.ts` | One batched child query per collection across a whole result set (`RunView.IncludeRelatedRecords`) |
138
+ | `entitySavePlan.ts` | `EntitySavePlan` + executor — the ordered unit of work a composite save produces |
139
+ | `saveEntityGraphOperation.ts` | `MJ.SaveEntityGraph` — routes a whole composite save to the server from a client provider |
134
140
  | `baseEngine.ts` | `BaseEngine` abstract singleton for building services with auto-loaded data |
135
141
  | `runQuery.ts` | `RunQuery` class for secure parameterized query execution |
136
142
  | `runReport.ts` | `RunReport` class for report generation |
@@ -796,6 +802,127 @@ const results = await txGroup.Submit();
796
802
 
797
803
  Each `TransactionResult` in the returned array contains a `Success` flag. If any operation fails, all are rolled back.
798
804
 
805
+ > **A TransactionGroup is an *arbitrary batch* facility, not a composite-save engine.** Under a
806
+ > transaction group `Save()` **defers**: the provider only registers an instruction, so the entity
807
+ > returns `true` before anything persists, the primary key is unavailable afterwards, there is no
808
+ > read-your-writes, and ordering is array position with a flat `Define`/`Use` variable namespace.
809
+ > To save a parent *and its children*, use an **entity graph** (below), not a transaction group.
810
+ > Full comparison: [Transactions, Batching & Entity Graphs](../../guides/TRANSACTIONS_AND_BATCHING_GUIDE.md).
811
+
812
+ ---
813
+
814
+ ### Entity Companions & Composite Graph Saves
815
+
816
+ A **companion** is named, serialisable state attached to a record that is not one of its fields —
817
+ most commonly a collection of child records. Companions load, validate, serialise and persist with
818
+ their parent, on **both tiers**, from one call: `entity.Save()`.
819
+
820
+ Declare a child collection on a **shared (client + server)** entity subclass:
821
+
822
+ ```typescript
823
+ @RegisterClass(BaseEntity, 'MJ_BizApps_Orders: Orders')
824
+ export class OrderEntity extends mjBizAppsOrdersOrderEntity {
825
+ public readonly Lines = this.DeclareRelatedRecords<OrderLineEntity>({
826
+ Name: 'Lines',
827
+ RelatedEntity: 'MJ_BizApps_Orders: Order Lines',
828
+ RelatedEntityJoinField: 'OrderHeaderID',
829
+ OrderBy: 'LineNumber ASC',
830
+ Load: 'explicit', // 'explicit' | 'immediate' | 'lazy' | 'never'
831
+ OnRemove: 'delete', // 'delete' | 'orphan' | 'refuse'
832
+ Sequence: { Field: 'LineNumber', From: 1 },
833
+ });
834
+
835
+ public override Validate(): ValidationResult {
836
+ const result = super.Validate(); // fans out to every companion
837
+ assertHasLines(this.Lines.Items, result);// sees the WHOLE graph, before any write
838
+ return result;
839
+ }
840
+ }
841
+ ```
842
+
843
+ ```typescript
844
+ const order = await md.GetEntityObject<OrderEntity>('MJ_BizApps_Orders: Orders');
845
+ order.NewRecord();
846
+ (await order.Lines.Create()).Quantity = 2;
847
+ (await order.Lines.Create()).Quantity = 5;
848
+ await order.Save(); // header + both lines, atomically
849
+ ```
850
+
851
+ **How it executes.** `Save()` builds an `EntitySavePlan`. A single-node plan takes the ordinary
852
+ save path unchanged. A multi-node plan runs:
853
+
854
+ | Provider | Behaviour |
855
+ |---|---|
856
+ | `SupportsEntityTransactions === true` (server) | Executes locally inside one transaction scope |
857
+ | `false` (browser) | Serialises the graph and routes the whole unit of work to the server via the `MJ.SaveEntityGraph` remote operation, which rebuilds the records as their **server-side** subclasses and runs the same local executor there |
858
+
859
+ There is exactly one cascade implementation; the remote path relocates it rather than
860
+ reimplementing it.
861
+
862
+ **Platform guarantees are preserved.** Every node is persisted via that record's own `Save()` /
863
+ `Delete()` — never direct SQL — so Record Changes, entity actions, validation, subclass overrides,
864
+ `PreSave` hooks, `save_started` / `save` / `delete` events and cache invalidation all fire per node.
865
+ The root additionally raises `graph_save_started` and `graph_save` so a UI can refresh once per unit
866
+ of work.
867
+
868
+ **Loading.** `Load: 'immediate'` is honoured by `Load()` and **never** by `LoadFromData()` — that method
869
+ is the per-row materialisation path for `RunView(ResultType:'entity_object')`, so loading children
870
+ there degrades a view into N+1 queries. For result sets, ask for children explicitly and get one
871
+ batched query per collection:
872
+
873
+ ```typescript
874
+ const result = await rv.RunView<OrderEntity>({
875
+ EntityName: 'MJ_BizApps_Orders: Orders',
876
+ ResultType: 'entity_object',
877
+ IncludeRelatedRecords: ['Lines'], // 1 query for ALL orders' lines, not one per order
878
+ });
879
+ ```
880
+
881
+ #### Declaring one without writing code
882
+
883
+ Set `EntityRelationship.RelatedRecordCollection` (a JSONType blob) and CodeGen emits the declaration
884
+ onto the **generated** entity class, so both tiers get it and no subclass is needed. `RelatedEntity`
885
+ and `RelatedEntityJoinField` stay as columns on that same row rather than being repeated in the JSON
886
+ — one source of truth each.
887
+
888
+ #### Reading from an engine cache instead of the database
889
+
890
+ ```typescript
891
+ { Name: 'Params', Source: 'cache', Load: 'lazy' } // ReadOnly defaults to true here
892
+ ```
893
+
894
+ `Source: 'cache'` finds whichever loaded `BaseEngine` already holds the entity — generically, via
895
+ `BaseEngineRegistry` — and filters it by the join field. **Zero queries**, and it falls back to a
896
+ database load on a miss. Read-only (the default for `cache`) hands out the engine's own instances as
897
+ a *live view*; writable copies them so the cache is never mutated in place.
898
+
899
+ `Load: 'lazy'` fills on first read of `Items`, reproducing a hand-written memoised getter — and
900
+ **throws** rather than returning an empty array when no engine caches the entity, because a lazy
901
+ declaration asserts that one does.
902
+
903
+ ```typescript
904
+ await action.LoadRelatedRecords(); // cache-backed free, database-backed batched into ONE RunViews
905
+ ```
906
+
907
+ ```typescript
908
+ for (const line of order.Lines) { … } // the collection is ITERABLE
909
+ const all = [...order.Lines]; // spread and destructuring work
910
+ order.Lines.length // and length
911
+ order.Lines.Items.map(l => l.Total) // Items for map/filter/find — it is readonly, so nothing
912
+ // can push around the removal tracking and sequencing
913
+ ```
914
+
915
+ **Key APIs:** `BaseEntity.DeclareRelatedRecords()`, `LoadRelatedRecords()`, `RegisterCompanion()`,
916
+ `GetCompanion()`, `Companions`, `SerializeCompanions()`, `DeserializeCompanions()`;
917
+ `RelatedRecordCollection<T>` (iterable; `Items`, `Removed`, `Count`/`length`, `Add`, `Create`,
918
+ `Remove`, `Clear`, `Load`, `Dirty`, `Source`, `IsReadOnly`, `LoadMode`, `RemovalMode`); `EntityCompanion` (subclass for a new *kind* of companion);
919
+ `EntitySavePlan`.
920
+
921
+ **Full guide — with flow diagrams for the local save and the network round trip:**
922
+ [Related-Record Collections](./docs/related-record-collections.md). For when to use this versus a
923
+ provider transaction versus a TransactionGroup, see
924
+ [Transactions, Batching & Entity Graphs](../../guides/TRANSACTIONS_AND_BATCHING_GUIDE.md).
925
+
799
926
  ---
800
927
 
801
928
  ### Datasets
@@ -1185,9 +1312,31 @@ abstract class DatabaseProviderBase extends ProviderBase {
1185
1312
  abstract BeginTransaction(): Promise<void>;
1186
1313
  abstract CommitTransaction(): Promise<void>;
1187
1314
  abstract RollbackTransaction(): Promise<void>;
1315
+
1316
+ // The unified, provider-arbitrated transaction primitive (6.2+). Starts a transaction or joins
1317
+ // one already in flight. Used by IS-A chains, composite graph saves, and application cascades
1318
+ // alike — so none of them has to know about the others.
1319
+ get SupportsEntityTransactions(): boolean; // true here, false on ProviderBase
1320
+ BeginEntityTransaction(): Promise<EntityTransactionScope>;
1188
1321
  }
1189
1322
  ```
1190
1323
 
1324
+ **Prefer `BeginEntityTransaction()` / `RunInEntityTransaction()` over calling `BeginTransaction()`
1325
+ directly.** The scope is settle-once and composes with any transaction already open:
1326
+
1327
+ ```typescript
1328
+ import { RunInEntityTransaction } from '@memberjunction/core';
1329
+
1330
+ await RunInEntityTransaction(this.ProviderToUse, async () => {
1331
+ await header.Save();
1332
+ for (const line of lines) { line.HeaderID = header.ID; await line.Save(); }
1333
+ });
1334
+ ```
1335
+
1336
+ See [Transactions, Batching & Entity Graphs](../../guides/TRANSACTIONS_AND_BATCHING_GUIDE.md) for
1337
+ the difference between provider transactions, Transaction Groups, and entity graphs — and for why
1338
+ the `BeginISATransaction` trio was retired in 6.2.
1339
+
1191
1340
  ---
1192
1341
 
1193
1342
  ## Provider Architecture
@@ -1663,6 +1812,14 @@ flowchart LR
1663
1812
  | `AllowUpdateAPI` | Configurable | Always `false` |
1664
1813
  | Stored procedures | Generated | None |
1665
1814
 
1815
+ > **Not the same thing as a custom base view.** A virtual entity has *no base table* and is read-only.
1816
+ > If you have a real table and only want to add computed columns to what its view exposes, you want a
1817
+ > **layered base view** instead — `GeneratedBaseViewName` keeps CodeGen generating an inner view while
1818
+ > your application owns and wraps `BaseView`, and the entity stays fully writable because
1819
+ > `spCreate`/`spUpdate`/`spDelete` still target `BaseView`. See
1820
+ > [Base Views: Generated, Custom, or Layered](../CodeGenLib/README.md#base-views-generated-custom-or-layered).
1821
+ > Layering is skipped entirely for virtual entities, which have no table to generate from.
1822
+
1666
1823
  ### Read-Only Enforcement
1667
1824
 
1668
1825
  Virtual entities are enforced as read-only at multiple layers:
@@ -1891,6 +2048,7 @@ For detailed guides on specific topics, see the [docs/](./docs/) folder:
1891
2048
 
1892
2049
  - [Virtual Entities](./docs/virtual-entities.md) — Config-driven creation, LLM decoration, read-only enforcement
1893
2050
  - [IS-A Relationships](./docs/isa-relationships.md) — Type inheritance, save/delete orchestration, provider integration
2051
+ - [Related-Record Collections](./docs/related-record-collections.md) — Parent + its FK rows as one unit: metadata declaration, the local save flow and the one-call network round trip (both diagrammed), cache-backed sources, load modes, sequencing and the cycle guard
1894
2052
  - [Organic Keys](./docs/organic-keys.md) — Cross-system matching by shared business data (email, phone, domain), CodeGen integration, transitive views
1895
2053
  - [RunQuery Pagination](./docs/runquery-pagination.md) — Parameterized queries with pagination support
1896
2054
  - [Full-Text Search](./docs/FULL_TEXT_SEARCH_GUIDE.md) — Database-native FTS via `Metadata.FullTextSearch()`, SQL Server FREETEXT / PostgreSQL tsvector, provider architecture, Knowledge Hub integration
@@ -1,25 +0,0 @@
1
- import { IRunReportProvider, RunReportResult } from './interfaces.js';
2
- import { UserInfo } from './securityInfo.js';
3
- export type RunReportParams = {
4
- ReportID: string;
5
- };
6
- /**
7
- * Class used to run a report and return the results.
8
- */
9
- export declare class RunReport {
10
- private _provider;
11
- /**
12
- * Optionally, you can pass in a provider to use for running the report. If you dont pass in a provider, the static provider will be used.
13
- * @param provider
14
- */
15
- constructor(provider?: IRunReportProvider | null);
16
- /**
17
- * Returns the provider to be used for this instance, if one was passed in. Otherwise, it returns the static provider.
18
- */
19
- get ProviderToUse(): IRunReportProvider;
20
- RunReport(params: RunReportParams, contextUser?: UserInfo): Promise<RunReportResult>;
21
- private static _globalProviderKey;
22
- static get Provider(): IRunReportProvider;
23
- static set Provider(value: IRunReportProvider);
24
- }
25
- //# sourceMappingURL=runReport.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runReport.d.ts","sourceRoot":"","sources":["../../src/generic/runReport.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,MAAM,eAAe,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;GAEG;AACH,qBAAa,SAAS;IAClB,OAAO,CAAC,SAAS,CAAqB;IACtC;;;OAGG;gBACS,QAAQ,GAAE,kBAAkB,GAAG,IAAW;IAItD;;OAEG;IACH,IAAW,aAAa,IAAI,kBAAkB,CAE7C;IAEY,SAAS,CAAC,MAAM,EAAE,eAAe,EAAE,WAAW,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,eAAe,CAAC;IAIjG,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAkC;IACnE,WAAkB,QAAQ,IAAI,kBAAkB,CAM/C;IACD,WAAkB,QAAQ,CAAC,KAAK,EAAE,kBAAkB,EAMnD;CAEJ"}
@@ -1,38 +0,0 @@
1
- import { MJGlobal } from '@memberjunction/global';
2
- /**
3
- * Class used to run a report and return the results.
4
- */
5
- export class RunReport {
6
- /**
7
- * Optionally, you can pass in a provider to use for running the report. If you dont pass in a provider, the static provider will be used.
8
- * @param provider
9
- */
10
- constructor(provider = null) {
11
- this._provider = provider;
12
- }
13
- /**
14
- * Returns the provider to be used for this instance, if one was passed in. Otherwise, it returns the static provider.
15
- */
16
- get ProviderToUse() {
17
- return this._provider || RunReport.Provider;
18
- }
19
- async RunReport(params, contextUser) {
20
- return this.ProviderToUse.RunReport(params, contextUser);
21
- }
22
- static { this._globalProviderKey = 'MJ_RunReportProvider'; }
23
- static get Provider() {
24
- const g = MJGlobal.Instance.GetGlobalObjectStore();
25
- if (g)
26
- return g[RunReport._globalProviderKey];
27
- else
28
- throw new Error('No global object store, so we cant get the static provider');
29
- }
30
- static set Provider(value) {
31
- const g = MJGlobal.Instance.GetGlobalObjectStore();
32
- if (g)
33
- g[RunReport._globalProviderKey] = value;
34
- else
35
- throw new Error('No global object store, so we cant set the static provider');
36
- }
37
- }
38
- //# sourceMappingURL=runReport.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runReport.js","sourceRoot":"","sources":["../../src/generic/runReport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAQlD;;GAEG;AACH,MAAM,OAAO,SAAS;IAElB;;;OAGG;IACH,YAAY,WAAsC,IAAI;QAClD,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,IAAW,aAAa;QACpB,OAAO,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,QAAQ,CAAC;IAChD,CAAC;IAEM,KAAK,CAAC,SAAS,CAAC,MAAuB,EAAE,WAAsB;QAClE,OAAO,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC7D,CAAC;aAEc,uBAAkB,GAAW,sBAAsB,CAAC;IAC5D,MAAM,KAAK,QAAQ;QACtB,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,oBAAoB,EAAE,CAAC;QACnD,IAAI,CAAC;YACD,OAAO,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;;YAEvC,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IACtF,CAAC;IACM,MAAM,KAAK,QAAQ,CAAC,KAAyB;QAChD,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,oBAAoB,EAAE,CAAC;QACnD,IAAI,CAAC;YACD,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,GAAG,KAAK,CAAC;;YAExC,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IACtF,CAAC"}