@memberjunction/testing-integration 0.0.0 → 5.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/IntegrationTestDriver.d.ts +63 -0
  2. package/dist/IntegrationTestDriver.d.ts.map +1 -0
  3. package/dist/IntegrationTestDriver.js +425 -0
  4. package/dist/IntegrationTestDriver.js.map +1 -0
  5. package/dist/ai-verify.d.ts +27 -0
  6. package/dist/ai-verify.d.ts.map +1 -0
  7. package/dist/ai-verify.js +137 -0
  8. package/dist/ai-verify.js.map +1 -0
  9. package/dist/bootstrap-client.d.ts +11 -0
  10. package/dist/bootstrap-client.d.ts.map +1 -0
  11. package/dist/bootstrap-client.js +68 -0
  12. package/dist/bootstrap-client.js.map +1 -0
  13. package/dist/bootstrap-shared.d.ts +94 -0
  14. package/dist/bootstrap-shared.d.ts.map +1 -0
  15. package/dist/bootstrap-shared.js +94 -0
  16. package/dist/bootstrap-shared.js.map +1 -0
  17. package/dist/bootstrap.d.ts +14 -0
  18. package/dist/bootstrap.d.ts.map +1 -0
  19. package/dist/bootstrap.js +133 -0
  20. package/dist/bootstrap.js.map +1 -0
  21. package/dist/check-registry.d.ts +39 -0
  22. package/dist/check-registry.d.ts.map +1 -0
  23. package/dist/check-registry.js +61 -0
  24. package/dist/check-registry.js.map +1 -0
  25. package/dist/check.d.ts +531 -0
  26. package/dist/check.d.ts.map +1 -0
  27. package/dist/check.js +2 -0
  28. package/dist/check.js.map +1 -0
  29. package/dist/checks/self-test.check.d.ts +2 -0
  30. package/dist/checks/self-test.check.d.ts.map +1 -0
  31. package/dist/checks/self-test.check.js +35 -0
  32. package/dist/checks/self-test.check.js.map +1 -0
  33. package/dist/config.d.ts +35 -0
  34. package/dist/config.d.ts.map +1 -0
  35. package/dist/config.js +81 -0
  36. package/dist/config.js.map +1 -0
  37. package/dist/index.d.ts +25 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +37 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/instrumented-cache.d.ts +47 -0
  42. package/dist/instrumented-cache.d.ts.map +1 -0
  43. package/dist/instrumented-cache.js +81 -0
  44. package/dist/instrumented-cache.js.map +1 -0
  45. package/dist/registry.d.ts +33 -0
  46. package/dist/registry.d.ts.map +1 -0
  47. package/dist/registry.js +37 -0
  48. package/dist/registry.js.map +1 -0
  49. package/dist/rls-fixture.d.ts +37 -0
  50. package/dist/rls-fixture.d.ts.map +1 -0
  51. package/dist/rls-fixture.js +91 -0
  52. package/dist/rls-fixture.js.map +1 -0
  53. package/dist/test-runner.d.ts +55 -0
  54. package/dist/test-runner.d.ts.map +1 -0
  55. package/dist/test-runner.js +128 -0
  56. package/dist/test-runner.js.map +1 -0
  57. package/dist/tiers.d.ts +28 -0
  58. package/dist/tiers.d.ts.map +1 -0
  59. package/dist/tiers.js +41 -0
  60. package/dist/tiers.js.map +1 -0
  61. package/dist/types.d.ts +51 -0
  62. package/dist/types.d.ts.map +1 -0
  63. package/dist/types.js +2 -0
  64. package/dist/types.js.map +1 -0
  65. package/package.json +59 -8
  66. package/README.md +0 -45
@@ -0,0 +1,137 @@
1
+ /**
2
+ * ai-verify.ts — deep persistence verifiers for the live-model integration tier (§8 prerequisite).
3
+ *
4
+ * Lifted verbatim from integration-test-scripts/lib/ai-bootstrap.ts so the graduated live-model bundles
5
+ * (prompt-runner, agent-runner, concurrent) read them from the package instead of the tsx harness. After a
6
+ * real prompt/agent run, these fetch the persisted records and assert the OUTPUT is correct — AI Prompt
7
+ * Runs, AI Agent Runs, AI Agent Run Steps (each terminal + CompletedAt set, never stuck at 'Running'), and
8
+ * the Action Execution Logs / child runs the steps point at via TargetLogID. This is the live end-to-end
9
+ * regression guard for the fire-and-forget BaseEntitySaveQueue.
10
+ *
11
+ * `settle` (the fire-and-forget landing delay) lives in test-runner.ts and is re-exported from the barrel.
12
+ */
13
+ import { RunView } from '@memberjunction/core';
14
+ import { Assert } from './test-runner.js';
15
+ /** Truly-finished states whose row MUST carry a CompletedAt (the save-queue finalize guarantee). */
16
+ const TERMINAL = new Set(['Completed', 'Failed', 'Cancelled']);
17
+ /** An agent run "ran without error" if it completed or intentionally suspended (awaiting feedback/paused). */
18
+ const RAN_OK = new Set(['Completed', 'AwaitingFeedback', 'Paused']);
19
+ /** Fixed inter-attempt delay (ms) for the bounded poll. */
20
+ const FETCH_POLL_INTERVAL_MS = 500;
21
+ /** Default total poll budget (ms). Overridable per-run via MJ_IT_FETCH_POLL_MS for loaded boxes. */
22
+ const FETCH_POLL_DEFAULT_BUDGET_MS = 12000;
23
+ /** Resolve the poll budget from MJ_IT_FETCH_POLL_MS (positive finite ms), else the default. */
24
+ function resolveFetchPollBudgetMs() {
25
+ const raw = Number(process.env.MJ_IT_FETCH_POLL_MS);
26
+ return Number.isFinite(raw) && raw > 0 ? raw : FETCH_POLL_DEFAULT_BUDGET_MS;
27
+ }
28
+ /** Fetches a single row by ID via the real RunView pipeline (BypassCache = true DB state), asserting one match. */
29
+ async function fetchById(entity, id, user) {
30
+ // Bounded poll: the rows this verifies (Action Execution Logs, child AI Prompt Runs) are
31
+ // written by the agent loop's FIRE-AND-FORGET save queue, which can land AFTER the run
32
+ // handle returns — especially under the fast server-in-process transport. A single-shot read
33
+ // raced that write (agent-loop-live AL2). Budget is MJ_IT_FETCH_POLL_MS-tunable (default 12s).
34
+ const attempts = Math.max(1, Math.round(resolveFetchPollBudgetMs() / FETCH_POLL_INTERVAL_MS));
35
+ // The bound we ACTUALLY wait is attempts × interval, which can differ from the nominal env
36
+ // budget when it isn't a multiple of the interval (e.g. 700ms → 1 attempt → 500ms) — report
37
+ // the real bound, not the configured one.
38
+ const budgetMs = attempts * FETCH_POLL_INTERVAL_MS;
39
+ for (let attempt = 0; attempt < attempts; attempt++) {
40
+ const result = await new RunView().RunView({ EntityName: entity, ExtraFilter: `ID='${id}'`, ResultType: 'simple', BypassCache: true }, user);
41
+ Assert(result.Success, `RunView('${entity}') failed: ${result.ErrorMessage}`);
42
+ if (result.Results.length === 1) {
43
+ return result.Results[0];
44
+ }
45
+ if (result.Results.length > 1) {
46
+ Assert(false, `${entity} ${id}: expected 1 row, got ${result.Results.length}`);
47
+ }
48
+ await new Promise(r => setTimeout(r, FETCH_POLL_INTERVAL_MS));
49
+ }
50
+ // NOT a claim of data loss: on a loaded box (MJAPI + runner + SQL Server co-hosted) the
51
+ // fire-and-forget write commonly commits just after the window closes (verified in the v5.49.0
52
+ // build: the rows landed, the poll simply closed first). State the bound and name the knob.
53
+ Assert(false, `${entity} ${id} not found within ${budgetMs}ms bounded poll — the fire-and-forget write may still be in flight on a loaded box; raise MJ_IT_FETCH_POLL_MS (and consider AGENT_LIVE_SETTLE_MS for the pre-poll settle)`);
54
+ throw new Error('unreachable');
55
+ }
56
+ /**
57
+ * Verifies an `MJ: AI Prompt Runs` row finalized correctly: terminal Status, **CompletedAt set** (the
58
+ * "stuck at Running" guard the save queue prevents), and on success a non-empty Result + recorded timing.
59
+ */
60
+ export async function verifyPromptRun(promptRunID, user) {
61
+ const row = await fetchById('MJ: AI Prompt Runs', promptRunID, user);
62
+ Assert(TERMINAL.has(String(row.Status)), `Prompt run ${promptRunID}: non-terminal Status '${row.Status}' (stuck at Running?)`);
63
+ Assert(row.CompletedAt != null, `Prompt run ${promptRunID}: CompletedAt is null while Status='${row.Status}' (finalize save lost)`);
64
+ if (row.Success === true) {
65
+ Assert(row.Result != null && String(row.Result).length > 0, `Prompt run ${promptRunID}: succeeded but Result is empty`);
66
+ Assert(Number(row.ExecutionTimeMS) > 0, `Prompt run ${promptRunID}: succeeded but ExecutionTimeMS not recorded`);
67
+ }
68
+ return row;
69
+ }
70
+ /** Verifies an `MJ: Action Execution Logs` row finalized: **EndedAt set** + a ResultCode recorded. */
71
+ export async function verifyActionLog(logID, user) {
72
+ const row = await fetchById('MJ: Action Execution Logs', logID, user);
73
+ Assert(row.EndedAt != null, `Action log ${logID}: EndedAt is null (stuck 'Running' — the action-log finalize bug class)`);
74
+ Assert(row.ResultCode != null && String(row.ResultCode).length > 0, `Action log ${logID}: no ResultCode recorded`);
75
+ return row;
76
+ }
77
+ /**
78
+ * Deep-verifies an agent run end to end: the `MJ: AI Agent Runs` header, every `MJ: AI Agent Run Steps`
79
+ * row (each terminal + CompletedAt set — never stuck at Running), and the records each step points at via
80
+ * TargetLogID — Prompt steps → AI Prompt Runs, Actions/Tool steps → Action Execution Logs, Sub-Agent steps
81
+ * → child AI Agent Runs (recursively). `expectSuccess` asserts the run reached 'Completed'.
82
+ */
83
+ export async function verifyAgentRun(agentRunID, user, expectSuccess = true, opts = {}) {
84
+ const run = await fetchById('MJ: AI Agent Runs', agentRunID, user);
85
+ const status = String(run.Status);
86
+ // The actual "stuck at Running" guard: a finalized run is anything except still-Running.
87
+ Assert(status !== 'Running', `Agent run ${agentRunID}: still 'Running' (never finalized — the stuck-at-Running bug)`);
88
+ // CompletedAt is required only for the truly-done states; AwaitingFeedback/Paused legitimately suspend without it.
89
+ if (TERMINAL.has(status)) {
90
+ Assert(run.CompletedAt != null, `Agent run ${agentRunID}: CompletedAt is null while Status='${status}' (finalize save lost)`);
91
+ }
92
+ if (expectSuccess) {
93
+ Assert(RAN_OK.has(status), `Agent run ${agentRunID}: expected Completed/AwaitingFeedback/Paused, got '${status}' (${run.ErrorMessage ?? ''})`);
94
+ }
95
+ const stepsResult = await new RunView().RunView({
96
+ EntityName: 'MJ: AI Agent Run Steps',
97
+ ExtraFilter: `AgentRunID='${agentRunID}'`,
98
+ OrderBy: 'StepNumber',
99
+ ResultType: 'simple',
100
+ BypassCache: true,
101
+ }, user);
102
+ Assert(stepsResult.Success, `RunView agent run steps failed: ${stepsResult.ErrorMessage}`);
103
+ const steps = stepsResult.Results;
104
+ let promptRunsVerified = 0;
105
+ let actionLogsVerified = 0;
106
+ let subAgentRunsVerified = 0;
107
+ for (const step of steps) {
108
+ const label = `${step.StepType}/${step.StepName ?? ''}`;
109
+ // The save-queue guarantee: every step must have finalized, never stuck at Running.
110
+ Assert(TERMINAL.has(String(step.Status)), `Agent run ${agentRunID} step '${label}': non-terminal Status '${step.Status}' (stuck at Running)`);
111
+ Assert(step.CompletedAt != null, `Agent run ${agentRunID} step '${label}': CompletedAt is null while Status='${step.Status}' (the exact stuck-at-Running bug)`);
112
+ const target = step.TargetLogID ? String(step.TargetLogID) : null;
113
+ if (!target) {
114
+ continue;
115
+ }
116
+ if (step.StepType === 'Prompt') {
117
+ await verifyPromptRun(target, user);
118
+ promptRunsVerified++;
119
+ }
120
+ else if (step.StepType === 'Actions' || step.StepType === 'Tool') {
121
+ // Action Execution Logs are written by the fire-and-forget queue and can land
122
+ // arbitrarily late relative to a run handle returning (esp. server-in-process).
123
+ // Callers that only care about run/step terminality pass skipActionLogs.
124
+ if (opts.skipActionLogs) {
125
+ continue;
126
+ }
127
+ await verifyActionLog(target, user);
128
+ actionLogsVerified++;
129
+ }
130
+ else if (step.StepType === 'Sub-Agent') {
131
+ await verifyAgentRun(target, user, false); // child success is the child's own concern
132
+ subAgentRunsVerified++;
133
+ }
134
+ }
135
+ return { run, stepCount: steps.length, promptRunsVerified, actionLogsVerified, subAgentRunsVerified };
136
+ }
137
+ //# sourceMappingURL=ai-verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-verify.js","sourceRoot":"","sources":["../src/ai-verify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import '@memberjunction/core-entities';
2
+ import { type IntegrationClientContext } from './bootstrap-shared.js';
3
+ /**
4
+ * Client (GraphQL) bootstrap — installs the instrumented cache as FIRST caller, then
5
+ * configures `GraphQLDataProvider` against a separately-running MJAPI (reachable via
6
+ * MJ_API_KEY + the resolved URL). Idempotent within a process. Browser-faithful: the
7
+ * process registers only CLIENT entity subclasses (via the `@memberjunction/core-entities`
8
+ * import above), never the server ones.
9
+ */
10
+ export declare function bootstrapIntegrationClient(): Promise<IntegrationClientContext>;
11
+ //# sourceMappingURL=bootstrap-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap-client.d.ts","sourceRoot":"","sources":["../src/bootstrap-client.ts"],"names":[],"mappings":"AAmBA,OAAO,+BAA+B,CAAC;AAGvC,OAAO,EAOH,KAAK,wBAAwB,EAChC,MAAM,oBAAoB,CAAC;AAE5B;;;;;;GAMG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,wBAAwB,CAAC,CA2CpF"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * bootstrap-client.ts — the CLIENT (GraphQL) integration bootstrap.
3
+ *
4
+ * This module is server-FREE by construction: it imports only client-safe packages —
5
+ * exactly what a real browser client (e.g. MJExplorer) loads: `@memberjunction/core`,
6
+ * `@memberjunction/graphql-dataprovider`, and `@memberjunction/core-entities` (the CLIENT
7
+ * generated entity subclasses). It must NEVER import `@memberjunction/sqlserver-dataprovider`
8
+ * or `@memberjunction/server-bootstrap-lite` (those register server-only `*EntityServer`
9
+ * subclasses whose constructors throw on a client provider — a real browser has none of them).
10
+ *
11
+ * A client dispatcher should import `bootstrapIntegrationClient` from HERE (the package's
12
+ * `./client` subpath export), NOT from the package barrel — the barrel evaluates every
13
+ * bundle's check file, which transitively pulls in server packages.
14
+ */
15
+ import { LocalCacheManager, InMemoryLocalStorageProvider } from '@memberjunction/core';
16
+ import { setupGraphQLClient, GraphQLProviderConfigData } from '@memberjunction/graphql-dataprovider';
17
+ // Side-effect import: registers the CLIENT generated entity subclasses on the ClassFactory,
18
+ // exactly as a browser client does. This is the client-faithful analog to the server's
19
+ // `@memberjunction/server-bootstrap-lite` — NOT that package.
20
+ import '@memberjunction/core-entities';
21
+ import { InstrumentedLocalStorageProvider } from './instrumented-cache.js';
22
+ import { LoadEnv, LoadClientConfig } from './config.js';
23
+ import { assertOwnsProcess, getActiveIntegrationStorage, preflightMJAPI, getActiveIntegrationClientBootstrap, _setActiveStorage, _setCurrentClientBootstrap } from './bootstrap-shared.js';
24
+ /**
25
+ * Client (GraphQL) bootstrap — installs the instrumented cache as FIRST caller, then
26
+ * configures `GraphQLDataProvider` against a separately-running MJAPI (reachable via
27
+ * MJ_API_KEY + the resolved URL). Idempotent within a process. Browser-faithful: the
28
+ * process registers only CLIENT entity subclasses (via the `@memberjunction/core-entities`
29
+ * import above), never the server ones.
30
+ */
31
+ export async function bootstrapIntegrationClient() {
32
+ const existing = getActiveIntegrationClientBootstrap();
33
+ if (existing) {
34
+ return existing;
35
+ }
36
+ LoadEnv();
37
+ // Under `mj test` the CLI installs the instrumented cache as this process's FIRST caller
38
+ // (installInstrumentedCacheFirst, before its own provider spins up). That pre-claimed,
39
+ // INSTRUMENTED cache satisfies the ownership invariant — every cache read/write is already
40
+ // counted — so reuse it rather than refusing. Only a cache initialized by someone ELSE
41
+ // (e.g. co-hosted inside a live MJAPI) is grounds for refusal.
42
+ const preInstalled = getActiveIntegrationStorage();
43
+ if (!preInstalled) {
44
+ assertOwnsProcess();
45
+ }
46
+ const client = LoadClientConfig();
47
+ // Preflight BEFORE mutating cache/provider state — a dead MJAPI fails fast and clearly.
48
+ await preflightMJAPI(client.Url, client.MJAPIKey);
49
+ let storage;
50
+ if (preInstalled) {
51
+ storage = preInstalled;
52
+ }
53
+ else {
54
+ storage = new InstrumentedLocalStorageProvider(new InMemoryLocalStorageProvider());
55
+ await LocalCacheManager.Instance.Initialize(storage, { verboseLogging: false });
56
+ _setActiveStorage(storage);
57
+ }
58
+ const config = new GraphQLProviderConfigData('', // JWT token — unused; the system API key authenticates us
59
+ client.Url, client.WsUrl, // wsurl — required by the RO-3 wire-progress subscription (RemoteOperationProgress)
60
+ async () => '', // refreshTokenFunction — stub; API key auth never refreshes
61
+ '__mj', undefined, undefined, client.MJAPIKey // mjAPIKey → sent as x-mj-api-key on every request
62
+ );
63
+ await setupGraphQLClient(config);
64
+ const ctx = { Storage: storage, Client: client };
65
+ _setCurrentClientBootstrap(ctx);
66
+ return ctx;
67
+ }
68
+ //# sourceMappingURL=bootstrap-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap-client.js","sourceRoot":"","sources":["../src/bootstrap-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,iBAAiB,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC;AACvF,OAAO,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AACrG,4FAA4F;AAC5F,uFAAuF;AACvF,8DAA8D;AAC9D,OAAO,+BAA+B,CAAC;AACvC,OAAO,EAAE,gCAAgC,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EACH,iBAAiB,EACjB,2BAA2B,EAC3B,cAAc,EACd,mCAAmC,EACnC,iBAAiB,EACjB,0BAA0B,EAE7B,MAAM,oBAAoB,CAAC;AAE5B;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B;IAC5C,MAAM,QAAQ,GAAG,mCAAmC,EAAE,CAAC;IACvD,IAAI,QAAQ,EAAE,CAAC;QACX,OAAO,QAAQ,CAAC;IACpB,CAAC;IACD,OAAO,EAAE,CAAC;IACV,yFAAyF;IACzF,uFAAuF;IACvF,2FAA2F;IAC3F,uFAAuF;IACvF,+DAA+D;IAC/D,MAAM,YAAY,GAAG,2BAA2B,EAAE,CAAC;IACnD,IAAI,CAAC,YAAY,EAAE,CAAC;QAChB,iBAAiB,EAAE,CAAC;IACxB,CAAC;IACD,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,wFAAwF;IACxF,MAAM,cAAc,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IAElD,IAAI,OAAyC,CAAC;IAC9C,IAAI,YAAY,EAAE,CAAC;QACf,OAAO,GAAG,YAAY,CAAC;IAC3B,CAAC;SAAM,CAAC;QACJ,OAAO,GAAG,IAAI,gCAAgC,CAAC,IAAI,4BAA4B,EAAE,CAAC,CAAC;QACnF,MAAM,iBAAiB,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;QAChF,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,yBAAyB,CACxC,EAAE,EAAkB,0DAA0D;IAC9E,MAAM,CAAC,GAAG,EACV,MAAM,CAAC,KAAK,EAAQ,oFAAoF;IACxG,KAAK,IAAI,EAAE,CAAC,EAAE,EAAM,4DAA4D;IAChF,MAAM,EACN,SAAS,EACT,SAAS,EACT,MAAM,CAAC,QAAQ,CAAK,mDAAmD;KAC1E,CAAC;IACF,MAAM,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAEjC,MAAM,GAAG,GAA6B,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC3E,0BAA0B,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,GAAG,CAAC;AACf,CAAC"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * bootstrap-shared.ts — the server-FREE core shared by both integration bootstrap paths.
3
+ *
4
+ * This module deliberately imports ONLY client-safe packages (`@memberjunction/core`,
5
+ * local config/cache). It must NOT import `@memberjunction/sqlserver-dataprovider`,
6
+ * `@memberjunction/server-bootstrap-lite`, or any other server package — so that the
7
+ * CLIENT bootstrap (`bootstrap-client.ts`) can be imported by a client dispatcher WITHOUT
8
+ * dragging server-only entity subclasses into the process. A real browser client never
9
+ * loads those; loading them here would make a "client" test a server/client hybrid whose
10
+ * ClassFactory resolves server-only `*EntityServer` subclasses (which throw on the client).
11
+ *
12
+ * The process-global handoff state lives here (one integration run owns its process,
13
+ * CANONICAL D) so BOTH the server bootstrap and the client bootstrap — and the driver's
14
+ * `getActiveIntegrationStorage()` reader — observe the same install.
15
+ */
16
+ import type * as sql from 'mssql';
17
+ import type { UserInfo, IMetadataProvider } from '@memberjunction/core';
18
+ import { InstrumentedLocalStorageProvider } from './instrumented-cache.js';
19
+ import type { DbConfig, ClientConfig } from './config.js';
20
+ /** Everything a server-side check/driver needs about the owned process. */
21
+ export interface IntegrationBootstrapContext {
22
+ /**
23
+ * The mssql connection pool — present on SQL Server, undefined on PostgreSQL.
24
+ * Checks that issue raw `sql.Request(pool)` queries are SQL-Server-only; use
25
+ * ClosePool() to release the underlying connection on either backend.
26
+ */
27
+ Pool?: sql.ConnectionPool;
28
+ User: UserInfo;
29
+ /** The instrumented cache wrapper installed as LocalCacheManager's storage provider. */
30
+ Storage: InstrumentedLocalStorageProvider;
31
+ /** The run-scoped provider (the SQLServerDataProvider / PostgreSQLDataProvider this bootstrap set up). */
32
+ Provider: IMetadataProvider;
33
+ Db: DbConfig;
34
+ /** Releases the underlying DB connection platform-agnostically (mssql pool.close() / pg pool.end()). */
35
+ ClosePool(): Promise<void>;
36
+ }
37
+ /** Result of the client (GraphQL) bootstrap. */
38
+ export interface IntegrationClientContext {
39
+ Storage: InstrumentedLocalStorageProvider;
40
+ Client: ClientConfig;
41
+ }
42
+ export interface BootstrapServerOptions {
43
+ /** Optional context-user override (else Owner-type, else Users[0]). Mirrors MJ_TEST_USER_EMAIL. */
44
+ ContextUserEmail?: string;
45
+ /** Verbose cache logging (default false). */
46
+ VerboseCacheLogging?: boolean;
47
+ }
48
+ /** @internal Setters used by the server/client bootstrap modules to publish process state. */
49
+ export declare function _setActiveStorage(s: InstrumentedLocalStorageProvider): void;
50
+ export declare function _setCurrentServerBootstrap(c: IntegrationBootstrapContext): void;
51
+ export declare function _setCurrentClientBootstrap(c: IntegrationClientContext): void;
52
+ /**
53
+ * The instrumented storage installed in this process (by any path), or null if none.
54
+ * The IntegrationTestDriver reads this to get the cache counters without re-bootstrapping.
55
+ */
56
+ export declare function getActiveIntegrationStorage(): InstrumentedLocalStorageProvider | null;
57
+ /**
58
+ * True when a server-transport integration run cannot own this process: no instrumented
59
+ * cache was installed first AND the cache is already initialized by another component
60
+ * (e.g. a serving MJAPI whose StartupManager claimed it). This is exactly the condition
61
+ * under which `bootstrapIntegrationServer()` would throw `assertOwnsProcess` — exposed so
62
+ * the driver can fail fast with a dashboard-friendly message instead of a bootstrap stack.
63
+ */
64
+ export declare function serverProcessAlreadyClaimed(): boolean;
65
+ /** The full server bootstrap context (with owned Pool), or null if not server-bootstrapped. */
66
+ export declare function getActiveIntegrationBootstrap(): IntegrationBootstrapContext | null;
67
+ /** The client (GraphQL) bootstrap context, or null if not client-bootstrapped in this process. */
68
+ export declare function getActiveIntegrationClientBootstrap(): IntegrationClientContext | null;
69
+ /**
70
+ * Fail fast with a clear message when MJAPI isn't reachable, before any cache or
71
+ * provider state is mutated. Used by both the driver (via bootstrapIntegrationClient)
72
+ * and the tsx scripts so they preflight identically.
73
+ */
74
+ export declare function preflightMJAPI(url: string, apiKey: string): Promise<void>;
75
+ /**
76
+ * Refuse to run an owning bootstrap when the cache is already initialized by some
77
+ * other component. SetStorageProvider is a destructive global mutation with no
78
+ * restore, so we never wedge instrumentation into a live host — we fail loudly.
79
+ */
80
+ export declare function assertOwnsProcess(): void;
81
+ /**
82
+ * Install the instrumented cache as the first caller WITHOUT owning the connection.
83
+ * For the testing-CLI path: call this BEFORE initializeMJProvider() so the CLI's
84
+ * own provider setup finds the cache already claimed (its StartupManager Initialize
85
+ * no-ops) and every cache read/write flows through the instrumented wrapper.
86
+ *
87
+ * Returns the instrumented storage, or null when the cache was ALREADY initialized
88
+ * by something else (can't instrument retroactively — the caller decides whether to
89
+ * proceed uninstrumented or abort).
90
+ */
91
+ export declare function installInstrumentedCacheFirst(opts?: {
92
+ VerboseCacheLogging?: boolean;
93
+ }): Promise<InstrumentedLocalStorageProvider | null>;
94
+ //# sourceMappingURL=bootstrap-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap-shared.d.ts","sourceRoot":"","sources":["../src/bootstrap-shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,KAAK,GAAG,MAAM,OAAO,CAAC;AAElC,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,gCAAgC,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEvD,2EAA2E;AAC3E,MAAM,WAAW,2BAA2B;IACxC;;;;OAIG;IACH,IAAI,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,wFAAwF;IACxF,OAAO,EAAE,gCAAgC,CAAC;IAC1C,0GAA0G;IAC1G,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,wGAAwG;IACxG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,gDAAgD;AAChD,MAAM,WAAW,wBAAwB;IACrC,OAAO,EAAE,gCAAgC,CAAC;IAC1C,MAAM,EAAE,YAAY,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACnC,mGAAmG;IACnG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6CAA6C;IAC7C,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACjC;AAOD,8FAA8F;AAC9F,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,gCAAgC,GAAG,IAAI,CAAuB;AACnG,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAgC;AAChH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,wBAAwB,GAAG,IAAI,CAAgC;AAE7G;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,gCAAgC,GAAG,IAAI,CAErF;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,IAAI,OAAO,CAErD;AAED,+FAA+F;AAC/F,wBAAgB,6BAA6B,IAAI,2BAA2B,GAAG,IAAI,CAElF;AAED,kGAAkG;AAClG,wBAAgB,mCAAmC,IAAI,wBAAwB,GAAG,IAAI,CAErF;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkB/E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CASxC;AAED;;;;;;;;;GASG;AACH,wBAAsB,6BAA6B,CAAC,IAAI,GAAE;IAAE,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,gCAAgC,GAAG,IAAI,CAAC,CAWlJ"}
@@ -0,0 +1,94 @@
1
+ import { LocalCacheManager, InMemoryLocalStorageProvider } from '@memberjunction/core';
2
+ import { InstrumentedLocalStorageProvider } from './instrumented-cache.js';
3
+ // Process-global handoff state — one integration run owns its process (CANONICAL D).
4
+ let activeStorage = null;
5
+ let currentServerBootstrap = null;
6
+ let currentClientBootstrap = null;
7
+ /** @internal Setters used by the server/client bootstrap modules to publish process state. */
8
+ export function _setActiveStorage(s) { activeStorage = s; }
9
+ export function _setCurrentServerBootstrap(c) { currentServerBootstrap = c; }
10
+ export function _setCurrentClientBootstrap(c) { currentClientBootstrap = c; }
11
+ /**
12
+ * The instrumented storage installed in this process (by any path), or null if none.
13
+ * The IntegrationTestDriver reads this to get the cache counters without re-bootstrapping.
14
+ */
15
+ export function getActiveIntegrationStorage() {
16
+ return activeStorage;
17
+ }
18
+ /**
19
+ * True when a server-transport integration run cannot own this process: no instrumented
20
+ * cache was installed first AND the cache is already initialized by another component
21
+ * (e.g. a serving MJAPI whose StartupManager claimed it). This is exactly the condition
22
+ * under which `bootstrapIntegrationServer()` would throw `assertOwnsProcess` — exposed so
23
+ * the driver can fail fast with a dashboard-friendly message instead of a bootstrap stack.
24
+ */
25
+ export function serverProcessAlreadyClaimed() {
26
+ return !activeStorage && LocalCacheManager.Instance.IsInitialized;
27
+ }
28
+ /** The full server bootstrap context (with owned Pool), or null if not server-bootstrapped. */
29
+ export function getActiveIntegrationBootstrap() {
30
+ return currentServerBootstrap;
31
+ }
32
+ /** The client (GraphQL) bootstrap context, or null if not client-bootstrapped in this process. */
33
+ export function getActiveIntegrationClientBootstrap() {
34
+ return currentClientBootstrap;
35
+ }
36
+ /**
37
+ * Fail fast with a clear message when MJAPI isn't reachable, before any cache or
38
+ * provider state is mutated. Used by both the driver (via bootstrapIntegrationClient)
39
+ * and the tsx scripts so they preflight identically.
40
+ */
41
+ export async function preflightMJAPI(url, apiKey) {
42
+ let response;
43
+ try {
44
+ response = await fetch(url, {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json', 'x-mj-api-key': apiKey },
47
+ body: JSON.stringify({ query: '{ __typename }' }),
48
+ signal: AbortSignal.timeout(5000)
49
+ });
50
+ }
51
+ catch (error) {
52
+ throw new Error(`MJAPI is not reachable at ${url} (${error instanceof Error ? error.message : String(error)}). ` +
53
+ `Start it first: cd packages/MJAPI && npm run start`);
54
+ }
55
+ if (!response.ok) {
56
+ throw new Error(`MJAPI at ${url} answered HTTP ${response.status} — check MJ_API_KEY and server logs.`);
57
+ }
58
+ }
59
+ /**
60
+ * Refuse to run an owning bootstrap when the cache is already initialized by some
61
+ * other component. SetStorageProvider is a destructive global mutation with no
62
+ * restore, so we never wedge instrumentation into a live host — we fail loudly.
63
+ */
64
+ export function assertOwnsProcess() {
65
+ if (LocalCacheManager.Instance.IsInitialized) {
66
+ throw new Error('Integration bootstrap must own its process — LocalCacheManager is already initialized by another component. ' +
67
+ 'When running via `mj test run`/`mj test suite`, set MJ_INTEGRATION_TEST=1 so the CLI installs the instrumented ' +
68
+ 'cache as the first caller (before its own provider setup). Otherwise run in a dedicated process and never ' +
69
+ 'co-host the integration test inside a serving MJAPI.');
70
+ }
71
+ }
72
+ /**
73
+ * Install the instrumented cache as the first caller WITHOUT owning the connection.
74
+ * For the testing-CLI path: call this BEFORE initializeMJProvider() so the CLI's
75
+ * own provider setup finds the cache already claimed (its StartupManager Initialize
76
+ * no-ops) and every cache read/write flows through the instrumented wrapper.
77
+ *
78
+ * Returns the instrumented storage, or null when the cache was ALREADY initialized
79
+ * by something else (can't instrument retroactively — the caller decides whether to
80
+ * proceed uninstrumented or abort).
81
+ */
82
+ export async function installInstrumentedCacheFirst(opts = {}) {
83
+ if (activeStorage) {
84
+ return activeStorage;
85
+ }
86
+ if (LocalCacheManager.Instance.IsInitialized) {
87
+ return null;
88
+ }
89
+ const storage = new InstrumentedLocalStorageProvider(new InMemoryLocalStorageProvider());
90
+ await LocalCacheManager.Instance.Initialize(storage, { verboseLogging: opts.VerboseCacheLogging ?? false });
91
+ activeStorage = storage;
92
+ return storage;
93
+ }
94
+ //# sourceMappingURL=bootstrap-shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap-shared.js","sourceRoot":"","sources":["../src/bootstrap-shared.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { type IntegrationBootstrapContext, type BootstrapServerOptions } from './bootstrap-shared.js';
2
+ import '@memberjunction/server-bootstrap-lite';
3
+ export * from './bootstrap-shared.js';
4
+ /**
5
+ * Own the process: install the instrumented cache as FIRST caller, then set up the
6
+ * data provider for the configured backend (SQL Server by default, PostgreSQL when
7
+ * DB_PLATFORM=postgresql) and resolve the context user. Idempotent within a process.
8
+ * THROWS if the cache is already initialized by a different component (see assertOwnsProcess).
9
+ *
10
+ * The instrumented-cache-first ordering is identical on both backends — the cache
11
+ * singleton is platform-agnostic. Only the provider setup differs, behind db.Platform.
12
+ */
13
+ export declare function bootstrapIntegrationServer(opts?: BootstrapServerOptions): Promise<IntegrationBootstrapContext>;
14
+ //# sourceMappingURL=bootstrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAyBA,OAAO,EAKH,KAAK,2BAA2B,EAChC,KAAK,sBAAsB,EAC9B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,uCAAuC,CAAC;AAG/C,cAAc,oBAAoB,CAAC;AAkCnC;;;;;;;;GAQG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,GAAE,sBAA2B,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAoBxH"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * bootstrap.ts — the SERVER (in-process) integration bootstrap.
3
+ *
4
+ * This module owns the SERVER transport: it connects directly to the database
5
+ * (SQL Server / PostgreSQL) and imports `@memberjunction/server-bootstrap-lite` to
6
+ * register the server generated entity subclasses. It is therefore server-laden by
7
+ * design — a CLIENT dispatcher must NOT import it (import `bootstrapIntegrationClient`
8
+ * from `./bootstrap-client` / the package's `./client` subpath instead, which loads
9
+ * only client packages).
10
+ *
11
+ * The load-bearing invariant (D1): the InstrumentedLocalStorageProvider MUST be
12
+ * installed as the FIRST caller of `LocalCacheManager.Instance.Initialize(...)` —
13
+ * before any provider setup (`setupSQLServerClient`) — or instrumentation is a silent
14
+ * no-op (`Initialize` is first-caller-wins). The process-global handoff state and the
15
+ * shared client-safe helpers (preflightMJAPI, assertOwnsProcess, installInstrumented-
16
+ * CacheFirst, the getActive* accessors, the interfaces) live in `./bootstrap-shared`
17
+ * so both this module and the client bootstrap publish/read the same install.
18
+ */
19
+ import sql from 'mssql';
20
+ import { LocalCacheManager, InMemoryLocalStorageProvider, Metadata, SetProvider } from '@memberjunction/core';
21
+ import { setupSQLServerClient, SQLServerProviderConfigData, UserCache } from '@memberjunction/sqlserver-dataprovider';
22
+ import { InstrumentedLocalStorageProvider } from './instrumented-cache.js';
23
+ import { LoadEnv, LoadDbConfig } from './config.js';
24
+ import { assertOwnsProcess, getActiveIntegrationBootstrap, _setActiveStorage, _setCurrentServerBootstrap } from './bootstrap-shared.js';
25
+ // Side-effect import: registers the SERVER generated entity subclasses on the ClassFactory
26
+ // so entity_object results materialize as real (server) BaseEntity instances.
27
+ import '@memberjunction/server-bootstrap-lite';
28
+ // Re-export the shared surface so existing barrel consumers keep working unchanged.
29
+ export * from './bootstrap-shared.js';
30
+ /** Resolve the context user: MJ_TEST_USER_EMAIL override → Owner-type → first user. */
31
+ function resolveContextUser(email) {
32
+ const wanted = email?.toLowerCase();
33
+ const users = UserCache.Instance.Users;
34
+ const user = (wanted ? users.find(u => u.Email?.toLowerCase() === wanted) : undefined)
35
+ ?? users.find(u => u?.Type?.trim().toLowerCase() === 'owner')
36
+ ?? users[0];
37
+ if (!user) {
38
+ throw new Error('No context user found in UserCache. Set MJ_TEST_USER_EMAIL or ensure the Users table is populated.');
39
+ }
40
+ return user;
41
+ }
42
+ /**
43
+ * Resolve the context user on the PostgreSQL path. UserCache (mssql-only Refresh) is
44
+ * not populated on PG, so this surfaces the missing PG-aware user bootstrap as a clear,
45
+ * actionable error (the Phase-0 prerequisite) instead of fabricating a user.
46
+ */
47
+ function resolvePostgresContextUser(email) {
48
+ const users = UserCache.Instance.Users ?? [];
49
+ if (users.length === 0) {
50
+ throw new Error('PostgreSQL integration bootstrap: no context user is available. UserCache.Refresh is ' +
51
+ 'mssql-only, so PG needs a PG-aware user-cache bootstrap before the integration suites can ' +
52
+ 'run against it (tracked Phase-0 prerequisite — plan §10 Step 6). The provider was configured ' +
53
+ 'successfully; only user resolution is blocked.');
54
+ }
55
+ return resolveContextUser(email);
56
+ }
57
+ /**
58
+ * Own the process: install the instrumented cache as FIRST caller, then set up the
59
+ * data provider for the configured backend (SQL Server by default, PostgreSQL when
60
+ * DB_PLATFORM=postgresql) and resolve the context user. Idempotent within a process.
61
+ * THROWS if the cache is already initialized by a different component (see assertOwnsProcess).
62
+ *
63
+ * The instrumented-cache-first ordering is identical on both backends — the cache
64
+ * singleton is platform-agnostic. Only the provider setup differs, behind db.Platform.
65
+ */
66
+ export async function bootstrapIntegrationServer(opts = {}) {
67
+ const existing = getActiveIntegrationBootstrap();
68
+ if (existing) {
69
+ return existing;
70
+ }
71
+ LoadEnv();
72
+ // Fail fast on mis-host BEFORE reading config — never wedge instrumentation into a live cache.
73
+ assertOwnsProcess();
74
+ const db = await LoadDbConfig();
75
+ // FIRST-CALLER cache init — MUST precede any provider setup (load-bearing on both backends).
76
+ const storage = new InstrumentedLocalStorageProvider(new InMemoryLocalStorageProvider());
77
+ await LocalCacheManager.Instance.Initialize(storage, { verboseLogging: opts.VerboseCacheLogging ?? false });
78
+ _setActiveStorage(storage);
79
+ const ctx = db.Platform === 'postgresql'
80
+ ? await setupPostgreSQLProvider(db, storage, opts)
81
+ : await setupSqlServerProvider(db, storage, opts);
82
+ _setCurrentServerBootstrap(ctx);
83
+ return ctx;
84
+ }
85
+ /** SQL Server provider setup — the locally-proven path (unchanged behavior). */
86
+ async function setupSqlServerProvider(db, storage, opts) {
87
+ // Dedicated mssql pool (encrypt:false, trustServerCertificate:true — harness parity).
88
+ const pool = await new sql.ConnectionPool({
89
+ server: db.Host,
90
+ port: db.Port,
91
+ user: db.User,
92
+ password: db.Password,
93
+ database: db.Database,
94
+ options: { encrypt: false, trustServerCertificate: true }
95
+ }).connect();
96
+ await setupSQLServerClient(new SQLServerProviderConfigData(pool, db.Schema));
97
+ await UserCache.Instance.Refresh(pool);
98
+ const user = resolveContextUser(opts.ContextUserEmail);
99
+ return {
100
+ Pool: pool, User: user, Storage: storage, Provider: Metadata.Provider, Db: db, // global-provider-ok: this dedicated-process bootstrap just installed the one global provider (setupSQLServerClient) — it IS the single provider for this run (D1)
101
+ ClosePool: async () => { await pool.close(); }
102
+ };
103
+ }
104
+ /**
105
+ * PostgreSQL provider setup — runs the SAME downstream check code against a PG backend
106
+ * (the PG parity lane, D5.5). PG is an OPTIONAL backend: the provider (and its `pg`
107
+ * dependency) is dynamically imported so it stays out of the default dependency graph
108
+ * for SQL-Server-only consumers (CLAUDE.md rule #8 category 2; declared in optionalDependencies).
109
+ *
110
+ * NOTE (Phase-0 prerequisite, see plan §10 Step 6): there is no PG-aware user-cache
111
+ * bootstrap yet — `UserCache.Refresh` is mssql-only (raw `sql.Request`), and a fresh
112
+ * provider has no users loaded to seed a RunView-based load. This helper sets up the
113
+ * real provider, then resolves the context user from whatever is already cached; if
114
+ * nothing is, it throws a clear, actionable error rather than fabricating a user. The
115
+ * PG parity CI lane is therefore non-blocking until that framework gap is closed.
116
+ */
117
+ async function setupPostgreSQLProvider(db, storage, opts) {
118
+ const { PostgreSQLDataProvider, PostgreSQLProviderConfigData } = await import('@memberjunction/postgresql-dataprovider');
119
+ const provider = new PostgreSQLDataProvider();
120
+ const pgConfig = new PostgreSQLProviderConfigData({ Host: db.Host, Port: db.Port, Database: db.Database, User: db.User, Password: db.Password }, db.Schema, 1 // checkRefreshIntervalSeconds > 0 → load metadata on Config
121
+ );
122
+ await provider.Config(pgConfig);
123
+ SetProvider(provider);
124
+ const user = resolvePostgresContextUser(opts.ContextUserEmail);
125
+ return {
126
+ Pool: undefined, User: user, Storage: storage, Provider: Metadata.Provider, Db: db, // global-provider-ok: this dedicated-process bootstrap just installed the one global provider (SetProvider above) — it IS the single provider for this run (D1)
127
+ ClosePool: async () => {
128
+ const pgPool = provider.DatabaseConnection;
129
+ await pgPool?.end?.();
130
+ }
131
+ };
132
+ }
133
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"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"}