@global-cache/core 0.2.3
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/README.md +5 -0
- package/dist/client/api.d.ts +21 -0
- package/dist/client/api.d.ts.map +1 -0
- package/dist/client/api.js +43 -0
- package/dist/client/api.js.map +1 -0
- package/dist/client/index.d.ts +27 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +135 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.d.ts +12 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +3 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/utils/http-error.d.ts +3 -0
- package/dist/client/utils/http-error.d.ts.map +1 -0
- package/dist/client/utils/http-error.js +13 -0
- package/dist/client/utils/http-error.js.map +1 -0
- package/dist/client/utils/http-json.d.ts +14 -0
- package/dist/client/utils/http-json.d.ts.map +1 -0
- package/dist/client/utils/http-json.js +29 -0
- package/dist/client/utils/http-json.js.map +1 -0
- package/dist/client/utils/http-query.d.ts +9 -0
- package/dist/client/utils/http-query.d.ts.map +1 -0
- package/dist/client/utils/http-query.js +25 -0
- package/dist/client/utils/http-query.js.map +1 -0
- package/dist/client/utils/preview-value.d.ts +2 -0
- package/dist/client/utils/preview-value.d.ts.map +1 -0
- package/dist/client/utils/preview-value.js +16 -0
- package/dist/client/utils/preview-value.js.map +1 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +57 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/server/config.d.ts +15 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +18 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/error.d.ts +3 -0
- package/dist/server/error.d.ts.map +1 -0
- package/dist/server/error.js +9 -0
- package/dist/server/error.js.map +1 -0
- package/dist/server/getter.d.ts +35 -0
- package/dist/server/getter.d.ts.map +1 -0
- package/dist/server/getter.js +109 -0
- package/dist/server/getter.js.map +1 -0
- package/dist/server/index.d.ts +14 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +73 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/routes/clear.d.ts +2 -0
- package/dist/server/routes/clear.d.ts.map +1 -0
- package/dist/server/routes/clear.js +15 -0
- package/dist/server/routes/clear.js.map +1 -0
- package/dist/server/routes/get-stale-list.d.ts +7 -0
- package/dist/server/routes/get-stale-list.d.ts.map +1 -0
- package/dist/server/routes/get-stale-list.js +20 -0
- package/dist/server/routes/get-stale-list.js.map +1 -0
- package/dist/server/routes/get-stale.d.ts +7 -0
- package/dist/server/routes/get-stale.d.ts.map +1 -0
- package/dist/server/routes/get-stale.js +20 -0
- package/dist/server/routes/get-stale.js.map +1 -0
- package/dist/server/routes/get.d.ts +9 -0
- package/dist/server/routes/get.d.ts.map +1 -0
- package/dist/server/routes/get.js +23 -0
- package/dist/server/routes/get.js.map +1 -0
- package/dist/server/routes/set.d.ts +9 -0
- package/dist/server/routes/set.d.ts.map +1 -0
- package/dist/server/routes/set.js +18 -0
- package/dist/server/routes/set.js.map +1 -0
- package/dist/server/setter.d.ts +12 -0
- package/dist/server/setter.d.ts.map +1 -0
- package/dist/server/setter.js +37 -0
- package/dist/server/setter.js.map +1 -0
- package/dist/server/storage/index.d.ts +6 -0
- package/dist/server/storage/index.d.ts.map +1 -0
- package/dist/server/storage/index.js +14 -0
- package/dist/server/storage/index.js.map +1 -0
- package/dist/server/storage/multi-instance/index.d.ts +9 -0
- package/dist/server/storage/multi-instance/index.d.ts.map +1 -0
- package/dist/server/storage/multi-instance/index.js +10 -0
- package/dist/server/storage/multi-instance/index.js.map +1 -0
- package/dist/server/storage/single-instance/fs.d.ts +16 -0
- package/dist/server/storage/single-instance/fs.d.ts.map +1 -0
- package/dist/server/storage/single-instance/fs.js +59 -0
- package/dist/server/storage/single-instance/fs.js.map +1 -0
- package/dist/server/storage/single-instance/index.d.ts +11 -0
- package/dist/server/storage/single-instance/index.d.ts.map +1 -0
- package/dist/server/storage/single-instance/index.js +33 -0
- package/dist/server/storage/single-instance/index.js.map +1 -0
- package/dist/server/storage/single-instance/memory.d.ts +15 -0
- package/dist/server/storage/single-instance/memory.d.ts.map +1 -0
- package/dist/server/storage/single-instance/memory.js +41 -0
- package/dist/server/storage/single-instance/memory.js.map +1 -0
- package/dist/server/storage/types.d.ts +14 -0
- package/dist/server/storage/types.d.ts.map +1 -0
- package/dist/server/storage/types.js +3 -0
- package/dist/server/storage/types.js.map +1 -0
- package/dist/server/utils/express.d.ts +8 -0
- package/dist/server/utils/express.d.ts.map +1 -0
- package/dist/server/utils/express.js +15 -0
- package/dist/server/utils/express.js.map +1 -0
- package/dist/shared/debug.d.ts +4 -0
- package/dist/shared/debug.d.ts.map +1 -0
- package/dist/shared/debug.js +14 -0
- package/dist/shared/debug.js.map +1 -0
- package/dist/shared/logger.d.ts +2 -0
- package/dist/shared/logger.d.ts.map +1 -0
- package/dist/shared/logger.js +5 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/sig.d.ts +23 -0
- package/dist/shared/sig.d.ts.map +1 -0
- package/dist/shared/sig.js +53 -0
- package/dist/shared/sig.js.map +1 -0
- package/dist/shared/ttl.d.ts +8 -0
- package/dist/shared/ttl.d.ts.map +1 -0
- package/dist/shared/ttl.js +25 -0
- package/dist/shared/ttl.js.map +1 -0
- package/dist/shared/utils/object.d.ts +2 -0
- package/dist/shared/utils/object.d.ts.map +1 -0
- package/dist/shared/utils/object.js +9 -0
- package/dist/shared/utils/object.js.map +1 -0
- package/dist/shared/value-info.d.ts +18 -0
- package/dist/shared/value-info.d.ts.map +1 -0
- package/dist/shared/value-info.js +32 -0
- package/dist/shared/value-info.js.map +1 -0
- package/package.json +41 -0
- package/src/client/api.ts +51 -0
- package/src/client/index.ts +137 -0
- package/src/client/types.ts +14 -0
- package/src/client/utils/http-error.ts +8 -0
- package/src/client/utils/http-json.ts +32 -0
- package/src/client/utils/http-query.ts +24 -0
- package/src/client/utils/preview-value.ts +10 -0
- package/src/config.ts +79 -0
- package/src/index.ts +2 -0
- package/src/server/config.ts +29 -0
- package/src/server/error.ts +7 -0
- package/src/server/getter.ts +138 -0
- package/src/server/index.ts +81 -0
- package/src/server/routes/clear.ts +15 -0
- package/src/server/routes/get-stale-list.ts +27 -0
- package/src/server/routes/get-stale.ts +27 -0
- package/src/server/routes/get.ts +31 -0
- package/src/server/routes/set.ts +27 -0
- package/src/server/setter.ts +35 -0
- package/src/server/storage/index.ts +16 -0
- package/src/server/storage/multi-instance/index.ts +7 -0
- package/src/server/storage/single-instance/fs.ts +64 -0
- package/src/server/storage/single-instance/index.ts +32 -0
- package/src/server/storage/single-instance/memory.ts +43 -0
- package/src/server/storage/types.ts +13 -0
- package/src/server/utils/express.ts +14 -0
- package/src/shared/debug.ts +7 -0
- package/src/shared/logger.ts +1 -0
- package/src/shared/sig.ts +84 -0
- package/src/shared/ttl.ts +19 -0
- package/src/shared/utils/object.ts +4 -0
- package/src/shared/value-info.ts +40 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Express, Router } from 'express';
|
|
2
|
+
import { getConfig } from '../config';
|
|
3
|
+
import { TestRunValueInfo } from '../../shared/value-info';
|
|
4
|
+
import { Setter } from '../setter';
|
|
5
|
+
import { getStorage } from '../storage';
|
|
6
|
+
|
|
7
|
+
export const router = Router();
|
|
8
|
+
|
|
9
|
+
export type SetValueParams = {
|
|
10
|
+
key: string;
|
|
11
|
+
value?: unknown; // The value to set.
|
|
12
|
+
error?: string; // An error occured during value computing.
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type SetValueResponse = TestRunValueInfo;
|
|
16
|
+
|
|
17
|
+
router.post('/run/:runId/set', async (req, res) => {
|
|
18
|
+
const { runId } = req.params;
|
|
19
|
+
const { key, value, error } = req.body as SetValueParams;
|
|
20
|
+
const config = getConfig(req.app as Express);
|
|
21
|
+
|
|
22
|
+
const { testRunStorage, persistentStorage } = getStorage(config, runId);
|
|
23
|
+
const setter = new Setter(testRunStorage, persistentStorage);
|
|
24
|
+
const valueInfo = await setter.set({ key, value, error });
|
|
25
|
+
|
|
26
|
+
res.json(valueInfo);
|
|
27
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { setComputed, setError } from '../shared/value-info';
|
|
2
|
+
import { IPersistentStorage, ITestRunStorage } from './storage/types';
|
|
3
|
+
|
|
4
|
+
export class Setter {
|
|
5
|
+
constructor(
|
|
6
|
+
private testRunStorage: ITestRunStorage,
|
|
7
|
+
private persistentStorage: IPersistentStorage,
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line visual/complexity, max-statements
|
|
11
|
+
async set({ key, value, error }: { key: string; value?: unknown; error?: string }) {
|
|
12
|
+
const valueInfo = await this.testRunStorage.load(key);
|
|
13
|
+
|
|
14
|
+
// todo: convert to warnings?
|
|
15
|
+
if (!valueInfo) {
|
|
16
|
+
throw new Error(`Cannot set value for key "${key}" that is not loaded.`);
|
|
17
|
+
}
|
|
18
|
+
if (valueInfo.state !== 'computing') {
|
|
19
|
+
throw new Error(`Cannot set value for key "${key}" that is not in "computing" state.`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (error) {
|
|
23
|
+
setError(valueInfo, error);
|
|
24
|
+
await this.testRunStorage.save(valueInfo, { notify: true });
|
|
25
|
+
// We delete file on error, as error may be related to the invalid value.
|
|
26
|
+
if (valueInfo.persistent) await this.persistentStorage.delete(key);
|
|
27
|
+
} else {
|
|
28
|
+
setComputed(valueInfo, value);
|
|
29
|
+
await this.testRunStorage.save(valueInfo, { notify: true });
|
|
30
|
+
if (valueInfo.persistent) await this.persistentStorage.save(valueInfo);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return valueInfo;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { GlobalCacheServerConfigResolved } from '../config';
|
|
2
|
+
import { getFsStorage, getMemoryStorage } from './single-instance';
|
|
3
|
+
|
|
4
|
+
export function getStorage(
|
|
5
|
+
{ multiInstance, basePath }: GlobalCacheServerConfigResolved,
|
|
6
|
+
runId: string,
|
|
7
|
+
) {
|
|
8
|
+
if (multiInstance) {
|
|
9
|
+
throw new Error('Multi-instance mode is not implemented yet.');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
testRunStorage: getMemoryStorage(runId),
|
|
14
|
+
persistentStorage: getFsStorage(basePath),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-instance mode of Global Storage Server.
|
|
3
|
+
* - can't reliably write to fs
|
|
4
|
+
* - can't wait for value via memory, as "set" request can land on another instance
|
|
5
|
+
* - both non-persistent and persistent values are stored in the external storage (redis/firebase)
|
|
6
|
+
* - compute-listeners are implemented via redis/firebase notification features
|
|
7
|
+
*/
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File system storage implementation.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { IPersistentStorage } from '../types';
|
|
7
|
+
import { TestRunValueInfo } from '../../../shared/value-info';
|
|
8
|
+
|
|
9
|
+
export type StoredInfo = {
|
|
10
|
+
computedAt: number;
|
|
11
|
+
sig: string;
|
|
12
|
+
value: unknown;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export class FilesystemStorage implements IPersistentStorage {
|
|
16
|
+
constructor(private basePath: string) {}
|
|
17
|
+
|
|
18
|
+
async load(key: string): Promise<TestRunValueInfo | undefined> {
|
|
19
|
+
const filePath = this.getFilePath(key);
|
|
20
|
+
const fileExists = fs.existsSync(filePath);
|
|
21
|
+
if (!fileExists) return;
|
|
22
|
+
|
|
23
|
+
const content = await fs.promises.readFile(filePath, 'utf8');
|
|
24
|
+
// todo: json parse safe (with warning)
|
|
25
|
+
const storedInfo: StoredInfo = JSON.parse(content);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
key,
|
|
29
|
+
state: 'computed',
|
|
30
|
+
persistent: true,
|
|
31
|
+
computedAt: storedInfo.computedAt,
|
|
32
|
+
value: storedInfo.value,
|
|
33
|
+
sig: storedInfo.sig,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async save({ key, value, sig }: TestRunValueInfo) {
|
|
38
|
+
const computedAt = Date.now();
|
|
39
|
+
const storedInfo: StoredInfo = { computedAt, sig, value };
|
|
40
|
+
const content = JSON.stringify(storedInfo, null, 2);
|
|
41
|
+
const filePath = this.getFilePath(key);
|
|
42
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
43
|
+
await fs.promises.writeFile(filePath, content, 'utf8');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async delete(key: string) {
|
|
47
|
+
const filePath = this.getFilePath(key);
|
|
48
|
+
if (fs.existsSync(filePath)) {
|
|
49
|
+
await fs.promises.rm(filePath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private getFilePath(key: string) {
|
|
54
|
+
return path.join(this.basePath, `${filenamify(key)}.json`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// eslint-disable-next-line no-control-regex
|
|
59
|
+
const filenameReservedRegex = /[<>:"/\\|?*\u0000-\u001F]/g;
|
|
60
|
+
function filenamify(s: string, replacement = '-') {
|
|
61
|
+
return s
|
|
62
|
+
.replace(filenameReservedRegex, replacement) // prettier-ignore
|
|
63
|
+
.replace(/\s+/g, replacement); // additionally replace spaces
|
|
64
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-instance mode of Global Storage Server.
|
|
3
|
+
* - non-persistent values are stored in memory
|
|
4
|
+
* - presistent values are stored on file system
|
|
5
|
+
* - compute-listeners are implemented via memory
|
|
6
|
+
*/
|
|
7
|
+
import { MemoryStorage } from './memory';
|
|
8
|
+
import { FilesystemStorage } from './fs';
|
|
9
|
+
|
|
10
|
+
// In-memory storage of test runs with values
|
|
11
|
+
const memoryStorageMap = new Map<string, MemoryStorage>();
|
|
12
|
+
const fsStorageMap = new Map<string, FilesystemStorage>();
|
|
13
|
+
|
|
14
|
+
// todo: cleanup old testRuns from the map
|
|
15
|
+
|
|
16
|
+
export function getMemoryStorage(runId: string) {
|
|
17
|
+
let storage = memoryStorageMap.get(runId);
|
|
18
|
+
if (!storage) {
|
|
19
|
+
storage = new MemoryStorage();
|
|
20
|
+
memoryStorageMap.set(runId, storage);
|
|
21
|
+
}
|
|
22
|
+
return storage;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getFsStorage(basePath: string) {
|
|
26
|
+
let storage = fsStorageMap.get(basePath);
|
|
27
|
+
if (!storage) {
|
|
28
|
+
storage = new FilesystemStorage(basePath);
|
|
29
|
+
fsStorageMap.set(basePath, storage);
|
|
30
|
+
}
|
|
31
|
+
return storage;
|
|
32
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { TestRunValueInfo } from '../../../shared/value-info';
|
|
2
|
+
import { ITestRunStorage } from '../types';
|
|
3
|
+
|
|
4
|
+
type Listener = (valueInfo: TestRunValueInfo) => void;
|
|
5
|
+
|
|
6
|
+
export class MemoryStorage implements ITestRunStorage {
|
|
7
|
+
private values = new Map<string, TestRunValueInfo>();
|
|
8
|
+
private listeners = new Map<string /* key */, Listener[]>();
|
|
9
|
+
|
|
10
|
+
async load(key: string) {
|
|
11
|
+
return this.values.get(key);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async loadByPrefix(prefix: string) {
|
|
15
|
+
return [...this.values.values()].filter((valueInfo) => valueInfo.key.startsWith(prefix));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async save(valueInfo: TestRunValueInfo, { notify = false } = {}) {
|
|
19
|
+
this.values.set(valueInfo.key, valueInfo);
|
|
20
|
+
if (notify) this.notify(valueInfo);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async clear() {
|
|
24
|
+
this.values.clear();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async wait(key: string) {
|
|
28
|
+
return new Promise<TestRunValueInfo>((resolve) => {
|
|
29
|
+
const listeners = this.listeners.get(key) || [];
|
|
30
|
+
listeners.push(resolve);
|
|
31
|
+
this.listeners.set(key, listeners);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private notify(valueInfo: TestRunValueInfo) {
|
|
36
|
+
const listeners = this.listeners.get(valueInfo.key);
|
|
37
|
+
try {
|
|
38
|
+
listeners?.forEach((resolve) => resolve(valueInfo));
|
|
39
|
+
} finally {
|
|
40
|
+
this.listeners.delete(valueInfo.key);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { TestRunValueInfo } from '../../shared/value-info';
|
|
2
|
+
|
|
3
|
+
export type ITestRunStorage = {
|
|
4
|
+
load(key: string): Promise<TestRunValueInfo | undefined>;
|
|
5
|
+
save(valueInfo: TestRunValueInfo, options?: { notify?: boolean }): Promise<void>;
|
|
6
|
+
wait(key: string): Promise<TestRunValueInfo>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type IPersistentStorage = {
|
|
10
|
+
load(key: string): Promise<TestRunValueInfo | undefined>;
|
|
11
|
+
save(valueInfo: TestRunValueInfo): Promise<void>;
|
|
12
|
+
delete(key: string): Promise<void>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import http from 'node:http';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Start server.
|
|
6
|
+
* See: https://github.com/nodejs/node/issues/21482#issuecomment-626025579
|
|
7
|
+
*/
|
|
8
|
+
export async function startExpressServer(app: express.Express, port = 0) {
|
|
9
|
+
return new Promise<http.Server>((resolve, reject) => {
|
|
10
|
+
const server = app.listen(port);
|
|
11
|
+
server.once('listening', () => resolve(server));
|
|
12
|
+
server.once('error', reject);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const logger = console;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate the calling signature for a given key:
|
|
3
|
+
* ttl + stack + computeFn body
|
|
4
|
+
*
|
|
5
|
+
* Signature is used to invalidate the key in case of changes in the calling code.
|
|
6
|
+
* Signature is computed on client and sent to the server.
|
|
7
|
+
* Server compares the signature with the stored one and invalidates the key if they differ.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { TTL } from './ttl';
|
|
11
|
+
|
|
12
|
+
type SingatureParams = {
|
|
13
|
+
fn: Function; // eslint-disable-line @typescript-eslint/no-unsafe-function-type
|
|
14
|
+
stack: string;
|
|
15
|
+
ttl?: TTL;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type SignatureObj = {
|
|
19
|
+
fn: string;
|
|
20
|
+
stack: string;
|
|
21
|
+
ttl?: TTL;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type SignatureMismatch = {
|
|
25
|
+
field: 'fn' | 'stack' | 'ttl' | 'sig';
|
|
26
|
+
value1: unknown;
|
|
27
|
+
value2: unknown;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function calcSignature({ ttl, stack, fn }: SingatureParams) {
|
|
31
|
+
// todo: use hashing?
|
|
32
|
+
// todo: remove spaces and newlines from stack and computeFn?
|
|
33
|
+
const fnBody = fn.toString().replace(/[\n\s]+/g, ' ');
|
|
34
|
+
const obj: SignatureObj = { ttl, stack, fn: fnBody };
|
|
35
|
+
|
|
36
|
+
return JSON.stringify(obj);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// eslint-disable-next-line visual/complexity
|
|
40
|
+
export function checkSignature(
|
|
41
|
+
key: string,
|
|
42
|
+
storedSigStr: string,
|
|
43
|
+
currentSigStr: string,
|
|
44
|
+
): SignatureMismatch | undefined {
|
|
45
|
+
const storedSig = parseSignatureSafe(storedSigStr);
|
|
46
|
+
const currentSig = parseSignatureSafe(currentSigStr);
|
|
47
|
+
|
|
48
|
+
// fallback to string comparison if parsing failed for any of the signatures
|
|
49
|
+
// todo: remove in the future releases
|
|
50
|
+
if (!storedSig || !currentSig) {
|
|
51
|
+
return storedSigStr !== currentSigStr
|
|
52
|
+
? createSignatureMismatch('sig', storedSigStr, currentSigStr)
|
|
53
|
+
: undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// checking stack first -> allows to navigate to the code by click in IDE.
|
|
57
|
+
if (storedSig.stack !== currentSig.stack) {
|
|
58
|
+
return createSignatureMismatch('stack', storedSig.stack, currentSig.stack);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (storedSig.ttl !== currentSig.ttl) {
|
|
62
|
+
return createSignatureMismatch('ttl', storedSig.ttl, currentSig.ttl);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (storedSig.fn !== currentSig.fn) {
|
|
66
|
+
return createSignatureMismatch('fn', storedSig.fn, currentSig.fn);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createSignatureMismatch(
|
|
71
|
+
field: SignatureMismatch['field'],
|
|
72
|
+
value1: unknown,
|
|
73
|
+
value2: unknown,
|
|
74
|
+
): SignatureMismatch {
|
|
75
|
+
return { field, value1, value2 };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseSignatureSafe(sigStr: string) {
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(sigStr) as SignatureObj;
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import ms, { StringValue } from 'ms';
|
|
2
|
+
|
|
3
|
+
// time to live, can be a number of milliseconds, a string like "1h" or "infinite".
|
|
4
|
+
export type TTL = number | StringValue | 'infinite';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* TTL can be a stringified number or a string like "1h" or "infinite".
|
|
8
|
+
*/
|
|
9
|
+
export function parseTTL(ttl: string | number | undefined) {
|
|
10
|
+
if (ttl === '' || ttl === undefined) return undefined;
|
|
11
|
+
if (ttl === 'infinite') return -1;
|
|
12
|
+
const ttlNumber = Number(ttl);
|
|
13
|
+
return Number.isNaN(ttlNumber) ? ms(ttl as StringValue) : ttlNumber;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isExpired(computedAt: number | undefined, ttl: number) {
|
|
17
|
+
if (!computedAt || ttl === -1) return false;
|
|
18
|
+
return Date.now() > computedAt + ttl;
|
|
19
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value info in the scope of particular test run.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type TestRunValueInfo = {
|
|
6
|
+
key: string;
|
|
7
|
+
sig: string;
|
|
8
|
+
state: 'computing' | 'computed' | 'error';
|
|
9
|
+
computedAt?: number;
|
|
10
|
+
value?: unknown;
|
|
11
|
+
persistent?: boolean; // indicates if the value is stored persistently (e.g., in file system)
|
|
12
|
+
prevValue?: unknown; // previous value (applicable for persistent keys, used for cleanup)
|
|
13
|
+
errorMessage?: string; // error message in case of 'error' states
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function updateValueInfo(valueInfo: TestRunValueInfo, props: Partial<TestRunValueInfo>) {
|
|
17
|
+
return Object.assign(valueInfo, props);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function setComputed(valueInfo: TestRunValueInfo, value: unknown) {
|
|
21
|
+
updateValueInfo(valueInfo, {
|
|
22
|
+
state: 'computed',
|
|
23
|
+
value,
|
|
24
|
+
computedAt: Date.now(),
|
|
25
|
+
errorMessage: undefined,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function setError(valueInfo: TestRunValueInfo, errorMessage: string) {
|
|
30
|
+
updateValueInfo(valueInfo, {
|
|
31
|
+
state: 'error',
|
|
32
|
+
errorMessage,
|
|
33
|
+
value: undefined,
|
|
34
|
+
computedAt: undefined,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getStaleValue(valueInfo?: TestRunValueInfo | null) {
|
|
39
|
+
return valueInfo?.persistent ? valueInfo.prevValue : valueInfo?.value;
|
|
40
|
+
}
|