@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.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/src/RepositoryWatcher.d.ts +4 -3
- package/dist/cjs/src/RepositoryWatcher.js +29 -23
- package/dist/cjs/src/assetManager.d.ts +4 -6
- package/dist/cjs/src/assetManager.js +49 -54
- package/dist/cjs/src/assets/routes.js +2 -2
- package/dist/cjs/src/cacheManager.d.ts +16 -0
- package/dist/cjs/src/cacheManager.js +47 -0
- package/dist/cjs/src/definitionsManager.d.ts +3 -4
- package/dist/cjs/src/definitionsManager.js +10 -21
- package/dist/cjs/src/repositoryManager.d.ts +7 -2
- package/dist/cjs/src/repositoryManager.js +10 -10
- package/dist/cjs/src/types.d.ts +2 -0
- package/dist/cjs/src/utils/utils.js +1 -1
- package/dist/esm/src/RepositoryWatcher.d.ts +4 -3
- package/dist/esm/src/RepositoryWatcher.js +29 -23
- package/dist/esm/src/assetManager.d.ts +4 -6
- package/dist/esm/src/assetManager.js +49 -54
- package/dist/esm/src/assets/routes.js +2 -2
- package/dist/esm/src/cacheManager.d.ts +16 -0
- package/dist/esm/src/cacheManager.js +39 -0
- package/dist/esm/src/definitionsManager.d.ts +3 -4
- package/dist/esm/src/definitionsManager.js +10 -21
- package/dist/esm/src/repositoryManager.d.ts +7 -2
- package/dist/esm/src/repositoryManager.js +10 -10
- package/dist/esm/src/types.d.ts +2 -0
- package/dist/esm/src/utils/utils.js +1 -1
- package/package.json +1 -1
- package/src/RepositoryWatcher.ts +30 -26
- package/src/assetManager.ts +63 -60
- package/src/assets/routes.ts +2 -2
- package/src/cacheManager.ts +54 -0
- package/src/definitionsManager.ts +13 -33
- package/src/repositoryManager.ts +11 -12
- package/src/types.ts +2 -2
- package/src/utils/utils.ts +1 -1
package/src/assetManager.ts
CHANGED
@@ -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
|
-
|
17
|
-
|
18
|
-
|
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 =
|
117
|
-
if (!noCache &&
|
118
|
-
return
|
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
|
-
|
121
|
+
cacheManager.set(cacheKey, asset, CACHE_TTL);
|
134
122
|
}
|
135
123
|
|
136
124
|
return asset;
|
137
125
|
}
|
138
126
|
|
139
|
-
async createAsset(
|
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
|
-
|
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
|
-
|
176
|
-
|
177
|
-
|
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
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
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) =>
|
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
|
-
|
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
|
-
|
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();
|
package/src/assets/routes.ts
CHANGED
@@ -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
|
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
|
-
|
25
|
-
this.
|
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.
|
20
|
+
public getDefinitions(kindFilter?: string | string[]): DefinitionInfo[] {
|
21
|
+
const key = this.getFullKey(kindFilter);
|
46
22
|
|
47
|
-
return
|
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
|
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();
|
package/src/repositoryManager.ts
CHANGED
@@ -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 {
|
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
|
-
|
59
|
-
|
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
|
-
|
63
|
-
return this.watcher.
|
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
|
-
|
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';
|
package/src/utils/utils.ts
CHANGED