@memberjunction/react-test-harness 6.2.0-edge.0 → 6.2.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.
@@ -2,6 +2,175 @@ import { BrowserManager } from './browser-context.js';
2
2
  import type { UserInfo } from '@memberjunction/core';
3
3
  import { Violation, type LinterOptions } from '@memberjunction/react-linter';
4
4
  import { ComponentSpec, SimpleAITools } from '@memberjunction/interactive-component-types';
5
+ /**
6
+ * Row ceilings applied to the data bridges the component under test calls through
7
+ * (`__mjRunView`, `__mjRunViews`, `__mjRunQuery`).
8
+ *
9
+ * A test run only needs enough rows to render and be looked at, so the harness does not
10
+ * honour an unbounded request. `MaxRows` is applied at the SQL level by the provider, so
11
+ * the surplus is never produced rather than fetched and discarded — which matters because
12
+ * a bridge result is serialized in Node, shipped over CDP and rehydrated in Chromium,
13
+ * leaving several resident copies of the same data.
14
+ */
15
+ export interface DataCapOptions {
16
+ /** Ceiling for `RunView` / `RunViews`. @default 1000 */
17
+ runViewMaxRows?: number;
18
+ /** Ceiling for `RunQuery`. @default 1000 */
19
+ runQueryMaxRows?: number;
20
+ }
21
+ /**
22
+ * One data call made by the component under test, recorded by the harness bridges.
23
+ *
24
+ * Lets a caller tell apart failure modes that a screenshot alone cannot: a component
25
+ * showing no data because its query returned zero rows is an upstream data or parameter
26
+ * problem, while one showing no data after rows *were* returned is a render or binding
27
+ * bug.
28
+ */
29
+ /**
30
+ * Machine-readable identity of what a data call resolved to.
31
+ *
32
+ * Field names mirror `ComponentSpec.dataRequirements` (`ComponentQueryDataRequirement.name`
33
+ * / `.categoryPath`, `ComponentEntityDataRequirement.name`) so a caller can correlate what
34
+ * a component declared against what it actually called, without parsing a display string.
35
+ *
36
+ * It mirrors that vocabulary rather than referencing the spec, because a record must be
37
+ * able to describe a call that appears nowhere in `dataRequirements`.
38
+ */
39
+ export interface DataAccessIdentity {
40
+ /** Entity name — corresponds to `ComponentEntityDataRequirement.name`. */
41
+ entityName?: string;
42
+ /** Query name — corresponds to `ComponentQueryDataRequirement.name`. Not unique alone. */
43
+ queryName?: string;
44
+ /** Category path that disambiguates `queryName` — `ComponentQueryDataRequirement.categoryPath`. */
45
+ categoryPath?: string;
46
+ /** Present when the call named a query by ID, which takes precedence over name. */
47
+ queryId?: string;
48
+ /** Present when the call named a saved view by ID. */
49
+ viewId?: string;
50
+ /** Present when the call named a saved view by name. */
51
+ viewName?: string;
52
+ }
53
+ export interface DataAccessRecord {
54
+ kind: 'RunView' | 'RunViews' | 'RunQuery';
55
+ /**
56
+ * Human/LLM-readable rendering of {@link identity} — a category-qualified query path,
57
+ * or the view/entity a `RunView` resolved to. For display and prompts; use `identity`
58
+ * for matching.
59
+ */
60
+ target: string;
61
+ /** Structured identity, for correlating against declared data requirements. */
62
+ identity: DataAccessIdentity;
63
+ /** Rows actually handed to the component. */
64
+ rowsReturned: number;
65
+ /**
66
+ * Rows the query would have produced without the harness ceiling, when the provider
67
+ * reports it (`TotalRowCount`). Equal to `rowsReturned` when nothing was capped.
68
+ */
69
+ totalRowCount?: number;
70
+ /** True when the harness ceiling reduced the result below what the source held. */
71
+ capped: boolean;
72
+ /** Ceiling actually applied to this call. */
73
+ appliedMaxRows?: number;
74
+ /** `MaxRows` the component asked for, when it asked for one. */
75
+ requestedMaxRows?: number;
76
+ /** Wall-clock duration of the call, in milliseconds. */
77
+ durationMs: number;
78
+ /** Present when the call failed; the bridge returns an empty result set in that case. */
79
+ error?: string;
80
+ }
81
+ /** Default row ceiling for `RunView` / `RunViews`. @see DataCapOptions */
82
+ export declare const DEFAULT_RUN_VIEW_MAX_ROWS = 1000;
83
+ /** Default row ceiling for `RunQuery`. @see DataCapOptions */
84
+ export declare const DEFAULT_RUN_QUERY_MAX_ROWS = 1000;
85
+ /**
86
+ * Resolves the `MaxRows` to send for one data call.
87
+ *
88
+ * Clamps downward only: a component asking for 50 rows still gets 50, while one asking for
89
+ * nothing or for more than the ceiling gets the ceiling. A nonsensical request (zero,
90
+ * negative, non-numeric) is treated as absent.
91
+ *
92
+ * Returns the requested value alongside the applied one, so a capped call can report what
93
+ * the component wanted.
94
+ *
95
+ * Pure (no I/O) so it can be unit-tested without launching a browser.
96
+ */
97
+ export declare function ResolveDataCapMaxRows(requested: number | undefined, ceiling: number): {
98
+ applied: number;
99
+ requested?: number;
100
+ };
101
+ /**
102
+ * Builds a {@link DataAccessRecord} from a completed provider call.
103
+ *
104
+ * `TotalRowCount` is the provider's count of rows the query would have produced without
105
+ * paging, making it the denominator for "capped at N of M". Providers that omit it, or
106
+ * leave it equal to `RowCount`, yield `capped: false` rather than a guess.
107
+ *
108
+ * Pure (no I/O) so it can be unit-tested without launching a browser.
109
+ */
110
+ export declare function BuildDataAccessRecord(kind: DataAccessRecord['kind'], resolved: {
111
+ target: string;
112
+ identity: DataAccessIdentity;
113
+ }, result: {
114
+ Results?: any[];
115
+ TotalRowCount?: number;
116
+ } | undefined, maxRows: {
117
+ applied: number;
118
+ requested?: number;
119
+ }, durationMs: number): DataAccessRecord;
120
+ /** Debug-log suffix naming the cap, e.g. ` (capped at 1000 of 205802)`. Empty when uncapped. */
121
+ export declare function DescribeDataCap(record: DataAccessRecord): string;
122
+ /**
123
+ * Names the query a `RunQuery` call resolved to, as both a display string and a structured
124
+ * {@link DataAccessIdentity}.
125
+ *
126
+ * A query name is not unique on its own — name and category path identify a query together
127
+ * — so the display form is category-qualified.
128
+ *
129
+ * Precedence mirrors how `RunQuery` itself resolves: `QueryID` wins and `QueryName` is
130
+ * ignored when both are supplied, so the ID is reported as the identity and the supplied
131
+ * name is kept only as a label. `identity` retains every part the caller passed, so a
132
+ * correlation by name and path still works when an ID drove the resolution.
133
+ */
134
+ export declare function ResolveQueryTarget(params: {
135
+ QueryID?: string;
136
+ QueryName?: string;
137
+ CategoryPath?: string;
138
+ CategoryID?: string;
139
+ }): {
140
+ target: string;
141
+ identity: DataAccessIdentity;
142
+ };
143
+ /** Display-only form of {@link ResolveQueryTarget}. */
144
+ export declare function DescribeQueryTarget(params: {
145
+ QueryID?: string;
146
+ QueryName?: string;
147
+ CategoryPath?: string;
148
+ CategoryID?: string;
149
+ }): string;
150
+ /**
151
+ * Names the view a `RunView` call resolved to, as both a display string and a structured
152
+ * {@link DataAccessIdentity}.
153
+ *
154
+ * Follows `RunViewParams`' own precedence — `ViewEntity` → `ViewID` → `ViewName` →
155
+ * `EntityName` — where each earlier field causes the later ones to be ignored. Reporting
156
+ * `EntityName` regardless would name something the call did not resolve by.
157
+ */
158
+ export declare function ResolveViewTarget(params: {
159
+ ViewEntity?: unknown;
160
+ ViewID?: string;
161
+ ViewName?: string;
162
+ EntityName?: string;
163
+ }): {
164
+ target: string;
165
+ identity: DataAccessIdentity;
166
+ };
167
+ /** Display-only form of {@link ResolveViewTarget}. */
168
+ export declare function DescribeViewTarget(params: {
169
+ ViewEntity?: unknown;
170
+ ViewID?: string;
171
+ ViewName?: string;
172
+ EntityName?: string;
173
+ }): string;
5
174
  /**
6
175
  * Browser-execution options for {@link ComponentRunner}. Extends {@link LinterOptions}
7
176
  * (which carries `componentSpec`, `contextUser`, `entityMetadata`, `utilities` —
@@ -94,6 +263,15 @@ export interface ComponentExecutionOptions extends LinterOptions {
94
263
  * @default 15000
95
264
  */
96
265
  dataIdleTimeoutMs?: number;
266
+ /**
267
+ * Row ceilings for the data bridges. Omitted fields fall back to
268
+ * {@link DEFAULT_RUN_VIEW_MAX_ROWS} /
269
+ * {@link DEFAULT_RUN_QUERY_MAX_ROWS} (1000).
270
+ *
271
+ * A component asking for fewer rows than the ceiling keeps its own smaller number;
272
+ * the ceiling only ever clamps downward.
273
+ */
274
+ dataCaps?: DataCapOptions;
97
275
  }
98
276
  export interface ComponentExecutionResult {
99
277
  success: boolean;
@@ -134,6 +312,13 @@ export interface ComponentExecutionResult {
134
312
  * This field gives you the "did the code work?" answer directly.
135
313
  */
136
314
  codeExecutionSuccess?: boolean;
315
+ /**
316
+ * Every data call the component made, in the order the bridges served them.
317
+ *
318
+ * Empty when the component fetched nothing — itself a useful signal, and distinct from
319
+ * a component whose calls all returned zero rows.
320
+ */
321
+ dataAccess?: DataAccessRecord[];
137
322
  }
138
323
  /**
139
324
  * ComponentRunner that uses the actual React runtime via Playwright UMD bundle
@@ -157,10 +342,17 @@ export declare class ComponentRunner {
157
342
  /**
158
343
  * Lint component code before execution
159
344
  */
345
+ LintComponent(componentCode: string, componentName: string, componentSpec?: any, isRootComponent?: boolean, contextUser?: UserInfo, options?: any): Promise<{
346
+ violations: Violation[];
347
+ hasErrors: boolean;
348
+ }>;
349
+ /** @deprecated Use {@link LintComponent}. */
160
350
  lintComponent(componentCode: string, componentName: string, componentSpec?: any, isRootComponent?: boolean, contextUser?: UserInfo, options?: any): Promise<{
161
351
  violations: Violation[];
162
352
  hasErrors: boolean;
163
353
  }>;
354
+ ExecuteComponent(options: ComponentExecutionOptions): Promise<ComponentExecutionResult>;
355
+ /** @deprecated Use {@link ExecuteComponent}. */
164
356
  executeComponent(options: ComponentExecutionOptions): Promise<ComponentExecutionResult>;
165
357
  /**
166
358
  * Load core runtime libraries into the page (React, ReactDOM, Babel, MJRuntime)
@@ -1 +1 @@
1
- {"version":3,"file":"component-runner.d.ts","sourceRoot":"","sources":["../../src/lib/component-runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"component-runner.d.ts","sourceRoot":"","sources":["../../src/lib/component-runner.ts"],"names":[],"mappings":"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"}
@@ -28,6 +28,141 @@ async function preResolveComponentSpec(spec, contextUser) {
28
28
  // For other types, return as-is
29
29
  return spec;
30
30
  }
31
+ /** Default row ceiling for `RunView` / `RunViews`. @see DataCapOptions */
32
+ export const DEFAULT_RUN_VIEW_MAX_ROWS = 1000;
33
+ /** Default row ceiling for `RunQuery`. @see DataCapOptions */
34
+ export const DEFAULT_RUN_QUERY_MAX_ROWS = 1000;
35
+ /**
36
+ * Resolves the `MaxRows` to send for one data call.
37
+ *
38
+ * Clamps downward only: a component asking for 50 rows still gets 50, while one asking for
39
+ * nothing or for more than the ceiling gets the ceiling. A nonsensical request (zero,
40
+ * negative, non-numeric) is treated as absent.
41
+ *
42
+ * Returns the requested value alongside the applied one, so a capped call can report what
43
+ * the component wanted.
44
+ *
45
+ * Pure (no I/O) so it can be unit-tested without launching a browser.
46
+ */
47
+ export function ResolveDataCapMaxRows(requested, ceiling) {
48
+ const validRequest = typeof requested === 'number' && Number.isFinite(requested) && requested > 0 ? requested : undefined;
49
+ return {
50
+ applied: validRequest === undefined ? ceiling : Math.min(validRequest, ceiling),
51
+ requested: validRequest,
52
+ };
53
+ }
54
+ /**
55
+ * Builds a {@link DataAccessRecord} from a completed provider call.
56
+ *
57
+ * `TotalRowCount` is the provider's count of rows the query would have produced without
58
+ * paging, making it the denominator for "capped at N of M". Providers that omit it, or
59
+ * leave it equal to `RowCount`, yield `capped: false` rather than a guess.
60
+ *
61
+ * Pure (no I/O) so it can be unit-tested without launching a browser.
62
+ */
63
+ export function BuildDataAccessRecord(kind, resolved, result, maxRows, durationMs) {
64
+ const rowsReturned = result?.Results?.length ?? 0;
65
+ const total = typeof result?.TotalRowCount === 'number' ? result.TotalRowCount : undefined;
66
+ return {
67
+ kind,
68
+ target: resolved.target,
69
+ identity: resolved.identity,
70
+ rowsReturned,
71
+ totalRowCount: total,
72
+ capped: total !== undefined && total > rowsReturned,
73
+ appliedMaxRows: maxRows.applied,
74
+ requestedMaxRows: maxRows.requested,
75
+ durationMs,
76
+ };
77
+ }
78
+ /** Debug-log suffix naming the cap, e.g. ` (capped at 1000 of 205802)`. Empty when uncapped. */
79
+ export function DescribeDataCap(record) {
80
+ return record.capped ? ` (capped at ${record.rowsReturned} of ${record.totalRowCount})` : '';
81
+ }
82
+ /**
83
+ * Normalizes a category path and appends a leaf name, e.g. (`/MJ/AI/Agents/`, `Foo`) →
84
+ * `/MJ/AI/Agents/Foo`. Tolerates missing or doubled slashes on either side.
85
+ */
86
+ function joinCategoryPath(categoryPath, name) {
87
+ const trimmed = categoryPath.replace(/^\/+|\/+$/g, '');
88
+ return trimmed.length === 0 ? `/${name}` : `/${trimmed}/${name}`;
89
+ }
90
+ /**
91
+ * Names the query a `RunQuery` call resolved to, as both a display string and a structured
92
+ * {@link DataAccessIdentity}.
93
+ *
94
+ * A query name is not unique on its own — name and category path identify a query together
95
+ * — so the display form is category-qualified.
96
+ *
97
+ * Precedence mirrors how `RunQuery` itself resolves: `QueryID` wins and `QueryName` is
98
+ * ignored when both are supplied, so the ID is reported as the identity and the supplied
99
+ * name is kept only as a label. `identity` retains every part the caller passed, so a
100
+ * correlation by name and path still works when an ID drove the resolution.
101
+ */
102
+ export function ResolveQueryTarget(params) {
103
+ // `identity` records what the caller supplied, so a correlation against declared
104
+ // requirements can match on name+categoryPath even when an ID drove the resolution.
105
+ const identity = {};
106
+ if (params.QueryID)
107
+ identity.queryId = params.QueryID;
108
+ if (params.QueryName)
109
+ identity.queryName = params.QueryName;
110
+ if (params.CategoryPath)
111
+ identity.categoryPath = params.CategoryPath;
112
+ if (params.QueryID) {
113
+ return {
114
+ target: params.QueryName
115
+ ? `${params.QueryName} (resolved by QueryID ${params.QueryID})`
116
+ : `QueryID ${params.QueryID}`,
117
+ identity,
118
+ };
119
+ }
120
+ if (params.QueryName) {
121
+ if (params.CategoryPath) {
122
+ return { target: joinCategoryPath(params.CategoryPath, params.QueryName), identity };
123
+ }
124
+ if (params.CategoryID) {
125
+ return { target: `${params.QueryName} (CategoryID ${params.CategoryID})`, identity };
126
+ }
127
+ // Neither qualifier given: the name is all the caller supplied, so it is all we can
128
+ // report — flagged, because this is the ambiguous case rather than a resolved identity.
129
+ return { target: `${params.QueryName} (uncategorized)`, identity };
130
+ }
131
+ return { target: 'unknown', identity };
132
+ }
133
+ /** Display-only form of {@link ResolveQueryTarget}. */
134
+ export function DescribeQueryTarget(params) {
135
+ return ResolveQueryTarget(params).target;
136
+ }
137
+ /**
138
+ * Names the view a `RunView` call resolved to, as both a display string and a structured
139
+ * {@link DataAccessIdentity}.
140
+ *
141
+ * Follows `RunViewParams`' own precedence — `ViewEntity` → `ViewID` → `ViewName` →
142
+ * `EntityName` — where each earlier field causes the later ones to be ignored. Reporting
143
+ * `EntityName` regardless would name something the call did not resolve by.
144
+ */
145
+ export function ResolveViewTarget(params) {
146
+ const identity = {};
147
+ if (params.EntityName)
148
+ identity.entityName = params.EntityName;
149
+ if (params.ViewID)
150
+ identity.viewId = params.ViewID;
151
+ if (params.ViewName)
152
+ identity.viewName = params.ViewName;
153
+ const entitySuffix = params.EntityName ? ` on ${params.EntityName}` : '';
154
+ if (params.ViewEntity)
155
+ return { target: `saved view via ViewEntity${entitySuffix}`, identity };
156
+ if (params.ViewID)
157
+ return { target: `ViewID ${params.ViewID}${entitySuffix}`, identity };
158
+ if (params.ViewName)
159
+ return { target: `view "${params.ViewName}"${entitySuffix}`, identity };
160
+ return { target: params.EntityName || 'unknown', identity };
161
+ }
162
+ /** Display-only form of {@link ResolveViewTarget}. */
163
+ export function DescribeViewTarget(params) {
164
+ return ResolveViewTarget(params).target;
165
+ }
31
166
  /**
32
167
  * ComponentRunner that uses the actual React runtime via Playwright UMD bundle
33
168
  */
@@ -94,7 +229,7 @@ export class ComponentRunner {
94
229
  /**
95
230
  * Lint component code before execution
96
231
  */
97
- async lintComponent(componentCode, componentName, componentSpec, isRootComponent, contextUser, options) {
232
+ async LintComponent(componentCode, componentName, componentSpec, isRootComponent, contextUser, options) {
98
233
  const lintResult = await ComponentLinter.lintComponent(componentCode, componentName, componentSpec, isRootComponent, contextUser, false, // debugMode
99
234
  options);
100
235
  const hasErrors = lintResult.violations.some(v => v.severity === 'critical' || v.severity === 'high');
@@ -103,12 +238,17 @@ export class ComponentRunner {
103
238
  hasErrors
104
239
  };
105
240
  }
106
- async executeComponent(options) {
241
+ /** @deprecated Use {@link LintComponent}. */
242
+ async lintComponent(componentCode, componentName, componentSpec, isRootComponent, contextUser, options) {
243
+ return this.LintComponent(componentCode, componentName, componentSpec, isRootComponent, contextUser, options);
244
+ }
245
+ async ExecuteComponent(options) {
107
246
  const startTime = Date.now();
108
247
  const errors = [];
109
248
  const warnings = [];
110
249
  const consoleLogs = [];
111
250
  const dataErrors = []; // Track data access errors from RunView/RunQuery
251
+ const dataAccess = []; // Track every data call: target, rows, cap state
112
252
  let renderCount = 0;
113
253
  const debug = options.debug !== false; // Default to true for debugging
114
254
  const globalTimeout = options.timeout || 30000; // Default 30 seconds total timeout
@@ -178,7 +318,7 @@ export class ComponentRunner {
178
318
  // Set up console logging
179
319
  this.setupConsoleLogging(page, consoleLogs, warnings);
180
320
  // Expose MJ utilities to the page
181
- await this.exposeMJUtilities(page, options, dataErrors, debug);
321
+ await this.exposeMJUtilities(page, options, dataErrors, dataAccess, debug);
182
322
  if (debug) {
183
323
  console.log('📤 NODE: About to call page.evaluate with:');
184
324
  console.log(' - spec.name:', options.componentSpec.name);
@@ -682,6 +822,10 @@ export class ComponentRunner {
682
822
  super(props);
683
823
  this.state = { hasError: false, error: null };
684
824
  }
825
+ // React reads this static into a local and calls it UNBOUND
826
+ // (`var f = fiber.type.getDerivedStateFromError; f(error)`), so a stub
827
+ // that forwards through `this` throws before the capture below can run —
828
+ // the harness would then miss the user component's error entirely.
685
829
  static getDerivedStateFromError(error) {
686
830
  // Capture the actual error message IMMEDIATELY
687
831
  window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
@@ -907,9 +1051,13 @@ export class ComponentRunner {
907
1051
  try {
908
1052
  // Try to get HTML content with a reasonable size limit
909
1053
  html = await page.content();
910
- // Check if HTML is excessively large (>100MB is suspicious)
1054
+ // Backstop only — the row ceilings on the data bridges (see DataCapOptions) stop a
1055
+ // runaway result set before it reaches the DOM. Deliberately far below Node's ~536MB
1056
+ // string limit: the harness shares a heap with the host process, so by the time a
1057
+ // page serializes to tens of megabytes the run is pathological whether or not the
1058
+ // string itself fits.
911
1059
  const htmlSizeEstimate = Buffer.byteLength(html, 'utf8');
912
- const maxSafeSize = 100 * 1024 * 1024; // 100MB
1060
+ const maxSafeSize = 25 * 1024 * 1024; // 25MB
913
1061
  if (htmlSizeEstimate > maxSafeSize) {
914
1062
  console.warn(`⚠️ HTML content is very large (${Math.round(htmlSizeEstimate / 1024 / 1024)}MB). Truncating to prevent crashes.`);
915
1063
  // Truncate to a safe size
@@ -1047,23 +1195,40 @@ export class ComponentRunner {
1047
1195
  // Medium, low, and warning severity don't affect this metric
1048
1196
  const nonCrashCriticalHighErrors = allErrorViolations.filter(e => (e.severity === 'critical' || e.severity === 'high') && e.rule !== 'browser-crash');
1049
1197
  const codeExecutionSuccess = nonCrashCriticalHighErrors.length === 0;
1198
+ // Surface capped data calls as their own warnings, so a downstream evaluator does not
1199
+ // read a deliberately truncated render as a defect.
1200
+ const capWarnings = dataAccess
1201
+ .filter(d => d.capped)
1202
+ .map(d => ({
1203
+ message: `Data capped: ${d.kind} "${d.target}" returned ${d.rowsReturned} of ${d.totalRowCount} rows ` +
1204
+ `(harness ceiling ${d.appliedMaxRows}). Values aggregated in the component from this ` +
1205
+ `result are computed over partial data.`,
1206
+ severity: 'medium',
1207
+ rule: 'data-row-cap',
1208
+ line: 0,
1209
+ column: 0
1210
+ }));
1050
1211
  const result = {
1051
1212
  success: success && dataErrors.length === 0 && criticalWarningViolations.length === 0, // Fail on critical warnings too
1052
1213
  html,
1053
1214
  errors: allErrorViolations,
1054
1215
  browserCrash: hasBrowserCrash,
1055
- warnings: regularWarnings.map(w => ({
1056
- message: w,
1057
- severity: 'low',
1058
- rule: 'warning',
1059
- line: 0,
1060
- column: 0
1061
- })),
1216
+ warnings: [
1217
+ ...regularWarnings.map(w => ({
1218
+ message: w,
1219
+ severity: 'low',
1220
+ rule: 'warning',
1221
+ line: 0,
1222
+ column: 0
1223
+ })),
1224
+ ...capWarnings,
1225
+ ],
1062
1226
  console: consoleLogs,
1063
1227
  screenshot,
1064
1228
  executionTime: Date.now() - startTime,
1065
1229
  renderCount,
1066
1230
  codeExecutionSuccess,
1231
+ dataAccess,
1067
1232
  sourceMaps: this._lastSourceMaps,
1068
1233
  };
1069
1234
  if (debug) {
@@ -1134,7 +1299,9 @@ export class ComponentRunner {
1134
1299
  executionTime: Date.now() - startTime,
1135
1300
  renderCount,
1136
1301
  browserCrash: hasBrowserCrash,
1137
- codeExecutionSuccess
1302
+ codeExecutionSuccess,
1303
+ // Data calls made before the failure still describe what the component reached for.
1304
+ dataAccess,
1138
1305
  };
1139
1306
  if (debug) {
1140
1307
  if (hasBrowserCrash && codeExecutionSuccess) {
@@ -1163,6 +1330,10 @@ export class ComponentRunner {
1163
1330
  }
1164
1331
  }
1165
1332
  }
1333
+ /** @deprecated Use {@link ExecuteComponent}. */
1334
+ async executeComponent(options) {
1335
+ return this.ExecuteComponent(options);
1336
+ }
1166
1337
  /**
1167
1338
  * Load core runtime libraries into the page (React, ReactDOM, Babel, MJRuntime)
1168
1339
  * Component-specific libraries are loaded by the runtime's ComponentCompiler
@@ -1880,7 +2051,10 @@ export class ComponentRunner {
1880
2051
  /**
1881
2052
  * Expose MJ utilities to the browser context
1882
2053
  */
1883
- async exposeMJUtilities(page, options, dataErrors, debug = false) {
2054
+ async exposeMJUtilities(page, options, dataErrors, dataAccess, debug = false) {
2055
+ // Row ceilings for this run, resolved once rather than per call.
2056
+ const viewCeiling = options.dataCaps?.runViewMaxRows ?? DEFAULT_RUN_VIEW_MAX_ROWS;
2057
+ const queryCeiling = options.dataCaps?.runQueryMaxRows ?? DEFAULT_RUN_QUERY_MAX_ROWS;
1884
2058
  // Don't check if already exposed - we always start fresh after goto('about:blank')
1885
2059
  // The page.exposeFunction calls need to be made for each new page instance
1886
2060
  // Serialize contextUser to pass to the browser context
@@ -1966,12 +2140,16 @@ export class ComponentRunner {
1966
2140
  }
1967
2141
  });
1968
2142
  await page.exposeFunction('__mjRunView', async (params) => {
2143
+ const viewTarget = ResolveViewTarget(params);
2144
+ const maxRows = ResolveDataCapMaxRows(params.MaxRows, viewCeiling);
2145
+ const startedAt = Date.now();
1969
2146
  try {
1970
- const result = await util.rv.RunView(params, options.contextUser);
2147
+ const result = await util.rv.RunView({ ...params, MaxRows: maxRows.applied }, options.contextUser);
2148
+ const record = BuildDataAccessRecord('RunView', viewTarget, result, maxRows, Date.now() - startedAt);
2149
+ dataAccess.push(record);
1971
2150
  // Debug logging for successful calls
1972
2151
  if (debug) {
1973
- const rowCount = result.Results?.length || 0;
1974
- console.log(`💾 RunView SUCCESS: Entity="${params.EntityName}" Rows=${rowCount}`);
2152
+ console.log(`💾 RunView SUCCESS: View="${viewTarget.target}" Rows=${record.rowsReturned}${DescribeDataCap(record)}`);
1975
2153
  if (params.ExtraFilter) {
1976
2154
  console.log(` Filter: ${params.ExtraFilter}`);
1977
2155
  }
@@ -1980,36 +2158,53 @@ export class ComponentRunner {
1980
2158
  }
1981
2159
  catch (error) {
1982
2160
  const errorMessage = error instanceof Error ? error.message : String(error);
2161
+ dataAccess.push({
2162
+ kind: 'RunView', target: viewTarget.target, identity: viewTarget.identity, rowsReturned: 0, capped: false,
2163
+ appliedMaxRows: maxRows.applied, requestedMaxRows: maxRows.requested,
2164
+ durationMs: Date.now() - startedAt, error: errorMessage,
2165
+ });
1983
2166
  // Debug logging for errors
1984
2167
  if (debug) {
1985
- console.log(`❌ RunView FAILED: Entity="${params.EntityName || 'unknown'}"`);
2168
+ console.log(`❌ RunView FAILED: View="${viewTarget.target}"`);
1986
2169
  console.log(` Error: ${errorMessage}`);
1987
2170
  }
1988
2171
  else {
1989
2172
  console.error('Error in __mjRunView:', errorMessage);
1990
2173
  }
1991
2174
  // Collect this error for the test report
1992
- dataErrors.push(`RunView error: ${errorMessage} (Entity: ${params.EntityName || 'unknown'})`);
2175
+ dataErrors.push(`RunView error: ${errorMessage} (View: ${viewTarget.target})`);
1993
2176
  // Return error result that won't crash the component
1994
2177
  return { Success: false, ErrorMessage: errorMessage, Results: [] };
1995
2178
  }
1996
2179
  });
1997
2180
  await page.exposeFunction('__mjRunViews', async (params) => {
2181
+ const perCallMaxRows = params.map(p => ResolveDataCapMaxRows(p.MaxRows, viewCeiling));
2182
+ const startedAt = Date.now();
1998
2183
  try {
1999
- const results = await util.rv.RunViews(params, options.contextUser);
2184
+ const results = await util.rv.RunViews(params.map((p, i) => ({ ...p, MaxRows: perCallMaxRows[i].applied })), options.contextUser);
2185
+ // One record per view in the batch — a batch that partially returns data is a
2186
+ // meaningfully different signal from one that returns none.
2187
+ const elapsed = Date.now() - startedAt;
2188
+ const records = params.map((p, i) => BuildDataAccessRecord('RunViews', ResolveViewTarget(p), results[i], perCallMaxRows[i], elapsed));
2189
+ records.forEach(r => dataAccess.push(r));
2000
2190
  // Debug logging for successful calls
2001
2191
  if (debug) {
2002
2192
  console.log(`💾 RunViews SUCCESS: ${params.length} queries executed`);
2003
- params.forEach((p, i) => {
2004
- const rowCount = results[i]?.Results?.length || 0;
2005
- console.log(` [${i + 1}] Entity="${p.EntityName}" Rows=${rowCount}`);
2193
+ records.forEach((r, i) => {
2194
+ console.log(` [${i + 1}] Entity="${r.target}" Rows=${r.rowsReturned}${DescribeDataCap(r)}`);
2006
2195
  });
2007
2196
  }
2008
2197
  return results;
2009
2198
  }
2010
2199
  catch (error) {
2011
2200
  const errorMessage = error instanceof Error ? error.message : String(error);
2012
- const entities = params.map(p => p.EntityName || 'unknown').join(', ');
2201
+ const entities = params.map(p => DescribeViewTarget(p)).join(', ');
2202
+ // RunViews fails as a batch, so every view in it failed.
2203
+ params.forEach((p, i) => dataAccess.push({
2204
+ kind: 'RunViews', ...ResolveViewTarget(p), rowsReturned: 0, capped: false,
2205
+ appliedMaxRows: perCallMaxRows[i].applied, requestedMaxRows: perCallMaxRows[i].requested,
2206
+ durationMs: Date.now() - startedAt, error: errorMessage,
2207
+ }));
2013
2208
  // Debug logging for errors
2014
2209
  if (debug) {
2015
2210
  console.log(`❌ RunViews FAILED: Entities=[${entities}]`);
@@ -2025,13 +2220,16 @@ export class ComponentRunner {
2025
2220
  }
2026
2221
  });
2027
2222
  await page.exposeFunction('__mjRunQuery', async (params) => {
2223
+ const queryIdentifier = ResolveQueryTarget(params);
2224
+ const maxRows = ResolveDataCapMaxRows(params.MaxRows, queryCeiling);
2225
+ const startedAt = Date.now();
2028
2226
  try {
2029
- const result = await util.rq.RunQuery(params, options.contextUser);
2227
+ const result = await util.rq.RunQuery({ ...params, MaxRows: maxRows.applied }, options.contextUser);
2228
+ const record = BuildDataAccessRecord('RunQuery', queryIdentifier, result, maxRows, Date.now() - startedAt);
2229
+ dataAccess.push(record);
2030
2230
  // Debug logging for successful calls
2031
2231
  if (debug) {
2032
- const queryIdentifier = params.QueryName || params.QueryID || 'unknown';
2033
- const rowCount = result.Results?.length || 0;
2034
- console.log(`💾 RunQuery SUCCESS: Query="${queryIdentifier}" Rows=${rowCount}`);
2232
+ console.log(`💾 RunQuery SUCCESS: Query="${queryIdentifier.target}" Rows=${record.rowsReturned}${DescribeDataCap(record)}`);
2035
2233
  if (params.Parameters && Object.keys(params.Parameters).length > 0) {
2036
2234
  console.log(` Parameters:`, params.Parameters);
2037
2235
  }
@@ -2040,17 +2238,21 @@ export class ComponentRunner {
2040
2238
  }
2041
2239
  catch (error) {
2042
2240
  const errorMessage = error instanceof Error ? error.message : String(error);
2043
- const queryIdentifier = params.QueryName || params.QueryID || 'unknown';
2241
+ dataAccess.push({
2242
+ kind: 'RunQuery', target: queryIdentifier.target, identity: queryIdentifier.identity, rowsReturned: 0, capped: false,
2243
+ appliedMaxRows: maxRows.applied, requestedMaxRows: maxRows.requested,
2244
+ durationMs: Date.now() - startedAt, error: errorMessage,
2245
+ });
2044
2246
  // Debug logging for errors
2045
2247
  if (debug) {
2046
- console.log(`❌ RunQuery FAILED: Query="${queryIdentifier}"`);
2248
+ console.log(`❌ RunQuery FAILED: Query="${queryIdentifier.target}"`);
2047
2249
  console.log(` Error: ${errorMessage}`);
2048
2250
  }
2049
2251
  else {
2050
2252
  console.error('Error in __mjRunQuery:', errorMessage);
2051
2253
  }
2052
2254
  // Collect this error for the test report
2053
- dataErrors.push(`RunQuery error: ${errorMessage} (Query: ${queryIdentifier})`);
2255
+ dataErrors.push(`RunQuery error: ${errorMessage} (Query: ${queryIdentifier.target})`);
2054
2256
  // Return error result that won't crash the component
2055
2257
  return { Success: false, ErrorMessage: errorMessage, Results: [] };
2056
2258
  }
@@ -2116,7 +2318,11 @@ export class ComponentRunner {
2116
2318
  const VectorService = window.MJReactRuntime?.SimpleVectorService ||
2117
2319
  class {
2118
2320
  // Stub implementation if not available
2119
- cosineSimilarity(_a, _b) { return 0; }
2321
+ CosineSimilarity(_a, _b) { return 0; }
2322
+ /** @deprecated Use {@link CosineSimilarity}. */
2323
+ cosineSimilarity(_a, _b) {
2324
+ return this.CosineSimilarity(_a, _b);
2325
+ }
2120
2326
  };
2121
2327
  window.__mjUtilities = {
2122
2328
  md: {