@redocly/realm 0.136.0-next.0 → 0.136.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/client/app/hooks/catalog/useCatalogViewMode.js +1 -1
  3. package/dist/client/app/hooks/catalog/useFetchCatalogEntities.js +1 -1
  4. package/dist/client/app/hooks/catalog/useFetchCatalogEntitiesRelations.js +1 -1
  5. package/dist/client/app/hooks/catalog/useSearchTracker.d.ts +2 -5
  6. package/dist/client/app/hooks/catalog/useSearchTracker.js +1 -1
  7. package/dist/server/api-routes/execute-api-route.js +1 -1
  8. package/dist/server/api-routes/helpers/enhance-context.d.ts +3 -1
  9. package/dist/server/api-routes/helpers/enhance-context.js +1 -1
  10. package/dist/server/api-routes/helpers/is-mock-server.d.ts +2 -0
  11. package/dist/server/api-routes/helpers/is-mock-server.js +1 -0
  12. package/dist/server/api-routes/mock-server/kv-state-store.d.ts +3 -0
  13. package/dist/server/api-routes/mock-server/kv-state-store.js +1 -0
  14. package/dist/server/api-routes/mock-server/mock-js-executor.d.ts +3 -0
  15. package/dist/server/api-routes/mock-server/mock-js-executor.js +13 -0
  16. package/dist/server/api-routes/run-api-routes-worker.js +1 -1
  17. package/dist/server/config/env-config.d.ts +4 -0
  18. package/dist/server/config/env-schema.d.ts +10 -0
  19. package/dist/server/config/env-schemas/catalog.d.ts +3 -0
  20. package/dist/server/config/env-schemas/catalog.js +1 -1
  21. package/dist/server/config/env-schemas/feature-flags.d.ts +4 -0
  22. package/dist/server/config/env-schemas/feature-flags.js +1 -1
  23. package/dist/server/plugins/catalog-entities/database/catalog-entities-diff-publisher.d.ts +27 -0
  24. package/dist/server/plugins/catalog-entities/database/catalog-entities-diff-publisher.js +12 -0
  25. package/dist/server/plugins/catalog-entities/database/catalog-entities-publisher.js +1 -12
  26. package/dist/server/plugins/catalog-entities/database/catalog-entities-revisions-pruner.d.ts +22 -0
  27. package/dist/server/plugins/catalog-entities/database/catalog-entities-revisions-pruner.js +62 -0
  28. package/dist/server/plugins/catalog-entities/database/consts.d.ts +5 -0
  29. package/dist/server/plugins/catalog-entities/database/consts.js +1 -1
  30. package/dist/server/plugins/catalog-entities/database/repositories/entities/entities-write-repository.js +1 -1
  31. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/base.d.ts +4 -1
  32. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/base.js +1 -1
  33. package/dist/server/plugins/catalog-entities/plugin.js +1 -1
  34. package/dist/server/plugins/markdown/markdoc/plugins/utils.d.ts +5 -0
  35. package/dist/server/plugins/markdown/markdoc/plugins/utils.js +1 -1
  36. package/dist/server/plugins/markdown/runtime-transform.js +1 -1
  37. package/dist/server/plugins/mcp/auth/auth-handlers.d.ts +2 -0
  38. package/dist/server/plugins/mcp/auth/auth-handlers.js +1 -1
  39. package/dist/server/plugins/mcp/codemode/constants.d.ts +3 -0
  40. package/dist/server/plugins/mcp/codemode/constants.js +1 -1
  41. package/dist/server/plugins/mcp/codemode/sandbox/create-sandbox-setup.d.ts +10 -0
  42. package/dist/server/plugins/mcp/codemode/sandbox/create-sandbox-setup.js +11 -0
  43. package/dist/server/plugins/mcp/codemode/sandbox/polyfills.d.ts +2 -0
  44. package/dist/server/plugins/mcp/codemode/sandbox/{sandbox-polyfills.js → polyfills.js} +7 -8
  45. package/dist/server/plugins/mcp/codemode/tools/execute.js +1 -1
  46. package/dist/server/plugins/mcp/codemode/types.d.ts +0 -8
  47. package/dist/server/sandbox/constants/host-functions.d.ts +3 -0
  48. package/dist/server/sandbox/constants/host-functions.js +1 -0
  49. package/dist/server/sandbox/constants/sandbox.d.ts +6 -0
  50. package/dist/server/sandbox/constants/sandbox.js +1 -0
  51. package/dist/server/sandbox/sandbox.d.ts +8 -0
  52. package/dist/server/sandbox/sandbox.js +3 -0
  53. package/dist/server/sandbox/types.d.ts +24 -0
  54. package/dist/server/sandbox/types.js +0 -0
  55. package/dist/server/sandbox/utils/eval-guest-code.d.ts +15 -0
  56. package/dist/server/sandbox/utils/eval-guest-code.js +1 -0
  57. package/dist/server/sandbox/utils/execution-budget.d.ts +11 -0
  58. package/dist/server/sandbox/utils/execution-budget.js +1 -0
  59. package/dist/server/sandbox/utils/setup-host-functions.d.ts +18 -0
  60. package/dist/server/sandbox/utils/setup-host-functions.js +5 -0
  61. package/dist/server/store.js +1 -1
  62. package/dist/server/utils/fetch-with-client.d.ts +10 -0
  63. package/dist/server/utils/fetch-with-client.js +1 -0
  64. package/dist/server/utils/sandbox-context.d.ts +18 -0
  65. package/dist/server/utils/sandbox-context.js +1 -0
  66. package/dist/server/web-server/middleware/apiKeyMiddleware.js +1 -1
  67. package/dist/server/web-server/middleware/catalogAuthMiddleware.js +1 -1
  68. package/dist/server/web-server/routes/ask-ai.js +1 -1
  69. package/dist/server/web-server/routes/auth.js +1 -1
  70. package/dist/server/web-server/routes/catalog/helpers/upsert-pages-stats.js +1 -1
  71. package/dist/server/web-server/routes/feedback.js +1 -1
  72. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.d.ts +5 -4
  73. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
  74. package/dist/server/web-server/routes/mcp-routes/mcp-routes.js +1 -1
  75. package/dist/server/web-server/routes/semantic-search.js +1 -1
  76. package/dist/server/workers/api-routes-worker-pool.js +1 -1
  77. package/dist/server/workers/mcp-tool-worker-pool.js +1 -1
  78. package/dist/server/workers/worker-pool.d.ts +1 -0
  79. package/dist/server/workers/worker-pool.js +1 -1
  80. package/package.json +7 -7
  81. package/dist/server/plugins/mcp/codemode/sandbox/sandbox-polyfills.d.ts +0 -3
  82. package/dist/server/plugins/mcp/codemode/sandbox/sandbox.d.ts +0 -3
  83. package/dist/server/plugins/mcp/codemode/sandbox/sandbox.js +0 -15
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @redocly/realm
2
2
 
3
+ ## 0.136.0-next.2
4
+
5
+ ### Minor Changes
6
+
7
+ - 94b73e18acd: Added `numbered-list` and `item` Markdoc tags to render step-by-step guides with numbered, icon, or dot markers and deep-linkable item headlines.
8
+
9
+ ### Patch Changes
10
+
11
+ - 14597a56ac1: Fixed the built-in Docs MCP OAuth flow for public sites so they no longer advertise an authorization server or force a login.
12
+
13
+ Resolved the real `idpId` for protected sites using custom SSO.
14
+
15
+ - Updated dependencies [94b73e18acd]
16
+ - @redocly/theme@0.68.0-next.1
17
+ - @redocly/asyncapi-docs@1.13.0-next.2
18
+ - @redocly/graphql-docs@1.13.0-next.2
19
+ - @redocly/openapi-docs@3.24.0-next.2
20
+ - @redocly/portal-plugin-mock-server@0.21.0-next.2
21
+
22
+ ## 0.136.0-next.1
23
+
24
+ ### Patch Changes
25
+
26
+ - 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.
27
+ - Updated dependencies [42547a5a76a]
28
+ - @redocly/openapi-docs@3.24.0-next.1
29
+ - @redocly/asyncapi-docs@1.13.0-next.1
30
+ - @redocly/graphql-docs@1.13.0-next.1
31
+ - @redocly/portal-plugin-mock-server@0.21.0-next.1
32
+
3
33
  ## 0.136.0-next.0
4
34
 
5
35
  ### Minor Changes
@@ -1 +1 @@
1
- import{useState as i}from"react";import{telemetry as n}from"../../telemetry/index.js";const s="catalog-view-mode",w="table";function u(o){const[r,a]=i(o??w);return{viewMode:r,setViewMode:t=>{if(a(t),!(typeof window>"u")){try{localStorage.setItem(s,t)}catch(e){console.warn("Failed to save view mode preference to localStorage",e)}try{const e=new URL(window.location.href);e.searchParams.set("viewMode",t),window.history.replaceState({},"",e.toString()),n.sendCatalogEntitiesViewModeChangedMessage([{mode:t,id:t,object:"view_mode",uri:window.location.href}])}catch(e){console.warn("Failed to update viewMode query parameter",e)}}}}}export{u as useCatalogViewMode};
1
+ import{useRef as s,useState as c}from"react";import{generateBeforeAfterContext as d}from"@redocly/theme/core/utils";import{telemetry as w}from"../../telemetry/index.js";const l="catalog-view-mode",u="table";function m(a){const[o,n]=c(a??u),r=s(o);return{viewMode:o,setViewMode:t=>{const i=r.current;if(r.current=t,n(t),!(typeof window>"u")){try{localStorage.setItem(l,t)}catch(e){console.warn("Failed to save view mode preference to localStorage",e)}try{const e=new URL(window.location.href);e.searchParams.set("viewMode",t),window.history.replaceState({},"",e.toString()),w.sendCatalogEntitiesViewModeChangedMessage([...d("catalogEntitiesViewModeId","button",{mode:i},{mode:t})])}catch(e){console.warn("Failed to update viewMode query parameter",e)}}}}}export{m as useCatalogViewMode};
@@ -1 +1 @@
1
- import{useInfiniteQuery as y}from"@tanstack/react-query";import{useMemo as d,useRef as w}from"react";import{withPathPrefix as R}from"@redocly/theme/core/utils";import{getNextPageParam as S}from"../../../utils/catalog/get-next-page-param";import{useSearchTracker as F}from"./useSearchTracker";function x({limit:o=20,filter:i,sort:s="updated_at",search:n}={},r){const m=r?r.items.map(e=>e.key).join("-"):[],c=w(!0),l=c.current&&r;c.current&&(c.current=!1);const a=y({queryFn:async e=>{const t=new URLSearchParams;i&&t.append("filter",i),s&&t.append("sort",s),o&&t.append("limit",o.toString()),n&&t.append("search",n),e.pageParam&&Object.entries(e.pageParam).forEach(([P,f])=>{f!=null&&t.append(P,f.toString())});const p=new URL(R("/bff/catalog-entities"),window.location.origin);p.search=t.toString();const u=await fetch(p.toString());if(!u.ok)throw new Error(`Failed to fetch catalog entities from ${p.pathname}`);return u.json()},queryKey:["bff/catalog-entities",{limit:o,filter:i,sort:s,search:n,initialDataKeys:m}],initialData:l?{pages:[r],pageParams:[null]}:void 0,initialPageParam:null,getNextPageParam:S,refetchOnMount:!0,placeholderData:e=>e}),h=d(()=>{const e=a.data?.pages||[];return e[e.length-1]?.page.total},[a.data?.pages]),g=d(()=>(a.data?.pages||[]).flatMap(e=>e.items||[]),[a.data?.pages]);return F({isLoading:a.isLoading||a.isFetching,items:g,apiResource:"entities",searchQuery:n??""}),{query:a,items:g,total:h}}export{x as useFetchCatalogEntities};
1
+ import{useInfiniteQuery as y}from"@tanstack/react-query";import{useMemo as u,useRef as w}from"react";import{withPathPrefix as R}from"@redocly/theme/core/utils";import{getNextPageParam as S}from"../../../utils/catalog/get-next-page-param";import{useSearchTracker as F}from"./useSearchTracker";function x({limit:o=20,filter:i,sort:s="updated_at",search:n}={},r){const d=r?r.items.map(e=>e.key).join("-"):[],c=w(!0),m=c.current&&r;c.current&&(c.current=!1);const a=y({queryFn:async e=>{const t=new URLSearchParams;i&&t.append("filter",i),s&&t.append("sort",s),o&&t.append("limit",o.toString()),n&&t.append("search",n),e.pageParam&&Object.entries(e.pageParam).forEach(([P,f])=>{f!=null&&t.append(P,f.toString())});const p=new URL(R("/bff/catalog-entities"),window.location.origin);p.search=t.toString();const g=await fetch(p.toString());if(!g.ok)throw new Error(`Failed to fetch catalog entities from ${p.pathname}`);return g.json()},queryKey:["bff/catalog-entities",{limit:o,filter:i,sort:s,search:n,initialDataKeys:d}],initialData:m?{pages:[r],pageParams:[null]}:void 0,initialPageParam:null,getNextPageParam:S,refetchOnMount:!0,placeholderData:e=>e}),l=u(()=>{const e=a.data?.pages||[];return e[e.length-1]?.page.total},[a.data?.pages]),h=u(()=>(a.data?.pages||[]).flatMap(e=>e.items||[]),[a.data?.pages]);return F({isLoading:a.isLoading||a.isFetching,apiResource:"entities",searchQuery:n??""}),{query:a,items:h,total:l}}export{x as useFetchCatalogEntities};
@@ -1 +1 @@
1
- import{useInfiniteQuery as y}from"@tanstack/react-query";import{useMemo as m,useRef as S}from"react";import{withPathPrefix as F}from"@redocly/theme/core/utils";import{getNextPageParam as R}from"../../../utils/catalog/get-next-page-param";import{useSearchTracker as b}from"./useSearchTracker";function E({entityKey:g,limit:o=20,filter:i,sort:s="updated_at",search:r},n){const l=n?n.items.map(e=>e.key).join("-"):[],c=S(!0),h=c.current&&n;c.current&&(c.current=!1);const t=y({queryFn:async e=>{const a=new URLSearchParams;i&&a.append("filter",i),s&&a.append("sort",s),o&&a.append("limit",o.toString()),r&&a.append("search",r),e.pageParam&&Object.entries(e.pageParam).forEach(([w,f])=>{f!=null&&a.append(w,f.toString())});const p=new URL(F(`/bff/catalog-related-entities/${g}`),window.location.origin);p.search=a.toString();const d=await fetch(p.toString());if(!d.ok)throw new Error(`Failed to fetch catalog entity relations from ${p.pathname}`);return d.json()},queryKey:["bff/catalog-entities-relations",{limit:o,filter:i,sort:s,search:r,entityKey:g,initialDataKeys:l}],initialPageParam:null,initialData:h?{pages:[n],pageParams:[null]}:void 0,refetchOnMount:!0,getNextPageParam:R}),P=m(()=>{const e=t.data?.pages||[];return e[e.length-1]?.page?.total},[t.data?.pages]),u=m(()=>(t.data?.pages||[]).flatMap(e=>e.items||[]),[t.data?.pages]);return b({isLoading:t.isLoading||t.isFetching,items:u,apiResource:"related_entities",searchQuery:r??""}),{query:t,items:u,total:P}}export{E as useFetchCatalogEntitiesRelations};
1
+ import{useInfiniteQuery as y}from"@tanstack/react-query";import{useMemo as d,useRef as S}from"react";import{withPathPrefix as F}from"@redocly/theme/core/utils";import{getNextPageParam as R}from"../../../utils/catalog/get-next-page-param";import{useSearchTracker as b}from"./useSearchTracker";function E({entityKey:g,limit:o=20,filter:i,sort:s="updated_at",search:r},n){const m=n?n.items.map(e=>e.key).join("-"):[],c=S(!0),l=c.current&&n;c.current&&(c.current=!1);const t=y({queryFn:async e=>{const a=new URLSearchParams;i&&a.append("filter",i),s&&a.append("sort",s),o&&a.append("limit",o.toString()),r&&a.append("search",r),e.pageParam&&Object.entries(e.pageParam).forEach(([w,f])=>{f!=null&&a.append(w,f.toString())});const p=new URL(F(`/bff/catalog-related-entities/${g}`),window.location.origin);p.search=a.toString();const u=await fetch(p.toString());if(!u.ok)throw new Error(`Failed to fetch catalog entity relations from ${p.pathname}`);return u.json()},queryKey:["bff/catalog-entities-relations",{limit:o,filter:i,sort:s,search:r,entityKey:g,initialDataKeys:m}],initialPageParam:null,initialData:l?{pages:[n],pageParams:[null]}:void 0,refetchOnMount:!0,getNextPageParam:R}),h=d(()=>{const e=t.data?.pages||[];return e[e.length-1]?.page?.total},[t.data?.pages]),P=d(()=>(t.data?.pages||[]).flatMap(e=>e.items||[]),[t.data?.pages]);return b({isLoading:t.isLoading||t.isFetching,apiResource:"related_entities",searchQuery:r??""}),{query:t,items:P,total:h}}export{E as useFetchCatalogEntitiesRelations};
@@ -1,12 +1,9 @@
1
- import type { BffCatalogEntity } from '@redocly/theme/core/types';
2
- type BaseEntity = Pick<BffCatalogEntity, 'id' | 'key' | 'type'>;
3
1
  type ApiResource = 'entities' | 'related_entities';
4
- type SearchTrackerProps<T extends BaseEntity> = {
2
+ type SearchTrackerProps = {
5
3
  isLoading: boolean;
6
- items: T[];
7
4
  apiResource: ApiResource;
8
5
  searchQuery: string;
9
6
  };
10
- export declare function useSearchTracker<T extends BaseEntity>({ isLoading, items, apiResource, searchQuery }: SearchTrackerProps<T>): void;
7
+ export declare function useSearchTracker({ isLoading, apiResource, searchQuery }: SearchTrackerProps): void;
11
8
  export {};
12
9
  //# sourceMappingURL=useSearchTracker.d.ts.map
@@ -1 +1 @@
1
- import{useCallback as g,useEffect as m,useRef as f}from"react";import{useCatalogEntityDetails as d}from"@redocly/theme/core/hooks";import{usePageProps as p}from"../../../providers/hooks.js";import{telemetry as c}from"../../telemetry/index.js";const l={id:"redocly-unknown",object:"catalog_entity",uri:"redocly-unknown"},C={entities:"catalog_entity",related_entities:"catalog_entity"},E={entities:({items:e,searchQuery:n})=>{const t=e[0]??l;c.sendCatalogEntitiesListSearchQueryMessage([{query:n,...t}])},related_entities:({items:e,searchQuery:n})=>{const t=e[0]??l;c.sendCatalogEntitiesRelatedEntitiesListSearchQueryMessage([{query:n,...t}])}};function j({isLoading:e,items:n,apiResource:t,searchQuery:i}){const{catalogConfig:u,entitiesCatalogConfig:y}=p(),o=f(i),{getEntityDetailsLink:s}=d({catalogConfig:u,entitiesCatalogConfig:y}),a=g(r=>({id:r.id,object:C[t],uri:s(r)}),[s,t]);m(()=>{if(e)return;const r=i?.trim();!r||r===o.current||(o.current=i,E[t]({items:n.map(a),searchQuery:i}))},[n,e,i,t,a])}export{j as useSearchTracker};
1
+ import{useEffect as c,useRef as o}from"react";import{getBaseDataAttributes as a}from"@redocly/theme/core/utils";import{telemetry as n}from"../../telemetry/index.js";const l={entities:t=>{n.sendCatalogEntitiesListSearchQueryMessage([{...a("catalogEntitiesListSearchId","catalogEntitiesListSearch"),query:t}])},related_entities:t=>{n.sendCatalogEntitiesRelatedEntitiesListSearchQueryMessage([{...a("catalogEntitiesRelatedEntitiesListSearchId","catalogEntitiesRelatedEntitiesListSearch"),query:t}])}};function m({isLoading:t,apiResource:i,searchQuery:e}){const r=o(e);c(()=>{if(t)return;const s=e?.trim();!s||s===r.current||(r.current=e,l[i](e))},[t,e,i])}export{m as useSearchTracker};
@@ -1 +1 @@
1
- import{Hono as f}from"hono";import{TrieRouter as m}from"hono/router/trie-router";import{Logger as w}from"../tools/notifiers/logger.js";import{envConfig as l}from"../config/env-config.js";import{importApiRoutesHandler as D}from"./import-api-routes-handlers.js";import{getBodyWithSizeCheck as q}from"./helpers/get-body-with-size-check.js";import{setupLogger as R}from"./helpers/setup-logger.js";import{telemetry as d}from"../telemetry/index.js";import{KvService as g}from"../persistence/kv/services/kv-service.js";import{enhanceContext as h}from"./helpers/enhance-context.js";const u=new w,y=8e3,A=100;async function T(e){d.initialize(),R(u,e.requestHandlerId);const n=new f({router:new m}),s=await H(e),o=async()=>g.getInstance({baseDbDir:e.serverOutDir,sqldRemoteDatabaseUrl:e.sqldRemoteDatabaseUrl,sqldRemoteDatabaseAuthToken:e.sqldRemoteDatabaseAuthToken});n.all(e.slug,async t=>{if(typeof s!="function"){const a=`API function "${e.slug}" does not export a default function. Add 'export default function (request, context) { ... }' to the file.`;return u.error(`[${e.requestHandlerId}] ${a}`),l.isDevelopMode?t.text(a,500):t.newResponse(null,404)}const r=await s(t.req.raw,h({honoCtx:t,ctx:e.ctxData,telemetry:d,getKv:o}),e.staticData);return r instanceof Response?r:typeof r=="string"?t.text(r):t.json(r)}),n.onError((t,r)=>{if(u.error(`[${e.requestHandlerId}] ${t.message} ${t.stack}`),t instanceof SyntaxError)return r.text(t.message,500);throw t});const i=await n.request(e.req.url,e.req),c=await q(i,e.maxResponseSizeMB);return{status:i.status,headers:Object.fromEntries(i.headers.entries()),body:c}}async function p(e){const n=Date.now()+(l.isDevelopMode?y:0);let s;for(;;){try{const{requestHandlers:o}=await D(e.serverOutDir),i=o?.[e.requestHandlerId];if(typeof i=="function")return i;s=new Error(`Request handler "${e.requestHandlerId}" is not available yet.`)}catch(o){s=o}if(Date.now()>=n)throw s;await new Promise(o=>setTimeout(o,A))}}async function H(e){return(await(await p(e))()).default}export{T as executeApiRoute};
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 S,getCookie as q,setCookie as b}from"hono/cookie";const j=new Set([101,103,204,205,304]),l=t=>t!==void 0&&j.has(t),k=({honoCtx:t,ctx:d,telemetry:o,getKv:m})=>{const u=t.req.routePath,p=t.req.param(),f=Object.fromEntries(Object.entries(p).map(([e,r])=>{if(r&&(u.includes(`/:${e}{.*}`)||u.includes(`/:${e}{.+}`))){const a=r.split("/").filter(Boolean);return[e,a.length?a:void 0]}return[e,r]}));let n;const i=e=>{n=e,t.status(e)},s={...t.res,status:e=>(i(e),s),json:e=>l(n)?t.body(null):t.json(e),text:(e,r)=>(r&&i(r),l(n)?t.body(null):t.text(e)),redirect:(e,r=302)=>t.redirect(e,r),setCookie:(e,r,c)=>(b(t,e,r,c),s),deleteCookie:e=>(S(t,e),s)};return{...d,...s,params:f,query:t.req.query()||{},cookies:q(t),telemetry:o,getKv:m}};export{k as enhanceContext};
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,2 @@
1
+ export declare function isMockServerRoute(requestHandlerId: string): boolean;
2
+ //# sourceMappingURL=is-mock-server.d.ts.map
@@ -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,3 @@
1
+ import type { KvKey, KvService, MockStateStore } from '@redocly/config';
2
+ export declare function createKvStateStore(getKv: () => Promise<KvService>, namespace?: KvKey): MockStateStore;
3
+ //# sourceMappingURL=kv-state-store.d.ts.map
@@ -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,3 @@
1
+ import type { MockJsExecutor, MockJsExecutorOptions } from '@redocly/config';
2
+ export declare function createQuickJsMockExecutor(options?: MockJsExecutorOptions): MockJsExecutor;
3
+ //# sourceMappingURL=mock-js-executor.d.ts.map
@@ -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};
@@ -1 +1 @@
1
- import*as p from"workerpool";import{withPathPrefix as f}from"@redocly/theme/core/utils";import{envConfig as l}from"../config/env-config.js";import{telemetry as a}from"../telemetry/index.js";import{TimeoutExceededError as E}from"./errors/timeout-exceeded.js";import{serializeRequest as _}from"./helpers/serialize-request.js";import{apiRoutesWorkers as A,API_ROUTES_WORKER_KEY as T}from"../workers/api-routes-worker-pool.js";import{API_FUNCTIONS_REQUEST_HANDLER_ID as u}from"../plugins/api-functions/index.js";const g=1e4,h=15;async function U(e,r,t){a.initialize();const s=r.get("auth"),i=await t.resolveRouteStaticData(e)||{},n=f(e.slug);try{const{status:o,headers:d,body:c}=await A.exec(T,[{serverOutDir:t.serverOutDir,slug:n,requestHandlerId:e.requestHandlerId,maxResponseSizeMB:h,ctxData:{user:{teams:s.teams,email:s.claims.email,claims:s.claims,idpAccessToken:s.idpAccessToken,idpId:s.claims.idpId,isAuthenticated:s.isAuthenticated},config:t.config,outdir:t.outdir,baseUrl:new URL(r.req.url).origin},staticData:{...i,props:{...i.props,routeSlug:n,outdir:t.outdir}},req:await _(r.req.raw),sqldRemoteDatabaseUrl:l.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:l.SQLD_REMOTE_DATABASE_AUTH_TOKEN}],{timeout:g});R({requestHandlerId:e.requestHandlerId,status:o,ctx:r});const m=c.data?Buffer.from(c.data):null;return new Response(m,{status:o,headers:d})}catch(o){throw M({requestHandlerId:e.requestHandlerId,error:o}),o instanceof p.Promise.TimeoutError?new E("Timeout exceeded"):o}}function R({requestHandlerId:e,status:r,ctx:t}){e.startsWith(u)?a.sendApiFunctionCalledMessage([{object:"api_function",error:null,handler:e,status:r}]):a.sendMockServerCalledMessage([{object:"mock_server",error:null,handler:e,headers:{"user-agent":t.req.header("user-agent")??null},status:r}])}function M({requestHandlerId:e,error:r}){const t={handler:e,message:r?.message??null,stack:r?.stack??null};e.startsWith(u)?a.sendApiFunctionErrorMessage([{object:"api_function",...t}]):a.sendMockServerErrorMessage([{object:"mock_server",...t}])}export{U as runApiRoutesWorker,M as sendTelemetryErrorMessage};
1
+ import*as p from"workerpool";import{withPathPrefix as E}from"@redocly/theme/core/utils";import{envConfig as u}from"../config/env-config.js";import{telemetry as a}from"../telemetry/index.js";import{TimeoutExceededError as f}from"./errors/timeout-exceeded.js";import{serializeRequest as g}from"./helpers/serialize-request.js";import{apiRoutesWorkers as A,API_ROUTES_WORKER_KEY as T}from"../workers/api-routes-worker-pool.js";import{API_FUNCTIONS_REQUEST_HANDLER_ID as d}from"../plugins/api-functions/index.js";const _=1e4,h=15;async function q(e,r,t){a.initialize();const i=r.get("auth"),o=await t.resolveRouteStaticData(e)||{},n=E(e.slug);try{const{status:s,headers:l,body:c}=await A.exec(T,[{serverOutDir:t.serverOutDir,slug:n,requestHandlerId:e.requestHandlerId,maxResponseSizeMB:h,ctxData:{user:{teams:i.teams,email:i.claims.email,claims:i.claims,idpAccessToken:i.idpAccessToken,idpId:i.claims.idpId,isAuthenticated:i.isAuthenticated},config:t.config,outdir:t.outdir,baseUrl:new URL(r.req.url).origin},staticData:{...o,props:{...o.props,routeSlug:n,outdir:t.outdir}},req:await g(r.req.raw),sqldRemoteDatabaseUrl:u.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:u.SQLD_REMOTE_DATABASE_AUTH_TOKEN}],{timeout:_});R({requestHandlerId:e.requestHandlerId,status:s,ctx:r});const m=c.data?Buffer.from(c.data):null;return new Response(m,{status:s,headers:l})}catch(s){throw I({requestHandlerId:e.requestHandlerId,error:s}),s instanceof p.Promise.TimeoutError?new f("Timeout exceeded"):s}}function R({requestHandlerId:e,status:r,ctx:t}){e.startsWith(d)?a.sendApiFunctionCalledMessage([{id:"apiFunctionId",object:"apiFunction",uri:"urn:redocly:realm:api:apiFunction:apiFunctionId",handler:e,status:r,message:null}]):a.sendMockServerCalledMessage([{object:"mock_server",error:null,handler:e,headers:{"user-agent":t.req.header("user-agent")??null},status:r}])}function I({requestHandlerId:e,error:r}){e.startsWith(d)?a.sendApiFunctionErrorMessage([{id:"apiFunctionId",object:"apiFunction",uri:"urn:redocly:realm:api:apiFunction:apiFunctionId",status:500,handler:e,message:r.message}]):a.sendMockServerErrorMessage([{object:"mock_server",handler:e,message:r.message}])}export{q as runApiRoutesWorker,I as sendTelemetryErrorMessage};
@@ -54,6 +54,7 @@ declare function loadEnvConfig(): {
54
54
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
55
55
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: boolean | undefined;
56
56
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: boolean | undefined;
57
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: boolean | undefined;
57
58
  SEARCH_DEV_REINIT?: string | undefined;
58
59
  SEARCH_DEV_DEBUG: boolean;
59
60
  TYPESENSE_API_URL?: string | undefined;
@@ -66,6 +67,7 @@ declare function loadEnvConfig(): {
66
67
  SQLD_REMOTE_DATABASE_URL?: string | undefined;
67
68
  SQLD_REMOTE_DATABASE_AUTH_TOKEN?: string | undefined;
68
69
  FORCE_CATALOG_CACHE_REVALIDATE?: boolean | undefined;
70
+ CATALOG_MAX_REVISIONS?: number | undefined;
69
71
  SCORECARDS_POLLING_INTERVAL_MS?: string | undefined;
70
72
  OTEL_TRACES_URL?: string | undefined;
71
73
  REQUEST_ID?: string | undefined;
@@ -134,6 +136,7 @@ export declare const envConfig: {
134
136
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
135
137
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: boolean | undefined;
136
138
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: boolean | undefined;
139
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: boolean | undefined;
137
140
  SEARCH_DEV_REINIT?: string | undefined;
138
141
  SEARCH_DEV_DEBUG: boolean;
139
142
  TYPESENSE_API_URL?: string | undefined;
@@ -146,6 +149,7 @@ export declare const envConfig: {
146
149
  SQLD_REMOTE_DATABASE_URL?: string | undefined;
147
150
  SQLD_REMOTE_DATABASE_AUTH_TOKEN?: string | undefined;
148
151
  FORCE_CATALOG_CACHE_REVALIDATE?: boolean | undefined;
152
+ CATALOG_MAX_REVISIONS?: number | undefined;
149
153
  SCORECARDS_POLLING_INTERVAL_MS?: string | undefined;
150
154
  OTEL_TRACES_URL?: string | undefined;
151
155
  REQUEST_ID?: string | undefined;
@@ -58,6 +58,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
58
58
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT: z.ZodOptional<z.ZodString>;
59
59
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
60
60
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
61
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
61
62
  } & {
62
63
  SEARCH_DEV_REINIT: z.ZodOptional<z.ZodString>;
63
64
  SEARCH_DEV_DEBUG: z.ZodEffects<z.ZodDefault<z.ZodEnum<["true", "false"]>>, boolean, "false" | "true" | undefined>;
@@ -74,6 +75,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
74
75
  SQLD_REMOTE_DATABASE_AUTH_TOKEN: z.ZodOptional<z.ZodString>;
75
76
  } & {
76
77
  FORCE_CATALOG_CACHE_REVALIDATE: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
78
+ CATALOG_MAX_REVISIONS: z.ZodOptional<z.ZodNumber>;
77
79
  } & {
78
80
  SCORECARDS_POLLING_INTERVAL_MS: z.ZodOptional<z.ZodString>;
79
81
  } & {
@@ -135,6 +137,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
135
137
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
136
138
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: boolean | undefined;
137
139
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: boolean | undefined;
140
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: boolean | undefined;
138
141
  SEARCH_DEV_REINIT?: string | undefined;
139
142
  SEARCH_DEV_DEBUG: boolean;
140
143
  TYPESENSE_API_URL?: string | undefined;
@@ -147,6 +150,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
147
150
  SQLD_REMOTE_DATABASE_URL?: string | undefined;
148
151
  SQLD_REMOTE_DATABASE_AUTH_TOKEN?: string | undefined;
149
152
  FORCE_CATALOG_CACHE_REVALIDATE?: boolean | undefined;
153
+ CATALOG_MAX_REVISIONS?: number | undefined;
150
154
  SCORECARDS_POLLING_INTERVAL_MS?: string | undefined;
151
155
  OTEL_TRACES_URL?: string | undefined;
152
156
  REQUEST_ID?: string | undefined;
@@ -205,6 +209,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
205
209
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
206
210
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: "false" | "true" | undefined;
207
211
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: "false" | "true" | undefined;
212
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: "false" | "true" | undefined;
208
213
  SEARCH_DEV_REINIT?: string | undefined;
209
214
  SEARCH_DEV_DEBUG?: "false" | "true" | undefined;
210
215
  TYPESENSE_API_URL?: string | undefined;
@@ -217,6 +222,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
217
222
  SQLD_REMOTE_DATABASE_URL?: string | undefined;
218
223
  SQLD_REMOTE_DATABASE_AUTH_TOKEN?: string | undefined;
219
224
  FORCE_CATALOG_CACHE_REVALIDATE?: "false" | "true" | undefined;
225
+ CATALOG_MAX_REVISIONS?: number | undefined;
220
226
  SCORECARDS_POLLING_INTERVAL_MS?: string | undefined;
221
227
  OTEL_TRACES_URL?: string | undefined;
222
228
  REQUEST_ID?: string | undefined;
@@ -275,6 +281,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
275
281
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
276
282
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: boolean | undefined;
277
283
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: boolean | undefined;
284
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: boolean | undefined;
278
285
  SEARCH_DEV_REINIT?: string | undefined;
279
286
  SEARCH_DEV_DEBUG: boolean;
280
287
  TYPESENSE_API_URL?: string | undefined;
@@ -287,6 +294,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
287
294
  SQLD_REMOTE_DATABASE_URL?: string | undefined;
288
295
  SQLD_REMOTE_DATABASE_AUTH_TOKEN?: string | undefined;
289
296
  FORCE_CATALOG_CACHE_REVALIDATE?: boolean | undefined;
297
+ CATALOG_MAX_REVISIONS?: number | undefined;
290
298
  SCORECARDS_POLLING_INTERVAL_MS?: string | undefined;
291
299
  OTEL_TRACES_URL?: string | undefined;
292
300
  REQUEST_ID?: string | undefined;
@@ -345,6 +353,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
345
353
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
346
354
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: "false" | "true" | undefined;
347
355
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: "false" | "true" | undefined;
356
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: "false" | "true" | undefined;
348
357
  SEARCH_DEV_REINIT?: string | undefined;
349
358
  SEARCH_DEV_DEBUG?: "false" | "true" | undefined;
350
359
  TYPESENSE_API_URL?: string | undefined;
@@ -357,6 +366,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
357
366
  SQLD_REMOTE_DATABASE_URL?: string | undefined;
358
367
  SQLD_REMOTE_DATABASE_AUTH_TOKEN?: string | undefined;
359
368
  FORCE_CATALOG_CACHE_REVALIDATE?: "false" | "true" | undefined;
369
+ CATALOG_MAX_REVISIONS?: number | undefined;
360
370
  SCORECARDS_POLLING_INTERVAL_MS?: string | undefined;
361
371
  OTEL_TRACES_URL?: string | undefined;
362
372
  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 e=o.object({FORCE_CATALOG_CACHE_REVALIDATE:t()});export{e as catalogSchema};
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};
@@ -19,6 +19,8 @@ export declare const featureFlagsSchema: z.ZodObject<{
19
19
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
20
20
  /** Experimental: give the docs MCP `execute` sandbox a guarded `fetch` that can call gateway-eligible APIs. */
21
21
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
22
+ /** Escape hatch: disables the Node Permission Model sandbox on API function and MCP tool worker processes (hosted Reunite environments only). */
23
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
22
24
  }, "strip", z.ZodTypeAny, {
23
25
  NEW_CATALOG_ENABLED?: boolean | undefined;
24
26
  NEW_SCORECARDS_ENABLED?: boolean | undefined;
@@ -31,6 +33,7 @@ export declare const featureFlagsSchema: z.ZodObject<{
31
33
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
32
34
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: boolean | undefined;
33
35
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: boolean | undefined;
36
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: boolean | undefined;
34
37
  }, {
35
38
  NEW_CATALOG_ENABLED?: "false" | "true" | undefined;
36
39
  NEW_SCORECARDS_ENABLED?: "false" | "true" | undefined;
@@ -43,5 +46,6 @@ export declare const featureFlagsSchema: z.ZodObject<{
43
46
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
44
47
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: "false" | "true" | undefined;
45
48
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: "false" | "true" | undefined;
49
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: "false" | "true" | undefined;
46
50
  }>;
47
51
  //# sourceMappingURL=feature-flags.d.ts.map
@@ -1 +1 @@
1
- import{z as _}from"zod";import{envBooleanStringOptional as E}from"../../utils/envs/env-boolean-string.js";const D=_.object({NEW_CATALOG_ENABLED:E(),NEW_SCORECARDS_ENABLED:E(),ENABLE_COMMENTS:_.string().optional(),REDOCLY_EXP_DISABLE_MD_VALIDATION:E(),ENABLE_SOURCE_MAPS:E(),PLAN_GATES:_.string().optional(),REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_ENABLED:E(),REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_URL:_.string().optional(),REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT:_.string().optional(),REDOCLY_EXP_MCP_CODE_MODE_ENABLED:E(),REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED:E()});export{D as featureFlagsSchema};
1
+ import{z as _}from"zod";import{envBooleanStringOptional as E}from"../../utils/envs/env-boolean-string.js";const D=_.object({NEW_CATALOG_ENABLED:E(),NEW_SCORECARDS_ENABLED:E(),ENABLE_COMMENTS:_.string().optional(),REDOCLY_EXP_DISABLE_MD_VALIDATION:E(),ENABLE_SOURCE_MAPS:E(),PLAN_GATES:_.string().optional(),REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_ENABLED:E(),REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_URL:_.string().optional(),REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT:_.string().optional(),REDOCLY_EXP_MCP_CODE_MODE_ENABLED:E(),REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED:E(),REDOCLY_DISABLE_API_FUNCTION_SANDBOX:E()});export{D as featureFlagsSchema};
@@ -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 F,eq as D,inArray as T,isNotNull as v,isNull as N,ne as $,or as g,sql as w}from"drizzle-orm";import{ulid as B}from"ulid";import{logger as A}from"../../../tools/notifiers/logger.js";import{DatabaseConnectionFactory as L}from"../../../providers/database/database-connection-factory.js";import{entitiesAttributesTable as p}from"../../../providers/database/databases/sqlite-db/schemas/entities-attributes-table.js";import{entitiesRelationsTable as d}from"../../../providers/database/databases/sqlite-db/schemas/entities-relations-table.js";import{entitiesTable as l}from"../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{BATCH_TRANSIENT_RETRY as H,withTransientErrorRetry as K}from"../../../providers/database/transient-sqld-error.js";import{envConfig as k}from"../../../config/env-config.js";import{ATTRIBUTES_UPSERT_SET as M,DEFAULT_CHUNK_SIZE as z,ENTITIES_UPSERT_SET as J,MAX_BATCH_PAYLOAD_BYTES as V,MAX_LOGGED_DB_ERROR_LENGTH as O,MIN_RETRYABLE_BATCH_SIZE as x,RELATIONS_UPSERT_SET as Y}from"./consts.js";import{INTERNAL_CATALOG_PUBLISH_KEY_PREFIX as X,RemotePublishLockService as b}from"./remote-publish-lock-service.js";const U=`${X}::last-successful-publish`;class o{static async publishFromLocalToRemote(e){const t=B();if(k.isDevelopMode||!k.isProductionEnv)return{runId:t,published:!1,shouldFinalizeFileHashes:!0};const{baseDbDir:n,chunkSize:r=z,changedSourceFiles:s=[],removedSourceFiles:a=[]}=e,[i,c]=await Promise.all([L.create({baseDbDir:n,databaseType:"local"}),L.create({baseDbDir:n,databaseType:"remote"})]);if(!i||!c)return A.warn("Skipping entities remote sync: local or remote connection is unavailable"),{runId:t,published:!1,shouldFinalizeFileHashes:!1};const u="changedSourceFiles"in e||"removedSourceFiles"in e,f=Array.from(new Set([...s,...a]));if(u&&f.length===0)return{runId:t,published:!0,shouldFinalizeFileHashes:!0};const h=u?s.length?s:"none":void 0,m=u?f:await o.#w(i),S=await b.runUnderRemotePublishLock({runId:t,remoteConnection:c,run:async()=>{const R=o.#d(),y=await o.#i({remoteConnection:c,token:R});if(!y.shouldPublish)return A.info(`Publishing entities to the database skipped: ${y.reason}`),!1;try{return await o.#h({runId:t,chunkSize:r,localConnection:i,remoteConnection:c,sourceFilesFilter:h}),await o.#m({runId:t,chunkSize:r,localConnection:i,remoteConnection:c,sourceFilesFilter:h}),await o.#y({chunkSize:r,localConnection:i,remoteConnection:c,sourceFilesFilter:h}),await o.#F({runId:t,remoteConnection:c,sourceFilesFilter:m}),await o.#a({runId:t,remoteConnection:c,token:R}),!0}catch(I){const E=o.#o(I);throw A.error(`Failed to publish entities to the database: ${E}`),new Error(`[CatalogEntitiesPublisher.publishFromLocalToRemote] Failed runId=${t}. ${E}`,{cause:I instanceof Error?I:void 0})}}});return{runId:t,published:S,shouldFinalizeFileHashes:!0}}static async#i({remoteConnection:e,token:t}){if(!t)return{shouldPublish:!0};const n=o.#n(),r=await b.getInternalCacheValue({remoteConnection:e,key:n});if(!r)return{shouldPublish:!0};const s=o.#l(r);return s?o.#p(t,s.token)<0?{shouldPublish:!1,reason:`Freshness gate blocked stale publish: current ${t.source}="${t.value}" is older than latest successful ${s.token.source}="${s.token.value}" recordedAt=${s.recordedAt}.`}:{shouldPublish:!0}:{shouldPublish:!0}}static async#a({runId:e,remoteConnection:t,token:n}){if(!n)return;const r=o.#n(),s=JSON.stringify({runId:e,recordedAt:new Date().toISOString(),token:n});await b.upsertInternalCacheValue({remoteConnection:t,key:r,value:s,ttl:null})}static#n(){const e=k.PUBLIC_REDOCLY_BRANCH_NAME?.trim();return e?`${U}::branch::${e}`:`${U}::default`}static#l(e){try{const t=JSON.parse(e);return typeof t.runId!="string"||typeof t.recordedAt!="string"||!o.#u(t.token)?null:{runId:t.runId,recordedAt:t.recordedAt,token:t.token}}catch{return null}}static#u(e){if(!e||typeof e!="object")return!1;const t=e;return typeof t.value=="string"&&t.source==="PROJECT_BUILD_ID"}static#d(){const e=[{source:"PROJECT_BUILD_ID",value:k.PROJECT_BUILD_ID}];for(const t of e){const n=t.value?.trim();if(!n)continue;const r=o.#f(n);if(r)return{source:t.source,value:r}}return null}static#f(e){return e.match(/[0-9A-HJKMNP-TV-Z]{26}/i)?.[0]?.toUpperCase()??null}static#p(e,t){return e.value===t.value?0:e.value>t.value?1:-1}static#h(e){const{runId:t,localConnection:n,remoteConnection:r,sourceFilesFilter:s,chunkSize:a}=e,i=o.#e(s);return o.#t({chunkSize:a,sourceFilesFilter:s,selectRows:(c,u)=>n.client.client.select().from(l).where(F(D(l.source,"file"),i?T(l.sourceFile,i):void 0)).limit(u).offset(c).all(),decorateRow:c=>({...c,lastSeenRunId:t}),runUpsert:async c=>{await r.client.client.insert(l).values(c).onConflictDoUpdate({target:[l.key,l.source,l.revision,l.version],set:J}).run()},describeSkippedRow:c=>`entity key=${String(c.key)}`})}static#m(e){const{runId:t,localConnection:n,remoteConnection:r,sourceFilesFilter:s,chunkSize:a}=e,i=o.#e(s);return o.#t({chunkSize:a,sourceFilesFilter:s,selectRows:(c,u)=>n.client.client.select().from(d).where(F(v(d.sourceFile),i?T(d.sourceFile,i):void 0)).limit(u).offset(c).all(),decorateRow:c=>({...c,lastSeenRunId:t}),runUpsert:async c=>{await r.client.client.insert(d).values(c).onConflictDoUpdate({target:[d.sourceKey,d.targetKey,d.sourceVersion,d.targetVersion,d.sourceRevision,d.targetRevision,d.sourceToTargetRelation],set:Y}).run()},describeSkippedRow:c=>{const u=c;return`relation sourceKey=${String(u.sourceKey)} targetKey=${String(u.targetKey)}`}})}static#y(e){const{localConnection:t,remoteConnection:n,sourceFilesFilter:r,chunkSize:s}=e,a=o.#e(r);return o.#t({chunkSize:s,sourceFilesFilter:r,selectRows:(i,c)=>t.client.client.select().from(p).where(a?w`EXISTS (
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,22 @@
1
+ import type { DatabaseConnection } from './types';
2
+ export type PruneLocalRevisionsResult = {
3
+ prunedEntities: number;
4
+ prunedRelations: number;
5
+ legacyRelationsHealed: number;
6
+ legacyRelationsDeleted: number;
7
+ affectedSourceFiles: string[];
8
+ vacuumed: boolean;
9
+ };
10
+ export declare class CatalogEntitiesRevisionsPruner {
11
+ #private;
12
+ static pruneLocalRevisions({ localConnection, maxRevisions, vacuumThreshold }: {
13
+ localConnection: DatabaseConnection;
14
+ maxRevisions: number;
15
+ vacuumThreshold?: number;
16
+ }): Promise<PruneLocalRevisionsResult>;
17
+ static confirmPrunedFiles({ localConnection, sourceFiles }: {
18
+ localConnection: DatabaseConnection;
19
+ sourceFiles: string[];
20
+ }): Promise<void>;
21
+ }
22
+ //# sourceMappingURL=catalog-entities-revisions-pruner.d.ts.map
@@ -0,0 +1,62 @@
1
+ import{and as U,eq as F,inArray as E,isNull as v,sql as a}from"drizzle-orm";import{entitiesRelationsTable as d}from"../../../providers/database/databases/sqlite-db/schemas/entities-relations-table.js";import{entitiesTable as w}from"../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{fileHashesTable as l}from"../../../providers/database/databases/sqlite-db/schemas/file-hashes-table.js";import{FileHashStatus as A}from"../../../persistence/file-hashes/types.js";import{DELETE_IDS_CHUNK_SIZE as _,VACUUM_AFTER_PRUNED_ROWS as g}from"./consts.js";const y={prunedEntities:0,prunedRelations:0,legacyRelationsHealed:0,legacyRelationsDeleted:0,affectedSourceFiles:[],vacuumed:!1};class c{static async pruneLocalRevisions({localConnection:n,maxRevisions:r,vacuumThreshold:s=g}){if(!Number.isFinite(r)||r<=0)return y;const t=n.client.client,S=await t.transaction(async i=>{const C=await i.all(a`
2
+ SELECT DISTINCT e.source_file
3
+ FROM entities_relations r
4
+ JOIN entities e ON e.key = r.source_key AND e.source_file IS NOT NULL
5
+ WHERE r.source_file IS NULL
6
+ `),h=await i.run(a`
7
+ UPDATE entities_relations
8
+ SET source_file = (
9
+ SELECT e.source_file FROM entities e
10
+ WHERE e.key = entities_relations.source_key
11
+ AND e.source_file IS NOT NULL
12
+ ORDER BY e.is_current DESC, e.revision DESC
13
+ LIMIT 1
14
+ )
15
+ WHERE source_file IS NULL
16
+ AND EXISTS (
17
+ SELECT 1 FROM entities e
18
+ WHERE e.key = entities_relations.source_key
19
+ AND e.source_file IS NOT NULL
20
+ )
21
+ `),N=Number(h.rowsAffected??0),k=await i.delete(d).where(v(d.sourceFile)).run(),T=Number(k.rowsAffected??0),u=await i.all(a`
22
+ SELECT id, source_file
23
+ FROM (
24
+ SELECT id,
25
+ source_file,
26
+ is_current,
27
+ is_default_version,
28
+ ROW_NUMBER() OVER (
29
+ PARTITION BY key, version
30
+ ORDER BY revision DESC, id DESC
31
+ ) AS revision_rank
32
+ FROM entities
33
+ WHERE source = 'file' AND source_file IS NOT NULL
34
+ )
35
+ WHERE revision_rank > ${r}
36
+ AND COALESCE(is_current, 1) = 0
37
+ AND COALESCE(is_default_version, 0) = 0
38
+ `),f=new Set(C.map(e=>e.source_file));for(const e of u)e.source_file&&f.add(e.source_file);if(u.length===0&&N===0&&T===0)return{legacyRelationsHealed:N,legacyRelationsDeleted:T,prune:null};for(const e of c.#e(u.map(O=>O.id),_))await i.delete(w).where(E(w.id,e)).run();const D=await i.all(a`
39
+ SELECT id, source_file
40
+ FROM entities_relations r
41
+ WHERE r.source_file IS NOT NULL
42
+ AND (
43
+ (
44
+ r.source_revision != ''
45
+ AND NOT EXISTS (
46
+ SELECT 1 FROM entities e
47
+ WHERE e.key = r.source_key
48
+ AND e.revision = r.source_revision
49
+ AND (r.source_version = '' OR e.version = r.source_version)
50
+ )
51
+ )
52
+ OR (
53
+ r.target_revision != ''
54
+ AND NOT EXISTS (
55
+ SELECT 1 FROM entities e
56
+ WHERE e.key = r.target_key
57
+ AND e.revision = r.target_revision
58
+ AND (r.target_version = '' OR e.version = r.target_version)
59
+ )
60
+ )
61
+ )
62
+ `);for(const e of D)e.source_file&&f.add(e.source_file);for(const e of c.#e(D.map(O=>O.id),_))await i.delete(d).where(E(d.id,e)).run();const I=new Date().toISOString();for(const e of c.#e(Array.from(f),_))await i.update(l).set({status:A.OUTDATED,updatedAt:I}).where(E(l.filePath,e)).run();return{legacyRelationsHealed:N,legacyRelationsDeleted:T,prune:{prunedEntities:u.length,prunedRelations:D.length,affectedSourceFiles:Array.from(f)}}}),{legacyRelationsHealed:p,legacyRelationsDeleted:R,prune:o}=S;if(!o&&p===0&&R===0)return y;const m=(o?.prunedEntities??0)+(o?.prunedRelations??0)+R;let L=!1;return m>=s&&(await t.run(a`VACUUM`),L=!0),{prunedEntities:o?.prunedEntities??0,prunedRelations:o?.prunedRelations??0,legacyRelationsHealed:p,legacyRelationsDeleted:R,affectedSourceFiles:o?.affectedSourceFiles??[],vacuumed:L}}static async confirmPrunedFiles({localConnection:n,sourceFiles:r}){if(r.length===0)return;const s=n.client.client,t=new Date().toISOString();for(const S of c.#e(r,_))await s.update(l).set({status:A.UP_TO_DATE,updatedAt:t}).where(U(E(l.filePath,S),F(l.status,A.OUTDATED))).run()}static#e(n,r){const s=[];for(let t=0;t<n.length;t+=r)s.push(n.slice(t,t+r));return s}}export{c 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,a=2,l=5e5,u={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`},r={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`},_={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{_ as ATTRIBUTES_UPSERT_SET,t as DEFAULT_CHUNK_SIZE,u as ENTITIES_UPSERT_SET,l as MAX_BATCH_PAYLOAD_BYTES,c as MAX_LOGGED_DB_ERROR_LENGTH,a as MIN_RETRYABLE_BATCH_SIZE,r as RELATIONS_UPSERT_SET};
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};