Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/oc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@
"getport": "^0.1.0",
"livereload": "^0.10.3",
"lodash.isequal": "^4.5.0",
"lru-cache": "^10.4.3",
"morgan": "^1.11.0",
"multer": "^2.0.2",
"nice-cache": "^0.0.5",
"oc-client": "^4.0.3",
"oc-client-browser": "*",
"oc-empty-response-handler": "^1.0.2",
Expand Down
11 changes: 0 additions & 11 deletions packages/oc/src/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,3 @@ declare module 'getport' {

export = getPort;
}

declare module 'nice-cache' {
class Cache {
constructor(opt: { refreshInterval?: number; verbose?: boolean });

get(type: string, key: string): any;
set(type: string, key: string, data: unknown): void;
}

export = Cache;
}
12 changes: 11 additions & 1 deletion packages/oc/src/registry/domain/components-details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ export default function componentsDetails(conf: Config, cdn: StorageAdapter) {
for (const [name, componentDetails] of Object.entries(
options.details?.components || {}
)) {
details.components[name] = { ...componentDetails };
details.components[name] = componentDetails;
}

const missing: Array<{ name: string; version: string }> = [];
const componentsNeedingClone = new Set<string>();
for (const [name, versions] of Object.entries(
options.componentsList.components
)) {
Expand All @@ -81,10 +82,19 @@ export default function componentsDetails(conf: Config, cdn: StorageAdapter) {
for (const version of versions) {
if (!componentDetails[version]) {
missing.push({ name, version });
if (options.details?.components?.[name]) {
componentsNeedingClone.add(name);
}
}
}
}

// Only shallow-clone component detail maps that will be mutated. This
// keeps memory usage lower when most components are already up-to-date.
for (const name of componentsNeedingClone) {
details.components[name] = { ...details.components[name] };
}

const limit = pLimit(cdn.maxConcurrentRequests);

await Promise.all(
Expand Down
4 changes: 4 additions & 0 deletions packages/oc/src/registry/domain/options-sanitiser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export default function optionsSanitiser(input: RegistryOptions): Config {
options.verbosity = 0;
}

if (typeof options.cacheMaxSize !== 'number' || options.cacheMaxSize <= 0) {
options.cacheMaxSize = 100;
}

const showApi =
typeof options.discovery === 'boolean'
? true
Expand Down
29 changes: 16 additions & 13 deletions packages/oc/src/registry/routes/helpers/get-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { IncomingHttpHeaders } from 'node:http';
import vm from 'node:vm';
import acceptLanguageParser from 'accept-language-parser';
import type { CookieOptions } from 'express';
import Cache from 'nice-cache';
import * as LRUCacheModule from 'lru-cache';
import Client from 'oc-client';
import emptyResponseHandler from 'oc-empty-response-handler';
import { fromPromise } from 'universalify';
Expand Down Expand Up @@ -135,24 +135,27 @@ function pluginConverter(plugins: Plugins = {}) {

export default function getComponent(conf: Config, repository: Repository) {
const client = Client({ templates: conf.templates });
const cache = new Cache({
verbose: !!conf.verbosity,
refreshInterval: conf.refreshInterval
const LRUCache =
(LRUCacheModule as any).LRUCache ||
(LRUCacheModule as any).default ||
LRUCacheModule;
const cache = new LRUCache({
max: conf.cacheMaxSize ?? 100
});
const convertPlugins = pluginConverter(conf.plugins);

const getEnv = async (
component: Component
): Promise<Record<string, string>> => {
const cacheKey = `${component.name}/${component.version}/.env`;
const cached = cache.get('file-contents', cacheKey);
const cacheKey = `file-contents:${component.name}/${component.version}/.env`;
const cached = cache.get(cacheKey);

if (cached) return cached;

const env = component.oc.files.env
? await repository.getEnv(component.name, component.version)
: {};
cache.set('file-contents', cacheKey, env);
cache.set(cacheKey, env);

return env;
};
Expand Down Expand Up @@ -496,8 +499,8 @@ export default function getComponent(conf: Config, repository: Repository) {
})
});
} else {
const cacheKey = `${component.name}/${component.version}/template.js`;
const cached = cache.get('file-contents', cacheKey);
const cacheKey = `file-contents:${component.name}/${component.version}/template.js`;
const cached = cache.get(cacheKey);
const key = component.oc.files.template.hashKey;
const id = randomUUID();
const renderOptions = {
Expand Down Expand Up @@ -548,7 +551,7 @@ export default function getComponent(conf: Config, repository: Repository) {
templateText,
key
);
cache.set('file-contents', cacheKey, template);
cache.set(cacheKey, template);
returnResult(template);
}
);
Expand Down Expand Up @@ -581,8 +584,8 @@ export default function getComponent(conf: Config, repository: Repository) {
});
}

const cacheKey = `${component.name}/${component.version}/server.js`;
const cached = cache.get('file-contents', cacheKey);
const cacheKey = `file-contents:${component.name}/${component.version}/server.js`;
const cached = cache.get(cacheKey);
const domain = Domain.create();
const setEmptyResponse =
emptyResponseHandler.contextDecorator(returnComponent);
Expand Down Expand Up @@ -731,7 +734,7 @@ export default function getComponent(conf: Config, repository: Repository) {
vm.runInNewContext(dataProvider.content, context, options);
const processData =
context.module.exports['data'] || context.exports['data'];
cache.set('file-contents', cacheKey, processData);
cache.set(cacheKey, processData);

domain.on('error', handleError);
domain.run(() => {
Expand Down
9 changes: 9 additions & 0 deletions packages/oc/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,15 @@ export interface Config<T = any> {
* @default 120000
*/
timeout: number;
/**
* Maximum number of compiled component files (templates, data providers,
* env files) kept in the in-memory LRU cache. Older entries are evicted
* when the limit is reached. Lower values reduce memory usage; higher
* values improve performance for frequently accessed components.
*
* @default 100
*/
cacheMaxSize: number;
/**
* Verbosity level of the console logger (0 = silent).
*
Expand Down
29 changes: 29 additions & 0 deletions packages/oc/test/unit/registry-domain-options-sanitiser.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,35 @@ describe('registry : domain : options-sanitiser', () => {
});
});

describe('cacheMaxSize', () => {
describe('when not provided', () => {
const options = { baseUrl: 'http://my-registry.com' };

it('should default to 100', () => {
expect(sanitise(options).cacheMaxSize).to.equal(100);
});
});

describe('when provided', () => {
const options = { baseUrl: 'http://my-registry.com', cacheMaxSize: 500 };

it('should leave value untouched', () => {
expect(sanitise(options).cacheMaxSize).to.equal(500);
});
});

describe('when provided with an invalid value', () => {
const options = {
baseUrl: 'http://my-registry.com',
cacheMaxSize: -1
};

it('should default to 100', () => {
expect(sanitise(options).cacheMaxSize).to.equal(100);
});
});
});

describe('customHeadersToSkipOnWeakVersion', () => {
describe('when it contains valid elements', () => {
const options = {
Expand Down