oc 0.50.61 → 0.50.63

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 (43) hide show
  1. package/.turbo/turbo-build.log +5 -5
  2. package/.turbo/turbo-lint.log +2 -2
  3. package/.turbo/turbo-test-silent.log +12 -11
  4. package/.turbo/turbo-test.log +1806 -1750
  5. package/CHANGELOG.md +13 -0
  6. package/README.md +16 -0
  7. package/dist/cli/domain/local.js +7 -4
  8. package/dist/components/oc-client/_package/package.json +4 -4
  9. package/dist/components/oc-client/_package/server.js +1 -1
  10. package/dist/components/oc-client/package.json +1 -1
  11. package/dist/index.d.ts +2 -2
  12. package/dist/registry/domain/events-handler.d.ts +2 -5
  13. package/dist/registry/domain/http-server/express-adapter.d.ts +2 -2
  14. package/dist/registry/domain/http-server/express-adapter.js +47 -2
  15. package/dist/registry/domain/http-server/types.d.ts +21 -7
  16. package/dist/registry/domain/metadata-migration.js +8 -1
  17. package/dist/registry/domain/options-sanitiser.d.ts +8 -2
  18. package/dist/registry/domain/options-sanitiser.js +2 -0
  19. package/dist/registry/domain/plugins-initialiser.js +41 -5
  20. package/dist/registry/domain/repository.js +100 -19
  21. package/dist/registry/domain/validators/registry-configuration.d.ts +5 -2
  22. package/dist/registry/domain/validators/registry-configuration.js +5 -0
  23. package/dist/registry/index.js +50 -29
  24. package/dist/registry/middleware/cors.d.ts +4 -0
  25. package/dist/registry/middleware/cors.js +62 -4
  26. package/dist/registry/router.js +4 -2
  27. package/dist/registry/routes/component-info.js +3 -2
  28. package/dist/registry/routes/component-preview.js +3 -2
  29. package/dist/registry/routes/component.d.ts +2 -1
  30. package/dist/registry/routes/component.js +1 -2
  31. package/dist/registry/routes/components.d.ts +2 -1
  32. package/dist/registry/routes/components.js +1 -2
  33. package/dist/registry/routes/helpers/get-component.d.ts +2 -1
  34. package/dist/registry/routes/helpers/get-component.js +7 -8
  35. package/dist/registry/routes/index.js +11 -7
  36. package/dist/resources/index.d.ts +5 -1
  37. package/dist/resources/index.js +5 -1
  38. package/dist/types.d.ts +31 -3
  39. package/dist/utils/bounded-cache.d.ts +10 -0
  40. package/dist/utils/bounded-cache.js +31 -0
  41. package/js-library-optimization-playbook.md +1379 -0
  42. package/package.json +4 -3
  43. package/tsconfig.types.json +10 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  ## Change Log
2
2
 
3
+ ## 0.50.63
4
+
5
+ ### Patch Changes
6
+
7
+ - fc64c56: Reduce registry startup metadata reconciliation requests by skipping versions already present in metadata storage.
8
+
9
+ ## 0.50.62
10
+
11
+ ### Patch Changes
12
+
13
+ - c826175: Add `clearTimeout` to allowed globals for component execution, complementing the existing `setTimeout` global to allow proper timer management.
14
+ - 704890d: Add promise-based `listen` and `close` methods to the HTTP server adapters while retaining callback compatibility with deprecation warnings.
15
+
3
16
  ## 0.50.61
4
17
 
5
18
  ### Patch Changes
package/README.md CHANGED
@@ -212,6 +212,22 @@ These files are one-way projections from the metadata store. They can help with
212
212
  rollback to storage mode, but they do not replace the storage adapter because
213
213
  component statics remain in storage.
214
214
 
215
+ ## Registry events
216
+
217
+ The registry emits an `error` event with the stable payload shape
218
+ `{ code: string, message: string }`:
219
+
220
+ ```js
221
+ registry.on('error', ({ code, message }) => {
222
+ console.error(code, message);
223
+ });
224
+ ```
225
+
226
+ Errors raised by the configured server adapter use the adapter-neutral code
227
+ `SERVER_ERROR`. The message is preserved from the adapter error, and the existing
228
+ `start` callback and promise rejection behavior is unchanged. The public
229
+ TypeScript payload is exported as `RegistryErrorEvent`.
230
+
215
231
  ## Requirements and build status
216
232
 
217
233
  Disclaimer: This project is still under heavy development and the API is likely to change at any time. In case you would find any issues, check the [troubleshooting page](../../CONTRIBUTING.md#troubleshooting).
@@ -41,7 +41,7 @@ const node_util_1 = require("node:util");
41
41
  const fs_extra_1 = __importDefault(require("fs-extra"));
42
42
  const targz_1 = __importDefault(require("targz"));
43
43
  const validator = __importStar(require("../../registry/domain/validators"));
44
- const resources_1 = __importDefault(require("../../resources"));
44
+ const deprecate_1 = __importDefault(require("../../utils/deprecate"));
45
45
  const is_template_legacy_1 = __importDefault(require("../../utils/is-template-legacy"));
46
46
  const clean = __importStar(require("./clean"));
47
47
  const get_components_by_dir_1 = __importDefault(require("./get-components-by-dir"));
@@ -67,16 +67,19 @@ function local() {
67
67
  },
68
68
  getComponentsByDir: (0, get_components_by_dir_1.default)(),
69
69
  async init(options) {
70
- const { componentName, logger } = options;
70
+ const { componentName } = options;
71
71
  let { templateType } = options;
72
72
  if (!validator.validateComponentName(componentName)) {
73
73
  throw 'name not valid';
74
74
  }
75
- // LEGACY TEMPLATES WARNING
76
75
  if ((0, is_template_legacy_1.default)(templateType)) {
77
76
  const legacyName = templateType;
78
77
  templateType = legacyName.replace(legacyName, `oc-template-${legacyName}`);
79
- logger.warn(resources_1.default.messages.cli.legacyTemplateDeprecationWarning(legacyName, templateType));
78
+ (0, deprecate_1.default)({
79
+ id: `cli-init-legacy-template-${legacyName}`,
80
+ subject: `The bare \`${legacyName}\` template type`,
81
+ replacement: 'the modern ESM component runtime (`oc-template-es6`)'
82
+ });
80
83
  }
81
84
  try {
82
85
  await (0, init_template_1.default)(Object.assign(options, {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "oc-client",
3
3
  "description": "The OpenComponents client-side javascript client",
4
- "version": "0.50.61",
4
+ "version": "0.50.63",
5
5
  "repository": "https://github.com/opencomponents/oc/tree/master/components/oc-client",
6
6
  "author": "Matteo Figus <matteofigus@gmail.com>",
7
7
  "oc": {
@@ -23,14 +23,14 @@
23
23
  ],
24
24
  "dataProvider": {
25
25
  "type": "node.js",
26
- "hashKey": "58122ecd2423162914c65f1985e725e17c6169f0",
26
+ "hashKey": "879a222b2e89b6a0685c2bad8ca3eb9798b6cc55",
27
27
  "src": "server.js",
28
28
  "size": 644
29
29
  }
30
30
  },
31
- "version": "0.50.61",
31
+ "version": "0.50.63",
32
32
  "packaged": true,
33
- "date": 1786078795573
33
+ "date": 1787207196137
34
34
  },
35
35
  "devDependencies": {
36
36
  "oc-template-es6-compiler": "^8.0.0"
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=(t,s)=>{const{staticPath:e,templates:a}=t;return s(null,{staticPath:e,templates:a})},r=(t,s)=>{o(t,(e,a,i={})=>{if(e)return s(e);if(a==null)return s(null,{__oc_emptyResponse:!0});const n=t.action?a:Object.assign({},a,{_staticPath:t.staticPath,_baseUrl:t.baseUrl,_componentName:"oc-client",_componentVersion:"0.50.61"}),c=t.staticPath.indexOf("http")===0?t.staticPath:"https:"+t.staticPath;return s(null,Object.assign({},{component:{key:"c4abb6bf4dc6657fb718a45b64bd6b2cb92e874a",src:c+"template.js",props:n,esm:!1,development:void 0}}))})};exports.data=r;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=(t,s)=>{const{staticPath:e,templates:a}=t;return s(null,{staticPath:e,templates:a})},r=(t,s)=>{o(t,(e,a,i={})=>{if(e)return s(e);if(a==null)return s(null,{__oc_emptyResponse:!0});const n=t.action?a:Object.assign({},a,{_staticPath:t.staticPath,_baseUrl:t.baseUrl,_componentName:"oc-client",_componentVersion:"0.50.63"}),c=t.staticPath.indexOf("http")===0?t.staticPath:"https:"+t.staticPath;return s(null,Object.assign({},{component:{key:"c4abb6bf4dc6657fb718a45b64bd6b2cb92e874a",src:c+"template.js",props:n,esm:!1,development:void 0}}))})};exports.data=r;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "oc-client",
3
3
  "description": "The OpenComponents client-side javascript client",
4
- "version": "0.50.61",
4
+ "version": "0.50.63",
5
5
  "repository": "https://github.com/opencomponents/oc/tree/master/components/oc-client",
6
6
  "author": "Matteo Figus <matteofigus@gmail.com>",
7
7
  "oc": {
package/dist/index.d.ts CHANGED
@@ -2,5 +2,5 @@ export { default as Client } from 'oc-client';
2
2
  export { default as cli } from './cli/programmatic-api';
3
3
  export type { RegistryType } from './registry';
4
4
  export { default as Registry, RegistryOptions } from './registry';
5
- export type { CookieOptions, ExpressMiddleware, HttpServerAdapter, HttpServerAdapterFactory, Method, NativeApp, OcHandler, OcRequest, OcResponse, UploadedFile } from './registry/domain/http-server/types';
6
- export type { Plugin, PluginContext } from './types';
5
+ export type { CookieOptions, ExpressMiddleware, HttpServerAdapter, HttpServerAdapterFactory, HttpServerListenOptions, Method, NativeApp, OcHandler, OcRequest, OcResponse, PromiseHttpServerAdapter, UploadedFile } from './registry/domain/http-server/types';
6
+ export type { CorsConfig, CorsOptions, Plugin, PluginContext, RegistryErrorEvent } from './types';
@@ -1,5 +1,5 @@
1
1
  import type { IncomingHttpHeaders } from 'node:http';
2
- import type { Component } from '../../types';
2
+ import type { Component, RegistryErrorEvent } from '../../types';
3
3
  type Subscription<T = any> = (data: T) => void;
4
4
  export interface RequestData {
5
5
  body: unknown;
@@ -15,10 +15,7 @@ export interface RequestData {
15
15
  errorCode?: string;
16
16
  }
17
17
  type Events = {
18
- error: {
19
- code: string;
20
- message: string;
21
- };
18
+ error: RegistryErrorEvent;
22
19
  start: unknown;
23
20
  'cache-poll': number;
24
21
  request: RequestData;
@@ -1,3 +1,3 @@
1
1
  import { type Express } from 'express';
2
- import type { HttpServerAdapter } from './types';
3
- export default function createExpressAdapter(options?: unknown): HttpServerAdapter<Express>;
2
+ import type { PromiseHttpServerAdapter } from './types';
3
+ export default function createExpressAdapter(options?: unknown): PromiseHttpServerAdapter<Express>;
@@ -11,9 +11,15 @@ const express_1 = __importDefault(require("express"));
11
11
  const morgan_1 = __importDefault(require("morgan"));
12
12
  const multer_1 = __importDefault(require("multer"));
13
13
  const response_time_1 = __importDefault(require("response-time"));
14
+ const deprecate_1 = __importDefault(require("../../../utils/deprecate"));
14
15
  const expressMiddleware = Symbol('expressMiddleware');
15
16
  const ocResponseSym = Symbol('ocResponse');
16
17
  const ocParamsSym = Symbol('ocParams');
18
+ const warnAboutCallback = () => (0, deprecate_1.default)({
19
+ id: 'http-server-adapter-callbacks',
20
+ subject: 'The HTTP server adapter callback API',
21
+ replacement: 'the returned promises'
22
+ });
17
23
  function stream(readable) {
18
24
  readable.pipe(this.raw);
19
25
  }
@@ -31,6 +37,7 @@ function createExpressAdapter(options) {
31
37
  }
32
38
  class ExpressHttpServerAdapter {
33
39
  name = 'express';
40
+ supportsPromiseLifecycle = true;
34
41
  app;
35
42
  server;
36
43
  serverErrorHandlers = [];
@@ -119,14 +126,52 @@ class ExpressHttpServerAdapter {
119
126
  for (const handler of this.serverErrorHandlers) {
120
127
  this.server.on('error', handler);
121
128
  }
122
- this.server.listen(opts.port, cb);
129
+ if (cb) {
130
+ warnAboutCallback();
131
+ this.server.listen(opts.port, cb);
132
+ return;
133
+ }
134
+ return new Promise((resolve, reject) => {
135
+ const onListening = () => {
136
+ cleanup();
137
+ resolve();
138
+ };
139
+ const onError = (err) => {
140
+ cleanup();
141
+ reject(err);
142
+ };
143
+ const cleanup = () => {
144
+ this.server?.off('listening', onListening);
145
+ this.server?.off('error', onError);
146
+ };
147
+ this.server?.once('listening', onListening);
148
+ this.server?.once('error', onError);
149
+ this.server?.listen(opts.port);
150
+ });
123
151
  }
124
152
  onServerError(cb) {
125
153
  this.serverErrorHandlers.push(cb);
126
154
  this.server?.on('error', cb);
127
155
  }
128
156
  close(cb) {
129
- this.server?.close(cb);
157
+ if (cb) {
158
+ warnAboutCallback();
159
+ this.server?.close(cb);
160
+ return;
161
+ }
162
+ if (!this.server) {
163
+ return Promise.resolve();
164
+ }
165
+ return new Promise((resolve, reject) => {
166
+ this.server?.close((err) => {
167
+ if (err) {
168
+ reject(err);
169
+ }
170
+ else {
171
+ resolve();
172
+ }
173
+ });
174
+ });
130
175
  }
131
176
  isListening() {
132
177
  return !!this.server?.listening;
@@ -69,6 +69,11 @@ export interface OcResponse {
69
69
  }
70
70
  export type OcHandler = (req: OcRequest, res: OcResponse) => void | Promise<void>;
71
71
  export type ExpressMiddleware = (req: any, res: any, next: (err?: unknown) => void) => void;
72
+ export interface HttpServerListenOptions {
73
+ port: number | string;
74
+ timeout: number;
75
+ keepAliveTimeout?: number;
76
+ }
72
77
  export type HttpServerAdapterFactory<TOptions = unknown, TNative = unknown> = {
73
78
  (options?: unknown): HttpServerAdapter<TNative>;
74
79
  readonly __serverAdapterOptions?: TOptions;
@@ -77,7 +82,7 @@ export type HttpServerAdapterOptions<TAdapter> = TAdapter extends {
77
82
  readonly __serverAdapterOptions?: infer TOptions;
78
83
  } ? TOptions : TAdapter extends (options?: infer TOptions) => HttpServerAdapter ? TOptions : unknown;
79
84
  export type NativeApp<TAdapter> = TAdapter extends (...args: any[]) => HttpServerAdapter<infer TNative> ? unknown extends TNative ? express.Express : TNative : TAdapter extends HttpServerAdapter<infer TNative> ? unknown extends TNative ? express.Express : TNative : express.Express;
80
- export interface HttpServerAdapter<TNative = unknown> {
85
+ interface HttpServerAdapterBase<TNative> {
81
86
  name: string;
82
87
  enableBodyParser(opts: {
83
88
  limit?: number | string;
@@ -95,14 +100,23 @@ export interface HttpServerAdapter<TNative = unknown> {
95
100
  use(handler: OcHandler): void;
96
101
  route(method: Method, path: string, id: string, handlers: OcHandler[]): void;
97
102
  fromConnect(handler: ExpressMiddleware): OcHandler;
98
- listen(opts: {
99
- port: number | string;
100
- timeout: number;
101
- keepAliveTimeout?: number;
102
- }, cb: (err?: Error) => void): void;
103
103
  onServerError(cb: (err: Error) => void): void;
104
- close(cb: (err?: Error) => void): void;
105
104
  isListening(): boolean;
106
105
  native(): TNative;
107
106
  httpServer(): http.Server;
108
107
  }
108
+ export interface PromiseHttpServerAdapterLifecycle {
109
+ readonly supportsPromiseLifecycle: true;
110
+ listen(opts: HttpServerListenOptions): Promise<void>;
111
+ listen(opts: HttpServerListenOptions, cb: (err?: Error) => void): void;
112
+ close(): Promise<void>;
113
+ close(cb: (err?: Error) => void): void;
114
+ }
115
+ interface CallbackHttpServerAdapterLifecycle {
116
+ readonly supportsPromiseLifecycle?: false | undefined;
117
+ listen(opts: HttpServerListenOptions, cb: (err?: Error) => void): void;
118
+ close(cb: (err?: Error) => void): void;
119
+ }
120
+ export type PromiseHttpServerAdapter<TNative = unknown> = HttpServerAdapterBase<TNative> & PromiseHttpServerAdapterLifecycle;
121
+ export type HttpServerAdapter<TNative = unknown> = PromiseHttpServerAdapter<TNative> | (HttpServerAdapterBase<TNative> & CallbackHttpServerAdapterLifecycle);
122
+ export {};
@@ -9,6 +9,7 @@ const pLimit_1 = __importDefault(require("../../utils/pLimit"));
9
9
  const metadata_index_1 = require("./metadata-index");
10
10
  const isNotFoundError = (err, code) => err === code || err?.code === code;
11
11
  const getTemplateSize = (component) => component.oc.files.template.size;
12
+ const getMetadataRowKey = (row) => JSON.stringify([row.name, row.version]);
12
13
  const getComponentRowsFromComponentsDetails = (componentsDetails) => {
13
14
  const rows = [];
14
15
  for (const [name, versions] of Object.entries(componentsDetails.components || {})) {
@@ -33,8 +34,14 @@ const backfillMetadataRows = async (metadataStore, rows) => {
33
34
  inserted: 0,
34
35
  skipped: 0
35
36
  };
37
+ if (rows.length === 0) {
38
+ return result;
39
+ }
40
+ const existingRowKeys = new Set((await metadataStore.getAllComponents()).map(getMetadataRowKey));
41
+ const pendingRows = rows.filter((row) => !existingRowKeys.has(getMetadataRowKey(row)));
42
+ result.skipped = rows.length - pendingRows.length;
36
43
  const limit = (0, pLimit_1.default)(10);
37
- await Promise.all(rows.map((row) => limit(async () => {
44
+ await Promise.all(pendingRows.map((row) => limit(async () => {
38
45
  try {
39
46
  await metadataStore.addVersion(row);
40
47
  result.inserted += 1;
@@ -1,8 +1,8 @@
1
1
  import { compileSync } from 'oc-client-browser';
2
- import type { Config } from '../../types';
2
+ import type { Config, CorsOptions } from '../../types';
3
3
  import type { HttpServerAdapterFactory } from './http-server/types';
4
4
  type CompileOptions = Omit<Exclude<Parameters<typeof compileSync>[0], undefined>, 'templates'>;
5
- export interface RegistryOptions<T = any, TServerAdapter extends HttpServerAdapterFactory = HttpServerAdapterFactory> extends Partial<Omit<Config<T, TServerAdapter>, 'beforePublish' | 'dataProvider' | 'discovery' | 'plugins'>> {
5
+ export interface RegistryOptions<T = any, TServerAdapter extends HttpServerAdapterFactory = HttpServerAdapterFactory> extends Partial<Omit<Config<T, TServerAdapter>, 'beforePublish' | 'cors' | 'dataProvider' | 'discovery' | 'plugins'>> {
6
6
  /**
7
7
  * Configuration for the data provider step, i.e. the component's `server.js`
8
8
  * that produces the model consumed by the view.
@@ -28,6 +28,12 @@ export interface RegistryOptions<T = any, TServerAdapter extends HttpServerAdapt
28
28
  validate?: boolean;
29
29
  robots?: boolean;
30
30
  } | boolean;
31
+ /**
32
+ * CORS response headers sent by the registry.
33
+ *
34
+ * @default Existing registry CORS headers
35
+ */
36
+ cors?: CorsOptions;
31
37
  /**
32
38
  * Public base URL where the registry will be accessible by consumers.
33
39
  * It **must** already include the chosen {@link Config.prefix} and end with a trailing slash.
@@ -41,6 +41,7 @@ const node_zlib_1 = __importDefault(require("node:zlib"));
41
41
  const oc_client_browser_1 = require("oc-client-browser");
42
42
  const settings_1 = __importDefault(require("../../resources/settings"));
43
43
  const deprecate_1 = __importDefault(require("../../utils/deprecate"));
44
+ const cors_1 = require("../middleware/cors");
44
45
  const auth = __importStar(require("./authentication"));
45
46
  const express_adapter_1 = __importDefault(require("./http-server/express-adapter"));
46
47
  const DEFAULT_NODE_KEEPALIVE_MS = 5000;
@@ -77,6 +78,7 @@ function optionsSanitiser(input) {
77
78
  ...options.dataProvider,
78
79
  enabled: options.dataProvider?.enabled !== false
79
80
  };
81
+ options.cors = (0, cors_1.normaliseCorsConfig)(options.cors);
80
82
  if (typeof options.discovery === 'boolean') {
81
83
  (0, deprecate_1.default)({
82
84
  id: 'registry-config-discovery-boolean',
@@ -4,9 +4,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.init = init;
7
- const node_util_1 = require("node:util");
8
7
  const dependency_graph_1 = require("dependency-graph");
9
8
  const resources_1 = __importDefault(require("../../resources"));
9
+ const deprecate_1 = __importDefault(require("../../utils/deprecate"));
10
10
  const pLimit_1 = __importDefault(require("../../utils/pLimit"));
11
11
  function validatePlugins(plugins) {
12
12
  for (let idx = 0; idx < plugins.length; idx++) {
@@ -39,9 +39,46 @@ function checkDependencies(plugins) {
39
39
  }
40
40
  return graph.overallOrder();
41
41
  }
42
- let deferredLoads = [];
42
+ const isPromiseLike = (value) => typeof value?.then === 'function';
43
+ const registerPlugin = (plugin, dependencies) => {
44
+ const register = plugin.register.register;
45
+ return new Promise((resolve, reject) => {
46
+ let callbackCalled = false;
47
+ let callbackError;
48
+ let useCallback = false;
49
+ let result;
50
+ try {
51
+ result = register(plugin.options || {}, dependencies, (error) => {
52
+ callbackCalled = true;
53
+ callbackError = error;
54
+ if (useCallback) {
55
+ error ? reject(error) : resolve();
56
+ }
57
+ });
58
+ }
59
+ catch (error) {
60
+ reject(error);
61
+ return;
62
+ }
63
+ if (isPromiseLike(result)) {
64
+ void result.then(resolve, reject);
65
+ }
66
+ else {
67
+ useCallback = true;
68
+ (0, deprecate_1.default)({
69
+ id: 'plugin-register-callback',
70
+ subject: 'Plugin register callbacks',
71
+ replacement: 'an async register(options, dependencies) function'
72
+ });
73
+ if (callbackCalled) {
74
+ callbackError ? reject(callbackError) : resolve();
75
+ }
76
+ }
77
+ });
78
+ };
43
79
  async function init(pluginsToRegister) {
44
80
  const registered = {};
81
+ const deferredLoads = [];
45
82
  validatePlugins(pluginsToRegister);
46
83
  checkDependencies(pluginsToRegister);
47
84
  const dependenciesRegistered = (dependencies) => {
@@ -68,9 +105,8 @@ async function init(pluginsToRegister) {
68
105
  return;
69
106
  }
70
107
  const dependencies = Object.fromEntries(Object.entries(registered).filter(([key]) => plugin.register.dependencies?.includes(key)));
71
- const register = (0, node_util_1.promisify)(plugin.register.register);
72
108
  const pluginCallback = plugin.callback || (() => { });
73
- await register(plugin.options || {}, dependencies).catch((err) => {
109
+ await registerPlugin(plugin, dependencies).catch((err) => {
74
110
  pluginCallback(err);
75
111
  throw err;
76
112
  });
@@ -84,7 +120,7 @@ async function init(pluginsToRegister) {
84
120
  const terminator = async () => {
85
121
  if (deferredLoads.length > 0) {
86
122
  const deferredPlugins = [...deferredLoads];
87
- deferredLoads = [];
123
+ deferredLoads.length = 0;
88
124
  await Promise.all(deferredPlugins.map((plugin) => onSeries(() => loadPlugin(plugin))));
89
125
  return terminator();
90
126
  }
@@ -44,6 +44,7 @@ const oc_get_unix_utc_timestamp_1 = __importDefault(require("oc-get-unix-utc-tim
44
44
  const oc_metadata_adapters_utils_1 = require("oc-metadata-adapters-utils");
45
45
  const resources_1 = __importDefault(require("../../resources"));
46
46
  const settings_1 = __importDefault(require("../../resources/settings"));
47
+ const bounded_cache_1 = __importDefault(require("../../utils/bounded-cache"));
47
48
  const error_to_string_1 = __importDefault(require("../../utils/error-to-string"));
48
49
  const components_cache_1 = __importDefault(require("./components-cache"));
49
50
  const components_details_1 = __importDefault(require("./components-details"));
@@ -56,6 +57,16 @@ const storage_adapter_1 = __importDefault(require("./storage-adapter"));
56
57
  const validator = __importStar(require("./validators"));
57
58
  const versionHandler = __importStar(require("./version-handler"));
58
59
  const packageInfo = fs_extra_1.default.readJsonSync(node_path_1.default.join(__dirname, '..', '..', '..', 'package.json'));
60
+ const freezeComponentInfo = (value) => {
61
+ if (value && typeof value === 'object') {
62
+ for (const nestedValue of Object.values(value)) {
63
+ freezeComponentInfo(nestedValue);
64
+ }
65
+ if (!Object.isFrozen(value))
66
+ Object.freeze(value);
67
+ }
68
+ return value;
69
+ };
59
70
  function repository(conf) {
60
71
  const cdn = !conf.local &&
61
72
  (0, storage_adapter_1.default)(conf.storage.adapter(conf.storage.options));
@@ -71,9 +82,25 @@ function repository(conf) {
71
82
  : undefined;
72
83
  const componentsCache = (0, components_cache_1.default)(conf, cdn, metadataIndex);
73
84
  const componentsDetails = (0, components_details_1.default)(conf, cdn, metadataIndex);
85
+ const componentInfoCache = new bounded_cache_1.default(1000);
86
+ const componentInfoLoads = new Map();
87
+ const localVersions = new Map();
88
+ const localVersionLoads = new Map();
74
89
  let exportLegacyFilesLoop;
75
90
  let closed = false;
76
91
  const getFilePath = (component, version, filePath) => `${options.componentsDir}/${component}/${version}/${filePath}`;
92
+ const getComponentInfoKey = (componentName, componentVersion) => JSON.stringify([componentName, componentVersion]);
93
+ const invalidateComponentInfo = (componentName, componentVersion) => {
94
+ const key = getComponentInfoKey(componentName, componentVersion);
95
+ componentInfoCache.delete('component-info', key);
96
+ const entry = componentInfoLoads.get(key);
97
+ if (entry) {
98
+ entry.invalidated = true;
99
+ if (componentInfoLoads.get(key) === entry) {
100
+ componentInfoLoads.delete(key);
101
+ }
102
+ }
103
+ };
77
104
  const exportLegacyFiles = () => {
78
105
  if (!metadataStore || !conf.metadata?.exportLegacyFiles) {
79
106
  return;
@@ -149,21 +176,37 @@ function repository(conf) {
149
176
  return components;
150
177
  },
151
178
  getComponentVersions(componentName) {
152
- if (componentName === 'oc-client') {
153
- return Promise.all([
154
- fs_extra_1.default
155
- .readJson(node_path_1.default.join(__dirname, '../../../package.json'))
156
- .then((x) => x.version)
157
- ]);
158
- }
159
- if (!local.getComponents().includes(componentName)) {
179
+ const isOcClient = componentName === 'oc-client';
180
+ if (!isOcClient && !local.getComponents().includes(componentName)) {
160
181
  return Promise.reject(resources_1.default.errors.registry.COMPONENT_NOT_FOUND(componentName, repositorySource));
161
182
  }
162
- return Promise.all([
163
- fs_extra_1.default
164
- .readJson(node_path_1.default.join(conf.path, `${componentName}/package.json`))
165
- .then((x) => x.version)
166
- ]);
183
+ const readVersion = () => fs_extra_1.default
184
+ .readJson(isOcClient
185
+ ? node_path_1.default.join(__dirname, '../../../package.json')
186
+ : node_path_1.default.join(conf.path, `${componentName}/package.json`))
187
+ .then((componentInfo) => componentInfo.version);
188
+ if (conf.hotReloading !== false) {
189
+ return readVersion().then((version) => [version]);
190
+ }
191
+ const cachedVersion = localVersions.get(componentName);
192
+ if (cachedVersion !== undefined) {
193
+ return Promise.resolve([cachedVersion]);
194
+ }
195
+ const activeLoad = localVersionLoads.get(componentName);
196
+ if (activeLoad)
197
+ return activeLoad;
198
+ const load = readVersion()
199
+ .then((version) => {
200
+ localVersions.set(componentName, version);
201
+ return [version];
202
+ })
203
+ .finally(() => {
204
+ if (localVersionLoads.get(componentName) === load) {
205
+ localVersionLoads.delete(componentName);
206
+ }
207
+ });
208
+ localVersionLoads.set(componentName, load);
209
+ return load;
167
210
  },
168
211
  getDataProvider(componentName) {
169
212
  const ocClientServerPath = '../../components/oc-client/_package/server.js';
@@ -197,12 +240,48 @@ function repository(conf) {
197
240
  if (!version) {
198
241
  throw resources_1.default.errors.registry.COMPONENT_VERSION_NOT_FOUND(componentName, componentVersion || '', repositorySource);
199
242
  }
200
- const component = await repository
201
- .getComponentInfo(componentName, version)
202
- .catch((err) => {
203
- throw `component not available: ${(0, error_to_string_1.default)(err)}`;
204
- });
205
- return Object.assign(component, { allVersions });
243
+ let component;
244
+ if (conf.local && conf.hotReloading) {
245
+ component = await repository
246
+ .getComponentInfo(componentName, version)
247
+ .catch((err) => {
248
+ throw `component not available: ${(0, error_to_string_1.default)(err)}`;
249
+ });
250
+ }
251
+ else {
252
+ const key = getComponentInfoKey(componentName, version);
253
+ const cached = componentInfoCache.get('component-info', key);
254
+ if (cached) {
255
+ component = cached;
256
+ }
257
+ else {
258
+ let entry = componentInfoLoads.get(key);
259
+ if (!entry) {
260
+ let currentEntry;
261
+ const promise = Promise.resolve()
262
+ .then(() => repository.getComponentInfo(componentName, version))
263
+ .then((loadedComponent) => {
264
+ const frozenComponent = freezeComponentInfo(loadedComponent);
265
+ if (!currentEntry.invalidated) {
266
+ componentInfoCache.set('component-info', key, frozenComponent);
267
+ }
268
+ return frozenComponent;
269
+ })
270
+ .finally(() => {
271
+ if (componentInfoLoads.get(key) === currentEntry) {
272
+ componentInfoLoads.delete(key);
273
+ }
274
+ });
275
+ currentEntry = { invalidated: false, promise };
276
+ entry = currentEntry;
277
+ componentInfoLoads.set(key, currentEntry);
278
+ }
279
+ component = await entry.promise.catch((err) => {
280
+ throw `component not available: ${(0, error_to_string_1.default)(err)}`;
281
+ });
282
+ }
283
+ }
284
+ return { ...component, allVersions: [...allVersions] };
206
285
  },
207
286
  getComponentInfo(componentName, componentVersion) {
208
287
  if (conf.local) {
@@ -344,6 +423,7 @@ function repository(conf) {
344
423
  try {
345
424
  await cdn.putDir(pkgDetails.outputFolder, `${options.componentsDir}/${componentName}/${componentVersion}`);
346
425
  await metadataStore.commitVersion(componentName, componentVersion, token);
426
+ invalidateComponentInfo(componentName, componentVersion);
347
427
  metadataIndex.add(componentRow);
348
428
  }
349
429
  catch (err) {
@@ -355,6 +435,7 @@ function repository(conf) {
355
435
  return;
356
436
  }
357
437
  await cdn.putDir(pkgDetails.outputFolder, `${options.componentsDir}/${componentName}/${componentVersion}`);
438
+ invalidateComponentInfo(componentName, componentVersion);
358
439
  void componentsCache
359
440
  .refresh()
360
441
  .then((componentsList) => componentsDetails.refresh(componentsList))
@@ -1,9 +1,12 @@
1
- import type { Config } from '../../../types';
1
+ import type { Config, CorsOptions } from '../../../types';
2
2
  type ValidationResult = {
3
3
  isValid: true;
4
4
  } | {
5
5
  isValid: false;
6
6
  message: string;
7
7
  };
8
- export default function registryConfiguration(conf: Partial<Omit<Config, 'dataProvider' | 'discovery'>>): ValidationResult;
8
+ type RegistryConfiguration = Partial<Omit<Config, 'dataProvider' | 'discovery' | 'cors'>> & {
9
+ cors?: CorsOptions;
10
+ };
11
+ export default function registryConfiguration(conf: RegistryConfiguration): ValidationResult;
9
12
  export {};
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.default = registryConfiguration;
40
40
  const resources_1 = __importDefault(require("../../../resources"));
41
+ const cors_1 = require("../../middleware/cors");
41
42
  const auth = __importStar(require("../authentication"));
42
43
  const metadata_adapter_options_1 = __importDefault(require("../metadata-adapter-options"));
43
44
  function registryConfiguration(conf) {
@@ -59,6 +60,10 @@ function registryConfiguration(conf) {
59
60
  return returnError(resources_1.default.errors.registry.CONFIGURATION_PREFIX_DOES_NOT_END_WITH_SLASH);
60
61
  }
61
62
  }
63
+ const corsError = (0, cors_1.validateCorsConfig)(conf.cors);
64
+ if (corsError) {
65
+ return returnError(corsError);
66
+ }
62
67
  const publishAuth = conf.publishAuth;
63
68
  if (publishAuth) {
64
69
  const res = auth.validate(publishAuth);