@memberjunction/data-context 4.0.0 → 4.1.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.
- package/package.json +4 -4
- package/readme.md +314 -164
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/data-context",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.1.0",
|
|
5
5
|
"description": "This library provides a set of objects that handle run-time loading of data contexts as well as types to be able to use across application tiers for interacting with data contexts.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"typescript": "^5.9.3"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@memberjunction/global": "4.
|
|
24
|
-
"@memberjunction/core-entities": "4.
|
|
25
|
-
"@memberjunction/core": "4.
|
|
23
|
+
"@memberjunction/global": "4.1.0",
|
|
24
|
+
"@memberjunction/core-entities": "4.1.0",
|
|
25
|
+
"@memberjunction/core": "4.1.0"
|
|
26
26
|
},
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|
package/readme.md
CHANGED
|
@@ -1,15 +1,49 @@
|
|
|
1
1
|
# @memberjunction/data-context
|
|
2
2
|
|
|
3
|
-
The `@memberjunction/data-context` library provides a
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
The `@memberjunction/data-context` library provides a metadata-driven framework for managing collections of related data items in MemberJunction applications. It enables developers to define, load, persist, and manipulate data from multiple sources -- views, queries, entities, single records, and raw SQL -- through a unified, type-safe API that works across both client and server tiers.
|
|
4
|
+
|
|
5
|
+
## Architecture Overview
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
graph TD
|
|
9
|
+
subgraph Consumer["Consumer Code"]
|
|
10
|
+
style Consumer fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
11
|
+
A["Application / Agent / Workflow"]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
subgraph DataContextPkg["@memberjunction/data-context"]
|
|
15
|
+
style DataContextPkg fill:#7c5295,stroke:#563a6b,color:#fff
|
|
16
|
+
DC["DataContext"]
|
|
17
|
+
DCI["DataContextItem"]
|
|
18
|
+
DCFI["DataContextFieldInfo"]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
subgraph Sources["Data Sources"]
|
|
22
|
+
style Sources fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
23
|
+
V["Views (RunView)"]
|
|
24
|
+
Q["Queries (RunQuery)"]
|
|
25
|
+
E["Full Entities"]
|
|
26
|
+
SR["Single Records"]
|
|
27
|
+
SQL["SQL Statements"]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
subgraph Persistence["Persistence Layer"]
|
|
31
|
+
style Persistence fill:#b8762f,stroke:#8a5722,color:#fff
|
|
32
|
+
DCE["DataContextEntity"]
|
|
33
|
+
DCIE["DataContextItemEntity"]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
A --> DC
|
|
37
|
+
DC -->|"manages"| DCI
|
|
38
|
+
DCI -->|"field metadata"| DCFI
|
|
39
|
+
DCI -->|"loads from"| V
|
|
40
|
+
DCI -->|"loads from"| Q
|
|
41
|
+
DCI -->|"loads from"| E
|
|
42
|
+
DCI -->|"loads from"| SR
|
|
43
|
+
DCI -->|"loads from"| SQL
|
|
44
|
+
DC -->|"persists via"| DCE
|
|
45
|
+
DC -->|"persists items via"| DCIE
|
|
46
|
+
```
|
|
13
47
|
|
|
14
48
|
## Installation
|
|
15
49
|
|
|
@@ -17,54 +51,167 @@ A Data Context in MemberJunction represents a collection of data items that can
|
|
|
17
51
|
npm install @memberjunction/data-context
|
|
18
52
|
```
|
|
19
53
|
|
|
20
|
-
## Key
|
|
54
|
+
## Key Concepts
|
|
55
|
+
|
|
56
|
+
A **Data Context** is a named collection of **Data Context Items**, each of which represents a distinct data source. An item can be one of five types:
|
|
57
|
+
|
|
58
|
+
| Type | Description | Required Fields |
|
|
59
|
+
|-----------------|--------------------------------------------------|----------------------------------|
|
|
60
|
+
| `view` | A saved MemberJunction User View | `ViewID`, `EntityID` |
|
|
61
|
+
| `query` | A registered MemberJunction Query | `QueryID` |
|
|
62
|
+
| `full_entity` | All records from an entity | `EntityID` |
|
|
63
|
+
| `single_record` | One record, optionally with related entity data | `EntityID`, `RecordID` |
|
|
64
|
+
| `sql` | A raw SQL statement (server-side only) | `SQL` |
|
|
65
|
+
|
|
66
|
+
## Class Hierarchy
|
|
67
|
+
|
|
68
|
+
```mermaid
|
|
69
|
+
classDiagram
|
|
70
|
+
class DataContext {
|
|
71
|
+
+ID: string
|
|
72
|
+
+DataContextEntity: DataContextEntity
|
|
73
|
+
+Items: DataContextItem[]
|
|
74
|
+
+LoadMetadata(id, contextUser, provider) Promise~boolean~
|
|
75
|
+
+LoadData(dataSource, forceRefresh, ...) Promise~boolean~
|
|
76
|
+
+Load(id, dataSource, ...) Promise~boolean~
|
|
77
|
+
+SaveItems(contextUser, persistItemData) Promise~boolean~
|
|
78
|
+
+AddDataContextItem() DataContextItem
|
|
79
|
+
+ValidateDataExists(ignoreFailedLoadItems) boolean
|
|
80
|
+
+ConvertToSimpleObject(itemPrefix, includeFailedLoadItems) object
|
|
81
|
+
+CreateSimpleObjectTypeDefinition(itemPrefix, includeFailedLoadItems) string
|
|
82
|
+
+LoadDataFromObject(data) boolean
|
|
83
|
+
+Clone(context, includeData, contextUser)$ Promise~DataContext~
|
|
84
|
+
+FromRawData(rawData)$ Promise~DataContext~
|
|
85
|
+
+CreateDataContextItem()$ DataContextItem
|
|
86
|
+
+MapEntityFieldsToDataContextFields(entity)$ DataContextFieldInfo[]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class DataContextItem {
|
|
90
|
+
+Type: ItemType
|
|
91
|
+
+RecordID: string
|
|
92
|
+
+EntityID: string
|
|
93
|
+
+ViewID: string
|
|
94
|
+
+QueryID: string
|
|
95
|
+
+RecordName: string
|
|
96
|
+
+SQL: string
|
|
97
|
+
+CodeName: string
|
|
98
|
+
+EntityName: string
|
|
99
|
+
+Fields: DataContextFieldInfo[]
|
|
100
|
+
+DataContextItemID: string
|
|
101
|
+
+Data: object[]
|
|
102
|
+
+DataLoaded: boolean
|
|
103
|
+
+DataLoadingError: string
|
|
104
|
+
+Description: string
|
|
105
|
+
+AdditionalDescription: string
|
|
106
|
+
+LoadData(dataSource, ...) Promise~boolean~
|
|
107
|
+
+LoadDataFromObject(data) boolean
|
|
108
|
+
+ValidateDataExists(ignoreFailedLoad) boolean
|
|
109
|
+
+FromViewEntity(viewEntity)$ DataContextItem
|
|
110
|
+
+FromSingleRecord(record)$ DataContextItem
|
|
111
|
+
+FromQuery(query)$ DataContextItem
|
|
112
|
+
+FromFullEntity(entity)$ DataContextItem
|
|
113
|
+
+FromRawItem(rawItem)$ DataContextItem
|
|
114
|
+
#LoadFromView(contextUser) Promise~boolean~
|
|
115
|
+
#LoadFromFullEntity(contextUser) Promise~boolean~
|
|
116
|
+
#LoadFromSingleRecord(contextUser, ...) Promise~boolean~
|
|
117
|
+
#LoadFromQuery(contextUser) Promise~boolean~
|
|
118
|
+
#LoadFromSQL(dataSource, contextUser) Promise~boolean~
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
class DataContextFieldInfo {
|
|
122
|
+
+Name: string
|
|
123
|
+
+Type: string
|
|
124
|
+
+Description: string
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
class DataContextItemServer {
|
|
128
|
+
#LoadFromSQL(dataSource, contextUser) Promise~boolean~
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
DataContext "1" --> "*" DataContextItem : contains
|
|
132
|
+
DataContextItem "1" --> "*" DataContextFieldInfo : describes fields
|
|
133
|
+
DataContextItemServer --|> DataContextItem : extends
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Data Lifecycle
|
|
137
|
+
|
|
138
|
+
```mermaid
|
|
139
|
+
flowchart LR
|
|
140
|
+
subgraph Step1["1. Create"]
|
|
141
|
+
style Step1 fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
142
|
+
C1["new DataContext()"]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
subgraph Step2["2. Load Metadata"]
|
|
146
|
+
style Step2 fill:#7c5295,stroke:#563a6b,color:#fff
|
|
147
|
+
C2["LoadMetadata(id)"]
|
|
148
|
+
end
|
|
21
149
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
150
|
+
subgraph Step3["3. Load Data"]
|
|
151
|
+
style Step3 fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
152
|
+
C3["LoadData(dataSource)"]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
subgraph Step4["4. Process"]
|
|
156
|
+
style Step4 fill:#b8762f,stroke:#8a5722,color:#fff
|
|
157
|
+
C4["ValidateDataExists()\nConvertToSimpleObject()"]
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
subgraph Step5["5. Persist"]
|
|
161
|
+
style Step5 fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
162
|
+
C5["SaveItems(contextUser)"]
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
Step1 --> Step2 --> Step3 --> Step4 --> Step5
|
|
166
|
+
```
|
|
28
167
|
|
|
29
168
|
## Usage
|
|
30
169
|
|
|
31
|
-
###
|
|
170
|
+
### Loading an Existing Data Context
|
|
171
|
+
|
|
172
|
+
Use `Load()` to fetch both metadata and data in a single call:
|
|
32
173
|
|
|
33
174
|
```typescript
|
|
34
|
-
import { DataContext
|
|
35
|
-
import { Metadata } from '@memberjunction/core';
|
|
175
|
+
import { DataContext } from '@memberjunction/data-context';
|
|
36
176
|
|
|
37
|
-
// Create a new data context
|
|
38
177
|
const context = new DataContext();
|
|
39
|
-
|
|
40
|
-
// Load metadata and data for an existing data context
|
|
41
|
-
const dataContextID = 'your-data-context-id';
|
|
42
178
|
const loaded = await context.Load(
|
|
43
|
-
dataContextID,
|
|
44
|
-
dataSource,
|
|
45
|
-
false,
|
|
46
|
-
true,
|
|
47
|
-
10,
|
|
48
|
-
|
|
179
|
+
dataContextID, // ID of the data context record
|
|
180
|
+
dataSource, // Required only for SQL-type items (server-side)
|
|
181
|
+
false, // forceRefresh -- reload even if data is cached
|
|
182
|
+
true, // loadRelatedDataOnSingleRecords
|
|
183
|
+
10, // maxRecordsPerRelationship
|
|
184
|
+
contextUser // required on server-side
|
|
49
185
|
);
|
|
50
186
|
|
|
51
187
|
if (loaded) {
|
|
52
|
-
// Access the loaded data
|
|
53
188
|
context.Items.forEach(item => {
|
|
54
|
-
console.log(
|
|
55
|
-
console.log(`Data rows: ${item.Data?.length || 0}`);
|
|
189
|
+
console.log(`${item.Description}: ${item.Data?.length ?? 0} rows`);
|
|
56
190
|
});
|
|
57
191
|
}
|
|
58
192
|
```
|
|
59
193
|
|
|
60
|
-
|
|
194
|
+
Alternatively, call `LoadMetadata()` and `LoadData()` separately for finer control:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const context = new DataContext();
|
|
198
|
+
await context.LoadMetadata(dataContextID, contextUser);
|
|
199
|
+
|
|
200
|
+
// Inspect or modify items before loading data
|
|
201
|
+
console.log(`Items to load: ${context.Items.length}`);
|
|
202
|
+
|
|
203
|
+
await context.LoadData(dataSource, false, true, 10, contextUser);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Creating Items Programmatically
|
|
207
|
+
|
|
208
|
+
Each item type has a dedicated static factory method.
|
|
61
209
|
|
|
62
210
|
#### From a View
|
|
63
211
|
|
|
64
212
|
```typescript
|
|
65
213
|
import { UserViewEntityExtended } from '@memberjunction/core-entities';
|
|
66
214
|
|
|
67
|
-
// Assuming you have a view entity loaded
|
|
68
215
|
const viewEntity: UserViewEntityExtended = await md.GetEntityObject<UserViewEntityExtended>('User Views');
|
|
69
216
|
await viewEntity.Load(viewID);
|
|
70
217
|
|
|
@@ -77,7 +224,6 @@ context.Items.push(viewItem);
|
|
|
77
224
|
```typescript
|
|
78
225
|
import { BaseEntity } from '@memberjunction/core';
|
|
79
226
|
|
|
80
|
-
// Assuming you have an entity record loaded
|
|
81
227
|
const record: BaseEntity = await md.GetEntityObject('Customers');
|
|
82
228
|
await record.Load(recordID);
|
|
83
229
|
|
|
@@ -90,8 +236,7 @@ context.Items.push(recordItem);
|
|
|
90
236
|
```typescript
|
|
91
237
|
import { QueryInfo } from '@memberjunction/core';
|
|
92
238
|
|
|
93
|
-
|
|
94
|
-
const queryInfo = md.Queries.find(q => q.Name === 'My Query');
|
|
239
|
+
const queryInfo = md.Queries.find(q => q.Name === 'Monthly Revenue');
|
|
95
240
|
if (queryInfo) {
|
|
96
241
|
const queryItem = DataContextItem.FromQuery(queryInfo);
|
|
97
242
|
context.Items.push(queryItem);
|
|
@@ -103,7 +248,6 @@ if (queryInfo) {
|
|
|
103
248
|
```typescript
|
|
104
249
|
import { EntityInfo } from '@memberjunction/core';
|
|
105
250
|
|
|
106
|
-
// Get entity info from metadata
|
|
107
251
|
const entityInfo = md.Entities.find(e => e.Name === 'Products');
|
|
108
252
|
if (entityInfo) {
|
|
109
253
|
const entityItem = DataContextItem.FromFullEntity(entityInfo);
|
|
@@ -115,75 +259,85 @@ if (entityInfo) {
|
|
|
115
259
|
|
|
116
260
|
```typescript
|
|
117
261
|
// Load data for all items in the context
|
|
118
|
-
const
|
|
119
|
-
dataSource, //
|
|
262
|
+
const allLoaded = await context.LoadData(
|
|
263
|
+
dataSource, // required for SQL-type items
|
|
120
264
|
false, // forceRefresh
|
|
121
265
|
true, // loadRelatedDataOnSingleRecords
|
|
122
|
-
10
|
|
266
|
+
10 // maxRecordsPerRelationship
|
|
123
267
|
);
|
|
124
268
|
|
|
125
|
-
// Or load data for a
|
|
269
|
+
// Or load data for a single item
|
|
126
270
|
const itemLoaded = await context.Items[0].LoadData(
|
|
127
|
-
dataSource,
|
|
128
|
-
false,
|
|
129
|
-
true,
|
|
130
|
-
10
|
|
271
|
+
dataSource, false, true, 10, contextUser
|
|
131
272
|
);
|
|
132
273
|
```
|
|
133
274
|
|
|
275
|
+
### Loading Pre-fetched Data
|
|
276
|
+
|
|
277
|
+
If you already have the data available (for example, from a cache or external API), load it directly:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// Load into a single item
|
|
281
|
+
const item = context.Items[0];
|
|
282
|
+
item.LoadDataFromObject(myDataArray);
|
|
283
|
+
|
|
284
|
+
// Load into all items at once (2D array, one sub-array per item in order)
|
|
285
|
+
context.LoadDataFromObject([
|
|
286
|
+
rowsForItem0,
|
|
287
|
+
rowsForItem1,
|
|
288
|
+
rowsForItem2
|
|
289
|
+
]);
|
|
290
|
+
```
|
|
291
|
+
|
|
134
292
|
### Saving Data Context Items
|
|
135
293
|
|
|
136
294
|
```typescript
|
|
137
|
-
// Save all items in the context to the database
|
|
138
295
|
const saved = await context.SaveItems(
|
|
139
|
-
|
|
140
|
-
true
|
|
296
|
+
contextUser, // context user for server-side operations
|
|
297
|
+
true // persistItemData -- also save loaded data as JSON
|
|
141
298
|
);
|
|
142
299
|
|
|
143
300
|
if (saved) {
|
|
144
|
-
console.log('Data context items saved successfully');
|
|
145
301
|
// Each item now has a DataContextItemID populated
|
|
302
|
+
context.Items.forEach(item => {
|
|
303
|
+
console.log(`Saved item ${item.DataContextItemID}`);
|
|
304
|
+
});
|
|
146
305
|
}
|
|
147
306
|
```
|
|
148
307
|
|
|
149
|
-
|
|
308
|
+
Items are saved in a single transaction group -- either all succeed or none are committed.
|
|
309
|
+
|
|
310
|
+
### Working with Loaded Data
|
|
150
311
|
|
|
151
312
|
```typescript
|
|
152
|
-
// Validate
|
|
313
|
+
// Validate all items have data
|
|
153
314
|
if (context.ValidateDataExists()) {
|
|
154
|
-
// Convert to a
|
|
155
|
-
const
|
|
315
|
+
// Convert to a flat keyed object
|
|
316
|
+
const simpleObj = context.ConvertToSimpleObject('item_', false);
|
|
156
317
|
// Result: { item_0: [...], item_1: [...], ... }
|
|
157
|
-
|
|
158
|
-
//
|
|
318
|
+
|
|
319
|
+
// Generate a TypeScript-style type definition string
|
|
159
320
|
const typeDef = context.CreateSimpleObjectTypeDefinition('item_');
|
|
160
|
-
console.log(typeDef);
|
|
161
321
|
// Output: {item_0: []; // View: Customer List, From Entity: Customers\n...}
|
|
162
322
|
}
|
|
163
323
|
|
|
164
|
-
//
|
|
324
|
+
// Check individual items for errors
|
|
165
325
|
context.Items.forEach(item => {
|
|
166
326
|
if (item.DataLoaded && item.Data) {
|
|
167
327
|
console.log(`${item.Description}: ${item.Data.length} rows`);
|
|
168
|
-
|
|
169
|
-
// Process the data
|
|
170
|
-
item.Data.forEach(row => {
|
|
171
|
-
// Work with row data
|
|
172
|
-
});
|
|
173
328
|
} else if (item.DataLoadingError) {
|
|
174
|
-
console.error(`
|
|
329
|
+
console.error(`Failed: ${item.DataLoadingError}`);
|
|
175
330
|
}
|
|
176
331
|
});
|
|
177
332
|
```
|
|
178
333
|
|
|
179
|
-
### Cloning Data
|
|
334
|
+
### Cloning a Data Context
|
|
180
335
|
|
|
181
336
|
```typescript
|
|
182
|
-
// Clone an existing data context
|
|
183
337
|
const clonedContext = await DataContext.Clone(
|
|
184
338
|
originalContext,
|
|
185
|
-
true,
|
|
186
|
-
|
|
339
|
+
true, // includeData -- copy loaded data into the clone
|
|
340
|
+
contextUser
|
|
187
341
|
);
|
|
188
342
|
|
|
189
343
|
if (clonedContext) {
|
|
@@ -191,116 +345,112 @@ if (clonedContext) {
|
|
|
191
345
|
}
|
|
192
346
|
```
|
|
193
347
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
### DataContext Class
|
|
197
|
-
|
|
198
|
-
#### Properties
|
|
199
|
-
|
|
200
|
-
- `ID: string` - The unique identifier of the data context
|
|
201
|
-
- `DataContextEntity: DataContextEntity` - The metadata entity for the data context
|
|
202
|
-
- `Items: DataContextItem[]` - Array of data context items
|
|
203
|
-
|
|
204
|
-
#### Methods
|
|
205
|
-
|
|
206
|
-
- `async LoadMetadata(DataContextID: string, contextUser?: UserInfo, provider?: IMetadataProvider): Promise<boolean>`
|
|
207
|
-
- Loads only the metadata for the data context and its items
|
|
208
|
-
|
|
209
|
-
- `async LoadData(dataSource: any, forceRefresh?: boolean, loadRelatedDataOnSingleRecords?: boolean, maxRecordsPerRelationship?: number, contextUser?: UserInfo): Promise<boolean>`
|
|
210
|
-
- Loads data for all items in the context
|
|
211
|
-
|
|
212
|
-
- `async Load(DataContextID: string, dataSource: any, forceRefresh?: boolean, loadRelatedDataOnSingleRecords?: boolean, maxRecordsPerRelationship?: number, contextUser?: UserInfo): Promise<boolean>`
|
|
213
|
-
- Loads both metadata and data in one operation
|
|
214
|
-
|
|
215
|
-
- `async SaveItems(contextUser?: UserInfo, persistItemData?: boolean): Promise<boolean>`
|
|
216
|
-
- Saves all data context items to the database
|
|
217
|
-
|
|
218
|
-
- `AddDataContextItem(): DataContextItem`
|
|
219
|
-
- Creates and adds a new item to the context
|
|
220
|
-
|
|
221
|
-
- `ValidateDataExists(ignoreFailedLoadItems?: boolean): boolean`
|
|
222
|
-
- Checks if all items have data loaded
|
|
223
|
-
|
|
224
|
-
- `ConvertToSimpleObject(itemPrefix?: string, includeFailedLoadItems?: boolean): any`
|
|
225
|
-
- Converts the context to a simple object structure
|
|
348
|
+
### Reconstructing from Raw Data
|
|
226
349
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
- Loads pre-fetched data into the context
|
|
232
|
-
|
|
233
|
-
- `static async Clone(context: DataContext, includeData?: boolean, contextUser?: UserInfo): Promise<DataContext>`
|
|
234
|
-
- Creates a deep copy of a data context
|
|
235
|
-
|
|
236
|
-
- `static async FromRawData(rawData: any): Promise<DataContext>`
|
|
237
|
-
- Creates a context from raw data object
|
|
238
|
-
|
|
239
|
-
### DataContextItem Class
|
|
240
|
-
|
|
241
|
-
#### Properties
|
|
242
|
-
|
|
243
|
-
- `Type: 'view' | 'query' | 'full_entity' | 'sql' | 'single_record'` - The type of data item
|
|
244
|
-
- `RecordID: string` - Primary key for single_record types
|
|
245
|
-
- `EntityID?: string` - Entity identifier
|
|
246
|
-
- `ViewID?: string` - View identifier
|
|
247
|
-
- `QueryID?: string` - Query identifier
|
|
248
|
-
- `RecordName: string` - Name of the view, query, or entity
|
|
249
|
-
- `SQL?: string` - SQL statement for 'sql' type
|
|
250
|
-
- `EntityName?: string` - Name of the entity
|
|
251
|
-
- `Fields: DataContextFieldInfo[]` - Field metadata
|
|
252
|
-
- `Data: any[]` - The loaded data
|
|
253
|
-
- `DataLoaded: boolean` - Indicates if data has been loaded
|
|
254
|
-
- `DataLoadingError?: string` - Error message if loading failed
|
|
255
|
-
- `Description: string` - Auto-generated description
|
|
256
|
-
- `AdditionalDescription?: string` - Optional custom description
|
|
257
|
-
|
|
258
|
-
#### Methods
|
|
259
|
-
|
|
260
|
-
- `async LoadData(dataSource: any, forceRefresh?: boolean, loadRelatedDataOnSingleRecords?: boolean, maxRecordsPerRelationship?: number, contextUser?: UserInfo): Promise<boolean>`
|
|
261
|
-
- Loads data for this specific item
|
|
350
|
+
```typescript
|
|
351
|
+
const context = await DataContext.FromRawData(rawObject);
|
|
352
|
+
// rawObject should have { ID, Items: [ { Type, RecordID, ... }, ... ] }
|
|
353
|
+
```
|
|
262
354
|
|
|
263
|
-
-
|
|
264
|
-
- Loads pre-fetched data into the item
|
|
355
|
+
## Server-Side SQL Support
|
|
265
356
|
|
|
266
|
-
|
|
267
|
-
- Validates that data has been loaded
|
|
357
|
+
The base `DataContextItem` class throws an error when `LoadFromSQL()` is called because raw SQL execution is inherently a server-side operation. The companion package `@memberjunction/data-context-server` provides `DataContextItemServer`, which overrides this method using an `mssql` connection pool:
|
|
268
358
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
359
|
+
```mermaid
|
|
360
|
+
flowchart TD
|
|
361
|
+
subgraph Client["Client Tier"]
|
|
362
|
+
style Client fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
363
|
+
DCI_C["DataContextItem\n(base class)"]
|
|
364
|
+
end
|
|
274
365
|
|
|
275
|
-
|
|
366
|
+
subgraph Server["Server Tier"]
|
|
367
|
+
style Server fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
368
|
+
DCI_S["DataContextItemServer\n(@RegisterClass override)"]
|
|
369
|
+
MSSQL["mssql ConnectionPool"]
|
|
370
|
+
end
|
|
276
371
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
Type: string;
|
|
281
|
-
Description?: string;
|
|
282
|
-
}
|
|
372
|
+
DCI_C -->|"LoadFromSQL() throws error"| X["Not supported client-side"]
|
|
373
|
+
DCI_S -->|"LoadFromSQL() executes via"| MSSQL
|
|
374
|
+
DCI_S -.->|"extends"| DCI_C
|
|
283
375
|
```
|
|
284
376
|
|
|
285
|
-
|
|
377
|
+
`DataContextItemServer` is registered via `@RegisterClass` with a higher priority, so the MemberJunction class factory automatically returns the server variant when the server package is included. No code changes are needed -- just include `@memberjunction/data-context-server` in your server project's dependencies.
|
|
378
|
+
|
|
379
|
+
## API Reference
|
|
286
380
|
|
|
287
|
-
|
|
381
|
+
### DataContext
|
|
382
|
+
|
|
383
|
+
| Method | Returns | Description |
|
|
384
|
+
|--------|---------|-------------|
|
|
385
|
+
| `LoadMetadata(id, contextUser?, provider?)` | `Promise<boolean>` | Loads metadata for the context and all its items from the database |
|
|
386
|
+
| `LoadData(dataSource, forceRefresh?, loadRelated?, maxRecords?, contextUser?)` | `Promise<boolean>` | Loads data for all items; must call `LoadMetadata()` first |
|
|
387
|
+
| `Load(id, dataSource, forceRefresh?, loadRelated?, maxRecords?, contextUser?)` | `Promise<boolean>` | Combined metadata + data load in one call |
|
|
388
|
+
| `SaveItems(contextUser?, persistItemData?)` | `Promise<boolean>` | Persists all items to the database in a single transaction |
|
|
389
|
+
| `AddDataContextItem()` | `DataContextItem` | Creates a new item and adds it to `Items` |
|
|
390
|
+
| `ValidateDataExists(ignoreFailedLoadItems?)` | `boolean` | Returns true if all items have their `Data` property set |
|
|
391
|
+
| `ConvertToSimpleObject(itemPrefix?, includeFailedLoadItems?)` | `object` | Converts context to a flat keyed object |
|
|
392
|
+
| `CreateSimpleObjectTypeDefinition(itemPrefix?, includeFailedLoadItems?)` | `string` | Generates a type definition string for the simple object |
|
|
393
|
+
| `LoadDataFromObject(data)` | `boolean` | Loads pre-fetched data as a 2D array mapped by item index |
|
|
394
|
+
| `Clone(context, includeData?, contextUser?)` (static) | `Promise<DataContext>` | Deep-clones a context and its items into new database records |
|
|
395
|
+
| `FromRawData(rawData)` (static) | `Promise<DataContext>` | Reconstructs a `DataContext` from a plain object |
|
|
396
|
+
| `CreateDataContextItem()` (static) | `DataContextItem` | Creates a new item via the class factory (does not add to context) |
|
|
397
|
+
| `MapEntityFieldsToDataContextFields(entity)` (static) | `DataContextFieldInfo[]` | Maps `EntityInfo` fields to simplified field info objects |
|
|
398
|
+
|
|
399
|
+
### DataContextItem
|
|
400
|
+
|
|
401
|
+
| Property | Type | Description |
|
|
402
|
+
|----------|------|-------------|
|
|
403
|
+
| `Type` | `'view' \| 'query' \| 'full_entity' \| 'sql' \| 'single_record'` | The data source type |
|
|
404
|
+
| `RecordID` | `string` | Primary key for `single_record` items (comma-separated for composite keys) |
|
|
405
|
+
| `EntityID` | `string` | Entity identifier (not used for `query` or `sql` types) |
|
|
406
|
+
| `ViewID` | `string` | View identifier (only for `view` type) |
|
|
407
|
+
| `QueryID` | `string` | Query identifier (only for `query` type) |
|
|
408
|
+
| `RecordName` | `string` | Display name of the view, query, or entity |
|
|
409
|
+
| `SQL` | `string` | SQL statement (only for `sql` type) |
|
|
410
|
+
| `CodeName` | `string` | System-generated unique code name within the context |
|
|
411
|
+
| `EntityName` | `string` | Entity name (not used for `query` or `sql` types) |
|
|
412
|
+
| `Fields` | `DataContextFieldInfo[]` | Field metadata for the item |
|
|
413
|
+
| `DataContextItemID` | `string` | Database record ID (populated after save) |
|
|
414
|
+
| `Data` | `object[]` | The loaded data rows |
|
|
415
|
+
| `DataLoaded` | `boolean` | Whether data has been successfully loaded |
|
|
416
|
+
| `DataLoadingError` | `string` | Error message if loading failed |
|
|
417
|
+
| `Description` | `string` (readonly) | Auto-generated description based on type |
|
|
418
|
+
| `AdditionalDescription` | `string` | Optional supplementary description |
|
|
419
|
+
|
|
420
|
+
| Static Factory Method | Description |
|
|
421
|
+
|----------------------|-------------|
|
|
422
|
+
| `FromViewEntity(viewEntity)` | Creates an item from a `UserViewEntityExtended` |
|
|
423
|
+
| `FromSingleRecord(record)` | Creates an item from a `BaseEntity` instance |
|
|
424
|
+
| `FromQuery(query)` | Creates an item from a `QueryInfo` |
|
|
425
|
+
| `FromFullEntity(entity)` | Creates an item from an `EntityInfo` |
|
|
426
|
+
| `FromRawItem(rawItem)` | Creates an item from a plain object |
|
|
427
|
+
|
|
428
|
+
### DataContextFieldInfo
|
|
429
|
+
|
|
430
|
+
| Property | Type | Description |
|
|
431
|
+
|----------|------|-------------|
|
|
432
|
+
| `Name` | `string` | Field name |
|
|
433
|
+
| `Type` | `string` | Field data type |
|
|
434
|
+
| `Description` | `string` (optional) | Field description |
|
|
288
435
|
|
|
289
436
|
## Dependencies
|
|
290
437
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
438
|
+
| Package | Purpose |
|
|
439
|
+
|---------|---------|
|
|
440
|
+
| `@memberjunction/global` | Class factory and `@RegisterClass` decorator |
|
|
441
|
+
| `@memberjunction/core` | Core framework: `Metadata`, `RunView`, `RunQuery`, `BaseEntity`, `EntityInfo`, `UserInfo` |
|
|
442
|
+
| `@memberjunction/core-entities` | Generated entity classes: `DataContextEntity`, `DataContextItemEntity`, `UserViewEntityExtended` |
|
|
294
443
|
|
|
295
444
|
## Best Practices
|
|
296
445
|
|
|
297
|
-
1. **Always load metadata before data
|
|
298
|
-
2. **
|
|
299
|
-
3. **
|
|
300
|
-
4. **
|
|
301
|
-
5. **Validate before
|
|
302
|
-
6. **
|
|
446
|
+
1. **Always load metadata before data.** Use `LoadMetadata()` then `LoadData()`, or use the combined `Load()` method.
|
|
447
|
+
2. **Check `DataLoaded` and `DataLoadingError`** on each item after loading. `LoadData()` does not throw exceptions for individual item failures -- it returns `false` and populates the error properties.
|
|
448
|
+
3. **Pass `contextUser` on the server side.** Server code serves multiple users concurrently and must include the context user for proper security and audit tracking.
|
|
449
|
+
4. **Use `maxRecordsPerRelationship`** to limit the amount of related data loaded for `single_record` items, especially in production environments.
|
|
450
|
+
5. **Validate before processing.** Call `ValidateDataExists()` before working with data to ensure all items are ready.
|
|
451
|
+
6. **Leverage transactions.** `SaveItems()` automatically wraps all item saves in a transaction group for atomicity.
|
|
452
|
+
7. **Include `@memberjunction/data-context-server`** in server projects if any items use the `sql` type. The class factory handles the override automatically.
|
|
303
453
|
|
|
304
454
|
## License
|
|
305
455
|
|
|
306
|
-
ISC
|
|
456
|
+
ISC
|