@commitspark/graphql-api 1.0.0-beta.9 → 2.0.0-beta.1
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/README.md +3 -3
- package/dist/cjs/persistence/cache.js +76 -27
- package/dist/cjs/persistence/cache.js.map +1 -1
- package/dist/esm/persistence/cache.js +76 -27
- package/dist/esm/persistence/cache.js.map +1 -1
- package/dist/types/persistence/cache.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/persistence/cache.ts +115 -28
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.0.0-beta.1] - 2026-09-24
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Cache entry content by content hash across commits so that only changed entries are retrieved from Git adapters
|
|
13
|
+
after a commit changes
|
|
14
|
+
- Upgrade to @commitspark/git-adapter 2.0.0-beta.1, which requires Git adapters of major version 2
|
|
15
|
+
|
|
16
|
+
## [1.0.0] - 2026-02-27
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Upgrade to @commitspark/git-adapter 1.0.0
|
|
21
|
+
|
|
8
22
|
## [1.0.0-beta.9] - 2026-02-26
|
|
9
23
|
|
|
10
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -22,10 +22,10 @@ There are two common ways to use this library:
|
|
|
22
22
|
```shell
|
|
23
23
|
npm i @commitspark/graphql-api
|
|
24
24
|
```
|
|
25
|
-
2. By making GraphQL calls over HTTP to this library wrapped in a webserver or Lambda function
|
|
25
|
+
2. By making GraphQL calls over HTTP to this library wrapped in a webserver or Lambda function.
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
You can find an example Node.js Express web server implementation
|
|
28
|
+
[here](https://github.com/commitspark/example-http-express).
|
|
29
29
|
|
|
30
30
|
## Installing Git provider support
|
|
31
31
|
|
|
@@ -5,66 +5,104 @@ const git_adapter_1 = require("@commitspark/git-adapter");
|
|
|
5
5
|
const errors_ts_1 = require("../graphql/errors.js");
|
|
6
6
|
const MAX_CACHE_ENTRIES = 50;
|
|
7
7
|
// intentionally not async so that the same in-flight promise is returned to all callers
|
|
8
|
-
const getEntriesRecordByRef = (entriesCache, inflightEntries, cacheSize, context, ref) => {
|
|
8
|
+
const getEntriesRecordByRef = (entriesCache, contentCache, inflightEntries, cacheSize, context, ref) => {
|
|
9
9
|
const cacheRecord = entriesCache.get(ref);
|
|
10
10
|
if (cacheRecord !== undefined) {
|
|
11
11
|
// move map key back to end of list (newest)
|
|
12
12
|
entriesCache.delete(ref);
|
|
13
13
|
entriesCache.set(ref, cacheRecord);
|
|
14
|
-
return Promise.resolve(cacheRecord);
|
|
14
|
+
return Promise.resolve(cacheRecord.entriesRecord);
|
|
15
15
|
}
|
|
16
16
|
const inflightPromise = inflightEntries.get(ref);
|
|
17
17
|
if (inflightPromise) {
|
|
18
18
|
return inflightPromise;
|
|
19
19
|
}
|
|
20
|
-
const fetchPromise = fetchAndCacheEntries(entriesCache, cacheSize, context, ref).finally(() => inflightEntries.delete(ref));
|
|
20
|
+
const fetchPromise = fetchAndCacheEntries(entriesCache, contentCache, cacheSize, context, ref).finally(() => inflightEntries.delete(ref));
|
|
21
21
|
inflightEntries.set(ref, fetchPromise);
|
|
22
22
|
return fetchPromise;
|
|
23
23
|
};
|
|
24
|
-
const fetchAndCacheEntries = async (entriesCache, cacheSize, context, ref) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
const fetchAndCacheEntries = async (entriesCache, contentCache, cacheSize, context, ref) => {
|
|
25
|
+
const entryHashes = await callGitAdapter(() => context.gitAdapter.getEntryHashes(ref));
|
|
26
|
+
// hold on to known content now, as it may be evicted from the content cache while missing content is fetched
|
|
27
|
+
const contentByHash = new Map();
|
|
28
|
+
const idsToFetchByHash = new Map();
|
|
29
|
+
for (const { id, hash } of entryHashes) {
|
|
30
|
+
const content = contentCache.get(hash);
|
|
31
|
+
if (content !== undefined) {
|
|
32
|
+
contentByHash.set(hash, content);
|
|
33
|
+
}
|
|
34
|
+
else if (!idsToFetchByHash.has(hash)) {
|
|
35
|
+
// entries with equal hash have equal content, so fetching one of them is sufficient
|
|
36
|
+
idsToFetchByHash.set(hash, id);
|
|
37
|
+
}
|
|
28
38
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
if (idsToFetchByHash.size > 0) {
|
|
40
|
+
const fetchedEntries = await callGitAdapter(() => context.gitAdapter.getEntriesByIds(ref, [...idsToFetchByHash.values()]));
|
|
41
|
+
const fetchedEntriesById = new Map(fetchedEntries.map((entry) => [entry.id, entry]));
|
|
42
|
+
for (const [hash, id] of idsToFetchByHash) {
|
|
43
|
+
const fetchedEntry = fetchedEntriesById.get(id);
|
|
44
|
+
if (fetchedEntry === undefined) {
|
|
45
|
+
throw (0, errors_ts_1.createError)(`Git adapter did not return requested entry "${id}".`, "INTERNAL_ERROR" /* ErrorCode.INTERNAL_ERROR */, {});
|
|
46
|
+
}
|
|
47
|
+
contentByHash.set(hash, createEntryContent(fetchedEntry));
|
|
32
48
|
}
|
|
33
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
34
|
-
throw (0, errors_ts_1.createError)(message, "INTERNAL_ERROR" /* ErrorCode.INTERNAL_ERROR */, {});
|
|
35
49
|
}
|
|
36
|
-
const entriesById = new Map(
|
|
50
|
+
const entriesById = new Map();
|
|
37
51
|
const entriesByType = new Map();
|
|
38
|
-
for (const
|
|
52
|
+
for (const { id, hash } of entryHashes) {
|
|
53
|
+
const entry = {
|
|
54
|
+
id: id,
|
|
55
|
+
...contentByHash.get(hash),
|
|
56
|
+
};
|
|
57
|
+
entriesById.set(id, entry);
|
|
39
58
|
const existingEntriesOfType = entriesByType.get(entry.metadata.type) || [];
|
|
40
59
|
existingEntriesOfType.push(entry);
|
|
41
60
|
entriesByType.set(entry.metadata.type, existingEntriesOfType);
|
|
42
61
|
}
|
|
43
62
|
const newCacheRecord = {
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
entriesRecord: {
|
|
64
|
+
byType: entriesByType,
|
|
65
|
+
byId: entriesById,
|
|
66
|
+
},
|
|
67
|
+
contentHashes: [...contentByHash.keys()],
|
|
46
68
|
};
|
|
69
|
+
for (const [hash, content] of contentByHash) {
|
|
70
|
+
contentCache.set(hash, content);
|
|
71
|
+
}
|
|
47
72
|
entriesCache.set(ref, newCacheRecord);
|
|
48
73
|
if (entriesCache.size > cacheSize) {
|
|
49
74
|
// get first map key (oldest)
|
|
50
75
|
const oldestKey = entriesCache.keys().next().value;
|
|
51
76
|
if (oldestKey !== undefined) {
|
|
52
77
|
entriesCache.delete(oldestKey);
|
|
78
|
+
removeUnreferencedContent(entriesCache, contentCache);
|
|
53
79
|
}
|
|
54
80
|
}
|
|
55
|
-
return newCacheRecord;
|
|
81
|
+
return newCacheRecord.entriesRecord;
|
|
56
82
|
};
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
schemaCache.delete(ref);
|
|
62
|
-
schemaCache.set(ref, schemaCacheRecord);
|
|
63
|
-
return schemaCacheRecord;
|
|
83
|
+
const createEntryContent = (entry) => {
|
|
84
|
+
const content = { metadata: entry.metadata };
|
|
85
|
+
if (entry.data !== undefined) {
|
|
86
|
+
content.data = entry.data;
|
|
64
87
|
}
|
|
65
|
-
|
|
88
|
+
return content;
|
|
89
|
+
};
|
|
90
|
+
const removeUnreferencedContent = (entriesCache, contentCache) => {
|
|
91
|
+
const referencedHashes = new Set();
|
|
92
|
+
for (const cacheRecord of entriesCache.values()) {
|
|
93
|
+
for (const hash of cacheRecord.contentHashes) {
|
|
94
|
+
referencedHashes.add(hash);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const hash of contentCache.keys()) {
|
|
98
|
+
if (!referencedHashes.has(hash)) {
|
|
99
|
+
contentCache.delete(hash);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const callGitAdapter = async (operation) => {
|
|
66
104
|
try {
|
|
67
|
-
|
|
105
|
+
return await operation();
|
|
68
106
|
}
|
|
69
107
|
catch (err) {
|
|
70
108
|
if (err instanceof git_adapter_1.GitAdapterError) {
|
|
@@ -73,6 +111,16 @@ const getSchemaStringByRef = async (schemaCache, cacheSize, context, ref) => {
|
|
|
73
111
|
const message = err instanceof Error ? err.message : String(err);
|
|
74
112
|
throw (0, errors_ts_1.createError)(message, "INTERNAL_ERROR" /* ErrorCode.INTERNAL_ERROR */, {});
|
|
75
113
|
}
|
|
114
|
+
};
|
|
115
|
+
const getSchemaStringByRef = async (schemaCache, cacheSize, context, ref) => {
|
|
116
|
+
const schemaCacheRecord = schemaCache.get(ref);
|
|
117
|
+
if (schemaCacheRecord !== undefined) {
|
|
118
|
+
// move map key back to end of list (newest)
|
|
119
|
+
schemaCache.delete(ref);
|
|
120
|
+
schemaCache.set(ref, schemaCacheRecord);
|
|
121
|
+
return schemaCacheRecord;
|
|
122
|
+
}
|
|
123
|
+
const schemaString = await callGitAdapter(() => context.gitAdapter.getSchema(ref));
|
|
76
124
|
schemaCache.set(ref, schemaString);
|
|
77
125
|
if (schemaCache.size > cacheSize) {
|
|
78
126
|
// get first map key (oldest)
|
|
@@ -85,10 +133,11 @@ const getSchemaStringByRef = async (schemaCache, cacheSize, context, ref) => {
|
|
|
85
133
|
};
|
|
86
134
|
const createCacheHandler = (cacheSize = MAX_CACHE_ENTRIES) => {
|
|
87
135
|
const entriesCache = new Map();
|
|
136
|
+
const contentCache = new Map();
|
|
88
137
|
const schemaCache = new Map();
|
|
89
138
|
const inflightEntries = new Map();
|
|
90
139
|
return {
|
|
91
|
-
getEntriesRecord: (...args) => getEntriesRecordByRef(entriesCache, inflightEntries, cacheSize, ...args),
|
|
140
|
+
getEntriesRecord: (...args) => getEntriesRecordByRef(entriesCache, contentCache, inflightEntries, cacheSize, ...args),
|
|
92
141
|
getSchema: (...args) => getSchemaStringByRef(schemaCache, cacheSize, ...args),
|
|
93
142
|
};
|
|
94
143
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/persistence/cache.ts"],"names":[],"mappings":";;;AAAA,0DAAiE;AAEjE,oDAA6D;
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/persistence/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,66 +2,104 @@ import { GitAdapterError } from '@commitspark/git-adapter';
|
|
|
2
2
|
import { createError } from "../graphql/errors.js";
|
|
3
3
|
const MAX_CACHE_ENTRIES = 50;
|
|
4
4
|
// intentionally not async so that the same in-flight promise is returned to all callers
|
|
5
|
-
const getEntriesRecordByRef = (entriesCache, inflightEntries, cacheSize, context, ref) => {
|
|
5
|
+
const getEntriesRecordByRef = (entriesCache, contentCache, inflightEntries, cacheSize, context, ref) => {
|
|
6
6
|
const cacheRecord = entriesCache.get(ref);
|
|
7
7
|
if (cacheRecord !== undefined) {
|
|
8
8
|
// move map key back to end of list (newest)
|
|
9
9
|
entriesCache.delete(ref);
|
|
10
10
|
entriesCache.set(ref, cacheRecord);
|
|
11
|
-
return Promise.resolve(cacheRecord);
|
|
11
|
+
return Promise.resolve(cacheRecord.entriesRecord);
|
|
12
12
|
}
|
|
13
13
|
const inflightPromise = inflightEntries.get(ref);
|
|
14
14
|
if (inflightPromise) {
|
|
15
15
|
return inflightPromise;
|
|
16
16
|
}
|
|
17
|
-
const fetchPromise = fetchAndCacheEntries(entriesCache, cacheSize, context, ref).finally(() => inflightEntries.delete(ref));
|
|
17
|
+
const fetchPromise = fetchAndCacheEntries(entriesCache, contentCache, cacheSize, context, ref).finally(() => inflightEntries.delete(ref));
|
|
18
18
|
inflightEntries.set(ref, fetchPromise);
|
|
19
19
|
return fetchPromise;
|
|
20
20
|
};
|
|
21
|
-
const fetchAndCacheEntries = async (entriesCache, cacheSize, context, ref) => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const fetchAndCacheEntries = async (entriesCache, contentCache, cacheSize, context, ref) => {
|
|
22
|
+
const entryHashes = await callGitAdapter(() => context.gitAdapter.getEntryHashes(ref));
|
|
23
|
+
// hold on to known content now, as it may be evicted from the content cache while missing content is fetched
|
|
24
|
+
const contentByHash = new Map();
|
|
25
|
+
const idsToFetchByHash = new Map();
|
|
26
|
+
for (const { id, hash } of entryHashes) {
|
|
27
|
+
const content = contentCache.get(hash);
|
|
28
|
+
if (content !== undefined) {
|
|
29
|
+
contentByHash.set(hash, content);
|
|
30
|
+
}
|
|
31
|
+
else if (!idsToFetchByHash.has(hash)) {
|
|
32
|
+
// entries with equal hash have equal content, so fetching one of them is sufficient
|
|
33
|
+
idsToFetchByHash.set(hash, id);
|
|
34
|
+
}
|
|
25
35
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
if (idsToFetchByHash.size > 0) {
|
|
37
|
+
const fetchedEntries = await callGitAdapter(() => context.gitAdapter.getEntriesByIds(ref, [...idsToFetchByHash.values()]));
|
|
38
|
+
const fetchedEntriesById = new Map(fetchedEntries.map((entry) => [entry.id, entry]));
|
|
39
|
+
for (const [hash, id] of idsToFetchByHash) {
|
|
40
|
+
const fetchedEntry = fetchedEntriesById.get(id);
|
|
41
|
+
if (fetchedEntry === undefined) {
|
|
42
|
+
throw createError(`Git adapter did not return requested entry "${id}".`, "INTERNAL_ERROR" /* ErrorCode.INTERNAL_ERROR */, {});
|
|
43
|
+
}
|
|
44
|
+
contentByHash.set(hash, createEntryContent(fetchedEntry));
|
|
29
45
|
}
|
|
30
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
31
|
-
throw createError(message, "INTERNAL_ERROR" /* ErrorCode.INTERNAL_ERROR */, {});
|
|
32
46
|
}
|
|
33
|
-
const entriesById = new Map(
|
|
47
|
+
const entriesById = new Map();
|
|
34
48
|
const entriesByType = new Map();
|
|
35
|
-
for (const
|
|
49
|
+
for (const { id, hash } of entryHashes) {
|
|
50
|
+
const entry = {
|
|
51
|
+
id: id,
|
|
52
|
+
...contentByHash.get(hash),
|
|
53
|
+
};
|
|
54
|
+
entriesById.set(id, entry);
|
|
36
55
|
const existingEntriesOfType = entriesByType.get(entry.metadata.type) || [];
|
|
37
56
|
existingEntriesOfType.push(entry);
|
|
38
57
|
entriesByType.set(entry.metadata.type, existingEntriesOfType);
|
|
39
58
|
}
|
|
40
59
|
const newCacheRecord = {
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
entriesRecord: {
|
|
61
|
+
byType: entriesByType,
|
|
62
|
+
byId: entriesById,
|
|
63
|
+
},
|
|
64
|
+
contentHashes: [...contentByHash.keys()],
|
|
43
65
|
};
|
|
66
|
+
for (const [hash, content] of contentByHash) {
|
|
67
|
+
contentCache.set(hash, content);
|
|
68
|
+
}
|
|
44
69
|
entriesCache.set(ref, newCacheRecord);
|
|
45
70
|
if (entriesCache.size > cacheSize) {
|
|
46
71
|
// get first map key (oldest)
|
|
47
72
|
const oldestKey = entriesCache.keys().next().value;
|
|
48
73
|
if (oldestKey !== undefined) {
|
|
49
74
|
entriesCache.delete(oldestKey);
|
|
75
|
+
removeUnreferencedContent(entriesCache, contentCache);
|
|
50
76
|
}
|
|
51
77
|
}
|
|
52
|
-
return newCacheRecord;
|
|
78
|
+
return newCacheRecord.entriesRecord;
|
|
53
79
|
};
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
schemaCache.delete(ref);
|
|
59
|
-
schemaCache.set(ref, schemaCacheRecord);
|
|
60
|
-
return schemaCacheRecord;
|
|
80
|
+
const createEntryContent = (entry) => {
|
|
81
|
+
const content = { metadata: entry.metadata };
|
|
82
|
+
if (entry.data !== undefined) {
|
|
83
|
+
content.data = entry.data;
|
|
61
84
|
}
|
|
62
|
-
|
|
85
|
+
return content;
|
|
86
|
+
};
|
|
87
|
+
const removeUnreferencedContent = (entriesCache, contentCache) => {
|
|
88
|
+
const referencedHashes = new Set();
|
|
89
|
+
for (const cacheRecord of entriesCache.values()) {
|
|
90
|
+
for (const hash of cacheRecord.contentHashes) {
|
|
91
|
+
referencedHashes.add(hash);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const hash of contentCache.keys()) {
|
|
95
|
+
if (!referencedHashes.has(hash)) {
|
|
96
|
+
contentCache.delete(hash);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const callGitAdapter = async (operation) => {
|
|
63
101
|
try {
|
|
64
|
-
|
|
102
|
+
return await operation();
|
|
65
103
|
}
|
|
66
104
|
catch (err) {
|
|
67
105
|
if (err instanceof GitAdapterError) {
|
|
@@ -70,6 +108,16 @@ const getSchemaStringByRef = async (schemaCache, cacheSize, context, ref) => {
|
|
|
70
108
|
const message = err instanceof Error ? err.message : String(err);
|
|
71
109
|
throw createError(message, "INTERNAL_ERROR" /* ErrorCode.INTERNAL_ERROR */, {});
|
|
72
110
|
}
|
|
111
|
+
};
|
|
112
|
+
const getSchemaStringByRef = async (schemaCache, cacheSize, context, ref) => {
|
|
113
|
+
const schemaCacheRecord = schemaCache.get(ref);
|
|
114
|
+
if (schemaCacheRecord !== undefined) {
|
|
115
|
+
// move map key back to end of list (newest)
|
|
116
|
+
schemaCache.delete(ref);
|
|
117
|
+
schemaCache.set(ref, schemaCacheRecord);
|
|
118
|
+
return schemaCacheRecord;
|
|
119
|
+
}
|
|
120
|
+
const schemaString = await callGitAdapter(() => context.gitAdapter.getSchema(ref));
|
|
73
121
|
schemaCache.set(ref, schemaString);
|
|
74
122
|
if (schemaCache.size > cacheSize) {
|
|
75
123
|
// get first map key (oldest)
|
|
@@ -82,10 +130,11 @@ const getSchemaStringByRef = async (schemaCache, cacheSize, context, ref) => {
|
|
|
82
130
|
};
|
|
83
131
|
export const createCacheHandler = (cacheSize = MAX_CACHE_ENTRIES) => {
|
|
84
132
|
const entriesCache = new Map();
|
|
133
|
+
const contentCache = new Map();
|
|
85
134
|
const schemaCache = new Map();
|
|
86
135
|
const inflightEntries = new Map();
|
|
87
136
|
return {
|
|
88
|
-
getEntriesRecord: (...args) => getEntriesRecordByRef(entriesCache, inflightEntries, cacheSize, ...args),
|
|
137
|
+
getEntriesRecord: (...args) => getEntriesRecordByRef(entriesCache, contentCache, inflightEntries, cacheSize, ...args),
|
|
89
138
|
getSchema: (...args) => getSchemaStringByRef(schemaCache, cacheSize, ...args),
|
|
90
139
|
};
|
|
91
140
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/persistence/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAEjE,OAAO,EAAE,WAAW,EAAa,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/persistence/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/persistence/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAmB,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/persistence/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAmB,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAY5C,UAAU,aAAa;IACrB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IACxB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;CAC7B;AAOD,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,CAChB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,KACR,OAAO,CAAC,aAAa,CAAC,CAAA;IAC3B,SAAS,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CACpE;AA+LD,eAAO,MAAM,kBAAkB,GAC7B,YAAW,MAA0B,KACpC,sBAoBF,CAAA"}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"gitops",
|
|
10
10
|
"api"
|
|
11
11
|
],
|
|
12
|
-
"version": "
|
|
12
|
+
"version": "2.0.0-beta.1",
|
|
13
13
|
"license": "ISC",
|
|
14
14
|
"private": false,
|
|
15
15
|
"files": [
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@apollo/server": "^5.2.0",
|
|
47
|
-
"@commitspark/git-adapter": "^0.
|
|
47
|
+
"@commitspark/git-adapter": "^2.0.0-beta.1",
|
|
48
48
|
"@graphql-tools/schema": "^9.0.0",
|
|
49
49
|
"@graphql-tools/utils": "^9.0.0"
|
|
50
50
|
},
|
package/src/persistence/cache.ts
CHANGED
|
@@ -3,7 +3,11 @@ import { ApolloContext } from '../client.ts'
|
|
|
3
3
|
import { createError, ErrorCode } from '../graphql/errors.ts'
|
|
4
4
|
|
|
5
5
|
type Ref = string
|
|
6
|
-
type
|
|
6
|
+
type ContentHash = string
|
|
7
|
+
type EntryContent = Omit<Entry, 'id'>
|
|
8
|
+
type EntriesCache = Map<Ref, CommitCacheRecord>
|
|
9
|
+
// entry content shared across all cached commits, so that unchanged entries are neither fetched nor stored twice
|
|
10
|
+
type ContentCache = Map<ContentHash, EntryContent>
|
|
7
11
|
type SchemaCache = Map<Ref, string>
|
|
8
12
|
type InflightEntries = Map<Ref, Promise<EntriesRecord>>
|
|
9
13
|
|
|
@@ -12,6 +16,11 @@ interface EntriesRecord {
|
|
|
12
16
|
byType: Map<string, Entry[]>
|
|
13
17
|
}
|
|
14
18
|
|
|
19
|
+
interface CommitCacheRecord {
|
|
20
|
+
entriesRecord: EntriesRecord
|
|
21
|
+
contentHashes: ContentHash[]
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
export interface RepositoryCacheHandler {
|
|
16
25
|
getEntriesRecord: (
|
|
17
26
|
context: ApolloContext,
|
|
@@ -25,6 +34,7 @@ const MAX_CACHE_ENTRIES = 50
|
|
|
25
34
|
// intentionally not async so that the same in-flight promise is returned to all callers
|
|
26
35
|
const getEntriesRecordByRef = (
|
|
27
36
|
entriesCache: EntriesCache,
|
|
37
|
+
contentCache: ContentCache,
|
|
28
38
|
inflightEntries: InflightEntries,
|
|
29
39
|
cacheSize: number,
|
|
30
40
|
context: ApolloContext,
|
|
@@ -36,7 +46,7 @@ const getEntriesRecordByRef = (
|
|
|
36
46
|
entriesCache.delete(ref)
|
|
37
47
|
entriesCache.set(ref, cacheRecord)
|
|
38
48
|
|
|
39
|
-
return Promise.resolve(cacheRecord)
|
|
49
|
+
return Promise.resolve(cacheRecord.entriesRecord)
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
const inflightPromise = inflightEntries.get(ref)
|
|
@@ -46,6 +56,7 @@ const getEntriesRecordByRef = (
|
|
|
46
56
|
|
|
47
57
|
const fetchPromise = fetchAndCacheEntries(
|
|
48
58
|
entriesCache,
|
|
59
|
+
contentCache,
|
|
49
60
|
cacheSize,
|
|
50
61
|
context,
|
|
51
62
|
ref,
|
|
@@ -58,34 +69,72 @@ const getEntriesRecordByRef = (
|
|
|
58
69
|
|
|
59
70
|
const fetchAndCacheEntries = async (
|
|
60
71
|
entriesCache: EntriesCache,
|
|
72
|
+
contentCache: ContentCache,
|
|
61
73
|
cacheSize: number,
|
|
62
74
|
context: ApolloContext,
|
|
63
75
|
ref: string,
|
|
64
76
|
): Promise<EntriesRecord> => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
const entryHashes = await callGitAdapter(() =>
|
|
78
|
+
context.gitAdapter.getEntryHashes(ref),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
// hold on to known content now, as it may be evicted from the content cache while missing content is fetched
|
|
82
|
+
const contentByHash = new Map<ContentHash, EntryContent>()
|
|
83
|
+
const idsToFetchByHash = new Map<ContentHash, string>()
|
|
84
|
+
for (const { id, hash } of entryHashes) {
|
|
85
|
+
const content = contentCache.get(hash)
|
|
86
|
+
if (content !== undefined) {
|
|
87
|
+
contentByHash.set(hash, content)
|
|
88
|
+
} else if (!idsToFetchByHash.has(hash)) {
|
|
89
|
+
// entries with equal hash have equal content, so fetching one of them is sufficient
|
|
90
|
+
idsToFetchByHash.set(hash, id)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (idsToFetchByHash.size > 0) {
|
|
95
|
+
const fetchedEntries = await callGitAdapter(() =>
|
|
96
|
+
context.gitAdapter.getEntriesByIds(ref, [...idsToFetchByHash.values()]),
|
|
97
|
+
)
|
|
98
|
+
const fetchedEntriesById = new Map(
|
|
99
|
+
fetchedEntries.map((entry) => [entry.id, entry]),
|
|
100
|
+
)
|
|
101
|
+
for (const [hash, id] of idsToFetchByHash) {
|
|
102
|
+
const fetchedEntry = fetchedEntriesById.get(id)
|
|
103
|
+
if (fetchedEntry === undefined) {
|
|
104
|
+
throw createError(
|
|
105
|
+
`Git adapter did not return requested entry "${id}".`,
|
|
106
|
+
ErrorCode.INTERNAL_ERROR,
|
|
107
|
+
{},
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
contentByHash.set(hash, createEntryContent(fetchedEntry))
|
|
71
111
|
}
|
|
72
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
73
|
-
throw createError(message, ErrorCode.INTERNAL_ERROR, {})
|
|
74
112
|
}
|
|
75
113
|
|
|
76
|
-
const entriesById = new Map
|
|
114
|
+
const entriesById = new Map<string, Entry>()
|
|
77
115
|
const entriesByType = new Map<string, Entry[]>()
|
|
78
|
-
for (const
|
|
116
|
+
for (const { id, hash } of entryHashes) {
|
|
117
|
+
const entry: Entry = {
|
|
118
|
+
id: id,
|
|
119
|
+
...(contentByHash.get(hash) as EntryContent),
|
|
120
|
+
}
|
|
121
|
+
entriesById.set(id, entry)
|
|
79
122
|
const existingEntriesOfType = entriesByType.get(entry.metadata.type) || []
|
|
80
123
|
existingEntriesOfType.push(entry)
|
|
81
124
|
entriesByType.set(entry.metadata.type, existingEntriesOfType)
|
|
82
125
|
}
|
|
83
126
|
|
|
84
|
-
const newCacheRecord = {
|
|
85
|
-
|
|
86
|
-
|
|
127
|
+
const newCacheRecord: CommitCacheRecord = {
|
|
128
|
+
entriesRecord: {
|
|
129
|
+
byType: entriesByType,
|
|
130
|
+
byId: entriesById,
|
|
131
|
+
},
|
|
132
|
+
contentHashes: [...contentByHash.keys()],
|
|
87
133
|
}
|
|
88
134
|
|
|
135
|
+
for (const [hash, content] of contentByHash) {
|
|
136
|
+
contentCache.set(hash, content)
|
|
137
|
+
}
|
|
89
138
|
entriesCache.set(ref, newCacheRecord)
|
|
90
139
|
|
|
91
140
|
if (entriesCache.size > cacheSize) {
|
|
@@ -93,10 +142,48 @@ const fetchAndCacheEntries = async (
|
|
|
93
142
|
const oldestKey = entriesCache.keys().next().value as Ref | undefined
|
|
94
143
|
if (oldestKey !== undefined) {
|
|
95
144
|
entriesCache.delete(oldestKey)
|
|
145
|
+
removeUnreferencedContent(entriesCache, contentCache)
|
|
96
146
|
}
|
|
97
147
|
}
|
|
98
148
|
|
|
99
|
-
return newCacheRecord
|
|
149
|
+
return newCacheRecord.entriesRecord
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const createEntryContent = (entry: Entry): EntryContent => {
|
|
153
|
+
const content: EntryContent = { metadata: entry.metadata }
|
|
154
|
+
if (entry.data !== undefined) {
|
|
155
|
+
content.data = entry.data
|
|
156
|
+
}
|
|
157
|
+
return content
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const removeUnreferencedContent = (
|
|
161
|
+
entriesCache: EntriesCache,
|
|
162
|
+
contentCache: ContentCache,
|
|
163
|
+
): void => {
|
|
164
|
+
const referencedHashes = new Set<ContentHash>()
|
|
165
|
+
for (const cacheRecord of entriesCache.values()) {
|
|
166
|
+
for (const hash of cacheRecord.contentHashes) {
|
|
167
|
+
referencedHashes.add(hash)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const hash of contentCache.keys()) {
|
|
171
|
+
if (!referencedHashes.has(hash)) {
|
|
172
|
+
contentCache.delete(hash)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const callGitAdapter = async <T>(operation: () => Promise<T>): Promise<T> => {
|
|
178
|
+
try {
|
|
179
|
+
return await operation()
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (err instanceof GitAdapterError) {
|
|
182
|
+
throw createError(err.message, err.code, {})
|
|
183
|
+
}
|
|
184
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
185
|
+
throw createError(message, ErrorCode.INTERNAL_ERROR, {})
|
|
186
|
+
}
|
|
100
187
|
}
|
|
101
188
|
|
|
102
189
|
const getSchemaStringByRef = async (
|
|
@@ -114,16 +201,9 @@ const getSchemaStringByRef = async (
|
|
|
114
201
|
return schemaCacheRecord
|
|
115
202
|
}
|
|
116
203
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
} catch (err) {
|
|
121
|
-
if (err instanceof GitAdapterError) {
|
|
122
|
-
throw createError(err.message, err.code, {})
|
|
123
|
-
}
|
|
124
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
125
|
-
throw createError(message, ErrorCode.INTERNAL_ERROR, {})
|
|
126
|
-
}
|
|
204
|
+
const schemaString = await callGitAdapter(() =>
|
|
205
|
+
context.gitAdapter.getSchema(ref),
|
|
206
|
+
)
|
|
127
207
|
|
|
128
208
|
schemaCache.set(ref, schemaString)
|
|
129
209
|
|
|
@@ -141,7 +221,8 @@ const getSchemaStringByRef = async (
|
|
|
141
221
|
export const createCacheHandler = (
|
|
142
222
|
cacheSize: number = MAX_CACHE_ENTRIES,
|
|
143
223
|
): RepositoryCacheHandler => {
|
|
144
|
-
const entriesCache: EntriesCache = new Map<Ref,
|
|
224
|
+
const entriesCache: EntriesCache = new Map<Ref, CommitCacheRecord>()
|
|
225
|
+
const contentCache: ContentCache = new Map<ContentHash, EntryContent>()
|
|
145
226
|
const schemaCache: SchemaCache = new Map<Ref, string>()
|
|
146
227
|
const inflightEntries: InflightEntries = new Map<
|
|
147
228
|
Ref,
|
|
@@ -149,7 +230,13 @@ export const createCacheHandler = (
|
|
|
149
230
|
>()
|
|
150
231
|
return {
|
|
151
232
|
getEntriesRecord: (...args) =>
|
|
152
|
-
getEntriesRecordByRef(
|
|
233
|
+
getEntriesRecordByRef(
|
|
234
|
+
entriesCache,
|
|
235
|
+
contentCache,
|
|
236
|
+
inflightEntries,
|
|
237
|
+
cacheSize,
|
|
238
|
+
...args,
|
|
239
|
+
),
|
|
153
240
|
getSchema: (...args) =>
|
|
154
241
|
getSchemaStringByRef(schemaCache, cacheSize, ...args),
|
|
155
242
|
}
|