oc 0.50.55 → 0.50.56

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 (33) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/.turbo/turbo-test-silent.log +11 -11
  3. package/.turbo/turbo-test.log +6 -1614
  4. package/CHANGELOG.md +8 -0
  5. package/README.md +180 -0
  6. package/dist/cli/commands.d.ts +8 -0
  7. package/dist/cli/commands.js +8 -0
  8. package/dist/cli/facade/dev.d.ts +2 -2
  9. package/dist/cli/facade/registry-migrate-metadata.d.ts +20 -0
  10. package/dist/cli/facade/registry-migrate-metadata.js +105 -0
  11. package/dist/cli/index.js +2 -0
  12. package/dist/components/oc-client/_package/package.json +4 -4
  13. package/dist/components/oc-client/_package/server.js +1 -1
  14. package/dist/components/oc-client/package.json +1 -1
  15. package/dist/registry/domain/components-cache/index.d.ts +3 -1
  16. package/dist/registry/domain/components-cache/index.js +28 -5
  17. package/dist/registry/domain/components-details.d.ts +3 -1
  18. package/dist/registry/domain/components-details.js +26 -5
  19. package/dist/registry/domain/metadata-adapter-options.d.ts +6 -0
  20. package/dist/registry/domain/metadata-adapter-options.js +12 -0
  21. package/dist/registry/domain/metadata-index.d.ts +15 -0
  22. package/dist/registry/domain/metadata-index.js +132 -0
  23. package/dist/registry/domain/metadata-migration.d.ts +38 -0
  24. package/dist/registry/domain/metadata-migration.js +128 -0
  25. package/dist/registry/domain/repository.d.ts +1 -0
  26. package/dist/registry/domain/repository.js +106 -7
  27. package/dist/registry/domain/validators/registry-configuration.js +21 -0
  28. package/dist/registry/index.d.ts +1 -1
  29. package/dist/registry/index.js +6 -2
  30. package/dist/resources/index.d.ts +3 -0
  31. package/dist/resources/index.js +3 -0
  32. package/dist/types.d.ts +41 -1
  33. package/package.json +3 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## Change Log
2
2
 
3
+ ## 0.50.56
4
+
5
+ ### Patch Changes
6
+
7
+ - Add storage metadata adapter
8
+ - Updated dependencies
9
+ - oc-metadata-adapters-utils@0.1.1
10
+
3
11
  ## 0.50.55
4
12
 
5
13
  ### Patch Changes
package/README.md CHANGED
@@ -32,6 +32,186 @@ We have been using it for more than two years in production at OpenTable, for sh
32
32
  - [Troubleshooting](../../CONTRIBUTING.md#troubleshooting)
33
33
  - [Gitter chat](https://gitter.im/opentable/oc)
34
34
 
35
+ ## Registry metadata stores
36
+
37
+ By default, an OC registry keeps its component index in storage files:
38
+ `components.json` and `components-details.json`. A registry can instead read and
39
+ write that index through a metadata adapter by adding a `metadata` block to the
40
+ registry configuration.
41
+
42
+ Storage is still required in metadata mode. The metadata store contains only the
43
+ component name, version, publish date, and template size. Component static files
44
+ and version `package.json` files continue to live in the configured storage
45
+ adapter.
46
+
47
+ ```js
48
+ const azureSqlMetadataAdapter = require('oc-azure-sql-metadata-adapter').default;
49
+ const s3StorageAdapter = require('oc-s3-storage-adapter');
50
+
51
+ registry.configure({
52
+ storage: {
53
+ adapter: s3StorageAdapter,
54
+ options: {
55
+ bucket: process.env.OC_STORAGE_BUCKET,
56
+ region: process.env.OC_STORAGE_REGION,
57
+ componentsDir: 'components',
58
+ path: process.env.OC_STORAGE_BASE_URL
59
+ }
60
+ },
61
+ metadata: {
62
+ adapter: azureSqlMetadataAdapter,
63
+ options: {
64
+ connectionString: process.env.OC_METADATA_SQL_CONNECTION_STRING
65
+ },
66
+ manageSchema: true,
67
+ reconcileFromStorage: false,
68
+ exportLegacyFiles: false
69
+ }
70
+ });
71
+ ```
72
+
73
+ `metadata.manageSchema` defaults to `true`. Set it to `false` when operators
74
+ manage the metadata schema/table separately; OC passes the value through to the
75
+ configured metadata adapter during validation, startup, and migration.
76
+
77
+ The registry initialises the metadata store during startup. If startup succeeds,
78
+ reads are served from OC's in-memory cache, and cache polling refreshes from the
79
+ metadata store. Publishing reserves the metadata row first, uploads package files
80
+ to storage only after the reservation succeeds, then commits the row. Duplicate
81
+ or in-progress metadata rows are treated as the existing "component version
82
+ already exists" publish error. When the registry is shut down via
83
+ `registry.close(callback)`, the metadata adapter's optional `close()` hook is
84
+ invoked so the adapter can release its connection pool.
85
+
86
+ Custom metadata adapters should implement the shared contract exported by
87
+ `oc-metadata-adapters-utils`:
88
+
89
+ ```ts
90
+ import type { ComponentRow, MetadataStore } from 'oc-metadata-adapters-utils';
91
+ import {
92
+ VERSION_ALREADY_EXISTS,
93
+ VERSION_PUBLISH_IN_PROGRESS
94
+ } from 'oc-metadata-adapters-utils';
95
+ ```
96
+
97
+ ### Migrating existing registries
98
+
99
+ Use the CLI backfill command before enabling metadata mode in production:
100
+
101
+ ```sh
102
+ oc registry migrate-metadata ./registry.config.js
103
+ ```
104
+
105
+ The argument is a path to a module that exports the same registry config object you would pass to `registry.configure()`. It must include both `storage` and `metadata`, and it must pass registry config validation. The module can be CommonJS, an ES module `default` export, or an async function returning the config — all three are accepted:
106
+
107
+ ```js
108
+ // registry.config.js — CommonJS
109
+ const azureSqlMetadataAdapter = require('oc-azure-sql-metadata-adapter').default;
110
+ const s3StorageAdapter = require('oc-s3-storage-adapter');
111
+
112
+ module.exports = {
113
+ baseUrl: 'http://my-registry.example.com/',
114
+ storage: {
115
+ adapter: s3StorageAdapter,
116
+ options: {
117
+ bucket: 'my-bucket',
118
+ region: 'us-east-1',
119
+ componentsDir: 'components',
120
+ path: 'https://cdn.example.com/'
121
+ }
122
+ },
123
+ metadata: {
124
+ adapter: azureSqlMetadataAdapter,
125
+ options: {
126
+ connectionString: process.env.OC_METADATA_SQL_CONNECTION_STRING
127
+ }
128
+ }
129
+ };
130
+ ```
131
+
132
+ ```ts
133
+ // registry.config.ts — ES module (transpiled or run with a native ESM loader)
134
+ import azureSqlMetadataAdapter from 'oc-azure-sql-metadata-adapter';
135
+ import s3StorageAdapter from 'oc-s3-storage-adapter';
136
+
137
+ export default {
138
+ baseUrl: 'http://my-registry.example.com/',
139
+ storage: {
140
+ adapter: s3StorageAdapter,
141
+ options: {
142
+ bucket: 'my-bucket',
143
+ region: 'us-east-1',
144
+ componentsDir: 'components',
145
+ path: 'https://cdn.example.com/'
146
+ }
147
+ },
148
+ metadata: {
149
+ adapter: azureSqlMetadataAdapter,
150
+ options: {
151
+ connectionString: process.env.OC_METADATA_SQL_CONNECTION_STRING
152
+ }
153
+ }
154
+ };
155
+ ```
156
+
157
+ ```js
158
+ // registry.config.js — async factory (e.g. to resolve secrets first)
159
+ module.exports = async () => {
160
+ const azureSqlMetadataAdapter = require('oc-azure-sql-metadata-adapter').default;
161
+ const s3StorageAdapter = require('oc-s3-storage-adapter');
162
+ const connectionString = await getSecret('oc-metadata-sql');
163
+
164
+ return {
165
+ baseUrl: 'http://my-registry.example.com/',
166
+ storage: {
167
+ adapter: s3StorageAdapter,
168
+ options: {
169
+ bucket: 'my-bucket',
170
+ region: 'us-east-1',
171
+ componentsDir: 'components',
172
+ path: 'https://cdn.example.com/'
173
+ }
174
+ },
175
+ metadata: {
176
+ adapter: azureSqlMetadataAdapter,
177
+ options: { connectionString }
178
+ }
179
+ };
180
+ };
181
+ ```
182
+
183
+ The command initialises the configured metadata adapter and backfills rows from
184
+ `${componentsDir}/components-details.json`. If that file is missing, it falls back
185
+ to scanning `${componentsDir}/<component>/<version>/package.json`. Existing rows
186
+ are skipped, so the command is idempotent.
187
+
188
+ A safe migration sequence is:
189
+
190
+ 1. Deploy the metadata adapter configuration to a non-serving environment.
191
+ 2. Run `oc registry migrate-metadata ./registry.config.js`.
192
+ 3. Start one registry instance with metadata mode enabled and verify reads.
193
+ 4. Roll out metadata mode to the remaining registry instances.
194
+
195
+ ### Bake-in and rollback options
196
+
197
+ Two optional flags help run storage and metadata side by side during migration:
198
+
199
+ - `metadata.reconcileFromStorage: true` scans storage on registry startup and
200
+ inserts missing metadata rows before cache hydration. Existing rows are skipped.
201
+ - `metadata.exportLegacyFiles: true` writes DB-derived `components.json` and
202
+ `components-details.json` projections to storage once on registry startup. It
203
+ is **not** triggered per publish, so publishing stays an O(1) append rather
204
+ than a full-registry scan + blob rewrite.
205
+ - `metadata.exportLegacyFilesInterval: <seconds>` additionally refreshes those
206
+ projections on a background timer (non-overlapping) when `exportLegacyFiles`
207
+ is enabled. Config validation rejects setting an interval without
208
+ `metadata.exportLegacyFiles: true`. Omit it to export at startup only. The
209
+ timer is stopped on `registry.close()`.
210
+
211
+ These files are one-way projections from the metadata store. They can help with
212
+ rollback to storage mode, but they do not replace the storage adapter because
213
+ component statics remain in storage.
214
+
35
215
  ## Requirements and build status
36
216
 
37
217
  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).
@@ -173,6 +173,14 @@ declare const _default: {
173
173
  description: string;
174
174
  usage: string;
175
175
  };
176
+ 'migrate-metadata': {
177
+ cmd: string;
178
+ example: {
179
+ cmd: string;
180
+ };
181
+ description: string;
182
+ usage: string;
183
+ };
176
184
  remove: {
177
185
  cmd: string;
178
186
  example: {
@@ -173,6 +173,14 @@ exports.default = {
173
173
  description: 'Show oc registries added to the current project',
174
174
  usage: 'Usage: $0 registry ls'
175
175
  },
176
+ 'migrate-metadata': {
177
+ cmd: 'migrate-metadata <configPath>',
178
+ example: {
179
+ cmd: '$0 registry migrate-metadata ./registry.config.js'
180
+ },
181
+ description: 'Backfill a configured metadata store from existing registry metadata files',
182
+ usage: 'Usage: $0 registry migrate-metadata <configPath>'
183
+ },
176
184
  remove: {
177
185
  cmd: 'remove <registryUrl>',
178
186
  example: {
@@ -18,7 +18,7 @@ declare const dev: ({ local, logger }: {
18
18
  verbose?: boolean;
19
19
  production?: boolean;
20
20
  }): Promise<{
21
- close: (callback: (err?: Error | undefined | string) => void) => void | import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>;
21
+ close: (callback: (err?: Error | undefined | string) => void) => void;
22
22
  on: <T extends keyof {
23
23
  error: {
24
24
  code: string;
@@ -100,7 +100,7 @@ declare const dev: ({ local, logger }: {
100
100
  verbose?: boolean;
101
101
  production?: boolean;
102
102
  }, arguments__1: (error: unknown, value: {
103
- close: (callback: (err?: Error | undefined | string) => void) => void | import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>;
103
+ close: (callback: (err?: Error | undefined | string) => void) => void;
104
104
  on: <T extends keyof {
105
105
  error: {
106
106
  code: string;
@@ -0,0 +1,20 @@
1
+ import type { Logger } from '../logger';
2
+ declare const registryMigrateMetadata: ({ logger }: {
3
+ logger: Logger;
4
+ }) => {
5
+ (opts: {
6
+ configPath: string;
7
+ }): Promise<{
8
+ scanned: number;
9
+ inserted: number;
10
+ skipped: number;
11
+ }>;
12
+ (opts: {
13
+ configPath: string;
14
+ }, arguments__1: (error: unknown, value: {
15
+ scanned: number;
16
+ inserted: number;
17
+ skipped: number;
18
+ }) => void): void;
19
+ };
20
+ export default registryMigrateMetadata;
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const node_path_1 = __importDefault(require("node:path"));
40
+ const node_url_1 = require("node:url");
41
+ const universalify_1 = require("universalify");
42
+ const metadata_adapter_options_1 = __importDefault(require("../../registry/domain/metadata-adapter-options"));
43
+ const metadata_migration_1 = require("../../registry/domain/metadata-migration");
44
+ const options_sanitiser_1 = __importDefault(require("../../registry/domain/options-sanitiser"));
45
+ const storage_adapter_1 = __importDefault(require("../../registry/domain/storage-adapter"));
46
+ const validator = __importStar(require("../../registry/domain/validators"));
47
+ const dynamicImport = new Function('specifier', 'return import(specifier)');
48
+ const loadConfigModule = async (resolvedConfigPath) => {
49
+ try {
50
+ const resolvedRequirePath = require.resolve(resolvedConfigPath);
51
+ delete require.cache[resolvedRequirePath];
52
+ return require(resolvedRequirePath);
53
+ }
54
+ catch (err) {
55
+ if (err?.code !== 'ERR_REQUIRE_ESM') {
56
+ throw err;
57
+ }
58
+ return dynamicImport(`${(0, node_url_1.pathToFileURL)(resolvedConfigPath).href}?t=${Date.now()}`);
59
+ }
60
+ };
61
+ const loadRegistryOptions = async (configPath) => {
62
+ const resolvedConfigPath = node_path_1.default.resolve(configPath);
63
+ const configModule = await loadConfigModule(resolvedConfigPath);
64
+ const candidate = configModule?.default ?? configModule;
65
+ const options = typeof candidate === 'function' ? await candidate() : candidate;
66
+ if (!options || typeof options !== 'object') {
67
+ throw new Error('Registry config must export an options object');
68
+ }
69
+ return options;
70
+ };
71
+ const registryMigrateMetadata = ({ logger }) => (0, universalify_1.fromPromise)(async (opts) => {
72
+ try {
73
+ const inputOptions = await loadRegistryOptions(opts.configPath);
74
+ const validationResult = validator.validateRegistryConfiguration(inputOptions);
75
+ if (!validationResult.isValid) {
76
+ throw new Error(validationResult.message);
77
+ }
78
+ const conf = (0, options_sanitiser_1.default)(inputOptions);
79
+ if (!conf.metadata) {
80
+ throw new Error('Registry config must include metadata options');
81
+ }
82
+ const metadataStore = conf.metadata.adapter((0, metadata_adapter_options_1.default)(conf));
83
+ const cdn = (0, storage_adapter_1.default)(conf.storage.adapter(conf.storage.options));
84
+ try {
85
+ await metadataStore.initialise();
86
+ const result = await (0, metadata_migration_1.backfillMetadataFromStorageDetails)({
87
+ metadataStore,
88
+ cdn,
89
+ componentsDir: conf.storage.options.componentsDir
90
+ });
91
+ logger.ok(`Metadata migration completed: ${result.scanned} scanned, ${result.inserted} inserted, ${result.skipped} skipped`);
92
+ return result;
93
+ }
94
+ finally {
95
+ if (metadataStore.close) {
96
+ await metadataStore.close();
97
+ }
98
+ }
99
+ }
100
+ catch (err) {
101
+ logger.err(String(err?.message || err));
102
+ throw err;
103
+ }
104
+ });
105
+ exports.default = registryMigrateMetadata;
package/dist/cli/index.js CHANGED
@@ -19,6 +19,7 @@ const publish_1 = __importDefault(require("./facade/publish"));
19
19
  const registry_2 = __importDefault(require("./facade/registry"));
20
20
  const registry_add_1 = __importDefault(require("./facade/registry-add"));
21
21
  const registry_ls_1 = __importDefault(require("./facade/registry-ls"));
22
+ const registry_migrate_metadata_1 = __importDefault(require("./facade/registry-migrate-metadata"));
22
23
  const registry_remove_1 = __importDefault(require("./facade/registry-remove"));
23
24
  const validate_1 = __importDefault(require("./facade/validate"));
24
25
  const logger_1 = __importDefault(require("./logger"));
@@ -34,6 +35,7 @@ const cliFunctions = {
34
35
  registry: registry_2.default,
35
36
  'registry-add': registry_add_1.default,
36
37
  'registry-ls': registry_ls_1.default,
38
+ 'registry-migrate-metadata': registry_migrate_metadata_1.default,
37
39
  'registry-remove': registry_remove_1.default,
38
40
  validate: validate_1.default
39
41
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "oc-client",
3
3
  "description": "The OpenComponents client-side javascript client",
4
- "version": "0.50.55",
4
+ "version": "0.50.56",
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": "ebd4132fcd55c1f8bfa966365e0f2b496a9505b4",
26
+ "hashKey": "620aa4d6457f2972e6d53d8b8b572b9fcb5aa0d3",
27
27
  "src": "server.js",
28
28
  "size": 644
29
29
  }
30
30
  },
31
- "version": "0.50.55",
31
+ "version": "0.50.56",
32
32
  "packaged": true,
33
- "date": 1783073481627
33
+ "date": 1783178172523
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.55"}),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.56"}),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.55",
4
+ "version": "0.50.56",
5
5
  "repository": "https://github.com/opencomponents/oc/tree/master/components/oc-client",
6
6
  "author": "Matteo Figus <matteofigus@gmail.com>",
7
7
  "oc": {
@@ -1,7 +1,9 @@
1
1
  import { type StorageAdapter } from 'oc-storage-adapters-utils';
2
2
  import type { ComponentsList, Config } from '../../../types';
3
- export default function componentsCache(conf: Config, cdn: StorageAdapter): {
3
+ import type { MetadataIndex } from '../metadata-index';
4
+ export default function componentsCache(conf: Config, cdn: StorageAdapter, metadataIndex?: MetadataIndex): {
4
5
  get(): ComponentsList;
5
6
  load(): Promise<ComponentsList>;
6
7
  refresh(): Promise<ComponentsList>;
8
+ close(): void;
7
9
  };
@@ -9,14 +9,18 @@ const oc_get_unix_utc_timestamp_1 = __importDefault(require("oc-get-unix-utc-tim
9
9
  const oc_storage_adapters_utils_1 = require("oc-storage-adapters-utils");
10
10
  const events_handler_1 = __importDefault(require("../events-handler"));
11
11
  const components_list_1 = __importDefault(require("./components-list"));
12
- function componentsCache(conf, cdn) {
12
+ function componentsCache(conf, cdn, metadataIndex) {
13
13
  let cachedComponentsList;
14
14
  let refreshLoop;
15
+ let closed = false;
15
16
  const componentsList = (0, components_list_1.default)(conf, cdn);
17
+ const getFromMetadataIndex = async () => (await metadataIndex.refresh()).componentsList;
16
18
  const poll = () => {
17
19
  return setTimeout(async () => {
18
20
  try {
19
- const data = await componentsList.getFromJson();
21
+ const data = metadataIndex
22
+ ? await getFromMetadataIndex()
23
+ : await componentsList.getFromJson();
20
24
  events_handler_1.default.fire('cache-poll', (0, oc_get_unix_utc_timestamp_1.default)());
21
25
  if (data.lastEdit > cachedComponentsList.lastEdit) {
22
26
  cachedComponentsList = data;
@@ -28,12 +32,16 @@ function componentsCache(conf, cdn) {
28
32
  message: err?.message || String(err)
29
33
  });
30
34
  }
31
- refreshLoop = poll();
35
+ if (!closed) {
36
+ refreshLoop = poll();
37
+ }
32
38
  }, conf.pollingInterval * 1000);
33
39
  };
34
40
  const cacheDataAndStartPolling = (data) => {
35
41
  cachedComponentsList = data;
36
- refreshLoop = poll();
42
+ if (!closed) {
43
+ refreshLoop = poll();
44
+ }
37
45
  return data;
38
46
  };
39
47
  const throwError = (code, message) => {
@@ -42,12 +50,21 @@ function componentsCache(conf, cdn) {
42
50
  };
43
51
  return {
44
52
  get() {
53
+ const metadataSnapshot = metadataIndex?.get();
54
+ if (metadataSnapshot) {
55
+ cachedComponentsList = metadataSnapshot.componentsList;
56
+ }
45
57
  if (!cachedComponentsList) {
46
58
  return throwError('components_cache_empty', `The component's cache was empty`);
47
59
  }
48
60
  return cachedComponentsList;
49
61
  },
50
62
  async load() {
63
+ if (metadataIndex) {
64
+ const components = await getFromMetadataIndex().catch((err) => throwError('components_list_get', err));
65
+ cacheDataAndStartPolling(components);
66
+ return components;
67
+ }
51
68
  const jsonComponents = await componentsList.getFromJson().catch((err) => {
52
69
  if (err?.code === oc_storage_adapters_utils_1.strings.errors.STORAGE.FILE_NOT_FOUND_CODE)
53
70
  return null;
@@ -69,13 +86,19 @@ function componentsCache(conf, cdn) {
69
86
  clearTimeout(refreshLoop);
70
87
  try {
71
88
  // Passing components that we know are fine, so it doesn't refresh invalid components
72
- const components = await componentsList.refresh(cachedComponentsList);
89
+ const components = metadataIndex
90
+ ? await getFromMetadataIndex()
91
+ : await componentsList.refresh(cachedComponentsList);
73
92
  cacheDataAndStartPolling(components);
74
93
  return components;
75
94
  }
76
95
  catch (err) {
77
96
  return throwError('components_cache_refresh', err);
78
97
  }
98
+ },
99
+ close() {
100
+ closed = true;
101
+ clearTimeout(refreshLoop);
79
102
  }
80
103
  };
81
104
  }
@@ -1,6 +1,8 @@
1
1
  import type { StorageAdapter } from 'oc-storage-adapters-utils';
2
2
  import type { ComponentsDetails, ComponentsList, Config } from '../../types';
3
- export default function componentsDetails(conf: Config, cdn: StorageAdapter): {
3
+ import type { MetadataIndex } from './metadata-index';
4
+ export default function componentsDetails(conf: Config, cdn: StorageAdapter, metadataIndex?: MetadataIndex): {
4
5
  get: () => Promise<ComponentsDetails>;
5
6
  refresh: (componentsList: ComponentsList) => Promise<ComponentsDetails>;
7
+ close: () => void;
6
8
  };
@@ -8,9 +8,10 @@ const lodash_isequal_1 = __importDefault(require("lodash.isequal"));
8
8
  const oc_get_unix_utc_timestamp_1 = __importDefault(require("oc-get-unix-utc-timestamp"));
9
9
  const pLimit_1 = __importDefault(require("../../utils/pLimit"));
10
10
  const events_handler_1 = __importDefault(require("./events-handler"));
11
- function componentsDetails(conf, cdn) {
11
+ function componentsDetails(conf, cdn, metadataIndex) {
12
12
  let cachedComponentsDetails;
13
13
  let refreshLoop;
14
+ let closed = false;
14
15
  const returnError = (code, message) => {
15
16
  events_handler_1.default.fire('error', {
16
17
  code,
@@ -20,10 +21,13 @@ function componentsDetails(conf, cdn) {
20
21
  };
21
22
  const filePath = () => `${conf.storage.options.componentsDir}/components-details.json`;
22
23
  const getFromJson = () => cdn.getJson(filePath(), true);
24
+ const getFromMetadataIndex = async () => (await metadataIndex.getOrRefresh()).componentsDetails;
23
25
  const poll = () => {
24
26
  return setTimeout(async () => {
25
27
  try {
26
- const data = await getFromJson();
28
+ const data = metadataIndex
29
+ ? await getFromMetadataIndex()
30
+ : await getFromJson();
27
31
  events_handler_1.default.fire('cache-poll', (0, oc_get_unix_utc_timestamp_1.default)());
28
32
  if (!cachedComponentsDetails ||
29
33
  data.lastEdit > cachedComponentsDetails.lastEdit) {
@@ -36,12 +40,16 @@ function componentsDetails(conf, cdn) {
36
40
  message: err?.message || String(err)
37
41
  });
38
42
  }
39
- refreshLoop = poll();
43
+ if (!closed) {
44
+ refreshLoop = poll();
45
+ }
40
46
  }, conf.pollingInterval * 1000);
41
47
  };
42
48
  const cacheDataAndStartPolling = (data) => {
43
49
  cachedComponentsDetails = data;
44
- refreshLoop = poll();
50
+ if (!metadataIndex && !closed) {
51
+ refreshLoop = poll();
52
+ }
45
53
  return data;
46
54
  };
47
55
  const getFromDirectories = async (options) => {
@@ -74,6 +82,10 @@ function componentsDetails(conf, cdn) {
74
82
  };
75
83
  const save = (data) => cdn.putFileContent(JSON.stringify(data), filePath(), true);
76
84
  const get = async () => {
85
+ if (metadataIndex) {
86
+ cachedComponentsDetails = await getFromMetadataIndex();
87
+ return cachedComponentsDetails;
88
+ }
77
89
  if (cachedComponentsDetails) {
78
90
  return cachedComponentsDetails;
79
91
  }
@@ -82,6 +94,10 @@ function componentsDetails(conf, cdn) {
82
94
  };
83
95
  const refresh = async (componentsList) => {
84
96
  clearTimeout(refreshLoop);
97
+ if (metadataIndex) {
98
+ const details = await getFromMetadataIndex().catch((err) => returnError('components_details_get', err));
99
+ return cacheDataAndStartPolling(details);
100
+ }
85
101
  const jsonDetails = await getFromJson().catch(() => undefined);
86
102
  const dirDetails = await getFromDirectories({
87
103
  componentsList,
@@ -94,8 +110,13 @@ function componentsDetails(conf, cdn) {
94
110
  }
95
111
  return cacheDataAndStartPolling(jsonDetails);
96
112
  };
113
+ const close = () => {
114
+ closed = true;
115
+ clearTimeout(refreshLoop);
116
+ };
97
117
  return {
98
118
  get,
99
- refresh
119
+ refresh,
120
+ close
100
121
  };
101
122
  }
@@ -0,0 +1,6 @@
1
+ import type { MetadataConfig } from '../../types';
2
+ type MetadataOptionsConfig = {
3
+ metadata?: Pick<MetadataConfig, 'manageSchema' | 'options'>;
4
+ };
5
+ declare const getMetadataAdapterOptions: (conf: MetadataOptionsConfig) => any;
6
+ export default getMetadataAdapterOptions;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const getMetadataAdapterOptions = (conf) => {
4
+ if (typeof conf.metadata?.manageSchema === 'undefined') {
5
+ return conf.metadata?.options;
6
+ }
7
+ return {
8
+ ...(conf.metadata.options || {}),
9
+ manageSchema: conf.metadata.manageSchema
10
+ };
11
+ };
12
+ exports.default = getMetadataAdapterOptions;
@@ -0,0 +1,15 @@
1
+ import type { Component, ComponentRow, ComponentsDetails, ComponentsList, MetadataStore } from '../../types';
2
+ export declare const getComponentsListFromRows: (rows: ComponentRow[]) => ComponentsList;
3
+ export declare const getComponentsDetailsFromRows: (rows: ComponentRow[]) => ComponentsDetails;
4
+ export declare const getComponentRow: (name: string, version: string, component: Component) => ComponentRow;
5
+ export interface MetadataSnapshot {
6
+ componentsList: ComponentsList;
7
+ componentsDetails: ComponentsDetails;
8
+ }
9
+ export interface MetadataIndex {
10
+ get(): MetadataSnapshot | undefined;
11
+ add(row: ComponentRow): MetadataSnapshot;
12
+ refresh(): Promise<MetadataSnapshot>;
13
+ getOrRefresh(): Promise<MetadataSnapshot>;
14
+ }
15
+ export declare const createMetadataIndex: (metadataStore: MetadataStore) => MetadataIndex;