mcp-creatio 0.6.5 → 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.
- package/README.md +21 -11
- package/dist/creatio/auth/headers.d.ts +7 -0
- package/dist/creatio/auth/headers.d.ts.map +1 -1
- package/dist/creatio/auth/headers.js +16 -0
- package/dist/creatio/auth/headers.js.map +1 -1
- package/dist/creatio/auth/providers/legacy-provider.d.ts +2 -0
- package/dist/creatio/auth/providers/legacy-provider.d.ts.map +1 -1
- package/dist/creatio/auth/providers/legacy-provider.js +14 -7
- package/dist/creatio/auth/providers/legacy-provider.js.map +1 -1
- package/dist/creatio/auth/providers/oauth2-bearer-provider.d.ts +8 -7
- package/dist/creatio/auth/providers/oauth2-bearer-provider.d.ts.map +1 -1
- package/dist/creatio/auth/providers/oauth2-bearer-provider.js +19 -14
- package/dist/creatio/auth/providers/oauth2-bearer-provider.js.map +1 -1
- package/dist/creatio/services/dataservice/data-service-schema.d.ts.map +1 -1
- package/dist/creatio/services/dataservice/data-service-schema.js +16 -15
- package/dist/creatio/services/dataservice/data-service-schema.js.map +1 -1
- package/dist/creatio/services/odata/metadata-store.d.ts +10 -17
- package/dist/creatio/services/odata/metadata-store.d.ts.map +1 -1
- package/dist/creatio/services/odata/metadata-store.js +47 -75
- package/dist/creatio/services/odata/metadata-store.js.map +1 -1
- package/dist/creatio/services/versioned-ttl-cache.d.ts +33 -0
- package/dist/creatio/services/versioned-ttl-cache.d.ts.map +1 -0
- package/dist/creatio/services/versioned-ttl-cache.js +95 -0
- package/dist/creatio/services/versioned-ttl-cache.js.map +1 -0
- package/dist/server/bearer/bearer-edge.d.ts +4 -1
- package/dist/server/bearer/bearer-edge.d.ts.map +1 -1
- package/dist/server/bearer/bearer-edge.js +41 -15
- package/dist/server/bearer/bearer-edge.js.map +1 -1
- package/dist/server/http/health.d.ts +19 -0
- package/dist/server/http/health.d.ts.map +1 -0
- package/dist/server/http/health.js +41 -0
- package/dist/server/http/health.js.map +1 -0
- package/dist/server/http/http-server.d.ts +1 -0
- package/dist/server/http/http-server.d.ts.map +1 -1
- package/dist/server/http/http-server.js +9 -0
- package/dist/server/http/http-server.js.map +1 -1
- package/dist/server/http/index.d.ts +1 -0
- package/dist/server/http/index.d.ts.map +1 -1
- package/dist/server/http/index.js +1 -0
- package/dist/server/http/index.js.map +1 -1
- package/dist/server/http/mcp-handlers.d.ts.map +1 -1
- package/dist/server/http/mcp-handlers.js +4 -4
- package/dist/server/http/mcp-handlers.js.map +1 -1
- package/dist/utils/context.d.ts +21 -6
- package/dist/utils/context.d.ts.map +1 -1
- package/dist/utils/context.js +6 -6
- package/dist/utils/context.js.map +1 -1
- package/dist/utils/network.d.ts +2 -0
- package/dist/utils/network.d.ts.map +1 -1
- package/dist/utils/network.js +11 -0
- package/dist/utils/network.js.map +1 -1
- 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
|
|
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
|
|
206
|
-
is sent, it is validated: set **`CREATIO_MCP_ALLOWED_BASE_URLS`** (comma-separated origins)
|
|
207
|
-
restrict it to your tenants. When unset, any `http(s)` host is accepted (trusting the gateway)
|
|
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> #
|
|
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,11 @@ 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
|
-
> **
|
|
238
|
-
>
|
|
239
|
-
>
|
|
240
|
-
>
|
|
246
|
+
> **Bearer or session cookie — nothing 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.
|
|
241
251
|
|
|
242
252
|
> **Per-tenant tool isolation.** A single MCP deployment serving many instances keeps each tenant's
|
|
243
253
|
> tool surface separate, keyed by the effective base URL (`X-Creatio-Base-Url`, else `CREATIO_BASE_URL`).
|
|
@@ -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;
|
|
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;
|
|
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
|
-
|
|
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":";;;;;;AAAA,uDAA+B;AAC/B,0CAA6C;AAC7C,0CAAgD;AAEhD,
|
|
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
|
|
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
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
|
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
|
|
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
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
|
20
|
-
if (!
|
|
21
|
-
// No
|
|
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('
|
|
24
|
+
throw new Error('credential_required');
|
|
24
25
|
}
|
|
25
|
-
|
|
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
|
|
29
|
-
// token surfaces as a 401 from Creatio, which the caller resolves by presenting a fresh
|
|
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,
|
|
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;
|
|
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
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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":";;;
|
|
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
|
|
9
|
-
private
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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;
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
this.
|
|
58
|
-
return
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
144
|
-
|
|
122
|
+
doc.entityTypeBySet = bySet;
|
|
123
|
+
doc.typeNodeByName = byType;
|
|
145
124
|
}
|
|
146
125
|
async _getEntitySetsFromMetadata() {
|
|
147
|
-
await this.
|
|
148
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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.
|
|
184
|
-
|
|
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 =
|
|
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 });
|