mcp-creatio 0.6.4 → 0.6.7

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 (60) hide show
  1. package/README.md +31 -11
  2. package/dist/creatio/auth/headers.d.ts +7 -0
  3. package/dist/creatio/auth/headers.d.ts.map +1 -1
  4. package/dist/creatio/auth/headers.js +16 -0
  5. package/dist/creatio/auth/headers.js.map +1 -1
  6. package/dist/creatio/auth/providers/legacy-provider.d.ts +2 -0
  7. package/dist/creatio/auth/providers/legacy-provider.d.ts.map +1 -1
  8. package/dist/creatio/auth/providers/legacy-provider.js +14 -7
  9. package/dist/creatio/auth/providers/legacy-provider.js.map +1 -1
  10. package/dist/creatio/auth/providers/oauth2-bearer-provider.d.ts +8 -7
  11. package/dist/creatio/auth/providers/oauth2-bearer-provider.d.ts.map +1 -1
  12. package/dist/creatio/auth/providers/oauth2-bearer-provider.js +19 -14
  13. package/dist/creatio/auth/providers/oauth2-bearer-provider.js.map +1 -1
  14. package/dist/creatio/services/dataservice/data-service-schema.d.ts.map +1 -1
  15. package/dist/creatio/services/dataservice/data-service-schema.js +16 -15
  16. package/dist/creatio/services/dataservice/data-service-schema.js.map +1 -1
  17. package/dist/creatio/services/odata/metadata-store.d.ts +10 -17
  18. package/dist/creatio/services/odata/metadata-store.d.ts.map +1 -1
  19. package/dist/creatio/services/odata/metadata-store.js +47 -75
  20. package/dist/creatio/services/odata/metadata-store.js.map +1 -1
  21. package/dist/creatio/services/versioned-ttl-cache.d.ts +33 -0
  22. package/dist/creatio/services/versioned-ttl-cache.d.ts.map +1 -0
  23. package/dist/creatio/services/versioned-ttl-cache.js +95 -0
  24. package/dist/creatio/services/versioned-ttl-cache.js.map +1 -0
  25. package/dist/server/bearer/bearer-edge.d.ts +4 -1
  26. package/dist/server/bearer/bearer-edge.d.ts.map +1 -1
  27. package/dist/server/bearer/bearer-edge.js +41 -15
  28. package/dist/server/bearer/bearer-edge.js.map +1 -1
  29. package/dist/server/http/health.d.ts +19 -0
  30. package/dist/server/http/health.d.ts.map +1 -0
  31. package/dist/server/http/health.js +41 -0
  32. package/dist/server/http/health.js.map +1 -0
  33. package/dist/server/http/http-server.d.ts +1 -0
  34. package/dist/server/http/http-server.d.ts.map +1 -1
  35. package/dist/server/http/http-server.js +9 -0
  36. package/dist/server/http/http-server.js.map +1 -1
  37. package/dist/server/http/index.d.ts +1 -0
  38. package/dist/server/http/index.d.ts.map +1 -1
  39. package/dist/server/http/index.js +1 -0
  40. package/dist/server/http/index.js.map +1 -1
  41. package/dist/server/http/mcp-handlers.d.ts.map +1 -1
  42. package/dist/server/http/mcp-handlers.js +12 -10
  43. package/dist/server/http/mcp-handlers.js.map +1 -1
  44. package/dist/server/mcp/server.d.ts +37 -28
  45. package/dist/server/mcp/server.d.ts.map +1 -1
  46. package/dist/server/mcp/server.js +96 -73
  47. package/dist/server/mcp/server.js.map +1 -1
  48. package/dist/server/mcp/tenant-tool-registry.d.ts +66 -0
  49. package/dist/server/mcp/tenant-tool-registry.d.ts.map +1 -0
  50. package/dist/server/mcp/tenant-tool-registry.js +113 -0
  51. package/dist/server/mcp/tenant-tool-registry.js.map +1 -0
  52. package/dist/utils/context.d.ts +21 -6
  53. package/dist/utils/context.d.ts.map +1 -1
  54. package/dist/utils/context.js +6 -6
  55. package/dist/utils/context.js.map +1 -1
  56. package/dist/utils/network.d.ts +2 -0
  57. package/dist/utils/network.d.ts.map +1 -1
  58. package/dist/utils/network.js +11 -0
  59. package/dist/utils/network.js.map +1 -1
  60. package/package.json +1 -1
package/README.md CHANGED
@@ -198,21 +198,30 @@ CREATIO_BASE_URL=https://your-creatio.com
198
198
  A request with **no** `Authorization` header gets `401` with a `WWW-Authenticate` challenge pointing
199
199
  at Creatio Identity (RFC 9728), so a compliant client knows where to log in.
200
200
 
201
+ > **Forwarding a Creatio session cookie instead of a Bearer.** A client that authenticated to Creatio
202
+ > the classic way holds a **Forms-auth session** (cookie + `BPMCSRF`), not an OAuth token. It can
203
+ > forward that session instead of a Bearer by sending the cookie in **`X-Creatio-Cookie`** (and,
204
+ > optionally, the anti-forgery token in `X-Creatio-Bpmcsrf` — otherwise it is read from the cookie).
205
+ > The MCP attaches `Cookie` + `BPMCSRF` + `ForceUseSession` statelessly and lets Creatio validate it.
206
+ > `Authorization: Bearer` takes precedence when both are present.
207
+
201
208
  ### `gateway`
202
209
 
203
- A trusted fronting service (Creatio.ai Control-Plane) injects the Bearer; the MCP trusts and uses
210
+ A trusted fronting service (Creatio.ai Control-Plane) injects the credential; the MCP trusts and uses
204
211
  it. The optional `X-Creatio-Base-Url` header routes a request to a specific Creatio instance
205
- (multi-tenant) — honored only in this mode. Because that override decides where the request's Bearer
206
- is sent, it is validated: set **`CREATIO_MCP_ALLOWED_BASE_URLS`** (comma-separated origins) to
207
- restrict it to your tenants. When unset, any `http(s)` host is accepted (trusting the gateway) except
208
- the cloud-metadata link-local address, which is always blocked (SSRF guard).
212
+ (multi-tenant) — honored only in this mode. Because that override decides where the request's
213
+ credential is sent, it is validated: set **`CREATIO_MCP_ALLOWED_BASE_URLS`** (comma-separated origins)
214
+ to restrict it to your tenants. When unset, any `http(s)` host is accepted (trusting the gateway)
215
+ except the cloud-metadata link-local address, which is always blocked (SSRF guard).
209
216
 
210
217
  **Who sends what.** Unlike `delegated`, the end client talks to the **gateway**, not to the MCP — so
211
- the **gateway** is what injects the per-request headers. On each forwarded `/mcp` call it sends:
218
+ the **gateway** is what injects the per-request headers. On each forwarded `/mcp` call it sends a
219
+ Creatio credential — either a Bearer token, or a forwarded Forms-auth session:
212
220
 
213
221
  ```http
214
222
  POST /mcp HTTP/1.1
215
- Authorization: Bearer <CREATIO_ACCESS_TOKEN> # required the tenant's Creatio token
223
+ Authorization: Bearer <CREATIO_ACCESS_TOKEN> # a Bearer token
224
+ X-Creatio-Cookie: BPMCSRF=<csrf>; .ASPXAUTH=<s> # … OR forward a Forms-auth session instead
216
225
  X-Creatio-Base-Url: https://tenant-a.creatio.com # optional — pick the tenant's instance (multi-tenant)
217
226
  ```
218
227
 
@@ -234,10 +243,21 @@ curl -sS http://localhost:3000/mcp \
234
243
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get-current-user-info","arguments":{}}}'
235
244
  ```
236
245
 
237
- > **Only the Bearer token is injected**not arbitrary credentials. The gateway owns auth: it should
238
- > exchange whatever it holds (SSO, cookie, etc.) into a Creatio OAuth access token **before** calling
239
- > the MCP. (Injecting other credential shapes, e.g. cookie/basic per tenant, is intentionally out of
240
- > scope for now.)
246
+ > **Bearer or session cookienothing else.** The gateway injects either a Creatio OAuth **Bearer**
247
+ > token or a forwarded **Forms-auth session** (`X-Creatio-Cookie` + `BPMCSRF`); the MCP forwards it
248
+ > statelessly (no cookie jar, no per-credential pool) and Creatio validates it. The gateway owns auth —
249
+ > it should hold a ready Creatio credential of one of those two shapes. Other shapes (Basic, API key)
250
+ > are intentionally out of scope.
251
+
252
+ > **Per-tenant tool isolation.** A single MCP deployment serving many instances keeps each tenant's
253
+ > tool surface separate, keyed by the effective base URL (`X-Creatio-Base-Url`, else `CREATIO_BASE_URL`).
254
+ > Optional capabilities are **probed per tenant** and the tools they expose (DataForge, Global Search,
255
+ > and any dynamically discovered per-instance tools) are registered only for the tenant they were
256
+ > discovered on. Tenant A's tools or DataForge verdict never leak into tenant B's session, even though
257
+ > both share one process. The
258
+ > per-tenant state is pooled with idle-TTL + LRU eviction, so memory stays bounded as the number of
259
+ > distinct instances grows. Single-tenant modes (everything except `gateway` with an override) all map
260
+ > to one bucket, so their behavior is unchanged.
241
261
 
242
262
  ### `client_credentials` / `legacy`
243
263
 
@@ -1,3 +1,10 @@
1
1
  /** Builds the standard Creatio request headers, optionally with a JSON content-type and a Bearer token. */
2
2
  export declare function buildHeaders(accept: string, isJson?: boolean, token?: string): Record<string, string>;
3
+ /**
4
+ * Builds Creatio request headers for a Forms-auth (cookie) session: the base headers plus the
5
+ * session `Cookie`, its `BPMCSRF` anti-forgery token, and `ForceUseSession` so Creatio honours the
6
+ * cookie session. Single source of the cookie-header convention, shared by the legacy self-login
7
+ * provider and the stateless gateway/delegated cookie passthrough.
8
+ */
9
+ export declare function buildCookieHeaders(accept: string, isJson: boolean, cookie: string, bpmcsrf?: string): Record<string, string>;
3
10
  //# sourceMappingURL=headers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../../src/creatio/auth/headers.ts"],"names":[],"mappings":"AAAA,2GAA2G;AAC3G,wBAAgB,YAAY,CAC3B,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,OAAO,EAChB,KAAK,CAAC,EAAE,MAAM,GACZ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASxB"}
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../../src/creatio/auth/headers.ts"],"names":[],"mappings":"AAAA,2GAA2G;AAC3G,wBAAgB,YAAY,CAC3B,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,OAAO,EAChB,KAAK,CAAC,EAAE,MAAM,GACZ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASxB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CACjC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB"}
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildHeaders = buildHeaders;
4
+ exports.buildCookieHeaders = buildCookieHeaders;
4
5
  /** Builds the standard Creatio request headers, optionally with a JSON content-type and a Bearer token. */
5
6
  function buildHeaders(accept, isJson, token) {
6
7
  const headers = { Accept: accept };
@@ -12,4 +13,19 @@ function buildHeaders(accept, isJson, token) {
12
13
  }
13
14
  return headers;
14
15
  }
16
+ /**
17
+ * Builds Creatio request headers for a Forms-auth (cookie) session: the base headers plus the
18
+ * session `Cookie`, its `BPMCSRF` anti-forgery token, and `ForceUseSession` so Creatio honours the
19
+ * cookie session. Single source of the cookie-header convention, shared by the legacy self-login
20
+ * provider and the stateless gateway/delegated cookie passthrough.
21
+ */
22
+ function buildCookieHeaders(accept, isJson, cookie, bpmcsrf) {
23
+ const headers = buildHeaders(accept, isJson);
24
+ headers['ForceUseSession'] = 'true';
25
+ headers['Cookie'] = cookie;
26
+ if (bpmcsrf) {
27
+ headers['BPMCSRF'] = bpmcsrf;
28
+ }
29
+ return headers;
30
+ }
15
31
  //# sourceMappingURL=headers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"headers.js","sourceRoot":"","sources":["../../../src/creatio/auth/headers.ts"],"names":[],"mappings":";;AACA,oCAaC;AAdD,2GAA2G;AAC3G,SAAgB,YAAY,CAC3B,MAAc,EACd,MAAgB,EAChB,KAAc;IAEd,MAAM,OAAO,GAA2B,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC3D,IAAI,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;IAC9C,CAAC;IACD,IAAI,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,KAAK,EAAE,CAAC;IAC9C,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../../../src/creatio/auth/headers.ts"],"names":[],"mappings":";;AACA,oCAaC;AAQD,gDAaC;AAnCD,2GAA2G;AAC3G,SAAgB,YAAY,CAC3B,MAAc,EACd,MAAgB,EAChB,KAAc;IAEd,MAAM,OAAO,GAA2B,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC3D,IAAI,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;IAC9C,CAAC;IACD,IAAI,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,KAAK,EAAE,CAAC;IAC9C,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,SAAgB,kBAAkB,CACjC,MAAc,EACd,MAAe,EACf,MAAc,EACd,OAAgB;IAEhB,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,OAAO,CAAC,iBAAiB,CAAC,GAAG,MAAM,CAAC;IACpC,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;IAC3B,IAAI,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC;IAC9B,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC"}
@@ -3,7 +3,9 @@ import { BaseProvider } from './base-provider';
3
3
  export declare class LegacyProvider extends BaseProvider<LegacyAuthConfig> {
4
4
  private _bpmCsrf;
5
5
  private _cookieHeader;
6
+ private _inflightLogin;
6
7
  private _ensureSession;
8
+ private _login;
7
9
  getHeaders(accept: string, isJson?: boolean): Promise<Record<string, string>>;
8
10
  refresh(): Promise<void>;
9
11
  }
@@ -1 +1 @@
1
- {"version":3,"file":"legacy-provider.d.ts","sourceRoot":"","sources":["../../../../src/creatio/auth/providers/legacy-provider.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,qBAAa,cAAe,SAAQ,YAAY,CAAC,gBAAgB,CAAC;IACjE,OAAO,CAAC,QAAQ,CAAqB;IAErC,OAAO,CAAC,aAAa,CAAqB;YAE5B,cAAc;IAyCf,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAW7E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAIrC"}
1
+ {"version":3,"file":"legacy-provider.d.ts","sourceRoot":"","sources":["../../../../src/creatio/auth/providers/legacy-provider.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,qBAAa,cAAe,SAAQ,YAAY,CAAC,gBAAgB,CAAC;IACjE,OAAO,CAAC,QAAQ,CAAqB;IAErC,OAAO,CAAC,aAAa,CAAqB;IAK1C,OAAO,CAAC,cAAc,CAA4B;YAEpC,cAAc;YAad,MAAM;IAsCP,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAK7E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAIrC"}
@@ -12,10 +12,23 @@ const base_provider_1 = require("./base-provider");
12
12
  class LegacyProvider extends base_provider_1.BaseProvider {
13
13
  _bpmCsrf;
14
14
  _cookieHeader;
15
+ // Single-flight: a burst of concurrent first-calls (or 401-driven refreshes) that all find no
16
+ // session triggers ONE login, not N — avoids a thundering herd against AuthService on expiry.
17
+ // Mirrors the dedup BaseOAuth2Provider and the broker provider already do.
18
+ _inflightLogin;
15
19
  async _ensureSession() {
16
20
  if (this._cookieHeader) {
17
21
  return;
18
22
  }
23
+ if (this._inflightLogin) {
24
+ return this._inflightLogin;
25
+ }
26
+ this._inflightLogin = this._login().finally(() => {
27
+ this._inflightLogin = undefined;
28
+ });
29
+ return this._inflightLogin;
30
+ }
31
+ async _login() {
19
32
  const url = `${this.config.baseUrl.replace(/\/$/, '')}/ServiceModel/AuthService.svc/Login`;
20
33
  const body = JSON.stringify({
21
34
  UserName: this.authConfig.login,
@@ -56,13 +69,7 @@ class LegacyProvider extends base_provider_1.BaseProvider {
56
69
  }
57
70
  async getHeaders(accept, isJson) {
58
71
  await this._ensureSession();
59
- const h = (0, auth_1.buildHeaders)(accept, Boolean(isJson));
60
- h['ForceUseSession'] = 'true';
61
- h['Cookie'] = this._cookieHeader;
62
- if (this._bpmCsrf) {
63
- h['BPMCSRF'] = this._bpmCsrf;
64
- }
65
- return h;
72
+ return (0, auth_1.buildCookieHeaders)(accept, Boolean(isJson), this._cookieHeader, this._bpmCsrf);
66
73
  }
67
74
  async refresh() {
68
75
  this._cookieHeader = undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"legacy-provider.js","sourceRoot":"","sources":["../../../../src/creatio/auth/providers/legacy-provider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"legacy-provider.js","sourceRoot":"","sources":["../../../../src/creatio/auth/providers/legacy-provider.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,15 @@
1
1
  import { BearerAuthConfig } from '../../client-config';
2
2
  import { BaseProvider } from './base-provider';
3
3
  /**
4
- * Stateless per-request Bearer passthrough provider.
4
+ * Stateless per-request credential passthrough provider (delegated / gateway).
5
5
  *
6
- * The MCP issues and stores no tokens: every request already carries a Creatio access token
7
- * (obtained by the client from Creatio Identity in `delegated` mode, or injected by a trusted
8
- * Control-Plane in `gateway` mode). This provider simply attaches that token read from the
9
- * per-request {@link getBearerToken} context to outgoing Creatio calls. Token acquisition and
10
- * refresh are the client's / gateway's responsibility, which is why there is nothing to refresh
11
- * here and no server-side token store.
6
+ * The MCP issues and stores no tokens: every request already carries a Creatio credential — obtained
7
+ * by the client from Creatio Identity in `delegated` mode, or injected by a trusted Control-Plane in
8
+ * `gateway` mode. This provider reads that credential from the per-request {@link getInjectedCredential}
9
+ * context and formats the matching headers: a Bearer token, or a Forms-auth session (Cookie + BPMCSRF
10
+ * + ForceUseSession). It holds no cookie jar and needs no client pool the headers are rebuilt from
11
+ * the context on every call. Token/session acquisition and refresh are the client's / gateway's
12
+ * responsibility, which is why there is nothing to refresh here.
12
13
  */
13
14
  export declare class OAuth2BearerProvider extends BaseProvider<BearerAuthConfig> {
14
15
  getHeaders(accept: string, isJson?: boolean): Promise<Record<string, string>>;
@@ -1 +1 @@
1
- {"version":3,"file":"oauth2-bearer-provider.d.ts","sourceRoot":"","sources":["../../../../src/creatio/auth/providers/oauth2-bearer-provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C;;;;;;;;;GASG;AACH,qBAAa,oBAAqB,SAAQ,YAAY,CAAC,gBAAgB,CAAC;IAC1D,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAU7E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAIrC"}
1
+ {"version":3,"file":"oauth2-bearer-provider.d.ts","sourceRoot":"","sources":["../../../../src/creatio/auth/providers/oauth2-bearer-provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C;;;;;;;;;;GAUG;AACH,qBAAa,oBAAqB,SAAQ,YAAY,CAAC,gBAAgB,CAAC;IAC1D,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAa7E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAKrC"}
@@ -5,28 +5,33 @@ const utils_1 = require("../../../utils");
5
5
  const auth_1 = require("../auth");
6
6
  const base_provider_1 = require("./base-provider");
7
7
  /**
8
- * Stateless per-request Bearer passthrough provider.
8
+ * Stateless per-request credential passthrough provider (delegated / gateway).
9
9
  *
10
- * The MCP issues and stores no tokens: every request already carries a Creatio access token
11
- * (obtained by the client from Creatio Identity in `delegated` mode, or injected by a trusted
12
- * Control-Plane in `gateway` mode). This provider simply attaches that token read from the
13
- * per-request {@link getBearerToken} context to outgoing Creatio calls. Token acquisition and
14
- * refresh are the client's / gateway's responsibility, which is why there is nothing to refresh
15
- * here and no server-side token store.
10
+ * The MCP issues and stores no tokens: every request already carries a Creatio credential — obtained
11
+ * by the client from Creatio Identity in `delegated` mode, or injected by a trusted Control-Plane in
12
+ * `gateway` mode. This provider reads that credential from the per-request {@link getInjectedCredential}
13
+ * context and formats the matching headers: a Bearer token, or a Forms-auth session (Cookie + BPMCSRF
14
+ * + ForceUseSession). It holds no cookie jar and needs no client pool the headers are rebuilt from
15
+ * the context on every call. Token/session acquisition and refresh are the client's / gateway's
16
+ * responsibility, which is why there is nothing to refresh here.
16
17
  */
17
18
  class OAuth2BearerProvider extends base_provider_1.BaseProvider {
18
19
  async getHeaders(accept, isJson) {
19
- const token = (0, utils_1.getBearerToken)();
20
- if (!token) {
21
- // No token in context ⇒ unauthenticated request. The HTTP edge turns this into a 401
20
+ const credential = (0, utils_1.getInjectedCredential)();
21
+ if (!credential) {
22
+ // No credential in context ⇒ unauthenticated request. The HTTP edge turns this into a 401
22
23
  // (delegated: with a WWW-Authenticate challenge; gateway: a plain rejection).
23
- throw new Error('bearer_token_required');
24
+ throw new Error('credential_required');
24
25
  }
25
- return (0, auth_1.buildHeaders)(accept, Boolean(isJson), token);
26
+ if (credential.kind === 'cookie') {
27
+ return (0, auth_1.buildCookieHeaders)(accept, Boolean(isJson), credential.cookie, credential.bpmcsrf);
28
+ }
29
+ return (0, auth_1.buildHeaders)(accept, Boolean(isJson), credential.token);
26
30
  }
27
31
  async refresh() {
28
- // Nothing to refresh: the client (delegated) or gateway owns the token lifecycle. A stale
29
- // token surfaces as a 401 from Creatio, which the caller resolves by presenting a fresh one.
32
+ // Nothing to refresh: the client (delegated) or gateway owns the credential lifecycle. A stale
33
+ // token/session surfaces as a 401 from Creatio, which the caller resolves by presenting a fresh
34
+ // one.
30
35
  }
31
36
  }
32
37
  exports.OAuth2BearerProvider = OAuth2BearerProvider;
@@ -1 +1 @@
1
- {"version":3,"file":"oauth2-bearer-provider.js","sourceRoot":"","sources":["../../../../src/creatio/auth/providers/oauth2-bearer-provider.ts"],"names":[],"mappings":";;;AAAA,0CAAgD;AAEhD,kCAAuC;AAEvC,mDAA+C;AAE/C;;;;;;;;;GASG;AACH,MAAa,oBAAqB,SAAQ,4BAA8B;IAChE,KAAK,CAAC,UAAU,CAAC,MAAc,EAAE,MAAgB;QACvD,MAAM,KAAK,GAAG,IAAA,sBAAc,GAAE,CAAC;QAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,qFAAqF;YACrF,8EAA8E;YAC9E,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,IAAA,mBAAY,EAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;IACrD,CAAC;IAEM,KAAK,CAAC,OAAO;QACnB,0FAA0F;QAC1F,6FAA6F;IAC9F,CAAC;CACD;AAfD,oDAeC"}
1
+ {"version":3,"file":"oauth2-bearer-provider.js","sourceRoot":"","sources":["../../../../src/creatio/auth/providers/oauth2-bearer-provider.ts"],"names":[],"mappings":";;;AAAA,0CAAuD;AAEvD,kCAA2D;AAE3D,mDAA+C;AAE/C;;;;;;;;;;GAUG;AACH,MAAa,oBAAqB,SAAQ,4BAA8B;IAChE,KAAK,CAAC,UAAU,CAAC,MAAc,EAAE,MAAgB;QACvD,MAAM,UAAU,GAAG,IAAA,6BAAqB,GAAE,CAAC;QAC3C,IAAI,CAAC,UAAU,EAAE,CAAC;YACjB,0FAA0F;YAC1F,8EAA8E;YAC9E,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACxC,CAAC;QACD,IAAI,UAAU,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAClC,OAAO,IAAA,yBAAkB,EAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;QAC3F,CAAC;QACD,OAAO,IAAA,mBAAY,EAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAChE,CAAC;IAEM,KAAK,CAAC,OAAO;QACnB,+FAA+F;QAC/F,gGAAgG;QAChG,OAAO;IACR,CAAC;CACD;AAnBD,oDAmBC"}
@@ -1 +1 @@
1
- {"version":3,"file":"data-service-schema.d.ts","sourceRoot":"","sources":["../../../../src/creatio/services/dataservice/data-service-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAoB7E;;;;;;;;;GASG;AACH,qBAAa,yBAAyB;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuB;IAClD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IACxD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8B;IACvD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAK7D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6E;gBAGzG,SAAS,EAAE,oBAAoB,EAC/B,YAAY,0BAAgC,EAC5C,OAAO,8BAAoC,EAC3C,SAAS,CAAC,EAAE,mBAAmB;YAQlB,iBAAiB;IAqB/B,OAAO,CAAC,QAAQ;IAIhB,6DAA6D;IACtD,cAAc,IAAI,sBAAsB;IAWlC,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAanC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAgBhF,4FAA4F;IAC/E,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAS7E"}
1
+ {"version":3,"file":"data-service-schema.d.ts","sourceRoot":"","sources":["../../../../src/creatio/services/dataservice/data-service-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAG/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAwB7E;;;;;;;;;GASG;AACH,qBAAa,yBAAyB;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuB;IAClD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IACxD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8B;IACvD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAI7D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA0E;gBAGtG,SAAS,EAAE,oBAAoB,EAC/B,YAAY,0BAAgC,EAC5C,OAAO,8BAAoC,EAC3C,SAAS,CAAC,EAAE,mBAAmB;YAQlB,iBAAiB;IAkB/B,OAAO,CAAC,QAAQ;IAIhB,6DAA6D;IACtD,cAAc,IAAI,sBAAsB;IAWlC,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAanC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAgBhF,4FAA4F;IAC/E,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAS7E"}
@@ -1,10 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DataServiceSchemaProvider = void 0;
4
+ const versioned_ttl_cache_1 = require("../versioned-ttl-cache");
4
5
  const data_service_filter_translator_1 = require("./data-service-filter-translator");
5
6
  const data_service_query_builder_1 = require("./data-service-query-builder");
6
7
  const data_service_types_1 = require("./data-service-types");
7
8
  const CACHE_TTL_MS = 30 * 60 * 1000;
9
+ // Keyed per (base URL, entity), so the cap spans every entity described across every tenant; sized
10
+ // generously since a single tenant can legitimately describe many entities, but still bounded so a
11
+ // long-lived multi-tenant gateway cannot grow the schema cache without limit.
12
+ const MAX_SCHEMA_ENTRIES = 1000;
8
13
  const ENTITY_LIST_SCHEMA = 'VwSysSchemaInWorkspace';
9
14
  const ENTITY_MANAGER = 'EntitySchemaManager';
10
15
  /**
@@ -23,10 +28,9 @@ class DataServiceSchemaProvider {
23
28
  _filters;
24
29
  _freshness;
25
30
  // Keyed by `${baseUrl}::${name}` so a multi-tenant gateway never serves tenant A's schema to B.
26
- // Each entry is stamped with the freshness version at fetch time; a version mismatch (the data
27
- // model changed) treats it as a miss. The TTL is a coarse backstop (and the sole freshness gate
28
- // when no SchemaFreshnessGate is wired — then `version` is constant and behaviour is unchanged).
29
- _schemaCache = new Map();
31
+ // Version-stamped (a data-model change is a miss), TTL-bounded, and LRU-capped all delegated to
32
+ // the shared cache so this provider and ODataMetadataStore share one freshness/eviction policy.
33
+ _schemaCache = new versioned_ttl_cache_1.VersionedTtlCache(CACHE_TTL_MS, MAX_SCHEMA_ENTRIES);
30
34
  constructor(transport, queryBuilder = new data_service_query_builder_1.DataServiceQueryBuilder(), filters = new data_service_filter_translator_1.DataServiceFilterTranslator(), freshness) {
31
35
  this._transport = transport;
32
36
  this._queryBuilder = queryBuilder;
@@ -37,17 +41,14 @@ class DataServiceSchemaProvider {
37
41
  const baseUrl = this._transport.baseUrl;
38
42
  const version = this._freshness ? await this._freshness.getSchemaVersion(baseUrl) : '';
39
43
  const key = `${baseUrl}::${name}`;
40
- const cached = this._schemaCache.get(key);
41
- if (cached && cached.version === version && Date.now() - cached.at < CACHE_TTL_MS) {
42
- return cached.schema;
43
- }
44
- const body = await this._transport.post('RuntimeEntitySchemaRequest', { name }, { logContext: { entity: name }, checkSuccess: true });
45
- const schema = body?.schema;
46
- if (!schema || !schema.name) {
47
- throw new Error(`entity_not_found:${name}`);
48
- }
49
- this._schemaCache.set(key, { schema, version, at: Date.now() });
50
- return schema;
44
+ return this._schemaCache.getOrLoad(key, version, async () => {
45
+ const body = await this._transport.post('RuntimeEntitySchemaRequest', { name }, { logContext: { entity: name }, checkSuccess: true });
46
+ const schema = body?.schema;
47
+ if (!schema || !schema.name) {
48
+ throw new Error(`entity_not_found:${name}`);
49
+ }
50
+ return schema;
51
+ });
51
52
  }
52
53
  _columns(schema) {
53
54
  return Object.values(schema.columns?.Items ?? {});
@@ -1 +1 @@
1
- {"version":3,"file":"data-service-schema.js","sourceRoot":"","sources":["../../../../src/creatio/services/dataservice/data-service-schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"data-service-schema.js","sourceRoot":"","sources":["../../../../src/creatio/services/dataservice/data-service-schema.ts"],"names":[],"mappings":"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"}
@@ -3,32 +3,25 @@ import { CreatioHttpClient } from '../http-client';
3
3
  import { SchemaFreshnessGate } from '../schema-freshness-gate';
4
4
  export declare class ODataMetadataStore {
5
5
  private static readonly METADATA_TTL_MS;
6
+ /** Max distinct base URLs (tenants) retained per cache; least-recently-used dropped past it. */
7
+ private static readonly DEFAULT_MAX_TENANTS;
6
8
  private readonly _client;
7
9
  private readonly _freshness;
8
- private _metadataXml?;
9
- private _metadataParsed?;
10
- private _metadataFetchedAt;
11
- private _entitySetsCache?;
12
- private _entitySetsFetchedAt;
13
- private _metadataBaseUrl?;
14
- private _metadataVersion?;
15
- private _entitySetsBaseUrl?;
16
- private _entitySetsVersion?;
17
- private _entityTypeBySet;
18
- private _typeNodeByName;
19
- constructor(client: CreatioHttpClient, freshness?: SchemaFreshnessGate);
20
- private _isFresh;
10
+ private readonly _docs;
11
+ private readonly _entitySets;
12
+ constructor(client: CreatioHttpClient, freshness?: SchemaFreshnessGate, maxTenants?: number);
21
13
  /** Freshness version for the current request's base URL (empty when no gate is wired, which
22
14
  * keeps the cache purely TTL-driven — the prior behaviour). */
23
15
  private _version;
24
16
  private _arrayify;
25
- private _getMetadataXml;
17
+ /** The cached metadata document for the current request's base URL, fetching/refreshing the raw
18
+ * `$metadata` on a miss (different tenant, stale TTL, or a changed freshness version). */
19
+ private _getDoc;
26
20
  private _getParsedMetadata;
27
21
  private _extractSchemas;
28
22
  private _tryGetEntitySetsFromService;
29
- /** Build (once per parsed document) the entitySet→entityType and typeName→typeNode lookups.
30
- * Always goes through {@link _getParsedMetadata} first so the TTL refresh runs (and nulls the
31
- * indexes on a refetch); only the index build itself is cached. */
23
+ /** Build (once per parsed document) the entitySet→entityType and typeName→typeNode lookups on the
24
+ * document, so describeEntity is O(1) instead of two O(N) scans over the entire EDMX per call. */
32
25
  private _ensureIndexes;
33
26
  private _getEntitySetsFromMetadata;
34
27
  private _parseEntityProperties;
@@ -1 +1 @@
1
- {"version":3,"file":"metadata-store.d.ts","sourceRoot":"","sources":["../../../../src/creatio/services/odata/metadata-store.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAI/D,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAkB;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,eAAe,CAAC,CAAM;IAC9B,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,gBAAgB,CAAC,CAAW;IACpC,OAAO,CAAC,oBAAoB,CAAK;IAKjC,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAClC,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAClC,OAAO,CAAC,kBAAkB,CAAC,CAAS;IACpC,OAAO,CAAC,kBAAkB,CAAC,CAAS;IAGpC,OAAO,CAAC,gBAAgB,CAAkC;IAC1D,OAAO,CAAC,eAAe,CAA+B;gBAE1C,MAAM,EAAE,iBAAiB,EAAE,SAAS,CAAC,EAAE,mBAAmB;IAKtE,OAAO,CAAC,QAAQ;IAIhB;oEACgE;YAClD,QAAQ;IAMtB,OAAO,CAAC,SAAS;YAOH,eAAe;YAwBf,kBAAkB;IAahC,OAAO,CAAC,eAAe;YAKT,4BAA4B;IA4B1C;;wEAEoE;YACtD,cAAc;YA4Bd,0BAA0B;IAKxC,OAAO,CAAC,sBAAsB;IA2BjB,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAoBnC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;CAkBhF"}
1
+ {"version":3,"file":"metadata-store.d.ts","sourceRoot":"","sources":["../../../../src/creatio/services/odata/metadata-store.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAe/D,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAkB;IACzD,gGAAgG;IAChG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAO;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAK7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA8B;gBAGzD,MAAM,EAAE,iBAAiB,EACzB,SAAS,CAAC,EAAE,mBAAmB,EAC/B,UAAU,GAAE,MAA+C;IAW5D;oEACgE;YAClD,QAAQ;IAMtB,OAAO,CAAC,SAAS;IAOjB;+FAC2F;YAC7E,OAAO;IAWrB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,eAAe;YAKT,4BAA4B;IA4B1C;uGACmG;IACnG,OAAO,CAAC,cAAc;YA4BR,0BAA0B;IAMxC,OAAO,CAAC,sBAAsB;IA2BjB,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IASnC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;CAmBhF"}
@@ -6,34 +6,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ODataMetadataStore = void 0;
7
7
  const fast_xml_parser_1 = require("fast-xml-parser");
8
8
  const log_1 = __importDefault(require("../../../log"));
9
+ const versioned_ttl_cache_1 = require("../versioned-ttl-cache");
9
10
  const odata_routes_1 = require("./odata-routes");
10
11
  class ODataMetadataStore {
11
12
  static METADATA_TTL_MS = 30 * 60 * 1000;
13
+ /** Max distinct base URLs (tenants) retained per cache; least-recently-used dropped past it. */
14
+ static DEFAULT_MAX_TENANTS = 100;
12
15
  _client;
13
16
  _freshness;
14
- _metadataXml;
15
- _metadataParsed;
16
- _metadataFetchedAt = 0;
17
- _entitySetsCache;
18
- _entitySetsFetchedAt = 0;
19
- // The base URL + freshness version the cached document/list were fetched under. A change in
20
- // either (a different tenant via the gateway override, or a data-model change) invalidates the
21
- // single-slot cache. Single-slot is intentional: per-tenant pooling is the multitenancy epic;
22
- // here correctness (never serve another tenant's / a stale model's metadata) is what matters.
23
- _metadataBaseUrl;
24
- _metadataVersion;
25
- _entitySetsBaseUrl;
26
- _entitySetsVersion;
27
- // Built once per parsed-metadata document so describeEntity is O(1) instead of two O(N) scans
28
- // over the entire (hundreds-to-thousands of types) Creatio EDMX on every call.
29
- _entityTypeBySet;
30
- _typeNodeByName;
31
- constructor(client, freshness) {
17
+ // Keyed by base URL so a multi-tenant gateway never serves tenant A's metadata to B AND never
18
+ // thrashes: tenant A's parsed document survives an interleaved call to B (the prior single-slot
19
+ // design re-fetched A's whole $metadata on every tenant switch). Version-stamping + TTL + LRU are
20
+ // all the shared cache's job.
21
+ _docs;
22
+ _entitySets;
23
+ constructor(client, freshness, maxTenants = ODataMetadataStore.DEFAULT_MAX_TENANTS) {
32
24
  this._client = client;
33
25
  this._freshness = freshness;
34
- }
35
- _isFresh(fetchedAt) {
36
- return Date.now() - fetchedAt < ODataMetadataStore.METADATA_TTL_MS;
26
+ this._docs = new versioned_ttl_cache_1.VersionedTtlCache(ODataMetadataStore.METADATA_TTL_MS, maxTenants);
27
+ this._entitySets = new versioned_ttl_cache_1.VersionedTtlCache(ODataMetadataStore.METADATA_TTL_MS, maxTenants);
37
28
  }
38
29
  /** Freshness version for the current request's base URL (empty when no gate is wired, which
39
30
  * keeps the cache purely TTL-driven — the prior behaviour). */
@@ -48,38 +39,27 @@ class ODataMetadataStore {
48
39
  }
49
40
  return Array.isArray(value) ? value : [value];
50
41
  }
51
- async _getMetadataXml() {
42
+ /** The cached metadata document for the current request's base URL, fetching/refreshing the raw
43
+ * `$metadata` on a miss (different tenant, stale TTL, or a changed freshness version). */
44
+ async _getDoc() {
52
45
  const baseUrl = this._client.normalizedBaseUrl;
53
46
  const version = await this._version();
54
- if (this._metadataXml &&
55
- this._metadataBaseUrl === baseUrl &&
56
- this._metadataVersion === version &&
57
- this._isFresh(this._metadataFetchedAt)) {
58
- return this._metadataXml;
59
- }
60
- const headers = await this._client.getXmlHeaders();
61
- const metadataUrl = `${(0, odata_routes_1.odataRoot)(baseUrl)}/$metadata`;
62
- const xmlContent = await this._client.fetchText(metadataUrl, async () => ({ headers }));
63
- this._metadataXml = xmlContent;
64
- this._metadataParsed = undefined; // force re-parse against the refreshed document
65
- this._entityTypeBySet = undefined; // and rebuild the lookup indexes
66
- this._typeNodeByName = undefined;
67
- this._metadataBaseUrl = baseUrl;
68
- this._metadataVersion = version;
69
- this._metadataFetchedAt = Date.now();
70
- return this._metadataXml;
47
+ return this._docs.getOrLoad(baseUrl, version, async () => {
48
+ const headers = await this._client.getXmlHeaders();
49
+ const metadataUrl = `${(0, odata_routes_1.odataRoot)(baseUrl)}/$metadata`;
50
+ const xml = await this._client.fetchText(metadataUrl, async () => ({ headers }));
51
+ return { xml };
52
+ });
71
53
  }
72
- async _getParsedMetadata() {
73
- const xmlContent = await this._getMetadataXml();
74
- if (this._metadataParsed) {
75
- return this._metadataParsed;
54
+ _getParsedMetadata(doc) {
55
+ if (!doc.parsed) {
56
+ const parser = new fast_xml_parser_1.XMLParser({
57
+ ignoreAttributes: false,
58
+ attributeNamePrefix: '@_',
59
+ });
60
+ doc.parsed = parser.parse(doc.xml);
76
61
  }
77
- const parser = new fast_xml_parser_1.XMLParser({
78
- ignoreAttributes: false,
79
- attributeNamePrefix: '@_',
80
- });
81
- this._metadataParsed = parser.parse(xmlContent);
82
- return this._metadataParsed;
62
+ return doc.parsed;
83
63
  }
84
64
  _extractSchemas(metadata) {
85
65
  const dataServices = metadata['edmx:Edmx']?.['edmx:DataServices'];
@@ -113,14 +93,13 @@ class ODataMetadataStore {
113
93
  }
114
94
  return null;
115
95
  }
116
- /** Build (once per parsed document) the entitySet→entityType and typeName→typeNode lookups.
117
- * Always goes through {@link _getParsedMetadata} first so the TTL refresh runs (and nulls the
118
- * indexes on a refetch); only the index build itself is cached. */
119
- async _ensureIndexes() {
120
- const metadata = await this._getParsedMetadata();
121
- if (this._entityTypeBySet && this._typeNodeByName) {
96
+ /** Build (once per parsed document) the entitySet→entityType and typeName→typeNode lookups on the
97
+ * document, so describeEntity is O(1) instead of two O(N) scans over the entire EDMX per call. */
98
+ _ensureIndexes(doc) {
99
+ if (doc.entityTypeBySet && doc.typeNodeByName) {
122
100
  return;
123
101
  }
102
+ const metadata = this._getParsedMetadata(doc);
124
103
  const schemas = this._extractSchemas(metadata);
125
104
  const bySet = new Map();
126
105
  const byType = new Map();
@@ -140,12 +119,13 @@ class ODataMetadataStore {
140
119
  }
141
120
  }
142
121
  }
143
- this._entityTypeBySet = bySet;
144
- this._typeNodeByName = byType;
122
+ doc.entityTypeBySet = bySet;
123
+ doc.typeNodeByName = byType;
145
124
  }
146
125
  async _getEntitySetsFromMetadata() {
147
- await this._ensureIndexes();
148
- return Array.from(this._entityTypeBySet.keys());
126
+ const doc = await this._getDoc();
127
+ this._ensureIndexes(doc);
128
+ return Array.from(doc.entityTypeBySet.keys());
149
129
  }
150
130
  _parseEntityProperties(entityTypeNode) {
151
131
  const keyRefs = this._arrayify(entityTypeNode.Key?.PropertyRef);
@@ -165,30 +145,22 @@ class ODataMetadataStore {
165
145
  async listEntitySets() {
166
146
  const baseUrl = this._client.normalizedBaseUrl;
167
147
  const version = await this._version();
168
- if (this._entitySetsCache &&
169
- this._entitySetsBaseUrl === baseUrl &&
170
- this._entitySetsVersion === version &&
171
- this._isFresh(this._entitySetsFetchedAt)) {
172
- return this._entitySetsCache;
173
- }
174
- const serviceSets = await this._tryGetEntitySetsFromService();
175
- const result = serviceSets ?? (await this._getEntitySetsFromMetadata());
176
- this._entitySetsCache = result;
177
- this._entitySetsBaseUrl = baseUrl;
178
- this._entitySetsVersion = version;
179
- this._entitySetsFetchedAt = Date.now();
180
- return result;
148
+ return this._entitySets.getOrLoad(baseUrl, version, async () => {
149
+ const serviceSets = await this._tryGetEntitySetsFromService();
150
+ return serviceSets ?? (await this._getEntitySetsFromMetadata());
151
+ });
181
152
  }
182
153
  async describeEntity(entitySet) {
183
- await this._ensureIndexes();
184
- const fullType = this._entityTypeBySet.get(entitySet) ?? '';
154
+ const doc = await this._getDoc();
155
+ this._ensureIndexes(doc);
156
+ const fullType = doc.entityTypeBySet.get(entitySet) ?? '';
185
157
  if (!fullType) {
186
158
  const error = `entity_not_found:${entitySet}`;
187
159
  log_1.default.error('creatio.metadata.describe_entity.error', { entitySet, error });
188
160
  throw new Error(error);
189
161
  }
190
162
  const typeName = fullType.split('.').pop();
191
- const entityTypeNode = this._typeNodeByName.get(typeName);
163
+ const entityTypeNode = doc.typeNodeByName.get(typeName);
192
164
  if (!entityTypeNode) {
193
165
  const error = `entity_type_not_found:${typeName}`;
194
166
  log_1.default.error('creatio.metadata.describe_entity.error', { entitySet, error });