@kapeta/local-cluster-service 0.16.0 → 0.16.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 (36) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/src/RepositoryWatcher.d.ts +4 -3
  3. package/dist/cjs/src/RepositoryWatcher.js +29 -23
  4. package/dist/cjs/src/assetManager.d.ts +4 -6
  5. package/dist/cjs/src/assetManager.js +49 -54
  6. package/dist/cjs/src/assets/routes.js +2 -2
  7. package/dist/cjs/src/cacheManager.d.ts +16 -0
  8. package/dist/cjs/src/cacheManager.js +47 -0
  9. package/dist/cjs/src/definitionsManager.d.ts +3 -4
  10. package/dist/cjs/src/definitionsManager.js +10 -21
  11. package/dist/cjs/src/repositoryManager.d.ts +7 -2
  12. package/dist/cjs/src/repositoryManager.js +10 -10
  13. package/dist/cjs/src/types.d.ts +2 -0
  14. package/dist/cjs/src/utils/utils.js +1 -1
  15. package/dist/esm/src/RepositoryWatcher.d.ts +4 -3
  16. package/dist/esm/src/RepositoryWatcher.js +29 -23
  17. package/dist/esm/src/assetManager.d.ts +4 -6
  18. package/dist/esm/src/assetManager.js +49 -54
  19. package/dist/esm/src/assets/routes.js +2 -2
  20. package/dist/esm/src/cacheManager.d.ts +16 -0
  21. package/dist/esm/src/cacheManager.js +39 -0
  22. package/dist/esm/src/definitionsManager.d.ts +3 -4
  23. package/dist/esm/src/definitionsManager.js +10 -21
  24. package/dist/esm/src/repositoryManager.d.ts +7 -2
  25. package/dist/esm/src/repositoryManager.js +10 -10
  26. package/dist/esm/src/types.d.ts +2 -0
  27. package/dist/esm/src/utils/utils.js +1 -1
  28. package/package.json +1 -1
  29. package/src/RepositoryWatcher.ts +30 -26
  30. package/src/assetManager.ts +63 -60
  31. package/src/assets/routes.ts +2 -2
  32. package/src/cacheManager.ts +54 -0
  33. package/src/definitionsManager.ts +13 -33
  34. package/src/repositoryManager.ts +11 -12
  35. package/src/types.ts +2 -2
  36. package/src/utils/utils.ts +1 -1
@@ -1,7 +1,6 @@
1
1
  import Path from 'node:path';
2
2
  import FS from 'fs-extra';
3
3
  import YAML from 'yaml';
4
- import NodeCache from 'node-cache';
5
4
  import { Definition, DefinitionInfo } from '@kapeta/local-cluster-config';
6
5
  import { codeGeneratorManager } from './codeGeneratorManager';
7
6
  import { ProgressListener } from './progressListener';
@@ -12,11 +11,12 @@ import { Actions } from '@kapeta/nodejs-registry-utils';
12
11
  import { definitionsManager } from './definitionsManager';
13
12
  import { normalizeKapetaUri } from './utils/utils';
14
13
  import { taskManager } from './taskManager';
14
+ import { SourceOfChange } from './types';
15
+ import { cacheManager } from './cacheManager';
15
16
 
16
- function clearAllCaches() {
17
- definitionsManager.clearCache();
18
- assetManager.clearCache();
19
- }
17
+ const CACHE_TTL = 60 * 60 * 1000; // 1 hour
18
+
19
+ const toKey = (ref: string) => `assetManager:asset:${ref}`;
20
20
 
21
21
  export interface EnrichedAsset {
22
22
  ref: string;
@@ -59,18 +59,6 @@ function parseRef(ref: string) {
59
59
  }
60
60
 
61
61
  class AssetManager {
62
- private cache: NodeCache;
63
-
64
- constructor() {
65
- this.cache = new NodeCache({
66
- stdTTL: 60 * 60, // 1 hour
67
- });
68
- }
69
-
70
- public clearCache() {
71
- this.cache.flushAll();
72
- }
73
-
74
62
  /**
75
63
  *
76
64
  * @param {string[]} [assetKinds]
@@ -113,9 +101,9 @@ class AssetManager {
113
101
  autoFetch: boolean = true
114
102
  ): Promise<EnrichedAsset | undefined> {
115
103
  ref = normalizeKapetaUri(ref);
116
- const cacheKey = `getAsset:${ref}`;
117
- if (!noCache && this.cache.has(cacheKey)) {
118
- return this.cache.get(cacheKey);
104
+ const cacheKey = toKey(ref);
105
+ if (!noCache && cacheManager.has(cacheKey)) {
106
+ return cacheManager.get(cacheKey);
119
107
  }
120
108
  const uri = parseKapetaUri(ref);
121
109
  if (autoFetch) {
@@ -130,13 +118,17 @@ class AssetManager {
130
118
  throw new Error('Asset not found: ' + ref);
131
119
  }
132
120
  if (asset) {
133
- this.cache.set(cacheKey, asset);
121
+ cacheManager.set(cacheKey, asset, CACHE_TTL);
134
122
  }
135
123
 
136
124
  return asset;
137
125
  }
138
126
 
139
- async createAsset(path: string, yaml: BlockDefinition): Promise<EnrichedAsset[]> {
127
+ async createAsset(
128
+ path: string,
129
+ yaml: BlockDefinition,
130
+ sourceOfChange: SourceOfChange = 'filesystem'
131
+ ): Promise<EnrichedAsset[]> {
140
132
  if (await FS.pathExists(path)) {
141
133
  throw new Error('File already exists: ' + path);
142
134
  }
@@ -145,11 +137,17 @@ class AssetManager {
145
137
  if (!(await FS.pathExists(dirName))) {
146
138
  await FS.mkdirp(dirName);
147
139
  }
148
-
140
+ await repositoryManager.setSourceOfChangeFor(path, sourceOfChange);
149
141
  await FS.writeFile(path, YAML.stringify(yaml));
150
142
  const asset = await this.importFile(path);
143
+ asset.forEach((a) => {
144
+ const ref = normalizeKapetaUri(a.ref);
145
+ const key = toKey(ref);
146
+ cacheManager.set(key, a, CACHE_TTL);
147
+ });
151
148
 
152
- clearAllCaches();
149
+ definitionsManager.clearCache();
150
+ console.log(`Created asset at: ${path}`);
153
151
 
154
152
  const ref = `kapeta://${yaml.metadata.name}:local`;
155
153
 
@@ -158,7 +156,8 @@ class AssetManager {
158
156
  return asset;
159
157
  }
160
158
 
161
- async updateAsset(ref: string, yaml: BlockDefinition) {
159
+ async updateAsset(ref: string, yaml: BlockDefinition, sourceOfChange: SourceOfChange = 'filesystem') {
160
+ ref = normalizeKapetaUri(ref);
162
161
  const asset = await this.getAsset(ref, true, false);
163
162
  if (!asset) {
164
163
  throw new Error('Attempted to update unknown asset: ' + ref);
@@ -172,39 +171,14 @@ class AssetManager {
172
171
  throw new Error('Attempted to update corrupted asset: ' + ref);
173
172
  }
174
173
 
175
- const path = asset.ymlPath;
176
- try {
177
- await repositoryManager.ignoreChangesFor(path);
178
- await FS.writeFile(asset.ymlPath, YAML.stringify(yaml));
179
- console.log('Wrote to ' + asset.ymlPath);
180
- clearAllCaches();
181
- this.maybeGenerateCode(asset.ref, asset.ymlPath, yaml);
182
- } finally {
183
- //We need to wait a bit for the disk to settle before we can resume watching
184
- setTimeout(async () => {
185
- try {
186
- await repositoryManager.resumeChangedFor(path);
187
- } catch (e) {}
188
- }, 500);
189
- }
190
- }
174
+ await repositoryManager.setSourceOfChangeFor(asset.ymlPath, sourceOfChange);
175
+ await FS.writeFile(asset.ymlPath, YAML.stringify(yaml));
176
+ console.log(`Updated asset at: ${asset.ymlPath}`);
191
177
 
192
- private maybeGenerateCode(ref: string, ymlPath: string, block: BlockDefinition) {
193
- ref = normalizeKapetaUri(ref);
194
- if (codeGeneratorManager.canGenerateCode(block)) {
195
- const assetTitle = block.metadata.title ? block.metadata.title : parseKapetaUri(block.metadata.name).name;
196
- taskManager.add(
197
- `codegen:${ref}`,
198
- async () => {
199
- await codeGeneratorManager.generate(ymlPath, block);
200
- },
201
- {
202
- name: `Generating code for ${assetTitle}`,
203
- }
204
- );
205
- return true;
206
- }
207
- return false;
178
+ cacheManager.remove(toKey(ref));
179
+ definitionsManager.clearCache();
180
+
181
+ this.maybeGenerateCode(asset.ref, asset.ymlPath, yaml);
208
182
  }
209
183
 
210
184
  async importFile(filePath: string) {
@@ -222,9 +196,15 @@ class AssetManager {
222
196
  await Actions.link(new ProgressListener(), Path.dirname(filePath));
223
197
 
224
198
  const version = 'local';
225
- const refs = assetInfos.map((assetInfo) => `kapeta://${assetInfo.metadata.name}:${version}`);
199
+ const refs = assetInfos.map((assetInfo) =>
200
+ normalizeKapetaUri(`kapeta://${assetInfo.metadata.name}:${version}`)
201
+ );
202
+ refs.forEach((ref) => {
203
+ const key = toKey(ref);
204
+ cacheManager.remove(key);
205
+ });
226
206
 
227
- clearAllCaches();
207
+ definitionsManager.clearCache();
228
208
 
229
209
  return this.getAssets().filter((a) => refs.some((ref) => compareRefs(ref, a.ref)));
230
210
  }
@@ -235,7 +215,9 @@ class AssetManager {
235
215
  throw new Error('Asset does not exists: ' + ref);
236
216
  }
237
217
 
238
- clearAllCaches();
218
+ const key = toKey(ref);
219
+ cacheManager.remove(key);
220
+ definitionsManager.clearCache();
239
221
 
240
222
  await Actions.uninstall(new ProgressListener(), [asset.ref]);
241
223
  }
@@ -247,9 +229,30 @@ class AssetManager {
247
229
  }
248
230
  const uri = parseKapetaUri(ref);
249
231
  console.log('Installing %s', ref);
232
+ const key = toKey(ref);
233
+ cacheManager.remove(key);
234
+ definitionsManager.clearCache();
250
235
 
251
236
  return await repositoryManager.ensureAsset(uri.handle, uri.name, uri.version, false);
252
237
  }
238
+
239
+ private maybeGenerateCode(ref: string, ymlPath: string, block: BlockDefinition) {
240
+ ref = normalizeKapetaUri(ref);
241
+ if (codeGeneratorManager.canGenerateCode(block)) {
242
+ const assetTitle = block.metadata.title ? block.metadata.title : parseKapetaUri(block.metadata.name).name;
243
+ taskManager.add(
244
+ `codegen:${ref}`,
245
+ async () => {
246
+ await codeGeneratorManager.generate(ymlPath, block);
247
+ },
248
+ {
249
+ name: `Generating code for ${assetTitle}`,
250
+ }
251
+ );
252
+ return true;
253
+ }
254
+ return false;
255
+ }
253
256
  }
254
257
 
255
258
  export const assetManager = new AssetManager();
@@ -70,7 +70,7 @@ router.post('/create', async (req: Request, res: Response) => {
70
70
  const content = parseBody(req);
71
71
 
72
72
  try {
73
- const assets = await assetManager.createAsset(req.query.path as string, content);
73
+ const assets = await assetManager.createAsset(req.query.path as string, content, 'user');
74
74
 
75
75
  res.status(200).send(assets);
76
76
  } catch (err: any) {
@@ -91,7 +91,7 @@ router.put('/update', async (req: Request, res: Response) => {
91
91
  const content = parseBody(req);
92
92
 
93
93
  try {
94
- await assetManager.updateAsset(req.query.ref as string, content);
94
+ await assetManager.updateAsset(req.query.ref as string, content, 'user');
95
95
 
96
96
  res.sendStatus(204);
97
97
  } catch (err: any) {
@@ -0,0 +1,54 @@
1
+ import NodeCache from 'node-cache';
2
+
3
+ const DEFAULT_CACHE_TTL = 60 * 1000; // 1 min
4
+ export interface CacheEntry<T = any> {
5
+ expires: number;
6
+ data: T;
7
+ }
8
+ export class CacheManager {
9
+ private cache: NodeCache = new NodeCache();
10
+
11
+ public flush() {
12
+ this.cache.flushAll();
13
+ }
14
+
15
+ public doCached<T>(key: string, getter: () => T, ttl = DEFAULT_CACHE_TTL) {
16
+ const data = this.cache.get<T>(key);
17
+ if (data !== undefined) {
18
+ return data;
19
+ }
20
+ const result = getter();
21
+ this.cache.set(key, result, ttl);
22
+ return result;
23
+ }
24
+
25
+ public get<T>(key: string): T | undefined {
26
+ return this.cache.get<T>(key);
27
+ }
28
+
29
+ public set<T>(key: string, data: T, ttl = DEFAULT_CACHE_TTL) {
30
+ this.cache.set(key, data, ttl);
31
+ }
32
+
33
+ public has(key: string): boolean {
34
+ return this.cache.has(key);
35
+ }
36
+
37
+ public remove(key: string) {
38
+ return this.cache.del(key);
39
+ }
40
+
41
+ public removePrefix(key: string) {
42
+ const keys = this.cache.keys();
43
+ for (const k of keys) {
44
+ if (k.startsWith(key)) {
45
+ this.remove(k);
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ export const cacheManager = new CacheManager();
52
+
53
+ export const doCached = <T>(key: string, getter: () => T, ttl = DEFAULT_CACHE_TTL) =>
54
+ cacheManager.doCached(key, getter, ttl);
@@ -1,17 +1,9 @@
1
1
  import ClusterConfiguration, { DefinitionInfo } from '@kapeta/local-cluster-config';
2
2
  import { parseKapetaUri } from '@kapeta/nodejs-utils';
3
-
4
- const CACHE_TTL = 60 * 1000; // 1 min
5
-
6
- interface DefinitionCacheEntry {
7
- expires: number;
8
- definitions: DefinitionInfo[];
9
- }
3
+ import { cacheManager, doCached } from './cacheManager';
10
4
 
11
5
  class DefinitionsManager {
12
- private cache: { [key: string]: DefinitionCacheEntry } = {};
13
-
14
- private getKey(kindFilter?: string | string[]) {
6
+ private getHash(kindFilter?: string | string[]) {
15
7
  if (kindFilter) {
16
8
  if (Array.isArray(kindFilter)) {
17
9
  return kindFilter.join(',');
@@ -21,38 +13,22 @@ class DefinitionsManager {
21
13
  return 'none';
22
14
  }
23
15
 
24
- public clearCache() {
25
- this.cache = {};
26
- }
27
-
28
- private doCached(key: string, getter: () => DefinitionInfo[]) {
29
- if (this.cache[key]) {
30
- if (this.cache[key].expires > Date.now()) {
31
- return this.cache[key].definitions;
32
- }
33
- delete this.cache[key];
34
- }
35
-
36
- this.cache[key] = {
37
- expires: Date.now() + CACHE_TTL,
38
- definitions: getter(),
39
- };
40
-
41
- return this.cache[key].definitions;
16
+ private getFullKey(kindFilter?: string | string[]) {
17
+ return `definitionsManager:${this.getHash(kindFilter)}`;
42
18
  }
43
19
 
44
- public getDefinitions(kindFilter?: string | string[]) {
45
- const key = this.getKey(kindFilter);
20
+ public getDefinitions(kindFilter?: string | string[]): DefinitionInfo[] {
21
+ const key = this.getFullKey(kindFilter);
46
22
 
47
- return this.doCached(key, () => ClusterConfiguration.getDefinitions(kindFilter));
23
+ return doCached<DefinitionInfo[]>(key, () => ClusterConfiguration.getDefinitions(kindFilter));
48
24
  }
49
25
 
50
26
  public exists(ref: string) {
51
27
  return !!this.getDefinition(ref);
52
28
  }
53
29
 
54
- public getProviderDefinitions() {
55
- return this.doCached('providers', () => ClusterConfiguration.getProviderDefinitions());
30
+ public getProviderDefinitions(): DefinitionInfo[] {
31
+ return doCached<DefinitionInfo[]>('providers', () => ClusterConfiguration.getProviderDefinitions());
56
32
  }
57
33
 
58
34
  public getDefinition(ref: string) {
@@ -64,6 +40,10 @@ class DefinitionsManager {
64
40
  return parseKapetaUri(`${d.definition.metadata.name}:${d.version}`).id === uri.id;
65
41
  });
66
42
  }
43
+
44
+ public clearCache() {
45
+ cacheManager.removePrefix('definitionsManager:');
46
+ }
67
47
  }
68
48
 
69
49
  export const definitionsManager = new DefinitionsManager();
@@ -5,15 +5,10 @@ import { Actions, Config, RegistryService } from '@kapeta/nodejs-registry-utils'
5
5
  import { definitionsManager } from './definitionsManager';
6
6
  import { Task, taskManager } from './taskManager';
7
7
  import { normalizeKapetaUri } from './utils/utils';
8
-
9
8
  import { ProgressListener } from './progressListener';
10
9
  import { RepositoryWatcher } from './RepositoryWatcher';
11
- import { assetManager } from './assetManager';
12
-
13
- function clearAllCaches() {
14
- definitionsManager.clearCache();
15
- assetManager.clearCache();
16
- }
10
+ import { SourceOfChange } from './types';
11
+ import { cacheManager } from './cacheManager';
17
12
 
18
13
  const EVENT_DEFAULT_PROVIDERS_START = 'default-providers-start';
19
14
  const EVENT_DEFAULT_PROVIDERS_END = 'default-providers-end';
@@ -55,12 +50,16 @@ class RepositoryManager {
55
50
  return this.watcher.unwatch();
56
51
  }
57
52
 
58
- ignoreChangesFor(file: string) {
59
- return this.watcher.ignoreChangesFor(file);
53
+ /**
54
+ * Setting the source of change helps us know
55
+ * how to react to changes in the UI.
56
+ */
57
+ setSourceOfChangeFor(file: string, source: SourceOfChange) {
58
+ return this.watcher.setSourceOfChangeFor(file, source);
60
59
  }
61
60
 
62
- resumeChangedFor(file: string) {
63
- return this.watcher.resumeChangedFor(file);
61
+ clearSourceOfChangeFor(file: string) {
62
+ return this.watcher.clearSourceOfChangeFor(file);
64
63
  }
65
64
 
66
65
  public ensureDefaultProviders(): void {
@@ -93,7 +92,7 @@ class RepositoryManager {
93
92
  console.error(`Failed to install asset: ${ref}`, e);
94
93
  throw e;
95
94
  }
96
- clearAllCaches();
95
+ cacheManager.flush();
97
96
  //console.log(`Asset installed: ${ref}`);
98
97
  };
99
98
  };
package/src/types.ts CHANGED
@@ -1,4 +1,3 @@
1
- import EventEmitter from 'events';
2
1
  import express from 'express';
3
2
  import { Resource } from '@kapeta/schemas';
4
3
  import { StringBodyRequest } from './middleware/stringBody';
@@ -6,7 +5,8 @@ import { KapetaRequest } from './middleware/kapeta';
6
5
 
7
6
  export type StringMap = { [key: string]: string };
8
7
  export type AnyMap = { [key: string]: any };
9
-
8
+ export type SourceOfChange = 'user' | 'filesystem';
9
+ export type WatchEventName = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
10
10
  export type LogLevel = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE' | 'FATAL';
11
11
  export type LogSource = 'stdout' | 'stderr';
12
12
  export type EnvironmentType = 'docker' | 'process';
@@ -26,7 +26,7 @@ export function readYML(path: string) {
26
26
  try {
27
27
  return YAML.parse(rawYaml.toString());
28
28
  } catch (err) {
29
- throw new Error('Failed to parse plan YAML: ' + err);
29
+ throw new Error(`Failed to parse plan YAML: ${err}`);
30
30
  }
31
31
  }
32
32