@vtex/api 6.45.14-beta.0 → 6.45.14
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 +20 -0
- package/lib/HttpClient/HttpClient.d.ts +1 -0
- package/lib/HttpClient/HttpClient.js +14 -1
- package/lib/clients/infra/VBase.js +29 -4
- package/lib/service/worker/runtime/utils/context.d.ts +1 -1
- package/lib/utils/MineWinsConflictsResolver.d.ts +2 -2
- package/lib/utils/MineWinsConflictsResolver.js +17 -22
- package/lib/utils/conflicts.mock.d.ts +17 -0
- package/lib/utils/conflicts.mock.js +27 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,12 +6,27 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [6.45.14] - 2023-02-01
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- New tests for `MineWinsConflictsResolver`.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Merged/Rebased content not being returned causing inconsistencies.
|
|
19
|
+
- Do not return empty content if no conflict is found.
|
|
20
|
+
|
|
21
|
+
## [6.45.13] - 2022-12-12
|
|
9
22
|
### Changed
|
|
23
|
+
- Deterministic cache for getWithBody
|
|
10
24
|
- Remove socket metrics per origin
|
|
11
25
|
|
|
12
26
|
## [6.45.12] - 2022-04-14
|
|
13
27
|
|
|
14
28
|
### Fixed
|
|
29
|
+
|
|
15
30
|
- Update sensitive fields list.
|
|
16
31
|
- Remove "error" field.
|
|
17
32
|
- Add missing fields.
|
|
@@ -1736,3 +1751,8 @@ instead
|
|
|
1736
1751
|
### Changed
|
|
1737
1752
|
|
|
1738
1753
|
- `HttpClient` now adds `'Accept-Encoding': 'gzip'` header by default.
|
|
1754
|
+
|
|
1755
|
+
|
|
1756
|
+
[Unreleased]: https://github.com/vtex/node-vtex-api/compare/v6.45.14...HEAD
|
|
1757
|
+
[6.45.14]: https://github.com/vtex/node-vtex-api/compare/v6.45.13...v6.45.14
|
|
1758
|
+
[6.45.13]: https://github.com/vtex/node-vtex-api/compare/v6.45.12...v6.45.13
|
|
@@ -6,6 +6,7 @@ import { InstanceOptions, IOResponse, RequestConfig } from './typings';
|
|
|
6
6
|
declare type ClientOptions = IOContext & Partial<InstanceOptions>;
|
|
7
7
|
export declare class HttpClient {
|
|
8
8
|
name: string;
|
|
9
|
+
private logger;
|
|
9
10
|
private runMiddlewares;
|
|
10
11
|
constructor(opts: ClientOptions);
|
|
11
12
|
get: <T = any>(url: string, config?: RequestConfig) => Promise<T>;
|
|
@@ -31,7 +31,19 @@ class HttpClient {
|
|
|
31
31
|
return this.request(cacheableConfig);
|
|
32
32
|
};
|
|
33
33
|
this.getWithBody = (url, data, config = {}) => {
|
|
34
|
-
const
|
|
34
|
+
const deterministicReplacer = (_, v) => {
|
|
35
|
+
try {
|
|
36
|
+
return typeof v !== 'object' || v === null || Array.isArray(v) ? v :
|
|
37
|
+
Object.fromEntries(Object.entries(v).sort(([ka], [kb]) => ka < kb ? -1 : ka > kb ? 1 : 0));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
// I don't believe this will ever happen, but just in case
|
|
41
|
+
// Also, I didn't include error as I am unsure if it would have sensitive information
|
|
42
|
+
this.logger.warn({ message: 'Error while sorting object for cache key' });
|
|
43
|
+
return v;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const bodyHash = crypto_1.createHash('md5').update(JSON.stringify(data, deterministicReplacer)).digest('hex');
|
|
35
47
|
const cacheableConfig = this.getConfig(url, {
|
|
36
48
|
...config,
|
|
37
49
|
data,
|
|
@@ -91,6 +103,7 @@ class HttpClient {
|
|
|
91
103
|
});
|
|
92
104
|
const { baseURL, authToken, authType, memoryCache, diskCache, locale, name, metrics, product, serverTiming, recorder, userAgent, timeout = DEFAULT_TIMEOUT_MS, segmentToken, sessionToken, retries, concurrency, headers: defaultHeaders, host, params, operationId, tenant, binding, verbose, cancellation, exponentialTimeoutCoefficient, initialBackoffDelay, exponentialBackoffCoefficient, httpsAgent, tracer, logger, } = opts;
|
|
93
105
|
this.name = name || baseURL || 'unknown';
|
|
106
|
+
this.logger = logger;
|
|
94
107
|
const limit = concurrency && concurrency > 0 && p_limit_1.default(concurrency) || undefined;
|
|
95
108
|
const headers = {
|
|
96
109
|
...defaultHeaders,
|
|
@@ -68,15 +68,40 @@ class VBase extends InfraClient_1.InfraClient {
|
|
|
68
68
|
const headers = conflictsResolver ? { 'X-Vtex-Detect-Conflicts': true } : {};
|
|
69
69
|
const inflightKey = HttpClient_1.inflightURL;
|
|
70
70
|
const metric = 'vbase-get-json';
|
|
71
|
-
return this.http
|
|
71
|
+
return this.http
|
|
72
|
+
.getRaw(routes.File(bucket, path), {
|
|
73
|
+
headers,
|
|
74
|
+
inflightKey,
|
|
75
|
+
metric,
|
|
76
|
+
nullIfNotFound,
|
|
77
|
+
tracing: {
|
|
72
78
|
requestSpanNameSuffix: metric,
|
|
73
79
|
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
|
|
74
|
-
}
|
|
80
|
+
},
|
|
81
|
+
})
|
|
75
82
|
.catch(async (error) => {
|
|
76
83
|
const { response } = error;
|
|
77
84
|
if (response && response.status === 409 && conflictsResolver) {
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
try {
|
|
86
|
+
const conflictsMergedData = await conflictsResolver.resolve(this.context.logger);
|
|
87
|
+
return { ...response, data: conflictsMergedData };
|
|
88
|
+
}
|
|
89
|
+
catch (resolverError) {
|
|
90
|
+
const typedResolverError = resolverError;
|
|
91
|
+
if ((typedResolverError === null || typedResolverError === void 0 ? void 0 : typedResolverError.status) === 404) {
|
|
92
|
+
return this.http.getRaw(routes.File(bucket, path), {
|
|
93
|
+
'X-Vtex-Detect-Conflicts': false,
|
|
94
|
+
inflightKey,
|
|
95
|
+
metric,
|
|
96
|
+
nullIfNotFound,
|
|
97
|
+
tracing: {
|
|
98
|
+
requestSpanNameSuffix: metric,
|
|
99
|
+
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
throw resolverError;
|
|
104
|
+
}
|
|
80
105
|
}
|
|
81
106
|
throw error;
|
|
82
107
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { IOContext, TracingContext } from '../typings';
|
|
2
|
-
export declare const prepareHandlerCtx: (header: any, tracingContext: TracingContext) => Pick<IOContext, "host" | "
|
|
2
|
+
export declare const prepareHandlerCtx: (header: any, tracingContext: TracingContext) => Pick<IOContext, "host" | "serverTiming" | "locale" | "account" | "workspace" | "region" | "logger" | "requestId" | "operationId" | "production" | "platform" | "authToken" | "adminUserAuthToken" | "storeUserAuthToken" | "product" | "recorder" | "userAgent" | "segmentToken" | "sessionToken" | "janusEnv" | "eventInfo" | "tenant" | "binding" | "cancellation" | "settings" | "tracer">;
|
|
@@ -12,8 +12,8 @@ export declare class MineWinsConflictsResolver<T> implements ConflictsResolver<T
|
|
|
12
12
|
private filePath;
|
|
13
13
|
private comparableKeys;
|
|
14
14
|
constructor(client: VBase, bucket: string, filePath: string, comparableKeys?: string[]);
|
|
15
|
-
resolve(logger?: Logger): Promise<
|
|
16
|
-
protected mergeMineWins(base: Configuration, master: Configuration, mine: Configuration):
|
|
15
|
+
resolve(logger?: Logger): Promise<any>;
|
|
16
|
+
protected mergeMineWins(base: Configuration, master: Configuration, mine: Configuration): Record<string, object | Record<string, object> | Record<string, object>[]> | Record<string, object>[];
|
|
17
17
|
private parseConflict;
|
|
18
18
|
private serializeContentByMimetype;
|
|
19
19
|
private parseContentByMimetype;
|
|
@@ -11,27 +11,19 @@ class MineWinsConflictsResolver {
|
|
|
11
11
|
this.comparableKeys = comparableKeys;
|
|
12
12
|
}
|
|
13
13
|
async resolve(logger) {
|
|
14
|
-
return await this.client.getConflicts(this.bucket).then(data => {
|
|
14
|
+
return await this.client.getConflicts(this.bucket).then((data) => {
|
|
15
15
|
const { data: conflicts } = data;
|
|
16
|
-
const selectedConflict = conflicts.find(conflict => conflict.path === this.filePath);
|
|
16
|
+
const selectedConflict = conflicts.find((conflict) => conflict.path === this.filePath);
|
|
17
17
|
if (!selectedConflict) {
|
|
18
|
-
|
|
18
|
+
throw {
|
|
19
|
+
message: 'No conflict could be found.',
|
|
20
|
+
status: 404,
|
|
21
|
+
};
|
|
19
22
|
}
|
|
20
23
|
selectedConflict.base.parsedContent = this.parseConflict(selectedConflict.base);
|
|
21
24
|
selectedConflict.master.parsedContent = this.parseConflict(selectedConflict.master);
|
|
22
25
|
selectedConflict.mine.parsedContent = this.parseConflict(selectedConflict.mine);
|
|
23
26
|
const resolved = this.resolveConflictMineWins(selectedConflict);
|
|
24
|
-
if (logger) {
|
|
25
|
-
logger.info({
|
|
26
|
-
base: selectedConflict.base.parsedContent,
|
|
27
|
-
bucket: this.bucket,
|
|
28
|
-
conflictsResolution: MineWinsConflictsResolver.constructor.name,
|
|
29
|
-
filePath: this.filePath,
|
|
30
|
-
master: selectedConflict.master.parsedContent,
|
|
31
|
-
mine: selectedConflict.mine.parsedContent,
|
|
32
|
-
resolved,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
27
|
return resolved;
|
|
36
28
|
});
|
|
37
29
|
}
|
|
@@ -64,16 +56,19 @@ class MineWinsConflictsResolver {
|
|
|
64
56
|
}
|
|
65
57
|
resolveConflictMineWins(conflict) {
|
|
66
58
|
if (!conflict) {
|
|
67
|
-
|
|
59
|
+
throw {
|
|
60
|
+
message: 'No conflict could be found.',
|
|
61
|
+
status: 404,
|
|
62
|
+
};
|
|
68
63
|
}
|
|
69
64
|
const { base, master, mine, path } = conflict;
|
|
70
|
-
this.mergeMineWins(base.parsedContent, master.parsedContent, mine.parsedContent);
|
|
65
|
+
const merged = this.mergeMineWins(base.parsedContent, master.parsedContent, mine.parsedContent);
|
|
71
66
|
const mergedContent = {
|
|
72
|
-
content: js_base64_1.Base64.encode(this.serializeContentByMimetype(
|
|
67
|
+
content: js_base64_1.Base64.encode(this.serializeContentByMimetype(merged, conflict.mine.mimeType)),
|
|
73
68
|
mimeType: conflict.mine.mimeType,
|
|
74
69
|
};
|
|
75
70
|
this.client.resolveConflict(this.bucket, path, mergedContent);
|
|
76
|
-
return
|
|
71
|
+
return merged;
|
|
77
72
|
}
|
|
78
73
|
mergeMineWinsObject(base, master, mine) {
|
|
79
74
|
const merged = { ...master, ...mine };
|
|
@@ -99,10 +94,10 @@ class MineWinsConflictsResolver {
|
|
|
99
94
|
return mine;
|
|
100
95
|
}
|
|
101
96
|
removeMasterDeletedElements(base, master, mine) {
|
|
102
|
-
base.forEach(baseItem => {
|
|
97
|
+
base.forEach((baseItem) => {
|
|
103
98
|
const foundInMaster = this.isObjectInArray(baseItem, master);
|
|
104
99
|
if (!foundInMaster) {
|
|
105
|
-
const foundInMine = mine.findIndex(mineItem => ramda_1.equals(mineItem, baseItem));
|
|
100
|
+
const foundInMine = mine.findIndex((mineItem) => ramda_1.equals(mineItem, baseItem));
|
|
106
101
|
if (foundInMine > -1) {
|
|
107
102
|
mine.splice(foundInMine, 1);
|
|
108
103
|
}
|
|
@@ -110,10 +105,10 @@ class MineWinsConflictsResolver {
|
|
|
110
105
|
});
|
|
111
106
|
}
|
|
112
107
|
isObjectInArray(obj, array) {
|
|
113
|
-
return array.find(item => ramda_1.equals(item, obj));
|
|
108
|
+
return array.find((item) => ramda_1.equals(item, obj));
|
|
114
109
|
}
|
|
115
110
|
appendMasterAddedElements(base, master, mine) {
|
|
116
|
-
master.forEach(item => {
|
|
111
|
+
master.forEach((item) => {
|
|
117
112
|
if (this.shouldAddToMine(item, base, mine)) {
|
|
118
113
|
mine.push(item);
|
|
119
114
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare function getMockConflicts(path: string, base: object, mine: object, master: object): {
|
|
2
|
+
data: {
|
|
3
|
+
base: {
|
|
4
|
+
content: string;
|
|
5
|
+
mimeType: string;
|
|
6
|
+
};
|
|
7
|
+
master: {
|
|
8
|
+
content: string;
|
|
9
|
+
mimeType: string;
|
|
10
|
+
};
|
|
11
|
+
mine: {
|
|
12
|
+
content: string;
|
|
13
|
+
mimeType: string;
|
|
14
|
+
};
|
|
15
|
+
path: string;
|
|
16
|
+
}[];
|
|
17
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
function jsonToBase64(json) {
|
|
4
|
+
return Buffer.from(JSON.stringify(json)).toString('base64');
|
|
5
|
+
}
|
|
6
|
+
function getMockConflicts(path, base, mine, master) {
|
|
7
|
+
return {
|
|
8
|
+
data: [
|
|
9
|
+
{
|
|
10
|
+
base: {
|
|
11
|
+
content: jsonToBase64(base),
|
|
12
|
+
mimeType: 'application/json',
|
|
13
|
+
},
|
|
14
|
+
master: {
|
|
15
|
+
content: jsonToBase64(master),
|
|
16
|
+
mimeType: 'application/json',
|
|
17
|
+
},
|
|
18
|
+
mine: {
|
|
19
|
+
content: jsonToBase64(mine),
|
|
20
|
+
mimeType: 'application/json',
|
|
21
|
+
},
|
|
22
|
+
path,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
exports.getMockConflicts = getMockConflicts;
|