@redocly/realm 0.136.0-next.0 → 0.136.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/server/api-routes/execute-api-route.js +1 -1
- package/dist/server/api-routes/helpers/enhance-context.d.ts +3 -1
- package/dist/server/api-routes/helpers/enhance-context.js +1 -1
- package/dist/server/api-routes/helpers/is-mock-server.d.ts +2 -0
- package/dist/server/api-routes/helpers/is-mock-server.js +1 -0
- package/dist/server/api-routes/mock-server/kv-state-store.d.ts +3 -0
- package/dist/server/api-routes/mock-server/kv-state-store.js +1 -0
- package/dist/server/api-routes/mock-server/mock-js-executor.d.ts +3 -0
- package/dist/server/api-routes/mock-server/mock-js-executor.js +13 -0
- package/dist/server/config/env-config.d.ts +2 -0
- package/dist/server/config/env-schema.d.ts +5 -0
- package/dist/server/config/env-schemas/catalog.d.ts +3 -0
- package/dist/server/config/env-schemas/catalog.js +1 -1
- package/dist/server/plugins/catalog-entities/database/catalog-entities-diff-publisher.d.ts +27 -0
- package/dist/server/plugins/catalog-entities/database/catalog-entities-diff-publisher.js +12 -0
- package/dist/server/plugins/catalog-entities/database/catalog-entities-publisher.js +1 -12
- package/dist/server/plugins/catalog-entities/database/catalog-entities-revisions-pruner.d.ts +20 -0
- package/dist/server/plugins/catalog-entities/database/catalog-entities-revisions-pruner.js +42 -0
- package/dist/server/plugins/catalog-entities/database/consts.d.ts +5 -0
- package/dist/server/plugins/catalog-entities/database/consts.js +1 -1
- package/dist/server/plugins/catalog-entities/plugin.js +1 -1
- package/dist/server/plugins/mcp/codemode/constants.d.ts +3 -0
- package/dist/server/plugins/mcp/codemode/constants.js +1 -1
- package/dist/server/plugins/mcp/codemode/sandbox/create-sandbox-setup.d.ts +10 -0
- package/dist/server/plugins/mcp/codemode/sandbox/create-sandbox-setup.js +11 -0
- package/dist/server/plugins/mcp/codemode/sandbox/polyfills.d.ts +2 -0
- package/dist/server/plugins/mcp/codemode/sandbox/{sandbox-polyfills.js → polyfills.js} +7 -8
- package/dist/server/plugins/mcp/codemode/tools/execute.js +1 -1
- package/dist/server/plugins/mcp/codemode/types.d.ts +0 -8
- package/dist/server/sandbox/constants/host-functions.d.ts +3 -0
- package/dist/server/sandbox/constants/host-functions.js +1 -0
- package/dist/server/sandbox/constants/sandbox.d.ts +6 -0
- package/dist/server/sandbox/constants/sandbox.js +1 -0
- package/dist/server/sandbox/sandbox.d.ts +8 -0
- package/dist/server/sandbox/sandbox.js +3 -0
- package/dist/server/sandbox/types.d.ts +24 -0
- package/dist/server/sandbox/types.js +0 -0
- package/dist/server/sandbox/utils/eval-guest-code.d.ts +15 -0
- package/dist/server/sandbox/utils/eval-guest-code.js +1 -0
- package/dist/server/sandbox/utils/execution-budget.d.ts +11 -0
- package/dist/server/sandbox/utils/execution-budget.js +1 -0
- package/dist/server/sandbox/utils/setup-host-functions.d.ts +18 -0
- package/dist/server/sandbox/utils/setup-host-functions.js +5 -0
- package/dist/server/utils/fetch-with-client.d.ts +10 -0
- package/dist/server/utils/fetch-with-client.js +1 -0
- package/dist/server/web-server/middleware/apiKeyMiddleware.js +1 -1
- package/dist/server/web-server/middleware/catalogAuthMiddleware.js +1 -1
- package/dist/server/web-server/routes/ask-ai.js +1 -1
- package/dist/server/web-server/routes/auth.js +1 -1
- package/dist/server/web-server/routes/catalog/helpers/upsert-pages-stats.js +1 -1
- package/dist/server/web-server/routes/feedback.js +1 -1
- package/dist/server/web-server/routes/semantic-search.js +1 -1
- package/package.json +6 -6
- package/dist/server/plugins/mcp/codemode/sandbox/sandbox-polyfills.d.ts +0 -3
- package/dist/server/plugins/mcp/codemode/sandbox/sandbox.d.ts +0 -3
- package/dist/server/plugins/mcp/codemode/sandbox/sandbox.js +0 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @redocly/realm
|
|
2
2
|
|
|
3
|
+
## 0.136.0-next.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 42547a5a76a: Improved rendering of schema property examples: object and array example values are now displayed as pretty-printed JSON with syntax highlighting and a copy button, instead of a single-line text string.
|
|
8
|
+
- Updated dependencies [42547a5a76a]
|
|
9
|
+
- @redocly/openapi-docs@3.24.0-next.1
|
|
10
|
+
- @redocly/asyncapi-docs@1.13.0-next.1
|
|
11
|
+
- @redocly/graphql-docs@1.13.0-next.1
|
|
12
|
+
- @redocly/portal-plugin-mock-server@0.21.0-next.1
|
|
13
|
+
|
|
3
14
|
## 0.136.0-next.0
|
|
4
15
|
|
|
5
16
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Hono as
|
|
1
|
+
import{Hono as l}from"hono";import{TrieRouter as f}from"hono/router/trie-router";import{Logger as d}from"../tools/notifiers/logger.js";import{envConfig as a}from"../config/env-config.js";import{importApiRoutesHandler as m}from"./import-api-routes-handlers.js";import{getBodyWithSizeCheck as g}from"./helpers/get-body-with-size-check.js";import{setupLogger as w}from"./helpers/setup-logger.js";import{telemetry as c}from"../telemetry/index.js";import{KvService as D}from"../persistence/kv/services/kv-service.js";import{enhanceContext as R,enhanceMockServerContext as p}from"./helpers/enhance-context.js";import{isMockServerRoute as q}from"./helpers/is-mock-server.js";const u=new d,h=8e3,v=100;async function z(e){c.initialize(),w(u,e.requestHandlerId);const n=await A(e),r=await H(e,n).request(e.req.url,e.req),t=await g(r,e.maxResponseSizeMB);return{status:r.status,headers:Object.fromEntries(r.headers.entries()),body:t}}async function A(e){return(await(await M(e))()).default}function H(e,n){const o=new l({router:new f}),r=y(e.requestHandlerId);return o.all(e.slug,async t=>{if(typeof n!="function")return E(e,t);const i=r({honoCtx:t,ctx:e.ctxData,telemetry:c,getKv:()=>x(e),slug:e.slug}),s=await n(t.req.raw,i,e.staticData);return s instanceof Response?s:typeof s=="string"?t.text(s):t.json(s)}),o.onError((t,i)=>{if(u.error(`[${e.requestHandlerId}] ${t.message} ${t.stack}`),t instanceof SyntaxError)return i.text(t.message,500);throw t}),o}function y(e){return q(e)?p:R}function x(e){return D.getInstance({baseDbDir:e.serverOutDir,sqldRemoteDatabaseUrl:e.sqldRemoteDatabaseUrl,sqldRemoteDatabaseAuthToken:e.sqldRemoteDatabaseAuthToken})}function E(e,n){const o=`API function "${e.slug}" does not export a default function. Add 'export default function (request, context) { ... }' to the file.`;return u.error(`[${e.requestHandlerId}] ${o}`),a.isDevelopMode?n.text(o,500):n.newResponse(null,404)}async function M(e){const n=Date.now()+(a.isDevelopMode?h:0);let o;for(;;){try{const{requestHandlers:r}=await m(e.serverOutDir),t=r?.[e.requestHandlerId];if(typeof t=="function")return t;o=new Error(`Request handler "${e.requestHandlerId}" is not available yet.`)}catch(r){o=r}if(Date.now()>=n)throw o;await new Promise(r=>setTimeout(r,v))}}export{z as executeApiRoute};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AsyncApiRealmAPI } from '@redocly/realm-asyncapi-sdk';
|
|
2
|
-
import type { ApiFunctionsContext, KvService } from '@redocly/config';
|
|
2
|
+
import type { ApiFunctionsContext, KvService, MockServerRouteContext } from '@redocly/config';
|
|
3
3
|
import type { Context } from 'hono';
|
|
4
4
|
import type { ApiFunctionsBasicContext } from '../../types';
|
|
5
5
|
export type EnhanceContextParams = {
|
|
@@ -7,6 +7,8 @@ export type EnhanceContextParams = {
|
|
|
7
7
|
ctx: ApiFunctionsBasicContext;
|
|
8
8
|
telemetry?: AsyncApiRealmAPI.Telemetry;
|
|
9
9
|
getKv: () => Promise<KvService>;
|
|
10
|
+
slug?: string;
|
|
10
11
|
};
|
|
11
12
|
export declare const enhanceContext: ({ honoCtx, ctx, telemetry, getKv, }: EnhanceContextParams) => ApiFunctionsContext;
|
|
13
|
+
export declare function enhanceMockServerContext(params: EnhanceContextParams): MockServerRouteContext;
|
|
12
14
|
//# sourceMappingURL=enhance-context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{deleteCookie as
|
|
1
|
+
import{deleteCookie as p,getCookie as k,setCookie as g}from"hono/cookie";import{createQuickJsMockExecutor as q}from"../mock-server/mock-js-executor.js";import{createKvStateStore as b}from"../mock-server/kv-state-store.js";const j=new Set([101,103,204,205,304]),a=e=>e!==void 0&&j.has(e),A=({honoCtx:e,ctx:l,telemetry:d,getKv:m})=>{const c=e.req.routePath,f=e.req.param(),S=Object.fromEntries(Object.entries(f).map(([t,r])=>{if(r&&(c.includes(`/:${t}{.*}`)||c.includes(`/:${t}{.+}`))){const o=r.split("/").filter(Boolean);return[t,o.length?o:void 0]}return[t,r]}));let n;const u=t=>{n=t,e.status(t)},s={...e.res,status:t=>(u(t),s),json:t=>a(n)?e.body(null):e.json(t),text:(t,r)=>(r&&u(r),a(n)?e.body(null):e.text(t)),redirect:(t,r=302)=>e.redirect(t,r),setCookie:(t,r,i)=>(g(e,t,r,i),s),deleteCookie:t=>(p(e,t),s)};return{...l,...s,params:S,query:e.req.query()||{},cookies:k(e),telemetry:d,getKv:m}};function P(e){return{...A(e),executor:q(),store:b(e.getKv,["mock",String(e.slug??"default")])}}export{A as enhanceContext,P as enhanceMockServerContext};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{MOCK_SERVER_REQUEST_HANDLER_ID as o}from"@redocly/portal-plugin-mock-server";function t(r){return r===o}export{t as isMockServerRoute};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const u=7200;function k(a,i=["mock"]){const s=t=>[...i,...t];return{async get(t){return(await a()).get(s(t))},async set(t,e,n){await(await a()).set(s(t),e,{ttlInSeconds:n?.ttlSeconds??7200})},async delete(t){await(await a()).delete(s(t))},async list(t,e){const n=await a(),{items:l,total:o,cursor:r}=await n.list({prefix:s(t)},{limit:e?.limit,cursor:e?.cursor});return{items:l.filter(c=>c.value!==null).map(c=>({key:c.key.slice(i.length),value:c.value})),total:o,cursor:r}}}}export{k as createKvStateStore};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import{runInSandbox as f}from"../../sandbox/sandbox.js";import{setupHostFunctions as p}from"../../sandbox/utils/setup-host-functions.js";import{evalGuestCode as m}from"../../sandbox/utils/eval-guest-code.js";const i="state",g=15e3;function E(r){const o=r?.timeoutMs,s=r?.maxLifetimeMs??g;return{async run(n,e,t){const{result:a,error:u}=await f(n,{setupContext:(c,d)=>{const l=p(c,M(e.state),{globalName:i,budget:d});return m(c,b(e.request)),l},timeoutMs:t?.timeoutMs??o,maxLifetimeMs:t?.maxLifetimeMs??s});if(u!==void 0)throw new Error(`Mock snippet execution failed: ${u}`);return a}}}function M(r){const o=e=>{const t=typeof e=="object"&&e!==null&&"resource"in e?e.resource:void 0;if(typeof t!="string"||t==="")throw new Error(`Mock snippet passed an invalid resource to ctx.state: expected a non-empty string, got ${typeof t}`);return t},s=e=>{const t=typeof e=="object"&&e!==null&&"id"in e?e.id:void 0;if(typeof t!="string"&&typeof t!="number")throw new Error(`Mock snippet passed an invalid id to ctx.state: expected a string or number, got ${typeof t}`);return t},n=e=>{const t=typeof e=="object"&&e!==null&&"value"in e?e.value:void 0;if(t===void 0)throw new Error(`Mock snippet passed an invalid value to ctx.state: expected a defined JSON value, got ${typeof t}`);return t};return{get(e){return r.get(o(e),s(e))},list(e){return r.list(o(e))},set(e){return r.set(o(e),s(e),n(e))},delete(e){return r.delete(o(e),s(e))}}}function b(r){return`(() => {
|
|
2
|
+
const state = globalThis[${JSON.stringify(i)}];
|
|
3
|
+
delete globalThis[${JSON.stringify(i)}];
|
|
4
|
+
globalThis.ctx = Object.freeze({
|
|
5
|
+
request: ${x(r)},
|
|
6
|
+
state: Object.freeze({
|
|
7
|
+
get: (resource, id) => state.get({ resource, id }),
|
|
8
|
+
list: (resource) => state.list({ resource }),
|
|
9
|
+
set: (resource, id, value) => state.set({ resource, id, value }),
|
|
10
|
+
delete: (resource, id) => state.delete({ resource, id }),
|
|
11
|
+
}),
|
|
12
|
+
});
|
|
13
|
+
})();`}function x(r){return JSON.stringify(r).replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029")}export{E as createQuickJsMockExecutor};
|
|
@@ -66,6 +66,7 @@ declare function loadEnvConfig(): {
|
|
|
66
66
|
SQLD_REMOTE_DATABASE_URL?: string | undefined;
|
|
67
67
|
SQLD_REMOTE_DATABASE_AUTH_TOKEN?: string | undefined;
|
|
68
68
|
FORCE_CATALOG_CACHE_REVALIDATE?: boolean | undefined;
|
|
69
|
+
CATALOG_MAX_REVISIONS?: number | undefined;
|
|
69
70
|
SCORECARDS_POLLING_INTERVAL_MS?: string | undefined;
|
|
70
71
|
OTEL_TRACES_URL?: string | undefined;
|
|
71
72
|
REQUEST_ID?: string | undefined;
|
|
@@ -146,6 +147,7 @@ export declare const envConfig: {
|
|
|
146
147
|
SQLD_REMOTE_DATABASE_URL?: string | undefined;
|
|
147
148
|
SQLD_REMOTE_DATABASE_AUTH_TOKEN?: string | undefined;
|
|
148
149
|
FORCE_CATALOG_CACHE_REVALIDATE?: boolean | undefined;
|
|
150
|
+
CATALOG_MAX_REVISIONS?: number | undefined;
|
|
149
151
|
SCORECARDS_POLLING_INTERVAL_MS?: string | undefined;
|
|
150
152
|
OTEL_TRACES_URL?: string | undefined;
|
|
151
153
|
REQUEST_ID?: string | undefined;
|
|
@@ -74,6 +74,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
|
|
|
74
74
|
SQLD_REMOTE_DATABASE_AUTH_TOKEN: z.ZodOptional<z.ZodString>;
|
|
75
75
|
} & {
|
|
76
76
|
FORCE_CATALOG_CACHE_REVALIDATE: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
|
|
77
|
+
CATALOG_MAX_REVISIONS: z.ZodOptional<z.ZodNumber>;
|
|
77
78
|
} & {
|
|
78
79
|
SCORECARDS_POLLING_INTERVAL_MS: z.ZodOptional<z.ZodString>;
|
|
79
80
|
} & {
|
|
@@ -147,6 +148,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
|
|
|
147
148
|
SQLD_REMOTE_DATABASE_URL?: string | undefined;
|
|
148
149
|
SQLD_REMOTE_DATABASE_AUTH_TOKEN?: string | undefined;
|
|
149
150
|
FORCE_CATALOG_CACHE_REVALIDATE?: boolean | undefined;
|
|
151
|
+
CATALOG_MAX_REVISIONS?: number | undefined;
|
|
150
152
|
SCORECARDS_POLLING_INTERVAL_MS?: string | undefined;
|
|
151
153
|
OTEL_TRACES_URL?: string | undefined;
|
|
152
154
|
REQUEST_ID?: string | undefined;
|
|
@@ -217,6 +219,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
|
|
|
217
219
|
SQLD_REMOTE_DATABASE_URL?: string | undefined;
|
|
218
220
|
SQLD_REMOTE_DATABASE_AUTH_TOKEN?: string | undefined;
|
|
219
221
|
FORCE_CATALOG_CACHE_REVALIDATE?: "false" | "true" | undefined;
|
|
222
|
+
CATALOG_MAX_REVISIONS?: number | undefined;
|
|
220
223
|
SCORECARDS_POLLING_INTERVAL_MS?: string | undefined;
|
|
221
224
|
OTEL_TRACES_URL?: string | undefined;
|
|
222
225
|
REQUEST_ID?: string | undefined;
|
|
@@ -287,6 +290,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
|
|
|
287
290
|
SQLD_REMOTE_DATABASE_URL?: string | undefined;
|
|
288
291
|
SQLD_REMOTE_DATABASE_AUTH_TOKEN?: string | undefined;
|
|
289
292
|
FORCE_CATALOG_CACHE_REVALIDATE?: boolean | undefined;
|
|
293
|
+
CATALOG_MAX_REVISIONS?: number | undefined;
|
|
290
294
|
SCORECARDS_POLLING_INTERVAL_MS?: string | undefined;
|
|
291
295
|
OTEL_TRACES_URL?: string | undefined;
|
|
292
296
|
REQUEST_ID?: string | undefined;
|
|
@@ -357,6 +361,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
|
|
|
357
361
|
SQLD_REMOTE_DATABASE_URL?: string | undefined;
|
|
358
362
|
SQLD_REMOTE_DATABASE_AUTH_TOKEN?: string | undefined;
|
|
359
363
|
FORCE_CATALOG_CACHE_REVALIDATE?: "false" | "true" | undefined;
|
|
364
|
+
CATALOG_MAX_REVISIONS?: number | undefined;
|
|
360
365
|
SCORECARDS_POLLING_INTERVAL_MS?: string | undefined;
|
|
361
366
|
OTEL_TRACES_URL?: string | undefined;
|
|
362
367
|
REQUEST_ID?: string | undefined;
|
|
@@ -4,9 +4,12 @@ import { z } from 'zod';
|
|
|
4
4
|
*/
|
|
5
5
|
export declare const catalogSchema: z.ZodObject<{
|
|
6
6
|
FORCE_CATALOG_CACHE_REVALIDATE: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
|
|
7
|
+
CATALOG_MAX_REVISIONS: z.ZodOptional<z.ZodNumber>;
|
|
7
8
|
}, "strip", z.ZodTypeAny, {
|
|
8
9
|
FORCE_CATALOG_CACHE_REVALIDATE?: boolean | undefined;
|
|
10
|
+
CATALOG_MAX_REVISIONS?: number | undefined;
|
|
9
11
|
}, {
|
|
10
12
|
FORCE_CATALOG_CACHE_REVALIDATE?: "false" | "true" | undefined;
|
|
13
|
+
CATALOG_MAX_REVISIONS?: number | undefined;
|
|
11
14
|
}>;
|
|
12
15
|
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z as o}from"zod";import{envBooleanStringOptional as t}from"../../utils/envs/env-boolean-string.js";const
|
|
1
|
+
import{z as o}from"zod";import{envBooleanStringOptional as t}from"../../utils/envs/env-boolean-string.js";const r=o.object({FORCE_CATALOG_CACHE_REVALIDATE:t(),CATALOG_MAX_REVISIONS:o.coerce.number().int().min(0).optional()});export{r as catalogSchema};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DatabaseConnection, PublishRowsResult } from './types';
|
|
2
|
+
export type PublishDiffTableResult = PublishRowsResult & {
|
|
3
|
+
deleted: number;
|
|
4
|
+
};
|
|
5
|
+
export type PublishDiffResult = {
|
|
6
|
+
entities: PublishDiffTableResult;
|
|
7
|
+
relations: PublishDiffTableResult;
|
|
8
|
+
attributes: PublishRowsResult;
|
|
9
|
+
orphanAttributesDeleted: number;
|
|
10
|
+
};
|
|
11
|
+
export declare class CatalogEntitiesDiffPublisher {
|
|
12
|
+
#private;
|
|
13
|
+
static publishDiff({ runId, localConnection, remoteConnection, scopeFiles, chunkSize }: {
|
|
14
|
+
runId: string;
|
|
15
|
+
localConnection: DatabaseConnection;
|
|
16
|
+
remoteConnection: DatabaseConnection;
|
|
17
|
+
/**
|
|
18
|
+
* Source files to converge (changed + removed for delta publishes, the
|
|
19
|
+
* full local file list for full publishes). Files outside this scope are
|
|
20
|
+
* never read or touched on the remote — this preserves the old sweep's
|
|
21
|
+
* "never delete data for files absent from the local snapshot" guarantee.
|
|
22
|
+
*/
|
|
23
|
+
scopeFiles: string[];
|
|
24
|
+
chunkSize: number;
|
|
25
|
+
}): Promise<PublishDiffResult>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=catalog-entities-diff-publisher.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import{and as K,eq as L,inArray as w,isNotNull as U,sql as f}from"drizzle-orm";import{logger as H}from"../../../tools/notifiers/logger.js";import{sha1 as M}from"../../../utils/crypto/sha1.js";import{entitiesAttributesTable as S}from"../../../providers/database/databases/sqlite-db/schemas/entities-attributes-table.js";import{entitiesRelationsTable as u}from"../../../providers/database/databases/sqlite-db/schemas/entities-relations-table.js";import{entitiesTable as g}from"../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{BATCH_TRANSIENT_RETRY as k,withTransientErrorRetry as A}from"../../../providers/database/transient-sqld-error.js";import{ATTRIBUTES_UPSERT_SET as $,DELETE_IDS_CHUNK_SIZE as b,ENTITIES_UPSERT_SET as F,MAX_BATCH_PAYLOAD_BYTES as _,MAX_LOGGED_DB_ERROR_LENGTH as v,MIN_RETRYABLE_BATCH_SIZE as J,RELATIONS_UPSERT_SET as V,SCOPE_FILES_BATCH_SIZE as W,SYNC_SCAN_PAGE_SIZE as m}from"./consts.js";class o{static async publishDiff({runId:t,localConnection:s,remoteConnection:i,scopeFiles:n,chunkSize:a}){await A(()=>i.client.sync(),k);const c={entities:{upserted:0,skipped:0,deleted:0},relations:{upserted:0,skipped:0,deleted:0},attributes:{upserted:0,skipped:0},orphanAttributesDeleted:0};for(const r of o.#i(n,W)){const l=await o.#n({runId:t,localConnection:s,remoteConnection:i,filesBatch:r,chunkSize:a});c.entities.upserted+=l.upserted,c.entities.skipped+=l.skipped,c.entities.deleted+=l.deleted;const e=await o.#c({runId:t,localConnection:s,remoteConnection:i,filesBatch:r,chunkSize:a});c.relations.upserted+=e.upserted,c.relations.skipped+=e.skipped,c.relations.deleted+=e.deleted;const d=await o.#a({localConnection:s,remoteConnection:i,filesBatch:r,chunkSize:a});c.attributes.upserted+=d.upserted,c.attributes.skipped+=d.skipped}return c.orphanAttributesDeleted=await o.#o(i),c}static async#n({runId:t,localConnection:s,remoteConnection:i,filesBatch:n,chunkSize:a}){const c=e=>JSON.stringify([e.key,e.source,e.revision,e.version]),r=e=>o.#s([e.organizationId,e.projectId,e.type,e.title,e.summary,e.tags,e.metadata,e.git,e.contact,e.links,e.sourceFile,e.fileHash,e.hash,o.#e(e.isCurrent),o.#e(e.isDefaultVersion),o.#e(e.isDeleted),e.scorecardsStatus]),l=K(L(g.source,"file"),w(g.sourceFile,n));return o.#t({chunkSize:a,selectRemotePage:(e,d)=>i.client.client.select().from(g).where(l).orderBy(f`rowid`).limit(d).offset(e).all(),selectLocalPage:(e,d)=>s.client.client.select().from(g).where(l).orderBy(f`rowid`).limit(d).offset(e).all(),logicalKey:c,digest:r,decorateRow:e=>({...e,lastSeenRunId:t}),runUpsert:async e=>{await i.client.client.insert(g).values(e).onConflictDoUpdate({target:[g.key,g.source,g.revision,g.version],set:F}).run()},runDeleteByIds:e=>i.client.client.delete(g).where(w(g.id,e)),describeSkippedRow:e=>`entity key=${String(e.key)}`})}static async#c({runId:t,localConnection:s,remoteConnection:i,filesBatch:n,chunkSize:a}){const c=e=>JSON.stringify([e.sourceKey,e.targetKey,e.sourceVersion,e.targetVersion,e.sourceRevision,e.targetRevision,e.sourceToTargetRelation]),r=e=>o.#s([e.organizationId,e.projectId,e.targetToSourceRelation,e.sourceFile,e.fileHash,o.#e(e.isDeleted)]),l=K(U(u.sourceFile),w(u.sourceFile,n));return o.#t({chunkSize:a,selectRemotePage:(e,d)=>i.client.client.select().from(u).where(l).orderBy(f`rowid`).limit(d).offset(e).all(),selectLocalPage:(e,d)=>s.client.client.select().from(u).where(l).orderBy(f`rowid`).limit(d).offset(e).all(),logicalKey:c,digest:r,decorateRow:e=>({...e,lastSeenRunId:t}),runUpsert:async e=>{await i.client.client.insert(u).values(e).onConflictDoUpdate({target:[u.sourceKey,u.targetKey,u.sourceVersion,u.targetVersion,u.sourceRevision,u.targetRevision,u.sourceToTargetRelation],set:V}).run()},runDeleteByIds:e=>i.client.client.delete(u).where(w(u.id,e)),describeSkippedRow:e=>{const d=e;return`relation sourceKey=${String(d.sourceKey)} targetKey=${String(d.targetKey)}`}})}static async#a({localConnection:t,remoteConnection:s,filesBatch:i,chunkSize:n}){const a=()=>f`EXISTS (
|
|
2
|
+
SELECT 1
|
|
3
|
+
FROM entities e
|
|
4
|
+
WHERE e.key = ${S.entityKey}
|
|
5
|
+
AND e.source = 'file'
|
|
6
|
+
AND e.source_file IN (${f.join(i.map(r=>f`${r}`),f`, `)})
|
|
7
|
+
)`,c=await o.#t({chunkSize:n,selectRemotePage:(r,l)=>s.client.client.select().from(S).where(a()).orderBy(f`rowid`).limit(l).offset(r).all(),selectLocalPage:(r,l)=>t.client.client.select().from(S).where(a()).orderBy(f`rowid`).limit(l).offset(r).all(),logicalKey:r=>r.entityKey,digest:r=>o.#s([r.organizationId,r.projectId,r.rbacTeams]),runUpsert:async r=>{await s.client.client.insert(S).values(r).onConflictDoUpdate({target:[S.entityKey],set:$}).run()},runDeleteByIds:null});return{upserted:c.upserted,skipped:c.skipped}}static async#t({chunkSize:t,selectRemotePage:s,selectLocalPage:i,logicalKey:n,digest:a,decorateRow:c,runUpsert:r,runDeleteByIds:l,describeSkippedRow:e}){const d=new Map;for(let R=0;;R+=m){const p=await A(()=>s(R,m),k);for(const y of p)d.set(n(y),{id:y.id,digest:a(y),seen:!1});if(p.length<m)break}let I=0,E=0,T=[];const B=async()=>{if(!T.length)return;const R=T;T=[];for(const p of o.#l(R,t,_)){const y=await o.#r({rows:p,runOperation:r,shouldSkipIsolatedRow:e?(h,O)=>o.#u(O)?(H.warn(`Skipping ${e(h)} due to remote insert error: ${o.#p(O)}`),!0):!1:void 0});I+=y.upserted,E+=y.skipped}};for(let R=0;;R+=m){const p=await i(R,m);for(const y of p){const h=d.get(n(y));if(h&&(h.seen=!0,h.digest===a(y))){E+=1;continue}T.push(c?c(y):y),T.length>=t&&await B()}if(p.length<m)break}await B();let N=0;if(l){const R=[];for(const p of d.values())p.seen||R.push(p.id);for(const p of o.#i(R,b))await A(()=>l(p).run(),k),N+=p.length}return{upserted:I,skipped:E,deleted:N}}static async#o(t){const s=await A(()=>t.client.client.delete(S).where(f`NOT EXISTS (
|
|
8
|
+
SELECT 1
|
|
9
|
+
FROM entities e
|
|
10
|
+
WHERE e.key = ${S.entityKey}
|
|
11
|
+
AND COALESCE(e.is_deleted, 0) = 0
|
|
12
|
+
)`).run(),k);return Number(s.rowsAffected??0)}static#e(t){return t==null?null:!!t}static#s(t){return M(JSON.stringify(t))}static#i(t,s){const i=[];for(let n=0;n<t.length;n+=s)i.push(t.slice(n,n+s));return i}static#l(t,s,i){const n=[];let a=[],c=0;for(const r of t){const l=o.#d(r);a.length>0&&(a.length>=s||c+l>i)&&(n.push(a),a=[],c=0),a.push(r),c+=l}return a.length>0&&n.push(a),n}static#d(t){try{return JSON.stringify(t).length}catch{return 0}}static async#r({rows:t,runOperation:s,shouldSkipIsolatedRow:i}){try{return await A(()=>s(t),k),{upserted:t.length,skipped:0}}catch(n){if(t.length<J){if(!i)throw n;if(await i(t[0],n))return{upserted:0,skipped:1};throw n}const a=Math.floor(t.length/2),c=await o.#r({rows:t.slice(0,a),runOperation:s,shouldSkipIsolatedRow:i}),r=await o.#r({rows:t.slice(a),runOperation:s,shouldSkipIsolatedRow:i});return{upserted:c.upserted+r.upserted,skipped:c.skipped+r.skipped}}}static#u(t){if(!(t instanceof Error))return!1;const s=t.message.toLowerCase();return s.includes("code: 11")||s.includes("too many sql variables")||s.includes("string or blob too big")||s.includes("statement too long")||s.includes("request body too large")||s.includes("payload too large")||s.includes("body too large")||s.includes("resource exhausted")}static#p(t){const n=(t instanceof Error?t.message:typeof t=="string"?t:JSON.stringify(t)).split(" params:")[0].replace(/\s+/g," ").trim();return n.length>v?`${n.slice(0,v)}...`:n}}export{o as CatalogEntitiesDiffPublisher};
|
|
@@ -1,12 +1 @@
|
|
|
1
|
-
import{and as
|
|
2
|
-
SELECT 1
|
|
3
|
-
FROM entities e
|
|
4
|
-
WHERE e.key = ${p.entityKey}
|
|
5
|
-
AND e.source = 'file'
|
|
6
|
-
AND e.source_file IN (${w.join(a.map(u=>w`${u}`),w`, `)})
|
|
7
|
-
)`:void 0).limit(c).offset(i).all(),runUpsert:async i=>{await n.client.client.insert(p).values(i).onConflictDoUpdate({target:[p.entityKey],set:M}).run()}})}static#e(e){return Array.isArray(e)&&e.length>0?e:void 0}static async#t({chunkSize:e,sourceFilesFilter:t,selectRows:n,decorateRow:r,runUpsert:s,describeSkippedRow:a}){if(t==="none")return{upserted:0,skipped:0};let i=0,c=0,u=0;for(;;){const f=await n(i,e);if(!f.length)return{upserted:c,skipped:u};const h=r?f.map(m=>r(m)):f;for(const m of o.#S(h,e,V)){const S=await o.#s({rows:m,runOperation:s,shouldSkipIsolatedRow:a?(R,y)=>o.#T(y)?(A.warn(`Skipping ${a(R)} due to remote insert error: ${o.#o(y)}`),!0):!1:void 0});c+=S.upserted,u+=S.skipped}i+=e}}static async#F({runId:e,remoteConnection:t,sourceFilesFilter:n}){if(n==="none")return{staleEntitiesDeleted:0,staleRelationsDeleted:0,orphanAttributesDeleted:0};const r=t.client.client,s=o.#e(n),a=await o.#r(()=>r.delete(d).where(F(v(d.sourceFile),s?T(d.sourceFile,s):void 0,g(N(d.lastSeenRunId),$(d.lastSeenRunId,e)))).run()),i=await o.#r(()=>r.delete(l).where(F(D(l.source,"file"),s?T(l.sourceFile,s):void 0,g(N(l.lastSeenRunId),$(l.lastSeenRunId,e)))).run()),c=await o.#r(()=>r.delete(p).where(w`NOT EXISTS (
|
|
8
|
-
SELECT 1
|
|
9
|
-
FROM entities e
|
|
10
|
-
WHERE e.key = ${p.entityKey}
|
|
11
|
-
AND COALESCE(e.is_deleted, 0) = 0
|
|
12
|
-
)`).run());return{staleEntitiesDeleted:i,staleRelationsDeleted:a,orphanAttributesDeleted:c}}static async#w(e){const n=(await e.client.client.selectDistinct({sourceFile:l.sourceFile}).from(l).where(F(D(l.source,"file"),v(l.sourceFile))).all()).map(({sourceFile:r})=>r).filter(r=>!!r);return n.length===0?"none":n}static async#r(e){const t=await o.#c(e);return Number(t.rowsAffected??0)}static#S(e,t,n){const r=[];let s=[],a=0;for(const i of e){const c=o.#R(i);s.length>0&&(s.length>=t||a+c>n)&&(r.push(s),s=[],a=0),s.push(i),a+=c}return s.length>0&&r.push(s),r}static#R(e){try{return JSON.stringify(e).length}catch{return 0}}static async#s({rows:e,runOperation:t,shouldSkipIsolatedRow:n}){try{return await o.#c(()=>t(e)),{upserted:e.length,skipped:0}}catch(r){if(e.length<x){if(!n)throw r;if(await n(e[0],r))return{upserted:0,skipped:1};throw r}const s=Math.floor(e.length/2),a=await o.#s({rows:e.slice(0,s),runOperation:t,shouldSkipIsolatedRow:n}),i=await o.#s({rows:e.slice(s),runOperation:t,shouldSkipIsolatedRow:n});return{upserted:a.upserted+i.upserted,skipped:a.skipped+i.skipped}}}static#T(e){if(!(e instanceof Error))return!1;const t=e.message.toLowerCase();return t.includes("code: 11")||t.includes("too many sql variables")||t.includes("string or blob too big")||t.includes("statement too long")||t.includes("request body too large")||t.includes("payload too large")||t.includes("body too large")||t.includes("resource exhausted")}static#o(e){const n=(e instanceof Error?e.message:typeof e=="string"?e:JSON.stringify(e)).split(" params:")[0],s=o.#A(n).replace(/\s+/g," ").trim();return s.length>O?`${s.slice(0,O)}...`:s}static#A(e){const t="Failed query:",n=e.indexOf(t);if(n===-1)return e;const r=e.slice(0,n).trim();return r?`${r}. ${t} <omitted for brevity>`:`${t} <omitted for brevity>`}static#c(e){return K(e,H)}}export{o as CatalogEntitiesPublisher};
|
|
1
|
+
import{and as T,eq as k,isNotNull as D}from"drizzle-orm";import{ulid as v}from"ulid";import{logger as c}from"../../../tools/notifiers/logger.js";import{DatabaseConnectionFactory as R}from"../../../providers/database/database-connection-factory.js";import{entitiesTable as u}from"../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{BATCH_TRANSIENT_RETRY as E,withTransientErrorRetry as g}from"../../../providers/database/transient-sqld-error.js";import{envConfig as l}from"../../../config/env-config.js";import{DEFAULT_CHUNK_SIZE as A,MAX_LOGGED_DB_ERROR_LENGTH as $}from"./consts.js";import{CatalogEntitiesDiffPublisher as L}from"./catalog-entities-diff-publisher.js";import{INTERNAL_CATALOG_PUBLISH_KEY_PREFIX as P,RemotePublishLockService as f}from"./remote-publish-lock-service.js";const w=`${P}::last-successful-publish`;class i{static async publishFromLocalToRemote(t){const e=v();if(l.isDevelopMode||!l.isProductionEnv)return{runId:e,published:!1,shouldFinalizeFileHashes:!0};const{baseDbDir:s,chunkSize:r=A,changedSourceFiles:n=[],removedSourceFiles:h=[]}=t,[d,a]=await Promise.all([R.create({baseDbDir:s,databaseType:"local"}),R.create({baseDbDir:s,databaseType:"remote"})]);if(!d||!a)return c.warn("Skipping entities remote sync: local or remote connection is unavailable"),{runId:e,published:!1,shouldFinalizeFileHashes:!1};const p="changedSourceFiles"in t||"removedSourceFiles"in t,m=Array.from(new Set([...n,...h]));if(p&&m.length===0)return{runId:e,published:!0,shouldFinalizeFileHashes:!0};const F=p?m:await i.#c(d),I=await f.runUnderRemotePublishLock({runId:e,remoteConnection:a,run:async()=>{await g(()=>a.client.sync(),E);const b=i.#o(),y=await i.#t({remoteConnection:a,token:b});if(!y.shouldPublish)return c.info(`Publishing entities to the database skipped: ${y.reason}`),!1;try{if(F!=="none"){const o=await L.publishDiff({runId:e,localConnection:d,remoteConnection:a,scopeFiles:F,chunkSize:r});c.info(`Entities publish diff: entities upserted=${o.entities.upserted} unchanged=${o.entities.skipped} deleted=${o.entities.deleted}, relations upserted=${o.relations.upserted} unchanged=${o.relations.skipped} deleted=${o.relations.deleted}, attributes upserted=${o.attributes.upserted} unchanged=${o.attributes.skipped}, orphan attributes deleted=${o.orphanAttributesDeleted}`)}return await i.#s({runId:e,remoteConnection:a,token:b}),!0}catch(o){const S=i.#u(o);throw c.error(`Failed to publish entities to the database: ${S}`),new Error(`[CatalogEntitiesPublisher.publishFromLocalToRemote] Failed runId=${e}. ${S}`,{cause:o instanceof Error?o:void 0})}}});return{runId:e,published:I,shouldFinalizeFileHashes:!0}}static async#t({remoteConnection:t,token:e}){if(!e)return{shouldPublish:!0};const s=i.#e(),r=await f.getInternalCacheValue({remoteConnection:t,key:s});if(!r)return{shouldPublish:!0};const n=i.#r(r);return n?i.#a(e,n.token)<0?{shouldPublish:!1,reason:`Freshness gate blocked stale publish: current ${e.source}="${e.value}" is older than latest successful ${n.token.source}="${n.token.value}" recordedAt=${n.recordedAt}.`}:{shouldPublish:!0}:{shouldPublish:!0}}static async#s({runId:t,remoteConnection:e,token:s}){if(!s)return;const r=i.#e(),n=JSON.stringify({runId:t,recordedAt:new Date().toISOString(),token:s});await f.upsertInternalCacheValue({remoteConnection:e,key:r,value:n,ttl:null})}static#e(){const t=l.PUBLIC_REDOCLY_BRANCH_NAME?.trim();return t?`${w}::branch::${t}`:`${w}::default`}static#r(t){try{const e=JSON.parse(t);return typeof e.runId!="string"||typeof e.recordedAt!="string"||!i.#n(e.token)?null:{runId:e.runId,recordedAt:e.recordedAt,token:e.token}}catch{return null}}static#n(t){if(!t||typeof t!="object")return!1;const e=t;return typeof e.value=="string"&&e.source==="PROJECT_BUILD_ID"}static#o(){const t=[{source:"PROJECT_BUILD_ID",value:l.PROJECT_BUILD_ID}];for(const e of t){const s=e.value?.trim();if(!s)continue;const r=i.#i(s);if(r)return{source:e.source,value:r}}return null}static#i(t){return t.match(/[0-9A-HJKMNP-TV-Z]{26}/i)?.[0]?.toUpperCase()??null}static#a(t,e){return t.value===e.value?0:t.value>e.value?1:-1}static async#c(t){const s=(await t.client.client.selectDistinct({sourceFile:u.sourceFile}).from(u).where(T(k(u.source,"file"),D(u.sourceFile))).all()).map(({sourceFile:r})=>r).filter(r=>!!r);return s.length===0?"none":s}static#u(t){const s=(t instanceof Error?t.message:typeof t=="string"?t:JSON.stringify(t)).split(" params:")[0],n=i.#l(s).replace(/\s+/g," ").trim();return n.length>$?`${n.slice(0,$)}...`:n}static#l(t){const e="Failed query:",s=t.indexOf(e);if(s===-1)return t;const r=t.slice(0,s).trim();return r?`${r}. ${e} <omitted for brevity>`:`${e} <omitted for brevity>`}}export{i as CatalogEntitiesPublisher};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DatabaseConnection } from './types';
|
|
2
|
+
export type PruneLocalRevisionsResult = {
|
|
3
|
+
prunedEntities: number;
|
|
4
|
+
prunedRelations: number;
|
|
5
|
+
affectedSourceFiles: string[];
|
|
6
|
+
vacuumed: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare class CatalogEntitiesRevisionsPruner {
|
|
9
|
+
#private;
|
|
10
|
+
static pruneLocalRevisions({ localConnection, maxRevisions, vacuumThreshold }: {
|
|
11
|
+
localConnection: DatabaseConnection;
|
|
12
|
+
maxRevisions: number;
|
|
13
|
+
vacuumThreshold?: number;
|
|
14
|
+
}): Promise<PruneLocalRevisionsResult>;
|
|
15
|
+
static confirmPrunedFiles({ localConnection, sourceFiles }: {
|
|
16
|
+
localConnection: DatabaseConnection;
|
|
17
|
+
sourceFiles: string[];
|
|
18
|
+
}): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=catalog-entities-revisions-pruner.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import{and as h,eq as m,inArray as l,sql as S}from"drizzle-orm";import{entitiesRelationsTable as O}from"../../../providers/database/databases/sqlite-db/schemas/entities-relations-table.js";import{entitiesTable as R}from"../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{fileHashesTable as a}from"../../../providers/database/databases/sqlite-db/schemas/file-hashes-table.js";import{FileHashStatus as T}from"../../../persistence/file-hashes/types.js";import{DELETE_IDS_CHUNK_SIZE as d,VACUUM_AFTER_PRUNED_ROWS as w}from"./consts.js";const p={prunedEntities:0,prunedRelations:0,affectedSourceFiles:[],vacuumed:!1};class u{static async pruneLocalRevisions({localConnection:i,maxRevisions:r,vacuumThreshold:n=w}){if(!Number.isFinite(r)||r<=0)return p;const t=i.client.client,o=await t.transaction(async s=>{const c=await s.all(S`
|
|
2
|
+
SELECT id, source_file
|
|
3
|
+
FROM (
|
|
4
|
+
SELECT id,
|
|
5
|
+
source_file,
|
|
6
|
+
is_current,
|
|
7
|
+
is_default_version,
|
|
8
|
+
ROW_NUMBER() OVER (
|
|
9
|
+
PARTITION BY key, version
|
|
10
|
+
ORDER BY revision DESC, id DESC
|
|
11
|
+
) AS revision_rank
|
|
12
|
+
FROM entities
|
|
13
|
+
WHERE source = 'file' AND source_file IS NOT NULL
|
|
14
|
+
)
|
|
15
|
+
WHERE revision_rank > ${r}
|
|
16
|
+
AND COALESCE(is_current, 1) = 0
|
|
17
|
+
AND COALESCE(is_default_version, 0) = 0
|
|
18
|
+
`);if(c.length===0)return null;const f=new Set;for(const e of c)e.source_file&&f.add(e.source_file);for(const e of u.#e(c.map(_=>_.id),d))await s.delete(R).where(l(R.id,e)).run();const E=await s.all(S`
|
|
19
|
+
SELECT id, source_file
|
|
20
|
+
FROM entities_relations r
|
|
21
|
+
WHERE r.source_file IS NOT NULL
|
|
22
|
+
AND (
|
|
23
|
+
(
|
|
24
|
+
r.source_revision != ''
|
|
25
|
+
AND NOT EXISTS (
|
|
26
|
+
SELECT 1 FROM entities e
|
|
27
|
+
WHERE e.key = r.source_key
|
|
28
|
+
AND e.revision = r.source_revision
|
|
29
|
+
AND (r.source_version = '' OR e.version = r.source_version)
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
OR (
|
|
33
|
+
r.target_revision != ''
|
|
34
|
+
AND NOT EXISTS (
|
|
35
|
+
SELECT 1 FROM entities e
|
|
36
|
+
WHERE e.key = r.target_key
|
|
37
|
+
AND e.revision = r.target_revision
|
|
38
|
+
AND (r.target_version = '' OR e.version = r.target_version)
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
`);for(const e of E)e.source_file&&f.add(e.source_file);for(const e of u.#e(E.map(_=>_.id),d))await s.delete(O).where(l(O.id,e)).run();const N=new Date().toISOString();for(const e of u.#e(Array.from(f),d))await s.update(a).set({status:T.OUTDATED,updatedAt:N}).where(l(a.filePath,e)).run();return{prunedEntities:c.length,prunedRelations:E.length,affectedSourceFiles:Array.from(f)}});if(!o)return p;const D=o.prunedEntities+o.prunedRelations;let A=!1;return D>=n&&(await t.run(S`VACUUM`),A=!0),{...o,vacuumed:A}}static async confirmPrunedFiles({localConnection:i,sourceFiles:r}){if(r.length===0)return;const n=i.client.client,t=new Date().toISOString();for(const o of u.#e(r,d))await n.update(a).set({status:T.UP_TO_DATE,updatedAt:t}).where(h(l(a.filePath,o),m(a.status,T.OUTDATED))).run()}static#e(i,r){const n=[];for(let t=0;t<i.length;t+=r)n.push(i.slice(t,t+r));return n}}export{u as CatalogEntitiesRevisionsPruner};
|
|
@@ -2,6 +2,11 @@ export declare const DEFAULT_CHUNK_SIZE = 20;
|
|
|
2
2
|
export declare const MAX_LOGGED_DB_ERROR_LENGTH = 1000;
|
|
3
3
|
export declare const MIN_RETRYABLE_BATCH_SIZE = 2;
|
|
4
4
|
export declare const MAX_BATCH_PAYLOAD_BYTES = 500000;
|
|
5
|
+
export declare const SCOPE_FILES_BATCH_SIZE = 50;
|
|
6
|
+
export declare const SYNC_SCAN_PAGE_SIZE = 1000;
|
|
7
|
+
export declare const DELETE_IDS_CHUNK_SIZE = 500;
|
|
8
|
+
export declare const DEFAULT_MAX_ENTITY_REVISIONS = 20;
|
|
9
|
+
export declare const VACUUM_AFTER_PRUNED_ROWS = 5000;
|
|
5
10
|
export declare const ENTITIES_UPSERT_SET: {
|
|
6
11
|
organizationId: import("drizzle-orm").SQL<unknown>;
|
|
7
12
|
projectId: import("drizzle-orm").SQL<unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{sql as e}from"drizzle-orm";const t=20,c=1e3,
|
|
1
|
+
import{sql as e}from"drizzle-orm";const t=20,c=1e3,_=2,a=5e5,o=50,l=1e3,u=500,r=20,s=5e3,i={organizationId:e`excluded.organization_id`,projectId:e`excluded.project_id`,type:e`excluded.type`,title:e`excluded.title`,summary:e`excluded.summary`,tags:e`excluded.tags`,metadata:e`excluded.metadata`,git:e`excluded.git`,contact:e`excluded.contact`,links:e`excluded.links`,createdAt:e`excluded.created_at`,updatedAt:e`excluded.updated_at`,sourceFile:e`excluded.source_file`,fileHash:e`excluded.file_hash`,hash:e`excluded.hash`,isCurrent:e`excluded.is_current`,isDefaultVersion:e`excluded.is_default_version`,isDeleted:e`excluded.is_deleted`,scorecardsStatus:e`excluded.scorecards_status`,lastSeenRunId:e`excluded.last_seen_run_id`},x={organizationId:e`excluded.organization_id`,projectId:e`excluded.project_id`,targetToSourceRelation:e`excluded.target_to_source_relation`,sourceFile:e`excluded.source_file`,fileHash:e`excluded.file_hash`,isDeleted:e`excluded.is_deleted`,createdAt:e`excluded.created_at`,updatedAt:e`excluded.updated_at`,lastSeenRunId:e`excluded.last_seen_run_id`},n={organizationId:e`excluded.organization_id`,projectId:e`excluded.project_id`,rbacTeams:e`excluded.rbac_teams`,createdAt:e`excluded.created_at`,updatedAt:e`excluded.updated_at`};export{n as ATTRIBUTES_UPSERT_SET,t as DEFAULT_CHUNK_SIZE,r as DEFAULT_MAX_ENTITY_REVISIONS,u as DELETE_IDS_CHUNK_SIZE,i as ENTITIES_UPSERT_SET,a as MAX_BATCH_PAYLOAD_BYTES,c as MAX_LOGGED_DB_ERROR_LENGTH,_ as MIN_RETRYABLE_BATCH_SIZE,x as RELATIONS_UPSERT_SET,o as SCOPE_FILES_BATCH_SIZE,l as SYNC_SCAN_PAGE_SIZE,s as VACUUM_AFTER_PRUNED_ROWS};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{FileHashStatus as
|
|
1
|
+
import{FileHashStatus as v,FileType as h}from"../../persistence/file-hashes/types.js";import{envConfig as C}from"../../config/env-config.js";import{CATALOG_BASE_SLUG as S}from"../../../constants/catalog-entities.js";import{telemetryTraceStep as k}from"../../../cli/telemetry/helpers/trace-step.js";import{catalogDataCollector as f}from"./utils/catalog-data-collector.js";import{CATALOG_FILTERS_CACHE_NAMESPACE as B,ENTITIES_MAP_GLOBAL_DATA_KEY as $}from"../../constants/plugins/catalog-entities.js";import{CacheService as z}from"../../persistence/cache/services/cache-service.js";import{getTemplatePath as O}from"./utils/get-template-path.js";import{getCompleteCatalogConfig as _}from"./get-complete-catalog-config.js";import{AsyncApiEntitiesExtractor as Y}from"./extensions/extractors/api-description/asyncapi-entities-extractor.js";import{GraphqlEntitiesExtractor as U}from"./extensions/extractors/api-description/graphql-entities-extractor.js";import{FileHashesService as j}from"../../persistence/file-hashes/services/file-hashes-service.js";import{CatalogEntitiesService as q}from"./database/catalog-entities-service.js";import{CatalogEntitiesPublisher as V}from"./database/catalog-entities-publisher.js";import{CatalogEntitiesRevisionsPruner as I}from"./database/catalog-entities-revisions-pruner.js";import{DEFAULT_MAX_ENTITY_REVISIONS as W}from"./database/consts.js";import{DatabaseConnectionFactory as J}from"../../providers/database/database-connection-factory.js";import{ArazzoEntitiesExtractor as X}from"./extensions/extractors/api-description/arazzo-entities-extractor.js";import{FsEntitiesExtractor as Z}from"./extensions/extractors/fs-entities-extractor.js";import{HashManager as K}from"./utils/hash-manager.js";import{RbacConfigHashCache as Q}from"./utils/rbac-config-hash-cache.js";import{OpenApiEntitiesExtractor as ee}from"./extensions/extractors/api-description/openapi-entities-extractor.js";const te="catalog-entity-template",ie="catalog-entity";let w=!0;async function ae(){return{id:"CatalogEntities",requiredEntitlements:["catalog"],async processContent(e,t){const s=await t.getConfig(),r=_(s.entitiesCatalog);if(!r.show)return;const{logger:n}=t,a=e.registerServerPropsGetter(ie,O("../get-server-props.js")),g=e.createTemplate(te,O("../template/index.js"));e.addRoute({duplicateInAllLocales:!0,slug:S,fsPath:"",templateId:g,excludeFromSidebar:!0,hasClientRoutes:!0,serverPropsGetterIds:[a],getNavText:()=>Promise.resolve("Catalog"),getStaticData:async()=>({props:{catalogConfig:r}})});const[l]=Object.entries(r.catalogs??{}).find(([P,m])=>!m?.hide)||[];l&&e.addRedirect(S,{type:302,to:`${S}/${l}`},{trackOriginalSource:!1}),n.info("Catalog Entities plugin finished")},async afterRoutesCreated(e,t){await k("build.plugin.catalog_entities",async s=>{const r=await t.getConfig(),n=_(r.entitiesCatalog);if(s?.setAttribute("config",JSON.stringify(n)),!n.show)return;const{logger:a}=t;f.resetForRun();const g=w&&C.isDevelopMode,l=await z.getInstance({baseDbDir:e.serverOutDir,databaseType:"local"}),P=new Q(l),{rbacConfigChanged:m}=await P.syncAndDetectChange(r.access?.rbac??null),c=g||m,o=await q.getInstance({baseDbDir:e.serverOutDir,removeExisting:g,databaseType:"local",runWithPragmaWalWriteOptimization:!0}),T=await j.getInstance({baseDbDir:e.serverOutDir,databaseType:"local"}),u=new K(T),F=[new Z({fileHashManager:u,context:t,catalogEntitiesService:o,catalogConfig:n,shouldCalculateEntities:c}),new ee({actions:e,context:t,catalogEntitiesService:o,fileHashManager:u,fileType:h.OPENAPI_DESCRIPTION,shouldCalculateEntities:c}),new Y({actions:e,context:t,catalogEntitiesService:o,fileHashManager:u,fileType:h.ASYNCAPI_DESCRIPTION,shouldCalculateEntities:c}),new U({actions:e,context:t,catalogEntitiesService:o,fileHashManager:u,fileType:h.GRAPHQL_DESCRIPTION,shouldCalculateEntities:c}),new X({actions:e,context:t,catalogEntitiesService:o,fileHashManager:u,fileType:h.ARAZZO_DESCRIPTION,shouldCalculateEntities:c})];a.info("Starting entities extractors...");const R=a.startTiming();await o.transaction(async()=>{await Promise.all(F.map(async i=>i.extract()))});const N=o.getEntitySources();e.setGlobalData({[$]:N}),await l.deleteByNamespace(B),a.infoTime(R,"Entities extractors finished");const b=C.CATALOG_MAX_REVISIONS??W,d=await J.create({baseDbDir:e.serverOutDir,databaseType:"local"}),H=!C.isDevelopMode&&C.isProductionEnv;let E=[];if(H&&d&&b>0){const i=await I.pruneLocalRevisions({localConnection:d,maxRevisions:b});if(i.prunedEntities>0||i.prunedRelations>0){a.info(`Pruned entity revision history: entities=${i.prunedEntities} relations=${i.prunedRelations} files=${i.affectedSourceFiles.length} (keeping ${b} revisions per entity)`),E=i.affectedSourceFiles;for(const D of E)f.markSourceFileAsChanged(D)}}const{changedSourceFiles:L,removedSourceFiles:A}=f.getPublishDelta(),x=a.startTiming(),y=await V.publishFromLocalToRemote(m?{baseDbDir:e.serverOutDir}:{baseDbDir:e.serverOutDir,changedSourceFiles:L,removedSourceFiles:A});if(y.published&&a.infoTime(x,"Entities published to the database"),y.shouldFinalizeFileHashes){A.length&&await T.deleteFileHashes({op:"AND",conditions:[{field:"status",operator:"equal",value:v.OUTDATED},{field:"file_path",operator:"in",value:A}]});const i=f.getFileHashConfirmations();for(const{fileType:D,filePath:G,hash:M}of i)await T.upsertFileHash({fileType:D,filePath:G,hash:M,status:v.UP_TO_DATE});y.published&&E.length&&d&&await I.confirmPrunedFiles({localConnection:d,sourceFiles:E})}else a.warn("Skipped file_hashes finalization because remote catalog publish connection is unavailable; next build will retry.");const p=await f.getCatalogEntitiesData(o);s?.setAttribute("totalEntities",p.totalEntitiesCount),s?.setAttribute("entitiesCountByType",JSON.stringify(p.countOfEntitiesByType)),s?.setAttribute("totalFilesSkippedByHash",p.totalFilesSkippedByHash),s?.setAttribute("totalProcessedFiles",p.totalProcessedFiles),s?.setAttribute("extractors",p.extractors),w=!1})}}}var ve=ae;export{ae as catalogEntitiesPlugin,ve as default};
|
|
@@ -2,4 +2,7 @@ export declare const EXECUTE_TOOL_NAME = "execute";
|
|
|
2
2
|
export declare const DESCRIBE_TOOLS_TOOL_NAME = "describe-tools";
|
|
3
3
|
export declare const FETCH_TOOL_NAME = "fetch";
|
|
4
4
|
export declare const CODE_MODE_TOOL_NAMES: ReadonlySet<string>;
|
|
5
|
+
export declare const TOOLS_BRIDGE_GLOBAL_NAME = "tools";
|
|
6
|
+
export declare const FETCH_BRIDGE_GLOBAL_NAME = "__fetchBridge";
|
|
7
|
+
export declare const FETCH_BRIDGE_FUNCTION_NAME = "fetch";
|
|
5
8
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const t="execute",_="describe-tools",E="fetch",e=new Set([t,_]),o="tools",O="__fetchBridge",c="fetch";export{e as CODE_MODE_TOOL_NAMES,_ as DESCRIBE_TOOLS_TOOL_NAME,t as EXECUTE_TOOL_NAME,c as FETCH_BRIDGE_FUNCTION_NAME,O as FETCH_BRIDGE_GLOBAL_NAME,E as FETCH_TOOL_NAME,o as TOOLS_BRIDGE_GLOBAL_NAME};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SetupSandboxContext } from '../../../../sandbox/types.js';
|
|
2
|
+
import type { SandboxFetch, SandboxTool } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds the `setupContext` that shapes the shared sandbox (`~/server/sandbox`) for code
|
|
5
|
+
* mode: the given tools — and, when provided, host fetch — become host-function rails, each
|
|
6
|
+
* capped and bound by the run's budget, and the prelude installs the code-mode guest surface
|
|
7
|
+
* over them.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createSandboxSetup(tools: Record<string, SandboxTool>, hostFetch?: SandboxFetch): SetupSandboxContext;
|
|
10
|
+
//# sourceMappingURL=create-sandbox-setup.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import{setupHostFunctions as a}from"../../../../sandbox/utils/setup-host-functions.js";import{evalGuestCode as p}from"../../../../sandbox/utils/eval-guest-code.js";import{SANDBOX_FETCH_SETUP as i}from"./polyfills.js";import{FETCH_BRIDGE_FUNCTION_NAME as u,FETCH_BRIDGE_GLOBAL_NAME as c,TOOLS_BRIDGE_GLOBAL_NAME as n}from"../constants.js";const m=100,f=50;function T(r,t){return(e,l)=>{const s=[a(e,r,{globalName:n,budget:l,maxCalls:m,messages:{outputTooLarge:o=>`Tool output is too large (${o} bytes); request narrower data instead`,callLimitExceeded:o=>`Exceeded the limit of ${o} tool calls`}})];return t&&s.push(a(e,{[u]:o=>t(o)},{globalName:c,budget:l,maxCalls:f,messages:{outputTooLarge:o=>`Response is too large (${o} bytes); request less data`,callLimitExceeded:o=>`Exceeded the limit of ${o} fetch calls`}})),p(e,E(!!t)),()=>{for(const o of s)o()}}}function E(r){const t=r?i:"",e=`globalThis[${JSON.stringify(n)}]`;return`(() => {${t}
|
|
2
|
+
const tools = ${e};
|
|
3
|
+
${e} = new Proxy(tools, {
|
|
4
|
+
get(target, prop) {
|
|
5
|
+
if (typeof prop !== 'string' || prop in target || prop === 'then' || prop === 'toJSON') {
|
|
6
|
+
return target[prop];
|
|
7
|
+
}
|
|
8
|
+
throw new TypeError('Unknown tool "' + prop + '". Available tools: ' + Object.keys(target).join(', '));
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
})();`}export{T as createSandboxSetup};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const SANDBOX_FETCH_SETUP = "\n const URLSearchParams = class URLSearchParams {\n constructor(init) {\n this._params = [];\n if (typeof init === 'string') {\n const str = init.startsWith('?') ? init.slice(1) : init;\n for (const pair of (str ? str.split('&') : [])) {\n const eq = pair.indexOf('=');\n const k = eq >= 0 ? pair.slice(0, eq) : pair;\n const v = eq >= 0 ? pair.slice(eq + 1) : '';\n if (k) this._params.push([decodeURIComponent(k), decodeURIComponent(v)]);\n }\n } else if (init != null && typeof init === 'object') {\n for (const [k, v] of Object.entries(init)) this._params.push([k, String(v)]);\n }\n }\n append(key, value) { this._params.push([String(key), String(value)]); }\n set(key, value) {\n const k = String(key), v = String(value);\n this._params = this._params.filter(([pk]) => pk !== k);\n this._params.push([k, v]);\n }\n get(key) { return (this._params.find(([k]) => k === String(key)) ?? [undefined, null])[1]; }\n getAll(key) { return this._params.filter(([k]) => k === String(key)).map(([, v]) => v); }\n has(key) { return this._params.some(([k]) => k === String(key)); }\n delete(key) { this._params = this._params.filter(([k]) => k !== String(key)); }\n entries() { return this._params.values(); }\n [Symbol.iterator]() { return this._params.values(); }\n toString() {\n return this._params\n .map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v))\n .join('&');\n }\n };\n globalThis.URLSearchParams = URLSearchParams;\n\n const FormData = class FormData {\n constructor() { this._fields = []; }\n append(key, value) { this._fields.push([String(key), String(value)]); }\n set(key, value) {\n const k = String(key), v = String(value);\n this._fields = this._fields.filter(([fk]) => fk !== k);\n this._fields.push([k, v]);\n }\n get(key) { return (this._fields.find(([k]) => k === String(key)) ?? [undefined, null])[1]; }\n has(key) { return this._fields.some(([k]) => k === String(key)); }\n delete(key) { this._fields = this._fields.filter(([k]) => k !== String(key)); }\n entries() { return this._fields.values(); }\n [Symbol.iterator]() { return this._fields.values(); }\n };\n globalThis.FormData = FormData;\n\n const hostFetch = globalThis.__fetchBridge.fetch;\n delete globalThis.__fetchBridge;\n globalThis.fetch = (url, init = {}) => {\n let body;\n let autoContentType;\n if (init.body instanceof URLSearchParams) {\n body = init.body.toString();\n autoContentType = 'application/x-www-form-urlencoded';\n } else if (init.body instanceof FormData) {\n const boundary = '----FormBoundary' + Math.random().toString(36).slice(2);\n body = init.body._fields\n .map(([k, v]) =>\n '--' + boundary + '\\r\\nContent-Disposition: form-data; name=\"' + k + '\"\\r\\n\\r\\n' + v,\n )\n .join('\\r\\n') + '\\r\\n--' + boundary + '--';\n autoContentType = 'multipart/form-data; boundary=' + boundary;\n } else if (init.body != null && typeof init.body !== 'string') {\n throw new TypeError('fetch body must be a string, URLSearchParams, or FormData');\n } else {\n body = init.body ?? undefined;\n }\n const callerHeaders = init.headers;\n const hasContentType = callerHeaders != null && Object.keys(callerHeaders).some(\n (k) => k.toLowerCase() === 'content-type',\n );\n const headers = autoContentType && !hasContentType\n ? Object.assign({ 'content-type': autoContentType }, callerHeaders)\n : callerHeaders;\n return hostFetch({ url: String(url), method: init.method, headers, body })\n .then((res) => {\n const resHeaders = res.headers || {};\n return {\n status: res.status,\n statusText: res.statusText,\n ok: res.ok,\n headers: {\n get: (name) => {\n const value = resHeaders[String(name).toLowerCase()];\n return value === undefined ? null : value;\n },\n },\n json: () => Promise.resolve(JSON.parse(res.bodyText)),\n text: () => Promise.resolve(res.bodyText),\n };\n });\n };\n";
|
|
2
|
+
//# sourceMappingURL=polyfills.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
import{FETCH_BRIDGE_FUNCTION_NAME as t,FETCH_BRIDGE_GLOBAL_NAME as e}from"../constants.js";const s=`
|
|
2
2
|
const URLSearchParams = class URLSearchParams {
|
|
3
3
|
constructor(init) {
|
|
4
4
|
this._params = [];
|
|
@@ -49,9 +49,9 @@ const e=`
|
|
|
49
49
|
[Symbol.iterator]() { return this._fields.values(); }
|
|
50
50
|
};
|
|
51
51
|
globalThis.FormData = FormData;
|
|
52
|
-
`,
|
|
53
|
-
const hostFetch = globalThis
|
|
54
|
-
delete globalThis
|
|
52
|
+
`,r=`
|
|
53
|
+
const hostFetch = globalThis.${e}.${t};
|
|
54
|
+
delete globalThis.${e};
|
|
55
55
|
globalThis.fetch = (url, init = {}) => {
|
|
56
56
|
let body;
|
|
57
57
|
let autoContentType;
|
|
@@ -78,9 +78,8 @@ const e=`
|
|
|
78
78
|
const headers = autoContentType && !hasContentType
|
|
79
79
|
? Object.assign({ 'content-type': autoContentType }, callerHeaders)
|
|
80
80
|
: callerHeaders;
|
|
81
|
-
return hostFetch(
|
|
82
|
-
.then((
|
|
83
|
-
const res = JSON.parse(json);
|
|
81
|
+
return hostFetch({ url: String(url), method: init.method, headers, body })
|
|
82
|
+
.then((res) => {
|
|
84
83
|
const resHeaders = res.headers || {};
|
|
85
84
|
return {
|
|
86
85
|
status: res.status,
|
|
@@ -97,4 +96,4 @@ const e=`
|
|
|
97
96
|
};
|
|
98
97
|
});
|
|
99
98
|
};
|
|
100
|
-
`;export{
|
|
99
|
+
`,i=`${s}${r}`;export{i as SANDBOX_FETCH_SETUP};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{mcpToolHandlers as
|
|
1
|
+
import{mcpToolHandlers as s}from"../../../../../client/mcp-tool-handlers-entry.js";import{runInSandbox as c}from"../../../../sandbox/sandbox.js";import{isMcpToolAvailable as l}from"../../workers/execute-mcp-tool.js";import{buildExecuteHostFetch as m,isFetchCapable as f}from"../../gateway-mcp/fetch-capability.js";import{CODE_MODE_TOOL_NAMES as u,EXECUTE_TOOL_NAME as p}from"../constants.js";import{sanitizeToolName as d}from"../build-tool-type-declarations.js";import{invokeTool as b}from"../invoke-tool.js";import{createSandboxSetup as T}from"../sandbox/create-sandbox-setup.js";const E=async(e,t,i)=>{const o=await O(t,i),n=f(t)?m(t):void 0,{result:r,error:a}=await c(e.code,{setupContext:T(o,n)});return{content:[{type:"text",text:JSON.stringify({result:r,error:a})}],isError:a!==void 0}};async function O(e,t){const i={};for(const o of Object.keys(s))u.has(o)||!await l({toolName:o,context:e}).catch(()=>!1)||(i[d(o)]=r=>b(o,r,e,t));return i}var C={[p]:E};export{C as default};
|
|
@@ -13,12 +13,4 @@ export type SandboxFetchResponse = {
|
|
|
13
13
|
bodyText: string;
|
|
14
14
|
};
|
|
15
15
|
export type SandboxFetch = (request: SandboxFetchRequest) => Promise<SandboxFetchResponse>;
|
|
16
|
-
export type SandboxResult = {
|
|
17
|
-
result?: unknown;
|
|
18
|
-
error?: string;
|
|
19
|
-
};
|
|
20
|
-
export type RunInSandboxOptions = {
|
|
21
|
-
timeoutMs?: number;
|
|
22
|
-
hostFetch?: SandboxFetch;
|
|
23
|
-
};
|
|
24
16
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const o=1048576,t="hostFunctions";export{t as DEFAULT_GLOBAL_NAME,o as MAX_HOST_OUTPUT_BYTES};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const DEFAULT_TIMEOUT_MS = 10000;
|
|
2
|
+
export declare const MEMORY_LIMIT_BYTES: number;
|
|
3
|
+
export declare const DEFAULT_MAX_LIFETIME_MS: number;
|
|
4
|
+
export declare const MAX_STACK_SIZE_BYTES: number;
|
|
5
|
+
export declare const MAX_RESULT_BYTES: number;
|
|
6
|
+
//# sourceMappingURL=sandbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const _=1e4,E=67108864,T=6e5,o=262144,t=1048576;export{T as DEFAULT_MAX_LIFETIME_MS,_ as DEFAULT_TIMEOUT_MS,t as MAX_RESULT_BYTES,o as MAX_STACK_SIZE_BYTES,E as MEMORY_LIMIT_BYTES};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RunInSandboxOptions, SandboxResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Evaluates `code` in a fresh, isolated QuickJS VM bounded by memory, stack, and time. The VM
|
|
4
|
+
* starts pristine — no host access of any kind. Consumers shape the guest realm through
|
|
5
|
+
* `setupContext` (see e.g. `setupHostFunctions`); the time bounds live in `createExecutionBudget`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runInSandbox(code: string, options?: RunInSandboxOptions): Promise<SandboxResult>;
|
|
8
|
+
//# sourceMappingURL=sandbox.d.ts.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import f from"@jitl/quickjs-singlefile-mjs-release-sync";import{newQuickJSWASMModuleFromVariant as p}from"quickjs-emscripten-core";import{createExecutionBudget as E}from"./utils/execution-budget.js";import{DEFAULT_MAX_LIFETIME_MS as _,DEFAULT_TIMEOUT_MS as T,MAX_RESULT_BYTES as g,MAX_STACK_SIZE_BYTES as x,MEMORY_LIMIT_BYTES as y}from"./constants/sandbox.js";let c;function A(){return c??=p(f)}async function v(t,l={}){const{timeoutMs:m=T,maxLifetimeMs:d=_,setupContext:M}=l,e=(await A()).newContext();let n,s,r;try{e.runtime.setMemoryLimit(y),e.runtime.setMaxStackSize(x),n=E(e,m,d),s=M?.(e,n)??void 0,r=e.unwrapResult(e.evalCode(`(async () => {
|
|
2
|
+
${t}
|
|
3
|
+
})()`));const o=e.resolvePromise(r);e.runtime.executePendingJobs();const S=await Promise.race([o,n.aborted]),i=e.unwrapResult(S),u=e.dump(i);i.dispose();const a=JSON.stringify(u??null).length;return a>g?{error:`Result is too large (${a} bytes); return less data`}:{result:u}}catch(o){return{error:I(o)}}finally{n?.dispose(),r?.dispose();try{s?.()}catch{}try{e.dispose()}catch{c=void 0}}}function I(t){return t instanceof Error?t.message:String(t)}export{v as runInSandbox};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { QuickJSContext } from 'quickjs-emscripten-core';
|
|
2
|
+
export type SandboxResult = {
|
|
3
|
+
result?: unknown;
|
|
4
|
+
error?: string;
|
|
5
|
+
};
|
|
6
|
+
export type ExecutionBudget = {
|
|
7
|
+
/**
|
|
8
|
+
* Run `work()` as a host call: its latency is exempt from the compute budget (`timeoutMs`).
|
|
9
|
+
* While the call is in flight the idle timer is paused, and the guest gets a fresh CPU
|
|
10
|
+
* deadline when it settles — so time spent off in the host isn't charged as guest compute.
|
|
11
|
+
*/
|
|
12
|
+
hostCall<T>(work: () => Promise<T>): Promise<T>;
|
|
13
|
+
/** Rejects when a wall-clock deadline fires */
|
|
14
|
+
aborted: Promise<never>;
|
|
15
|
+
dispose(): void;
|
|
16
|
+
};
|
|
17
|
+
export type SetupSandboxContext = (context: QuickJSContext, budget: ExecutionBudget) => void | (() => void);
|
|
18
|
+
export type RunInSandboxOptions = {
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
maxLifetimeMs?: number;
|
|
21
|
+
setupContext?: SetupSandboxContext;
|
|
22
|
+
};
|
|
23
|
+
export type SandboxHostFunction = (input: unknown) => Promise<unknown> | unknown;
|
|
24
|
+
//# sourceMappingURL=types.d.ts.map
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { QuickJSContext } from 'quickjs-emscripten-core';
|
|
2
|
+
/**
|
|
3
|
+
* Evaluates guest source inside a QuickJS context for its side effects
|
|
4
|
+
*
|
|
5
|
+
* @param context - The QuickJS context to evaluate in
|
|
6
|
+
* @param source - Guest JavaScript source evaluated for side effects
|
|
7
|
+
* @throws When the source throws or fails to parse (unwrapped from the VM)
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* evalGuestCode(context, 'globalThis.ctx = Object.freeze({ n: 41 });');
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare function evalGuestCode(context: QuickJSContext, source: string): void;
|
|
15
|
+
//# sourceMappingURL=eval-guest-code.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function s(e,o){e.unwrapResult(e.evalCode(o)).dispose()}export{s as evalGuestCode};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ExecutionBudget } from '../types.js';
|
|
2
|
+
import type { QuickJSContext } from 'quickjs-emscripten-core';
|
|
3
|
+
/**
|
|
4
|
+
* Time-bounds a single sandbox run. Two deadlines — a refreshable per-stretch compute budget
|
|
5
|
+
* (`timeoutMs`) and a fixed whole-run ceiling (`maxLifetimeMs`) — are each enforced two ways,
|
|
6
|
+
* because the guest is only ever in one of two states and each enforcer reaches only one:
|
|
7
|
+
* - while the guest RUNS bytecode → the interrupt handler
|
|
8
|
+
* - while the guest is PARKED on an `await` → a wall-clock `setTimeout`
|
|
9
|
+
*/
|
|
10
|
+
export declare function createExecutionBudget(context: QuickJSContext, timeoutMs: number, maxLifetimeMs: number): ExecutionBudget;
|
|
11
|
+
//# sourceMappingURL=execution-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function f(a,t,i){const s=Date.now()+i;let c=Date.now()+t,l=()=>{};const d=new Promise((e,o)=>{l=r=>o(new Error(r))});let n;const u=()=>{clearTimeout(n),n=setTimeout(()=>l(`Execution timed out after ${t}ms`),t)};a.runtime.setInterruptHandler(()=>{const e=Date.now();return e>c||e>s}),u();const T=setTimeout(()=>l(`Exceeded the maximum lifetime of ${i}ms`),i);let m=0;return{hostCall:e=>{m++,clearTimeout(n);const o=()=>{a.alive&&(c=Date.now()+t,--m===0&&u())},r=e();return r.then(o,o),r},aborted:d,dispose(){clearTimeout(n),clearTimeout(T)}}}export{f as createExecutionBudget};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ExecutionBudget, SandboxHostFunction } from '../types.js';
|
|
2
|
+
import type { QuickJSContext } from 'quickjs-emscripten-core';
|
|
3
|
+
/**
|
|
4
|
+
* Optional adapter for `runInSandbox`'s `setupContext`: exposes named host functions to the
|
|
5
|
+
* guest as one frozen global — `hostFunctions` by default, or the `globalName` the consumer
|
|
6
|
+
* chooses. Values cross the boundary as JSON strings — the guest only ever receives copies.
|
|
7
|
+
* Returns the cleanup callback.
|
|
8
|
+
*/
|
|
9
|
+
export declare function setupHostFunctions(context: QuickJSContext, hostFunctions: Record<string, SandboxHostFunction>, options?: {
|
|
10
|
+
globalName?: string;
|
|
11
|
+
budget?: ExecutionBudget;
|
|
12
|
+
maxCalls?: number;
|
|
13
|
+
messages?: {
|
|
14
|
+
outputTooLarge?: (bytes: number) => string;
|
|
15
|
+
callLimitExceeded?: (max: number) => string;
|
|
16
|
+
};
|
|
17
|
+
}): () => void;
|
|
18
|
+
//# sourceMappingURL=setup-host-functions.d.ts.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{DEFAULT_GLOBAL_NAME as v,MAX_HOST_OUTPUT_BYTES as _}from"../constants/host-functions.js";import{evalGuestCode as E}from"./eval-guest-code.js";function w(e,o,s){const l=s?.globalName??v,u=s?.maxCalls,f=s?.messages?.outputTooLarge??(n=>`Host function output is too large (${n} bytes); request narrower data instead`),m=s?.messages?.callLimitExceeded??(n=>`Exceeded the limit of ${n} host calls`),d=s?.budget?.hostCall??(n=>n()),c=new Set;let S=0;return e.newFunction("__invoke",(n,a)=>{const h=e.getString(n),p=JSON.parse(e.getString(a)),r=e.newPromise();return c.add(r),u!==void 0&&++S>u?e.newError(m(u)).consume(r.reject):d(()=>Promise.resolve().then(()=>o[h](p))).then(t=>{if(!e.alive)return;let i;try{i=JSON.stringify(t??null)}catch(g){const b=g instanceof Error?g.message:String(g);e.newError(`Host function output is not serializable: ${b}`).consume(r.reject);return}i.length>_?e.newError(f(i.length)).consume(r.reject):e.newString(i).consume(r.resolve)},t=>{if(!e.alive)return;const i=t instanceof Error?t.message:String(t);e.newError(i).consume(r.reject)}),r.settled.then(()=>{e.alive&&e.runtime.executePendingJobs()}),r.handle}).consume(n=>e.setProp(e.global,"__invoke",n)),E(e,O(Object.keys(o),l)),function(){for(const a of c)a.alive&&a.dispose()}}function O(e,o){const s=e.map(l=>`${JSON.stringify(l)}: (input) => invoke(${JSON.stringify(l)}, JSON.stringify(input ?? {})).then(JSON.parse)`).join(", ");return`(() => {
|
|
2
|
+
const invoke = globalThis.__invoke;
|
|
3
|
+
delete globalThis.__invoke;
|
|
4
|
+
globalThis[${JSON.stringify(o)}] = Object.freeze({ ${s} });
|
|
5
|
+
})();`}export{w as setupHostFunctions};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fetch` for requests to Redocly APIs (Reunite main API, feedback, search,
|
|
3
|
+
* Ask AI). Stamps the `x-redocly-client` identification header so portal
|
|
4
|
+
* traffic is attributed correctly — use it instead of bare `fetch` for any
|
|
5
|
+
* Redocly-bound request.
|
|
6
|
+
*/
|
|
7
|
+
export declare function fetchWithClient(input: string | URL, init?: Omit<RequestInit, 'headers'> & {
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
}): Promise<Response>;
|
|
10
|
+
//# sourceMappingURL=fetch-with-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{envConfig as o}from"../config/env-config.js";const r="realm-server",t=o.REDOCLY_PORTAL_VERSION,E=t?`${r}/${t}`:r;function s(n,e={}){return fetch(n,{...e,headers:{...e.headers,"x-redocly-client":E}})}export{s as fetchWithClient};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{envConfig as
|
|
1
|
+
import{envConfig as s}from"../../config/env-config.js";import{fetchWithClient as a}from"../../utils/fetch-with-client.js";const d=(r,e)=>r.secureMethods?.includes(e)??!1;function u(r={}){return async(e,i)=>{const t=e.req.method;if(!d(r,t))return await i();const n=e.req.header("apiKey");if(!n)return e.json({message:"API key is required"},401);try{if(!s.BH_API_URL)return e.json({message:"API key validation service not configured"},500);const o=new URL("api-keys-verify",s.BH_API_URL).toString();if(!(await a(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({apiKey:n})})).ok)return e.json({message:"Invalid API key"},401);await i()}catch{return e.json({message:"API key validation failed"},400)}}}export{u as apiKeyMiddleware};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{KvService as l}from"../../persistence/kv/services/kv-service.js";import{DEFAULT_AUTHENTICATED_TEAM as
|
|
1
|
+
import{KvService as l}from"../../persistence/kv/services/kv-service.js";import{DEFAULT_AUTHENTICATED_TEAM as h}from"../../../constants/common.js";import{envConfig as s}from"../../config/env-config.js";import{fetchWithClient as T}from"../../utils/fetch-with-client.js";import{CACHE_CONTROL_NO_STORE_HEADER_VALUE as y,JWT_SECRET_KEY as p}from"../../constants/common.js";import*as m from"../jwt/jwt.js";import{AlgorithmTypes as I}from"../jwt/types.js";const f=60,E=e=>["POST","PUT","DELETE","PATCH"].includes(e),_=e=>["GET"].includes(e);function S({serverOutDir:e,protectReadMethods:t=!0}){return async(r,a)=>await w(r,a,e,t)}const w=async(e,t,r,a=!0)=>{e.header("Cache-Control",y);const n=e.req.method,i=E(n)||_(n)&&a,o=e.req.header("apiKey");if(o)return await C(e,t,o,r);const u=e.req.header("authorization")?.replace("Bearer ","");return u?await g(e,t,u):i?e.json({message:"API key is required"},401):await t()},C=async(e,t,r,a)=>{if(!s.BH_API_URL||!s.ORGANIZATION_ID)return e.json({message:"API key validation service not configured"},500);try{const n=await l.getInstance({baseDbDir:a});let i=await v(n,r);if(i)return e.set("apiKeyTeams",i.teams),await t();const o=new URL(`/api/orgs/${s.ORGANIZATION_ID}/session`,s.BH_API_URL).toString(),c=await T(o,{method:"GET",headers:{Authorization:`Bearer ${r}`}});if(!c.ok)return e.json({message:"Invalid API key"},401);const A=(await c.json())?.user?.teams?.[s.ORGANIZATION_ID]??[],d=[h,...A];return e.set("apiKeyTeams",d),await n.set(["api-keys","reunite",r],{valid:!0,teams:d},{ttlInSeconds:f}),await t()}catch{return e.json({message:"API key validation failed"},400)}},g=async(e,t,r)=>{try{const a=await m.verify(r,p,I.HS256),n=m.decode(r).payload.isInternalConnection;return!a||!n?e.json({message:"API key is required"},401):await t()}catch{return e.json({message:"API key validation failed"},400)}},v=async(e,t)=>{try{return await e.get(["api-keys","reunite",t])}catch{return null}};export{S as catalogAuthMiddleware};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{RbacFeatures as p}from"../../../constants/common.js";import{ASK_AI_API_URL as a}from"../../constants/common.js";import{
|
|
1
|
+
import{RbacFeatures as p}from"../../../constants/common.js";import{ASK_AI_API_URL as a}from"../../constants/common.js";import{fetchWithClient as h}from"../../utils/fetch-with-client.js";import{canAccessFeature as f}from"../../utils/rbac.js";import{handleUnauthorizedApiRequest as m}from"../utils.js";import{isAiSearchEnabled as A}from"../../plugins/search/utils.js";function g(c){return async e=>{if(!a)return e.newResponse(null,404);const t=c.getConfig();if(!A(t))return e.newResponse(null,403,{});const o=e.get("auth");if(!f(p.AI_SEARCH,o,t.access?.rbac,t.access?.requiresLogin))return m(e);const i={"Content-Type":"application/json"},n=[];o.claims?.authCookie&&n.push(`authorization=${o.claims.authCookie}`),o.idpAccessToken&&n.push(`accessToken=${o.idpAccessToken}`),n.length>0&&(i.Cookie=n.join("; "));const r=e.req.header("Authorization");r&&(i.Authorization=r);const u=await e.req.json(),s=await h(a,{method:"POST",body:JSON.stringify(u),headers:i});return s.ok?e.newResponse(s.body,200,{"Content-Type":"text/event-stream"}):e.newResponse(s.body,s.status,{"Content-Type":"application/json"})}}export{g as askAiHandler};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{setCookie as R,deleteCookie as Y}from"hono/cookie";import{AuthProviderType as Q,DEFAULT_TEAM_CLAIM_NAME as x}from"@redocly/config";import{withPathPrefix as _,getPathPrefix as y}from"@redocly/theme/core/utils";import{compareURIs as ee}from"../../../utils/url/compare-uris.js";import{ensureArray as q}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as F,JWT_SECRET_KEY as v,ORG_SLUG as re,ORG_ID as oe}from"../../constants/common.js";import{AUTH_SEGMENT as ne,ServerRoutes as P}from"../../../constants/api.js";import{AuthCookieNames as L,DEFAULT_COOKIE_EXPIRATION as H}from"../../../constants/common.js";import{sanitizeRedirectPathname as B}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as T}from"../../telemetry/index.js";import{envConfig as j}from"../../config/env-config.js";import{clearAuthCookies as K,clearPostLogoutCookie as $,hasPostLogoutCookie as te,setPostLogoutCookie as z}from"../../utils/cookie.js";import{getAuthProviderLoginParams as se,isOidcProviderConfig as E,isSaml2ProviderConfig as ie,oidcExchangeCodeForToken as ae,buildLoginUrl as de,decodeSamlResponse as ce,extractUserClaims as le,parseSamlResponse as ue,parseOidcState as pe,verifySAMLResponse as ge,getUsernameFromPayload as fe,buildOidcLogoutUrl as me,getOidcMetadata as G,getRedoclyTokenPayload as he,isRedoclySso as Ce,rewritePreviewAuthRedirectUri as Ae,parsePreviewBranch as J,buildOidcLoginUrl as Ie,createMcpSessionResource as D}from"../auth.js";import*as k from"../jwt/jwt.js";import{AlgorithmTypes as U}from"../jwt/types.js";import{handleErrorPageRender as we}from"../utils.js";import{encodeBase64URL as Le}from"../jwt/encode.js";import{resolveUiLocalesForIdpLogin as Re}from"./helpers/resolve-ui-locales-for-idp-login.js";async function He(s){if(j.isProductionEnv)return s.newResponse(null,404,{});const{password:e,...r}=await s.req.json(),a=await k.sign({...r,name:r.username||r.email||"Unknown"},v,U.HS256);return R(s,L.AUTHORIZATION,a,{path:y()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),$(s),s.newResponse(null,200,{})}function Be(){return async s=>{const e=s.get("logger"),r=encodeURIComponent(s.req.query("message")||"");e.error(`Login error: ${r}`);const a=`${P.LOGIN}/?error=${encodeURIComponent(r)}`;return s.newResponse(null,301,{Location:a})}}function Z(s){if(!s||!s.includes(P.MCP_CALLBACK))return null;try{const e=s.split("/"),r=e[e.length-1];if(r){const a=Buffer.from(r,"base64url").toString("utf-8");return JSON.parse(a).mcpSessionId||null}}catch{}return null}function je(s){return async e=>{const r=e.get("logger"),a=s.getConfig().ssoDirect,o=pe(e.req.query("state")),m=o.idpId,t=o.source==="mcp"||o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(P.MCP_CALLBACK),c=t?Z(typeof o.redirectTo=="string"?o.redirectTo:void 0):null,i=a?.[m];if(!E(i))return r.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await G(m,i);if(a&&!d.token_endpoint){const p="Invalid OIDC configuration: token_endpoint is required";return r.error(`OIDC login error: ${p}`),e.text(p,500)}try{const p=d.token_endpoint,u=e.req.query("code"),h=e.req.query("error");if(h)return t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:`OIDC error: ${h}`,error_details:e.req.query("error_description")||null}]),we(e,s,{slug:"/"},403,"403OIDC");if(!u){const A="Code is expected but not present";return r.error(`OIDC login error: ${A}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:A,error_details:null}]),new Response(`Forbidden: ${A}`,{status:403})}const w=typeof o.redirectUri=="string"?o.redirectUri:new URL(_(P.OIDC_CALLBACK),e.req.url).toString(),C=e.get("cookies")?.code_verifier,l=await ae(p,u,w,i,{...i.tokenRequestCustomParams,...C?{code_verifier:C}:{}});if(l.error)return r.error(`Error from OIDC provider: "${l.error}"`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:`Token exchange error: ${l.error}`,error_details:l.error_description||null}]),e.text(`Forbidden: ${l.error_description||l.error}`,403);if(!l?.id_token){const A="No id_token, please, add openid to scopes";return r.error(`OIDC login error: ${A}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:A,error_details:null}]),new Response(`Forbidden: ${A}`,{status:403})}const{payload:f,header:M}=k.decode(l.id_token),n=M.alg===U.RS256;if(i.audience?.length&&![...q(f.aud||[]),...q(f[F]||[])].some(O=>i.audience?.includes(O))){const O="No valid audience found in id_token";return r.error(`OIDC login error: ${O}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:O,error_details:null}]),new Response(`Forbidden: ${O}`)}const g=n?l.id_token:await k.sign({...f,idpId:m},v,U.HS256);fe(f)||r.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const S=i?.tokenExpirationTime?Date.now()+i.tokenExpirationTime*1e3:f.exp*1e3||Date.now()+H*1e3;if(i.introspectEndpoint){const A=await fetch(i.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:l.access_token})});if(A.ok){const b=(await A.json()).ext?.federatedIdentity;b&&(R(e,L.FEDERATED_ACCESS_TOKEN,b.access_token||"",{path:y()||"/",httpOnly:!1,expires:new Date(S)}),R(e,L.FEDERATED_ID_TOKEN,b.id_token||"",{path:y()||"/",httpOnly:!1,expires:new Date(S)}))}else r.warn(`OIDC introspect error: ${A.statusText}`)}if(R(e,L.AUTHORIZATION,g,{path:y()||"/",httpOnly:!0,expires:new Date(S)}),g!==l.id_token&&R(e,L.IDP_ID_TOKEN,l.id_token||"",{path:y()||"/",httpOnly:!0,expires:new Date(S)}),R(e,L.IDP_ACCESS_TOKEN,l.access_token||"",{path:y()||"/",httpOnly:!0,expires:new Date(S)}),Y(e,"code_verifier",{path:y()||"/"}),$(e),t&&o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(P.MCP_CALLBACK)){const O=`${e.req.url.split("?")[0].replace(P.OIDC_CALLBACK,"")}${o.redirectTo}`;return e.newResponse(null,302,{Location:O})}const V=typeof o.redirectTo=="string"?o.redirectTo:void 0;let X=B(new URL(V||"/",e.req.url).pathname);const W=e.newResponse(null,302,{Location:X});return r.updateContext({email:f.email,subject:f.sub}),r.info("OIDC login successful"),W}catch(p){const u=p instanceof Error?p.message:String(p),h=p instanceof Error?p.stack:String(p);if(r.error(`OIDC login error: ${u}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:u,error_details:h}]),p.error==="access_denied")return r.info("Access denied"),e.text("Forbidden",403)}const I="Something went wrong";return r.error(`OIDC login error: ${I}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:I,error_details:null}]),e.text(I,500)}}function Ke(s){return async e=>{const r=e.get("logger"),o=e.get("auth").claims?.idpId,t=s.getConfig().ssoDirect?.[o];if(e.req.method==="POST")return E(t)||(K(e),z(e)),r.info("Logout successful"),e.newResponse(null,200,{});let c;if(E(t)){const i=(await G(o,t)).end_session_endpoint;if(i){const d=new URL(e.req.url),I=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",p=e.req.header("x-forwarded-host")||d.host,u=`${I}://${p}`,h=J(u),w=h?Le(JSON.stringify({branch:J(u)})):void 0,C=h?`${Ae(u)}/${ne}/logout`:`${u}${_(P.POST_LOGOUT)}`;c=me(i,C,e.get("cookies")?.[L.IDP_ID_TOKEN]||e.get("cookies")?.[L.AUTHORIZATION]||"",w)}}return r.info("Logout successful"),K(e),z(e),e.newResponse(null,302,{Location:c||_("/")})}}function ze(s){return async e=>{const r=s.getConfig().access?.logoutReturnUrl,a=r||_("/");return e.newResponse(null,302,{Location:a})}}function Ge(s){return async e=>{const r=e.get("logger"),a=e.req.param("code"),o=j.BH_API_URL,m=(t,c,i)=>t&&c?`${t} ${c.charAt(0)}`:i;try{if(!o)throw new Error("BH_API_URL is not set");const t=s.getConfig().ssoDirect;if(!t||!Object.keys(t).length)return r.warn("Invite no sso configured to handle"),e.redirect(_("/"));const c=await fetch(`${o}/user-invites/public/${a}`);if(!c.ok)return c.status===404?(r.warn(`Invite ${a} not found redirect to homepage`),e.redirect(_("/"))):(r.error("Invite error",await c.text()),e.redirect(_("/")));const i=await c.json(),d=new URL(_("/invite"),e.req.url);return d.searchParams.set("code",a),d.searchParams.set("org",i.organization.name),d.searchParams.set("invitedBy",m(i.invitedBy.firstName,i.invitedBy.lastName,i.invitedBy.name)),e.newResponse(null,302,{Location:d.toString()})}catch(t){return r.error("Error processing invite",{error:t,inviteCode:a}),e.text(t.message||"Failed to process invite",400)}}}function Je(s){return async e=>{const r=e.get("logger"),a=s.getConfig().ssoDirect,o=new URL(e.req.url),m=e.req.query("inviteCode"),t=e.req.header("x-forwarded-proto")||o.protocol.slice(0,-1)||"https",c=e.req.header("x-forwarded-host")||o.host,i=`${t}://${c}`;let d=o.searchParams.get("idpId");const I=o.searchParams.get("redirectTo"),p=Object.keys(a||{})[0];d=d||p;const u=o.searchParams.get("mcp_redirect_uri"),h=!!u;if(!a?.[d]){const g="Invalid idpId";if(r.error(`IdP login error: ${g}`),h){const N=Z(I||void 0);T.sendMcpAuthorizationFailedMessage([{...D(N),error:g,error_details:null}])}return e.text(`Forbidden: ${g}`,403)}const C=Re({localePrefixParam:o.searchParams.get("localePrefix"),l10n:s.getGlobalData()?.l10n}),l=d&&a?await se(d,a[d]):void 0,f={};for(const g of Object.keys(l?.extraParams||{}))f[g]=o.searchParams.get(g)||l?.extraParams?.[g]||void 0;te(e)&&(f.prompt="login",f.post_logout="1");let M,n={};if(h&&u&&l&&l.type===Q.OIDC){r.info(`Building MCP OAuth login URL with redirect_uri: ${u}`);const g=Ie("",{...l,extraParams:f},I,m,{redirectUriOverride:u,sourceOverride:"mcp",branchOverride:void 0,uiLocales:C});M=g.loginUrl,n=g.cookies||{}}else if(l){const g=de({...l,extraParams:f},i,I,m,C);M=g.loginUrl,n=g.cookies||{}}return Object.keys(n).forEach(g=>{R(e,g,n[g].value,n[g].options)}),r.info(`IdP login initiated for ID '${d}'`),e.newResponse(null,302,{Location:M||new URL(e.req.url).pathname})}}function Ze(s){return async e=>{const r=e.get("logger"),a=await e.req.formData(),o=a.get("SAMLResponse"),m=a.get("RelayState");if(typeof o!="string"||typeof m!="string"){const n="SAMLResponse is required";return r.error(`SAML2 login error: ${n}`),e.text(`Bad request: ${n}`,400)}const t=ce(o),{success:c,uid:i,nameFormat:d,attrs:I,issuerId:p,expiresAt:u}=ue(t),{idpId:h,redirectTo:w}=JSON.parse(m);if(!c){const n="SAML2 assertion is not successful";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!u||Math.ceil(Date.now()/1e3)>=u){const n="SAML2 Token Expired";return r.error(`SAML2 login error: ${n}`),e.text(n,401)}const C=s.getConfig().ssoDirect?.[h];if(!C||!ie(C)){const n="Cannot find valid IdP";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!(C.issuerId&&p&&ee(C.issuerId,p))){const n="IssuerID is misconfigured or untrusted assertions issuer received";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!await ge(t,C.x509PublicCert)){const n="SAMLResponse signature invalid";return r.error(`SAML2 login error: ${n}`),e.text(n,401)}const f=le(i,d,I,C.teamsAttributeName??x);if(!f.sub){const n="The provider did not return a valid user identity.";return r.error(`SAML2 login error: ${n}`),e.text(n,400)}if(!f.email){const n="The provider did not return a valid user email.";return r.error(`SAML2 login error: ${n}`),e.text(n,400)}const M=await k.sign({...f,idpId:h},v,U.HS256);return R(e,L.AUTHORIZATION,M,{path:y()||"/",httpOnly:!0,expires:new Date(u*1e3)}),$(e),r.updateContext({email:f.email,subject:f.sub}),r.info("SAML2 login successful"),e.newResponse(null,302,{Location:w||"/"})}}function Ve(s){return async e=>{const r=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),o=_(B(a.pathname)),m=s.getConfig().ssoDirect,t=Object.entries(m||{}).find(([,w])=>E(w)&&Ce(w));if(!(m&&t))return e.newResponse(null,302,{Location:o});const i=e.req.query("token"),d=i&&await he(i);if(!d)return e.newResponse(null,302,{Location:o});if(!q(d[F]||[]).some(w=>w===re||w===oe))return e.newResponse(null,302,{Location:o});const u=await k.sign({...d,idpId:t?.at(0)},v,U.HS256),h=Date.now()+H*1e3;return R(e,L.AUTHORIZATION,u,{path:y()||"/",httpOnly:!0,expires:new Date(h),sameSite:"None",secure:!0}),$(e),r.info("Token login successful"),e.newResponse(null,302,{Location:o})}}export{He as authorizeHandler,Je as idpLoginHandler,Ge as inviteHandler,Ke as logoutHandler,je as oidcCallbackHandler,ze as postLogoutHandler,Be as redoclyLoginCallbackHandler,Ve as redoclyTokenLoginHandler,Ze as samlCallbackHandler};
|
|
1
|
+
import{setCookie as R,deleteCookie as Y}from"hono/cookie";import{AuthProviderType as Q,DEFAULT_TEAM_CLAIM_NAME as x}from"@redocly/config";import{withPathPrefix as _,getPathPrefix as y}from"@redocly/theme/core/utils";import{compareURIs as ee}from"../../../utils/url/compare-uris.js";import{ensureArray as q}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as F,JWT_SECRET_KEY as v,ORG_SLUG as re,ORG_ID as oe}from"../../constants/common.js";import{AUTH_SEGMENT as ne,ServerRoutes as P}from"../../../constants/api.js";import{AuthCookieNames as L,DEFAULT_COOKIE_EXPIRATION as H}from"../../../constants/common.js";import{sanitizeRedirectPathname as B}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as T}from"../../telemetry/index.js";import{envConfig as j}from"../../config/env-config.js";import{fetchWithClient as te}from"../../utils/fetch-with-client.js";import{clearAuthCookies as K,clearPostLogoutCookie as $,hasPostLogoutCookie as se,setPostLogoutCookie as z}from"../../utils/cookie.js";import{getAuthProviderLoginParams as ie,isOidcProviderConfig as E,isSaml2ProviderConfig as ae,oidcExchangeCodeForToken as de,buildLoginUrl as ce,decodeSamlResponse as le,extractUserClaims as ue,parseSamlResponse as pe,parseOidcState as ge,verifySAMLResponse as fe,getUsernameFromPayload as me,buildOidcLogoutUrl as he,getOidcMetadata as G,getRedoclyTokenPayload as Ce,isRedoclySso as Ae,rewritePreviewAuthRedirectUri as Ie,parsePreviewBranch as J,buildOidcLoginUrl as we,createMcpSessionResource as D}from"../auth.js";import*as k from"../jwt/jwt.js";import{AlgorithmTypes as U}from"../jwt/types.js";import{handleErrorPageRender as Le}from"../utils.js";import{encodeBase64URL as Re}from"../jwt/encode.js";import{resolveUiLocalesForIdpLogin as _e}from"./helpers/resolve-ui-locales-for-idp-login.js";async function je(s){if(j.isProductionEnv)return s.newResponse(null,404,{});const{password:e,...r}=await s.req.json(),a=await k.sign({...r,name:r.username||r.email||"Unknown"},v,U.HS256);return R(s,L.AUTHORIZATION,a,{path:y()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),$(s),s.newResponse(null,200,{})}function Ke(){return async s=>{const e=s.get("logger"),r=encodeURIComponent(s.req.query("message")||"");e.error(`Login error: ${r}`);const a=`${P.LOGIN}/?error=${encodeURIComponent(r)}`;return s.newResponse(null,301,{Location:a})}}function Z(s){if(!s||!s.includes(P.MCP_CALLBACK))return null;try{const e=s.split("/"),r=e[e.length-1];if(r){const a=Buffer.from(r,"base64url").toString("utf-8");return JSON.parse(a).mcpSessionId||null}}catch{}return null}function ze(s){return async e=>{const r=e.get("logger"),a=s.getConfig().ssoDirect,o=ge(e.req.query("state")),m=o.idpId,t=o.source==="mcp"||o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(P.MCP_CALLBACK),c=t?Z(typeof o.redirectTo=="string"?o.redirectTo:void 0):null,i=a?.[m];if(!E(i))return r.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await G(m,i);if(a&&!d.token_endpoint){const p="Invalid OIDC configuration: token_endpoint is required";return r.error(`OIDC login error: ${p}`),e.text(p,500)}try{const p=d.token_endpoint,u=e.req.query("code"),h=e.req.query("error");if(h)return t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:`OIDC error: ${h}`,error_details:e.req.query("error_description")||null}]),Le(e,s,{slug:"/"},403,"403OIDC");if(!u){const A="Code is expected but not present";return r.error(`OIDC login error: ${A}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:A,error_details:null}]),new Response(`Forbidden: ${A}`,{status:403})}const w=typeof o.redirectUri=="string"?o.redirectUri:new URL(_(P.OIDC_CALLBACK),e.req.url).toString(),C=e.get("cookies")?.code_verifier,l=await de(p,u,w,i,{...i.tokenRequestCustomParams,...C?{code_verifier:C}:{}});if(l.error)return r.error(`Error from OIDC provider: "${l.error}"`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:`Token exchange error: ${l.error}`,error_details:l.error_description||null}]),e.text(`Forbidden: ${l.error_description||l.error}`,403);if(!l?.id_token){const A="No id_token, please, add openid to scopes";return r.error(`OIDC login error: ${A}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:A,error_details:null}]),new Response(`Forbidden: ${A}`,{status:403})}const{payload:f,header:M}=k.decode(l.id_token),n=M.alg===U.RS256;if(i.audience?.length&&![...q(f.aud||[]),...q(f[F]||[])].some(O=>i.audience?.includes(O))){const O="No valid audience found in id_token";return r.error(`OIDC login error: ${O}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:O,error_details:null}]),new Response(`Forbidden: ${O}`)}const g=n?l.id_token:await k.sign({...f,idpId:m},v,U.HS256);me(f)||r.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const S=i?.tokenExpirationTime?Date.now()+i.tokenExpirationTime*1e3:f.exp*1e3||Date.now()+H*1e3;if(i.introspectEndpoint){const A=await fetch(i.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:l.access_token})});if(A.ok){const b=(await A.json()).ext?.federatedIdentity;b&&(R(e,L.FEDERATED_ACCESS_TOKEN,b.access_token||"",{path:y()||"/",httpOnly:!1,expires:new Date(S)}),R(e,L.FEDERATED_ID_TOKEN,b.id_token||"",{path:y()||"/",httpOnly:!1,expires:new Date(S)}))}else r.warn(`OIDC introspect error: ${A.statusText}`)}if(R(e,L.AUTHORIZATION,g,{path:y()||"/",httpOnly:!0,expires:new Date(S)}),g!==l.id_token&&R(e,L.IDP_ID_TOKEN,l.id_token||"",{path:y()||"/",httpOnly:!0,expires:new Date(S)}),R(e,L.IDP_ACCESS_TOKEN,l.access_token||"",{path:y()||"/",httpOnly:!0,expires:new Date(S)}),Y(e,"code_verifier",{path:y()||"/"}),$(e),t&&o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(P.MCP_CALLBACK)){const O=`${e.req.url.split("?")[0].replace(P.OIDC_CALLBACK,"")}${o.redirectTo}`;return e.newResponse(null,302,{Location:O})}const V=typeof o.redirectTo=="string"?o.redirectTo:void 0;let W=B(new URL(V||"/",e.req.url).pathname);const X=e.newResponse(null,302,{Location:W});return r.updateContext({email:f.email,subject:f.sub}),r.info("OIDC login successful"),X}catch(p){const u=p instanceof Error?p.message:String(p),h=p instanceof Error?p.stack:String(p);if(r.error(`OIDC login error: ${u}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:u,error_details:h}]),p.error==="access_denied")return r.info("Access denied"),e.text("Forbidden",403)}const I="Something went wrong";return r.error(`OIDC login error: ${I}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:I,error_details:null}]),e.text(I,500)}}function Ge(s){return async e=>{const r=e.get("logger"),o=e.get("auth").claims?.idpId,t=s.getConfig().ssoDirect?.[o];if(e.req.method==="POST")return E(t)||(K(e),z(e)),r.info("Logout successful"),e.newResponse(null,200,{});let c;if(E(t)){const i=(await G(o,t)).end_session_endpoint;if(i){const d=new URL(e.req.url),I=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",p=e.req.header("x-forwarded-host")||d.host,u=`${I}://${p}`,h=J(u),w=h?Re(JSON.stringify({branch:J(u)})):void 0,C=h?`${Ie(u)}/${ne}/logout`:`${u}${_(P.POST_LOGOUT)}`;c=he(i,C,e.get("cookies")?.[L.IDP_ID_TOKEN]||e.get("cookies")?.[L.AUTHORIZATION]||"",w)}}return r.info("Logout successful"),K(e),z(e),e.newResponse(null,302,{Location:c||_("/")})}}function Je(s){return async e=>{const r=s.getConfig().access?.logoutReturnUrl,a=r||_("/");return e.newResponse(null,302,{Location:a})}}function Ze(s){return async e=>{const r=e.get("logger"),a=e.req.param("code"),o=j.BH_API_URL,m=(t,c,i)=>t&&c?`${t} ${c.charAt(0)}`:i;try{if(!o)throw new Error("BH_API_URL is not set");const t=s.getConfig().ssoDirect;if(!t||!Object.keys(t).length)return r.warn("Invite no sso configured to handle"),e.redirect(_("/"));const c=await te(`${o}/user-invites/public/${a}`);if(!c.ok)return c.status===404?(r.warn(`Invite ${a} not found redirect to homepage`),e.redirect(_("/"))):(r.error("Invite error",await c.text()),e.redirect(_("/")));const i=await c.json(),d=new URL(_("/invite"),e.req.url);return d.searchParams.set("code",a),d.searchParams.set("org",i.organization.name),d.searchParams.set("invitedBy",m(i.invitedBy.firstName,i.invitedBy.lastName,i.invitedBy.name)),e.newResponse(null,302,{Location:d.toString()})}catch(t){return r.error("Error processing invite",{error:t,inviteCode:a}),e.text(t.message||"Failed to process invite",400)}}}function Ve(s){return async e=>{const r=e.get("logger"),a=s.getConfig().ssoDirect,o=new URL(e.req.url),m=e.req.query("inviteCode"),t=e.req.header("x-forwarded-proto")||o.protocol.slice(0,-1)||"https",c=e.req.header("x-forwarded-host")||o.host,i=`${t}://${c}`;let d=o.searchParams.get("idpId");const I=o.searchParams.get("redirectTo"),p=Object.keys(a||{})[0];d=d||p;const u=o.searchParams.get("mcp_redirect_uri"),h=!!u;if(!a?.[d]){const g="Invalid idpId";if(r.error(`IdP login error: ${g}`),h){const N=Z(I||void 0);T.sendMcpAuthorizationFailedMessage([{...D(N),error:g,error_details:null}])}return e.text(`Forbidden: ${g}`,403)}const C=_e({localePrefixParam:o.searchParams.get("localePrefix"),l10n:s.getGlobalData()?.l10n}),l=d&&a?await ie(d,a[d]):void 0,f={};for(const g of Object.keys(l?.extraParams||{}))f[g]=o.searchParams.get(g)||l?.extraParams?.[g]||void 0;se(e)&&(f.prompt="login",f.post_logout="1");let M,n={};if(h&&u&&l&&l.type===Q.OIDC){r.info(`Building MCP OAuth login URL with redirect_uri: ${u}`);const g=we("",{...l,extraParams:f},I,m,{redirectUriOverride:u,sourceOverride:"mcp",branchOverride:void 0,uiLocales:C});M=g.loginUrl,n=g.cookies||{}}else if(l){const g=ce({...l,extraParams:f},i,I,m,C);M=g.loginUrl,n=g.cookies||{}}return Object.keys(n).forEach(g=>{R(e,g,n[g].value,n[g].options)}),r.info(`IdP login initiated for ID '${d}'`),e.newResponse(null,302,{Location:M||new URL(e.req.url).pathname})}}function We(s){return async e=>{const r=e.get("logger"),a=await e.req.formData(),o=a.get("SAMLResponse"),m=a.get("RelayState");if(typeof o!="string"||typeof m!="string"){const n="SAMLResponse is required";return r.error(`SAML2 login error: ${n}`),e.text(`Bad request: ${n}`,400)}const t=le(o),{success:c,uid:i,nameFormat:d,attrs:I,issuerId:p,expiresAt:u}=pe(t),{idpId:h,redirectTo:w}=JSON.parse(m);if(!c){const n="SAML2 assertion is not successful";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!u||Math.ceil(Date.now()/1e3)>=u){const n="SAML2 Token Expired";return r.error(`SAML2 login error: ${n}`),e.text(n,401)}const C=s.getConfig().ssoDirect?.[h];if(!C||!ae(C)){const n="Cannot find valid IdP";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!(C.issuerId&&p&&ee(C.issuerId,p))){const n="IssuerID is misconfigured or untrusted assertions issuer received";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!await fe(t,C.x509PublicCert)){const n="SAMLResponse signature invalid";return r.error(`SAML2 login error: ${n}`),e.text(n,401)}const f=ue(i,d,I,C.teamsAttributeName??x);if(!f.sub){const n="The provider did not return a valid user identity.";return r.error(`SAML2 login error: ${n}`),e.text(n,400)}if(!f.email){const n="The provider did not return a valid user email.";return r.error(`SAML2 login error: ${n}`),e.text(n,400)}const M=await k.sign({...f,idpId:h},v,U.HS256);return R(e,L.AUTHORIZATION,M,{path:y()||"/",httpOnly:!0,expires:new Date(u*1e3)}),$(e),r.updateContext({email:f.email,subject:f.sub}),r.info("SAML2 login successful"),e.newResponse(null,302,{Location:w||"/"})}}function Xe(s){return async e=>{const r=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),o=_(B(a.pathname)),m=s.getConfig().ssoDirect,t=Object.entries(m||{}).find(([,w])=>E(w)&&Ae(w));if(!(m&&t))return e.newResponse(null,302,{Location:o});const i=e.req.query("token"),d=i&&await Ce(i);if(!d)return e.newResponse(null,302,{Location:o});if(!q(d[F]||[]).some(w=>w===re||w===oe))return e.newResponse(null,302,{Location:o});const u=await k.sign({...d,idpId:t?.at(0)},v,U.HS256),h=Date.now()+H*1e3;return R(e,L.AUTHORIZATION,u,{path:y()||"/",httpOnly:!0,expires:new Date(h),sameSite:"None",secure:!0}),$(e),r.info("Token login successful"),e.newResponse(null,302,{Location:o})}}export{je as authorizeHandler,Ve as idpLoginHandler,Ze as inviteHandler,Ge as logoutHandler,ze as oidcCallbackHandler,Je as postLogoutHandler,Ke as redoclyLoginCallbackHandler,Xe as redoclyTokenLoginHandler,We as samlCallbackHandler};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{envConfig as t}from"../../../../config/env-config.js";async function
|
|
1
|
+
import{envConfig as t}from"../../../../config/env-config.js";import{fetchWithClient as c}from"../../../../utils/fetch-with-client.js";async function h({ctx:r,catalogEntitiesService:s,addedEntities:n,removedEntities:i}){if(!t.isProductionEnv){r.get("logger").info("Skipping pages stats upsert in non-production environment");return}if(!t.BH_API_URL||!t.ORGANIZATION_ID||!t.PROJECT_ID)throw new Error("Pages stats service not configured");const o=r.req.header("apiKey");if(!o)throw new Error("API key is required to upsert pages stats");const{total:a}=await s.getEntitiesCount("remote",n,i?.length),p={remoteEntityPages:a,isReported:!1},g=new URL(`/api/orgs/${t.ORGANIZATION_ID}/projects/${t.PROJECT_ID}/project-pages-stats`,t.BH_API_URL).toString();try{const e=await c(g,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify(p)});if(!e.ok)throw new Error(e.statusText)}catch(e){throw new Error(`Failed to upsert project pages stats: ${e?.message}`)}}export{h as upsertPagesStats};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{FEEDBACK_API_URL as E}from"../../constants/common.js";import{MAX_AGENT_LENGTH as
|
|
1
|
+
import{FEEDBACK_API_URL as E}from"../../constants/common.js";import{fetchWithClient as T}from"../../utils/fetch-with-client.js";import{MAX_AGENT_LENGTH as k,MAX_CONTEXT_LENGTH as A,MAX_EMAIL_LENGTH as y,MAX_LANG_LENGTH as N,MAX_PATH_LENGTH as F,MAX_REASONS_COUNT as j,MAX_TARGET_FEATURE_LENGTH as C}from"../../constants/feedback.js";import{mapObject as L}from"../../../utils/object/map-object.js";import{getFeedbackClientIp as R}from"../utils/feedback-ip.js";import{canAccessResource as S}from"../../utils/rbac.js";function r(n,e){if(n!=null)return String(n).replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g,"").trim().slice(0,e)||void 0}function G(n){if(!n)return n;const e=["userAgent","firstName","lastName","auth_time","platform","id","email","ipAddress"];return L(n,(t,a)=>e.includes(a)?t:"*****")}function M({component:n,path:e,location:t,lang:a,agent:p,targetFeature:m,score:s,max:o,reasons:i,comment:f,metadata:l,email:h}){const u=Array.isArray(i)?i.map(c=>r(String(c),A)).filter(c=>!!c).slice(0,j):void 0;return{feedbackComponent:n.toUpperCase(),path:r(e,F),location:r(t,A),lang:r(a,N),score:typeof s=="number"?s:void 0,maxScore:typeof o=="number"?o:void 0,reasons:u?.length?u:void 0,comment:r(f,A),email:r(h,y),agent:r(p,k),targetFeature:r(m,C),metadata:G(l)}}async function w(n,e){return(await T(E,{method:"POST",body:JSON.stringify(M(n)),headers:e})).json()}function B(n){return async e=>{const t=await e.req.json(),a=e.req.header("user-agent"),p=R(e.req.raw),m=e.req.header("Sec-Ch-Ua-Platform"),s={...t.metadata,userAgent:a,ipAddress:p,platform:m?m.replace(/"/g,""):"unknown"},o=[];(!t.path||t.path==="")&&o.push("`path` is required");const i=["sentiment","rating","comment","problem","mood","scale"];if(i.includes(t.component)||o.push(`\`component\` field should be one of ${i.join(", ")}.`),o.length)return e.json({errors:o},400);const{claims:f,isAuthenticated:l,teams:h}=e.get("auth"),u={isAuthenticated:l,email:f?.email,teams:h};if(Object.keys(n.config.access?.rbac||{}).length>0){const d=t.path,_=new URL(d).pathname,b=n.getRouteBySlug(_);if(!b)return e.json({errors:["Resource not found"]},404);if(!S(b,u,n.config.access?.rbac,n.config.access?.requiresLogin))return e.json({errors:["Forbidden: no permission to send feedback for resource"]},403)}const c={"Content-Type":"application/json"},g=f?.email||t?.email||s?.email;try{const d=await w({...t,email:g,metadata:{email:g,...s}},c);return e.json({message:"Thanks for your feedback",...d},200,{})}catch(d){return e.json({errors:["Failed to send feedback",d.message]},500)}}}export{B as feedbackHandler,M as normalizeFeedbackData,G as normalizeFeedbackMetadata};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{withPathPrefix as p}from"@redocly/theme/core/utils";import{SEMANTIC_SEARCH_API_URL as
|
|
1
|
+
import{withPathPrefix as p}from"@redocly/theme/core/utils";import{SEMANTIC_SEARCH_API_URL as s}from"../../constants/common.js";import{fetchWithClient as u}from"../../utils/fetch-with-client.js";function T(d){return async t=>{if(!s)return t.newResponse(null,404);const n=t.get("auth"),o={"Content-Type":"application/json"};n.idpAccessToken&&(o.Cookie=`accessToken=${n.idpAccessToken}`);const a=await t.req.json();try{const e=await u(s,{method:"POST",body:JSON.stringify(a),headers:o});if(e.ok){const c=(await e.json()).documents.map(r=>({...r,url:p(r.url)}));return t.json(c)}const i=await e.text();return t.newResponse(i,e.status,{"Content-Type":"application/json"})}catch(e){return t.json({error:"Failed to fetch semantic search results",details:String(e)},500)}}}export{T as semanticSearchHandler};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/realm",
|
|
3
|
-
"version": "0.136.0-next.
|
|
3
|
+
"version": "0.136.0-next.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"@jitl/quickjs-singlefile-mjs-release-sync": "0.32.0",
|
|
22
22
|
"@libsql/client": "0.17.3",
|
|
23
23
|
"@markdoc/markdoc": "0.5.2",
|
|
24
|
-
"@opentelemetry/api": "1.9.
|
|
24
|
+
"@opentelemetry/api": "1.9.1",
|
|
25
25
|
"@opentelemetry/context-zone": "2.0.1",
|
|
26
26
|
"@opentelemetry/core": "2.8.0",
|
|
27
27
|
"@opentelemetry/exporter-trace-otlp-http": "0.219.0",
|
|
@@ -93,12 +93,12 @@
|
|
|
93
93
|
"xpath": "0.0.34",
|
|
94
94
|
"yaml-ast-parser": "0.0.43",
|
|
95
95
|
"zod": "^3.25.76",
|
|
96
|
-
"@redocly/asyncapi-docs": "1.13.0-next.
|
|
96
|
+
"@redocly/asyncapi-docs": "1.13.0-next.1",
|
|
97
97
|
"@redocly/config": "0.52.0",
|
|
98
|
-
"@redocly/graphql-docs": "1.13.0-next.
|
|
99
|
-
"@redocly/openapi-docs": "3.24.0-next.
|
|
98
|
+
"@redocly/graphql-docs": "1.13.0-next.1",
|
|
99
|
+
"@redocly/openapi-docs": "3.24.0-next.1",
|
|
100
100
|
"@redocly/portal-legacy-ui": "0.18.0",
|
|
101
|
-
"@redocly/portal-plugin-mock-server": "0.21.0-next.
|
|
101
|
+
"@redocly/portal-plugin-mock-server": "0.21.0-next.1",
|
|
102
102
|
"@redocly/realm-asyncapi-sdk": "0.14.0-next.0",
|
|
103
103
|
"@redocly/theme": "0.68.0-next.0"
|
|
104
104
|
},
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
export declare const SANDBOX_POLYFILLS = "\n const URLSearchParams = class URLSearchParams {\n constructor(init) {\n this._params = [];\n if (typeof init === 'string') {\n const str = init.startsWith('?') ? init.slice(1) : init;\n for (const pair of (str ? str.split('&') : [])) {\n const eq = pair.indexOf('=');\n const k = eq >= 0 ? pair.slice(0, eq) : pair;\n const v = eq >= 0 ? pair.slice(eq + 1) : '';\n if (k) this._params.push([decodeURIComponent(k), decodeURIComponent(v)]);\n }\n } else if (init != null && typeof init === 'object') {\n for (const [k, v] of Object.entries(init)) this._params.push([k, String(v)]);\n }\n }\n append(key, value) { this._params.push([String(key), String(value)]); }\n set(key, value) {\n const k = String(key), v = String(value);\n this._params = this._params.filter(([pk]) => pk !== k);\n this._params.push([k, v]);\n }\n get(key) { return (this._params.find(([k]) => k === String(key)) ?? [undefined, null])[1]; }\n getAll(key) { return this._params.filter(([k]) => k === String(key)).map(([, v]) => v); }\n has(key) { return this._params.some(([k]) => k === String(key)); }\n delete(key) { this._params = this._params.filter(([k]) => k !== String(key)); }\n entries() { return this._params.values(); }\n [Symbol.iterator]() { return this._params.values(); }\n toString() {\n return this._params\n .map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v))\n .join('&');\n }\n };\n globalThis.URLSearchParams = URLSearchParams;\n\n const FormData = class FormData {\n constructor() { this._fields = []; }\n append(key, value) { this._fields.push([String(key), String(value)]); }\n set(key, value) {\n const k = String(key), v = String(value);\n this._fields = this._fields.filter(([fk]) => fk !== k);\n this._fields.push([k, v]);\n }\n get(key) { return (this._fields.find(([k]) => k === String(key)) ?? [undefined, null])[1]; }\n has(key) { return this._fields.some(([k]) => k === String(key)); }\n delete(key) { this._fields = this._fields.filter(([k]) => k !== String(key)); }\n entries() { return this._fields.values(); }\n [Symbol.iterator]() { return this._fields.values(); }\n };\n globalThis.FormData = FormData;\n";
|
|
2
|
-
export declare const SANDBOX_FETCH_SETUP = "\n const hostFetch = globalThis.__fetch;\n delete globalThis.__fetch;\n globalThis.fetch = (url, init = {}) => {\n let body;\n let autoContentType;\n if (init.body instanceof URLSearchParams) {\n body = init.body.toString();\n autoContentType = 'application/x-www-form-urlencoded';\n } else if (init.body instanceof FormData) {\n const boundary = '----FormBoundary' + Math.random().toString(36).slice(2);\n body = init.body._fields\n .map(([k, v]) =>\n '--' + boundary + '\\r\\nContent-Disposition: form-data; name=\"' + k + '\"\\r\\n\\r\\n' + v,\n )\n .join('\\r\\n') + '\\r\\n--' + boundary + '--';\n autoContentType = 'multipart/form-data; boundary=' + boundary;\n } else if (init.body != null && typeof init.body !== 'string') {\n throw new TypeError('fetch body must be a string, URLSearchParams, or FormData');\n } else {\n body = init.body ?? undefined;\n }\n const callerHeaders = init.headers;\n const hasContentType = callerHeaders != null && Object.keys(callerHeaders).some(\n (k) => k.toLowerCase() === 'content-type',\n );\n const headers = autoContentType && !hasContentType\n ? Object.assign({ 'content-type': autoContentType }, callerHeaders)\n : callerHeaders;\n return hostFetch(JSON.stringify({ url: String(url), method: init.method, headers, body }))\n .then((json) => {\n const res = JSON.parse(json);\n const resHeaders = res.headers || {};\n return {\n status: res.status,\n statusText: res.statusText,\n ok: res.ok,\n headers: {\n get: (name) => {\n const value = resHeaders[String(name).toLowerCase()];\n return value === undefined ? null : value;\n },\n },\n json: () => Promise.resolve(JSON.parse(res.bodyText)),\n text: () => Promise.resolve(res.bodyText),\n };\n });\n };\n";
|
|
3
|
-
//# sourceMappingURL=sandbox-polyfills.d.ts.map
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import P from"@jitl/quickjs-singlefile-mjs-release-sync";import{newQuickJSWASMModuleFromVariant as A,shouldInterruptAfterDeadline as j}from"quickjs-emscripten-core";import{SANDBOX_FETCH_SETUP as J,SANDBOX_POLYFILLS as N}from"./sandbox-polyfills.js";const B=1e4,C=64*1024*1024,L=256*1024,y=100,$=1024*1024,I=1024*1024,R=1024*1024;let v;function x(){return v??=A(P)}function F(t,r){const u=t.map(l=>`${JSON.stringify(l)}: (input) => invoke(${JSON.stringify(l)}, JSON.stringify(input ?? {})).then(JSON.parse)`).join(", ");return`(() => {
|
|
2
|
-
const invoke = globalThis.__invoke;
|
|
3
|
-
delete globalThis.__invoke;${r?N+J:""}
|
|
4
|
-
const tools = Object.freeze({ ${u} });
|
|
5
|
-
globalThis.tools = new Proxy(tools, {
|
|
6
|
-
get(target, prop) {
|
|
7
|
-
if (typeof prop !== 'string' || prop in target || prop === 'then' || prop === 'toJSON') {
|
|
8
|
-
return target[prop];
|
|
9
|
-
}
|
|
10
|
-
throw new TypeError('Unknown tool "' + prop + '". Available tools: ' + Object.keys(target).join(', '));
|
|
11
|
-
},
|
|
12
|
-
});
|
|
13
|
-
})();`}async function H(t,r,u={}){const{timeoutMs:i=B,hostFetch:l}=u,T=new Set;let b=0,_=0;const e=(await x()).newContext();let c,h=()=>{};const k=new Promise((o,a)=>{h=()=>a(new Error(`Execution timed out after ${i}ms`))}),O=()=>e.runtime.setInterruptHandler(j(Date.now()+i)),w=()=>{clearTimeout(c),c=setTimeout(h,i)},E=(o,a,g,d)=>{X(e,o,(...p)=>{const f=p.map(s=>e.getString(s)),n=e.newPromise();return T.add(n),_++,clearTimeout(c),Promise.resolve().then(()=>a(f)).then(s=>{if(!e.alive)return;const S=JSON.stringify(s??null);S.length>g?e.newError(d(S.length)).consume(n.reject):e.newString(S).consume(n.resolve)},s=>{e.alive&&e.newError(M(s)).consume(n.reject)}),n.settled.then(()=>{e.alive&&(O(),--_===0&&w(),e.runtime.executePendingJobs())}),n.handle})};let m;try{e.runtime.setMemoryLimit(C),e.runtime.setMaxStackSize(L),O(),w();const o=()=>{if(++b>y)throw new Error(`Exceeded the limit of ${y} tool calls`)};E("__invoke",([n,s])=>(o(),r[n](JSON.parse(s))),$,n=>`Tool output is too large (${n} bytes); request narrower data instead`),l&&E("__fetch",([n])=>(o(),l(JSON.parse(n))),I,n=>`Response is too large (${n} bytes); request less data`),Y(e,F(Object.keys(r),!!l)),m=e.unwrapResult(e.evalCode(`(async () => {
|
|
14
|
-
${t}
|
|
15
|
-
})()`));const a=e.resolvePromise(m);e.runtime.executePendingJobs();const g=await Promise.race([a,k]),d=e.unwrapResult(g),p=e.dump(d);d.dispose();const f=JSON.stringify(p??null).length;return f>R?{error:`Result is too large (${f} bytes); return less data`}:{result:p}}catch(o){return{error:M(o)}}finally{clearTimeout(c),m?.dispose();for(const o of T)o.alive&&o.dispose();try{e.dispose()}catch{v=void 0}}}function X(t,r,u){t.newFunction(r,u).consume(i=>t.setProp(t.global,r,i))}function Y(t,r){t.unwrapResult(t.evalCode(r)).dispose()}function M(t){return t instanceof Error?t.message:String(t)}export{H as runInSandbox};
|