@dhis2/app-service-data 3.4.1 → 3.4.4

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 (32) hide show
  1. package/build/cjs/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.js +3 -10
  2. package/build/cjs/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.test.js +0 -17
  3. package/build/cjs/links/RestAPILink/queryToRequestOptions/requestContentType.js +23 -10
  4. package/build/cjs/links/RestAPILink/queryToRequestOptions/requestContentType.test.js +22 -1
  5. package/build/cjs/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.js +13 -0
  6. package/build/cjs/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.test.js +21 -0
  7. package/build/cjs/links/RestAPILink/queryToResourcePath.js +1 -1
  8. package/build/cjs/links/RestAPILink/queryToResourcePath.test.js +6 -0
  9. package/build/cjs/react/hooks/mergeAndCompareVariables.js +33 -0
  10. package/build/cjs/react/hooks/mergeAndCompareVariables.test.js +57 -0
  11. package/build/cjs/react/hooks/useDataQuery.js +43 -53
  12. package/build/cjs/react/hooks/useDataQuery.test.js +59 -0
  13. package/build/cjs/react/hooks/useStaticInput.js +1 -0
  14. package/build/es/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.js +1 -5
  15. package/build/es/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.test.js +1 -18
  16. package/build/es/links/RestAPILink/queryToRequestOptions/requestContentType.js +19 -8
  17. package/build/es/links/RestAPILink/queryToRequestOptions/requestContentType.test.js +23 -2
  18. package/build/es/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.js +4 -0
  19. package/build/es/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.test.js +18 -0
  20. package/build/es/links/RestAPILink/queryToResourcePath.js +1 -1
  21. package/build/es/links/RestAPILink/queryToResourcePath.test.js +6 -0
  22. package/build/es/react/hooks/mergeAndCompareVariables.js +23 -0
  23. package/build/es/react/hooks/mergeAndCompareVariables.test.js +53 -0
  24. package/build/es/react/hooks/useDataQuery.js +44 -54
  25. package/build/es/react/hooks/useDataQuery.test.js +59 -0
  26. package/build/es/react/hooks/useStaticInput.js +2 -1
  27. package/build/types/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.d.ts +0 -1
  28. package/build/types/links/RestAPILink/queryToRequestOptions/requestContentType.d.ts +3 -3
  29. package/build/types/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.d.ts +2 -0
  30. package/build/types/react/hooks/mergeAndCompareVariables.d.ts +6 -0
  31. package/build/types/react/hooks/useDataQuery.d.ts +2 -2
  32. package/package.json +2 -2
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.isSvgConversion = exports.isAppInstall = exports.isStaticContentUpload = exports.isMessageConversationAttachment = exports.isFileResourceUpload = exports.isDataValue = void 0;
6
+ exports.isAppInstall = exports.isStaticContentUpload = exports.isMessageConversationAttachment = exports.isFileResourceUpload = exports.isDataValue = void 0;
7
7
 
8
8
  /*
9
9
  * Requests that expect a "multipart/form-data" Content-Type have been collected by scanning
@@ -45,13 +45,6 @@ exports.isStaticContentUpload = isStaticContentUpload;
45
45
 
46
46
  const isAppInstall = (type, {
47
47
  resource
48
- }) => type === 'create' && resource === 'apps'; // POST to convert an SVG file
48
+ }) => type === 'create' && resource === 'apps';
49
49
 
50
-
51
- exports.isAppInstall = isAppInstall;
52
-
53
- const isSvgConversion = (type, {
54
- resource
55
- }) => type === 'create' && (resource === 'svg.png' || resource === 'svg.pdf');
56
-
57
- exports.isSvgConversion = isSvgConversion;
50
+ exports.isAppInstall = isAppInstall;
@@ -71,21 +71,4 @@ describe('isAppInstall', () => {
71
71
  resource: 'notApps'
72
72
  })).toEqual(false);
73
73
  });
74
- });
75
- describe('isSvgConversion', () => {
76
- it('returns true for a POST to "svg.png"', () => {
77
- expect((0, _multipartFormDataMatchers.isSvgConversion)('create', {
78
- resource: 'svg.png'
79
- })).toEqual(true);
80
- });
81
- it('returns true for a POST to "svg.pdf"', () => {
82
- expect((0, _multipartFormDataMatchers.isSvgConversion)('create', {
83
- resource: 'svg.pdf'
84
- })).toEqual(true);
85
- });
86
- it('retuns false for a POST to a different resource', () => {
87
- expect((0, _multipartFormDataMatchers.isSvgConversion)('create', {
88
- resource: 'notSvg'
89
- })).toEqual(false);
90
- });
91
74
  });
@@ -3,12 +3,14 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.requestBodyForContentType = exports.requestHeadersForContentType = exports.requestContentType = exports.FORM_DATA_ERROR_MSG = void 0;
6
+ exports.requestBodyForContentType = exports.requestHeadersForContentType = exports.requestContentType = exports.getConversionErrorMessage = void 0;
7
7
 
8
8
  var multipartFormDataMatchers = _interopRequireWildcard(require("./multipartFormDataMatchers"));
9
9
 
10
10
  var textPlainMatchers = _interopRequireWildcard(require("./textPlainMatchers"));
11
11
 
12
+ var xWwwFormUrlencodedMatchers = _interopRequireWildcard(require("./xWwwFormUrlencodedMatchers"));
13
+
12
14
  function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
13
15
 
14
16
  function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
@@ -17,20 +19,23 @@ const resourceExpectsTextPlain = (type, query) => Object.values(textPlainMatcher
17
19
 
18
20
  const resourceExpectsMultipartFormData = (type, query) => Object.values(multipartFormDataMatchers).some(multipartFormDataMatcher => multipartFormDataMatcher(type, query));
19
21
 
20
- const FORM_DATA_ERROR_MSG = 'Could not convert data to FormData: object does not have own enumerable string-keyed properties';
21
- exports.FORM_DATA_ERROR_MSG = FORM_DATA_ERROR_MSG;
22
+ const resourceExpectsXWwwFormUrlencoded = (type, query) => Object.values(xWwwFormUrlencodedMatchers).some(xWwwFormUrlencodedMatcher => xWwwFormUrlencodedMatcher(type, query));
23
+
24
+ const getConversionErrorMessage = outputType => "Could not convert data to ".concat(outputType, ": object does not have own enumerable string-keyed properties");
22
25
 
23
- const convertToFormData = data => {
26
+ exports.getConversionErrorMessage = getConversionErrorMessage;
27
+
28
+ const convertData = (data, initialValue) => {
24
29
  const dataEntries = Object.entries(data);
25
30
 
26
31
  if (dataEntries.length === 0) {
27
- throw new Error(FORM_DATA_ERROR_MSG);
32
+ throw new Error(getConversionErrorMessage(initialValue.constructor.name));
28
33
  }
29
34
 
30
- return dataEntries.reduce((formData, [key, value]) => {
31
- formData.append(key, value);
32
- return formData;
33
- }, new FormData());
35
+ return dataEntries.reduce((convertedData, [key, value]) => {
36
+ convertedData.append(key, value);
37
+ return convertedData;
38
+ }, initialValue);
34
39
  };
35
40
 
36
41
  const requestContentType = (type, query) => {
@@ -50,6 +55,10 @@ const requestContentType = (type, query) => {
50
55
  return 'multipart/form-data';
51
56
  }
52
57
 
58
+ if (resourceExpectsXWwwFormUrlencoded(type, query)) {
59
+ return 'application/x-www-form-urlencoded';
60
+ }
61
+
53
62
  return 'application/json';
54
63
  };
55
64
 
@@ -86,7 +95,11 @@ const requestBodyForContentType = (contentType, {
86
95
  }
87
96
 
88
97
  if (contentType === 'multipart/form-data') {
89
- return convertToFormData(data);
98
+ return convertData(data, new FormData());
99
+ }
100
+
101
+ if (contentType === 'application/x-www-form-urlencoded') {
102
+ return convertData(data, new URLSearchParams());
90
103
  } // 'text/plain'
91
104
 
92
105
 
@@ -88,7 +88,28 @@ describe('requestBodyForContentType', () => {
88
88
  type: 'text/plain'
89
89
  })
90
90
  });
91
- }).toThrow(new Error(_requestContentType.FORM_DATA_ERROR_MSG));
91
+ }).toThrow(new Error('Could not convert data to FormData: object does not have own enumerable string-keyed properties'));
92
+ });
93
+ it('converts to URLSearchParams if contentType is "application/x-www-form-urlencoded"', () => {
94
+ const data = {
95
+ a: 'AAA'
96
+ };
97
+ const result = (0, _requestContentType.requestBodyForContentType)('application/x-www-form-urlencoded', {
98
+ resource: 'test',
99
+ data
100
+ });
101
+ expect(result instanceof URLSearchParams).toEqual(true);
102
+ expect(result.get('a')).toEqual('AAA');
103
+ });
104
+ it('throws an error if contentType is "application/x-www-form-urlencoded" and data does have own string-keyd properties', () => {
105
+ expect(() => {
106
+ (0, _requestContentType.requestBodyForContentType)('application/x-www-form-urlencoded', {
107
+ resource: 'test',
108
+ data: new File(['foo'], 'foo.txt', {
109
+ type: 'text/plain'
110
+ })
111
+ });
112
+ }).toThrow(new Error('Could not convert data to URLSearchParams: object does not have own enumerable string-keyed properties'));
92
113
  });
93
114
  it('returns the data as received if contentType is "text/plain"', () => {
94
115
  const data = 'Something';
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isSvgConversion = void 0;
7
+
8
+ // POST to convert an SVG file
9
+ const isSvgConversion = (type, {
10
+ resource
11
+ }) => type === 'create' && (resource === 'svg.png' || resource === 'svg.pdf');
12
+
13
+ exports.isSvgConversion = isSvgConversion;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+
3
+ var _xWwwFormUrlencodedMatchers = require("./xWwwFormUrlencodedMatchers");
4
+
5
+ describe('isSvgConversion', () => {
6
+ it('returns true for a POST to "svg.png"', () => {
7
+ expect((0, _xWwwFormUrlencodedMatchers.isSvgConversion)('create', {
8
+ resource: 'svg.png'
9
+ })).toEqual(true);
10
+ });
11
+ it('returns true for a POST to "svg.pdf"', () => {
12
+ expect((0, _xWwwFormUrlencodedMatchers.isSvgConversion)('create', {
13
+ resource: 'svg.pdf'
14
+ })).toEqual(true);
15
+ });
16
+ it('retuns false for a POST to a different resource', () => {
17
+ expect((0, _xWwwFormUrlencodedMatchers.isSvgConversion)('create', {
18
+ resource: 'notSvg'
19
+ })).toEqual(false);
20
+ });
21
+ });
@@ -64,7 +64,7 @@ const isAction = resource => resource.startsWith(actionPrefix);
64
64
  const makeActionPath = resource => (0, _path.joinPath)('dhis-web-commons', "".concat(resource.substr(actionPrefix.length), ".action"));
65
65
 
66
66
  const skipApiVersion = (resource, config) => {
67
- if (resource === 'tracker') {
67
+ if (resource === 'tracker' || resource.startsWith('tracker/')) {
68
68
  var _config$serverVersion, _config$serverVersion2;
69
69
 
70
70
  if (!((_config$serverVersion = config.serverVersion) !== null && _config$serverVersion !== void 0 && _config$serverVersion.minor) || ((_config$serverVersion2 = config.serverVersion) === null || _config$serverVersion2 === void 0 ? void 0 : _config$serverVersion2.minor) < 38) {
@@ -153,6 +153,12 @@ describe('queryToResourcePath', () => {
153
153
  };
154
154
  expect((0, _queryToResourcePath.queryToResourcePath)(link, query, 'read')).toBe("".concat(link.unversionedApiPath, "/tracker"));
155
155
  });
156
+ it('should return an unversioned endpoint sub-resources of the new tracker importer (in version 2.37)', () => {
157
+ const query = {
158
+ resource: 'tracker/test'
159
+ };
160
+ expect((0, _queryToResourcePath.queryToResourcePath)(link, query, 'read')).toBe("".concat(link.unversionedApiPath, "/tracker/test"));
161
+ });
156
162
  it('should return a VERSIONED endpoint for the new tracker importer (in version 2.38)', () => {
157
163
  const query = {
158
164
  resource: 'tracker'
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.mergeAndCompareVariables = void 0;
7
+
8
+ var _stableVariablesHash = require("./stableVariablesHash");
9
+
10
+ const mergeAndCompareVariables = (previousVariables, newVariables, previousHash) => {
11
+ if (!newVariables) {
12
+ return {
13
+ identical: true,
14
+ mergedVariablesHash: previousHash,
15
+ mergedVariables: previousVariables
16
+ };
17
+ } // Use cached hash if it exists
18
+
19
+
20
+ const currentHash = previousHash || (0, _stableVariablesHash.stableVariablesHash)(previousVariables);
21
+ const mergedVariables = { ...previousVariables,
22
+ ...newVariables
23
+ };
24
+ const mergedVariablesHash = (0, _stableVariablesHash.stableVariablesHash)(mergedVariables);
25
+ const identical = currentHash === mergedVariablesHash;
26
+ return {
27
+ identical,
28
+ mergedVariablesHash,
29
+ mergedVariables
30
+ };
31
+ };
32
+
33
+ exports.mergeAndCompareVariables = mergeAndCompareVariables;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+
3
+ var _mergeAndCompareVariables = require("./mergeAndCompareVariables");
4
+
5
+ var _stableVariablesHash = require("./stableVariablesHash");
6
+
7
+ jest.mock('./stableVariablesHash', () => ({
8
+ stableVariablesHash: object => JSON.stringify(object)
9
+ }));
10
+ const testVariables = {
11
+ question: 'What do you get when you multiply six by nine?',
12
+ answer: 42
13
+ };
14
+ const testHash = (0, _stableVariablesHash.stableVariablesHash)(testVariables);
15
+ describe('mergeAndCompareVariables', () => {
16
+ it('Should return previous variables and hash when no new variables are provided', () => {
17
+ expect((0, _mergeAndCompareVariables.mergeAndCompareVariables)(testVariables, undefined, undefined)).toMatchObject({
18
+ identical: true,
19
+ mergedVariables: testVariables,
20
+ mergedVariablesHash: undefined
21
+ });
22
+ });
23
+ it('Should return identical: true when merged variables are identical to old variables (without prev hash)', () => {
24
+ const newVariables = {
25
+ answer: testVariables.answer
26
+ };
27
+ expect((0, _mergeAndCompareVariables.mergeAndCompareVariables)(testVariables, newVariables, undefined)).toMatchObject({
28
+ identical: true,
29
+ mergedVariables: testVariables,
30
+ mergedVariablesHash: testHash
31
+ });
32
+ });
33
+ it('Should return identical: false with incorrect previous hash', () => {
34
+ const incorrectPreviousHash = 'IAmAHash';
35
+ const newVariables = {
36
+ answer: 42
37
+ };
38
+ expect((0, _mergeAndCompareVariables.mergeAndCompareVariables)(testVariables, newVariables, incorrectPreviousHash)).toMatchObject({
39
+ identical: false,
40
+ mergedVariables: testVariables,
41
+ mergedVariablesHash: testHash
42
+ });
43
+ });
44
+ it('Should return identical: false when merged variables are different than old variables', () => {
45
+ const newVariables = {
46
+ answer: 43
47
+ };
48
+ const expectedMergedVariables = { ...testVariables,
49
+ ...newVariables
50
+ };
51
+ expect((0, _mergeAndCompareVariables.mergeAndCompareVariables)(testVariables, newVariables, testHash)).toMatchObject({
52
+ identical: false,
53
+ mergedVariables: expectedMergedVariables,
54
+ mergedVariablesHash: (0, _stableVariablesHash.stableVariablesHash)(expectedMergedVariables)
55
+ });
56
+ });
57
+ });
@@ -9,7 +9,7 @@ var _react = require("react");
9
9
 
10
10
  var _reactQuery = require("react-query");
11
11
 
12
- var _stableVariablesHash = require("./stableVariablesHash");
12
+ var _mergeAndCompareVariables = require("./mergeAndCompareVariables");
13
13
 
14
14
  var _useDataEngine = require("./useDataEngine");
15
15
 
@@ -35,24 +35,35 @@ const useDataQuery = (query, {
35
35
  variables: initialVariables = {},
36
36
  lazy: initialLazy = false
37
37
  } = {}) => {
38
- const variablesHash = (0, _react.useRef)(null);
39
- const [variables, setVariables] = (0, _react.useState)(initialVariables);
40
- const [enabled, setEnabled] = (0, _react.useState)(!initialLazy);
41
38
  const [staticQuery] = (0, _useStaticInput.useStaticInput)(query, {
42
39
  warn: true,
43
40
  name: 'query'
44
41
  });
42
+ const [variablesUpdateCount, setVariablesUpdateCount] = (0, _react.useState)(0);
43
+ const queryState = (0, _react.useRef)({
44
+ variables: initialVariables,
45
+ variablesHash: undefined,
46
+ enabled: !initialLazy,
47
+ refetchCallback: undefined
48
+ });
45
49
  /**
46
- * User callbacks and refetch handling
50
+ * Display current query state and refetch count in React DevTools
47
51
  */
48
52
 
49
- const refetchCallback = (0, _react.useRef)(null);
53
+ (0, _react.useDebugValue)({
54
+ variablesUpdateCount,
55
+ enabled: queryState.current.enabled,
56
+ variables: queryState.current.variables
57
+ }, debugValue => JSON.stringify(debugValue));
58
+ /**
59
+ * User callbacks and refetch handling
60
+ */
50
61
 
51
62
  const onSuccess = data => {
52
- if (refetchCallback.current) {
53
- refetchCallback.current(data);
54
- refetchCallback.current = null;
55
- }
63
+ var _queryState$current$r, _queryState$current;
64
+
65
+ (_queryState$current$r = (_queryState$current = queryState.current).refetchCallback) === null || _queryState$current$r === void 0 ? void 0 : _queryState$current$r.call(_queryState$current, data);
66
+ queryState.current.refetchCallback = undefined;
56
67
 
57
68
  if (userOnSuccess) {
58
69
  userOnSuccess(data);
@@ -61,9 +72,7 @@ const useDataQuery = (query, {
61
72
 
62
73
  const onError = error => {
63
74
  // If we'd want to reject on errors we'd call the cb with the error here
64
- if (refetchCallback.current) {
65
- refetchCallback.current = null;
66
- }
75
+ queryState.current.refetchCallback = undefined;
67
76
 
68
77
  if (userOnError) {
69
78
  userOnError(error);
@@ -75,10 +84,10 @@ const useDataQuery = (query, {
75
84
 
76
85
 
77
86
  const engine = (0, _useDataEngine.useDataEngine)();
78
- const queryKey = [staticQuery, variables];
87
+ const queryKey = [staticQuery, queryState.current.variables];
79
88
 
80
89
  const queryFn = () => engine.query(staticQuery, {
81
- variables
90
+ variables: queryState.current.variables
82
91
  });
83
92
 
84
93
  const {
@@ -89,7 +98,7 @@ const useDataQuery = (query, {
89
98
  data,
90
99
  refetch: queryRefetch
91
100
  } = (0, _reactQuery.useQuery)(queryKey, queryFn, {
92
- enabled,
101
+ enabled: queryState.current.enabled,
93
102
  onSuccess,
94
103
  onError
95
104
  });
@@ -103,11 +112,17 @@ const useDataQuery = (query, {
103
112
  */
104
113
 
105
114
  const refetch = (0, _react.useCallback)(newVariables => {
115
+ const {
116
+ identical,
117
+ mergedVariables,
118
+ mergedVariablesHash
119
+ } = (0, _mergeAndCompareVariables.mergeAndCompareVariables)(queryState.current.variables, newVariables, queryState.current.variablesHash);
106
120
  /**
107
121
  * If there are no updates that will trigger an automatic refetch
108
122
  * we'll need to call react-query's refetch directly
109
123
  */
110
- if (enabled && !newVariables) {
124
+
125
+ if (queryState.current.enabled && identical) {
111
126
  return queryRefetch({
112
127
  cancelRefetch: true,
113
128
  throwOnError: false
@@ -116,44 +131,19 @@ const useDataQuery = (query, {
116
131
  }) => data);
117
132
  }
118
133
 
119
- if (newVariables) {
120
- // Use cached hash if it exists
121
- const currentHash = variablesHash.current || (0, _stableVariablesHash.stableVariablesHash)(variables);
122
- const mergedVariables = { ...variables,
123
- ...newVariables
124
- };
125
- const mergedHash = (0, _stableVariablesHash.stableVariablesHash)(mergedVariables);
126
- const identical = currentHash === mergedHash;
127
-
128
- if (identical && enabled) {
129
- /**
130
- * If the variables are identical and the query is enabled
131
- * we'll need to trigger the refetch manually
132
- */
133
- return queryRefetch({
134
- cancelRefetch: true,
135
- throwOnError: false
136
- }).then(({
137
- data
138
- }) => data);
139
- } else {
140
- variablesHash.current = mergedHash;
141
- setVariables(mergedVariables);
142
- }
143
- } // Enable the query after the variables have been set to prevent extra request
144
-
145
-
146
- if (!enabled) {
147
- setEnabled(true);
148
- } // This promise does not currently reject on errors
149
-
150
-
151
- return new Promise(resolve => {
152
- refetchCallback.current = data => {
134
+ queryState.current.variables = mergedVariables;
135
+ queryState.current.variablesHash = mergedVariablesHash;
136
+ queryState.current.enabled = true; // This promise does not currently reject on errors
137
+
138
+ const refetchPromise = new Promise(resolve => {
139
+ queryState.current.refetchCallback = data => {
153
140
  resolve(data);
154
141
  };
155
- });
156
- }, [enabled, queryRefetch, variables]);
142
+ }); // Trigger a react-query refetch by incrementing variablesUpdateCount state
143
+
144
+ setVariablesUpdateCount(prevCount => prevCount + 1);
145
+ return refetchPromise;
146
+ }, [queryRefetch]);
157
147
  /**
158
148
  * react-query returns null or an error, but we return undefined
159
149
  * or an error, so this ensures consistency with the other types.
@@ -507,6 +507,65 @@ describe('useDataQuery', () => {
507
507
  });
508
508
  });
509
509
  describe('return values: refetch', () => {
510
+ it('Should be stable if the query variables change', async () => {
511
+ let count = 0;
512
+ const spy = jest.fn(() => {
513
+ count++;
514
+ return count;
515
+ });
516
+ const data = {
517
+ answer: spy
518
+ };
519
+ const query = {
520
+ x: {
521
+ resource: 'answer'
522
+ }
523
+ };
524
+
525
+ const wrapper = ({
526
+ children
527
+ }) => /*#__PURE__*/React.createElement(_CustomDataProvider.CustomDataProvider, {
528
+ data: data
529
+ }, children);
530
+
531
+ const {
532
+ result,
533
+ waitFor
534
+ } = (0, _reactHooks.renderHook)(() => (0, _useDataQuery.useDataQuery)(query, {
535
+ lazy: true
536
+ }), {
537
+ wrapper
538
+ });
539
+ expect(spy).not.toHaveBeenCalled();
540
+ const initialRefetch = result.current.refetch;
541
+ (0, _reactHooks.act)(() => {
542
+ initialRefetch();
543
+ });
544
+ await waitFor(() => {
545
+ expect(result.current).toMatchObject({
546
+ loading: false,
547
+ called: true,
548
+ data: {
549
+ x: 1
550
+ }
551
+ });
552
+ });
553
+ expect(spy).toHaveBeenCalledTimes(1);
554
+ (0, _reactHooks.act)(() => {
555
+ initialRefetch();
556
+ });
557
+ await waitFor(() => {
558
+ expect(result.current).toMatchObject({
559
+ loading: false,
560
+ called: true,
561
+ data: {
562
+ x: 2
563
+ }
564
+ });
565
+ });
566
+ expect(spy).toHaveBeenCalledTimes(2);
567
+ expect(initialRefetch).toBe(result.current.refetch);
568
+ });
510
569
  it('Should only trigger a single request when refetch is called on a lazy query with new variables', async () => {
511
570
  const spy = jest.fn((type, query) => {
512
571
  if (query.id === '1') {
@@ -13,6 +13,7 @@ const useStaticInput = (staticValue, {
13
13
  } = {}) => {
14
14
  const originalValue = (0, _react.useRef)(staticValue);
15
15
  const [value, setValue] = (0, _react.useState)(() => originalValue.current);
16
+ (0, _react.useDebugValue)(value, debugValue => "".concat(name, ": ").concat(JSON.stringify(debugValue)));
16
17
  (0, _react.useEffect)(() => {
17
18
  if (warn && originalValue.current !== staticValue) {
18
19
  console.warn("The ".concat(name, " should be static, don't create it within the render loop!"));
@@ -26,8 +26,4 @@ export const isStaticContentUpload = (type, {
26
26
 
27
27
  export const isAppInstall = (type, {
28
28
  resource
29
- }) => type === 'create' && resource === 'apps'; // POST to convert an SVG file
30
-
31
- export const isSvgConversion = (type, {
32
- resource
33
- }) => type === 'create' && (resource === 'svg.png' || resource === 'svg.pdf');
29
+ }) => type === 'create' && resource === 'apps';
@@ -1,4 +1,4 @@
1
- import { isFileResourceUpload, isMessageConversationAttachment, isStaticContentUpload, isAppInstall, isSvgConversion, isDataValue } from './multipartFormDataMatchers';
1
+ import { isFileResourceUpload, isMessageConversationAttachment, isStaticContentUpload, isAppInstall, isDataValue } from './multipartFormDataMatchers';
2
2
  describe('isDataValue', () => {
3
3
  it('returns true for a POST to "dataValues"', () => {
4
4
  expect(isDataValue('create', {
@@ -68,21 +68,4 @@ describe('isAppInstall', () => {
68
68
  resource: 'notApps'
69
69
  })).toEqual(false);
70
70
  });
71
- });
72
- describe('isSvgConversion', () => {
73
- it('returns true for a POST to "svg.png"', () => {
74
- expect(isSvgConversion('create', {
75
- resource: 'svg.png'
76
- })).toEqual(true);
77
- });
78
- it('returns true for a POST to "svg.pdf"', () => {
79
- expect(isSvgConversion('create', {
80
- resource: 'svg.pdf'
81
- })).toEqual(true);
82
- });
83
- it('retuns false for a POST to a different resource', () => {
84
- expect(isSvgConversion('create', {
85
- resource: 'notSvg'
86
- })).toEqual(false);
87
- });
88
71
  });
@@ -1,23 +1,26 @@
1
1
  import * as multipartFormDataMatchers from './multipartFormDataMatchers';
2
2
  import * as textPlainMatchers from './textPlainMatchers';
3
+ import * as xWwwFormUrlencodedMatchers from './xWwwFormUrlencodedMatchers';
3
4
 
4
5
  const resourceExpectsTextPlain = (type, query) => Object.values(textPlainMatchers).some(textPlainMatcher => textPlainMatcher(type, query));
5
6
 
6
7
  const resourceExpectsMultipartFormData = (type, query) => Object.values(multipartFormDataMatchers).some(multipartFormDataMatcher => multipartFormDataMatcher(type, query));
7
8
 
8
- export const FORM_DATA_ERROR_MSG = 'Could not convert data to FormData: object does not have own enumerable string-keyed properties';
9
+ const resourceExpectsXWwwFormUrlencoded = (type, query) => Object.values(xWwwFormUrlencodedMatchers).some(xWwwFormUrlencodedMatcher => xWwwFormUrlencodedMatcher(type, query));
9
10
 
10
- const convertToFormData = data => {
11
+ export const getConversionErrorMessage = outputType => "Could not convert data to ".concat(outputType, ": object does not have own enumerable string-keyed properties");
12
+
13
+ const convertData = (data, initialValue) => {
11
14
  const dataEntries = Object.entries(data);
12
15
 
13
16
  if (dataEntries.length === 0) {
14
- throw new Error(FORM_DATA_ERROR_MSG);
17
+ throw new Error(getConversionErrorMessage(initialValue.constructor.name));
15
18
  }
16
19
 
17
- return dataEntries.reduce((formData, [key, value]) => {
18
- formData.append(key, value);
19
- return formData;
20
- }, new FormData());
20
+ return dataEntries.reduce((convertedData, [key, value]) => {
21
+ convertedData.append(key, value);
22
+ return convertedData;
23
+ }, initialValue);
21
24
  };
22
25
 
23
26
  export const requestContentType = (type, query) => {
@@ -37,6 +40,10 @@ export const requestContentType = (type, query) => {
37
40
  return 'multipart/form-data';
38
41
  }
39
42
 
43
+ if (resourceExpectsXWwwFormUrlencoded(type, query)) {
44
+ return 'application/x-www-form-urlencoded';
45
+ }
46
+
40
47
  return 'application/json';
41
48
  };
42
49
  export const requestHeadersForContentType = contentType => {
@@ -67,7 +74,11 @@ export const requestBodyForContentType = (contentType, {
67
74
  }
68
75
 
69
76
  if (contentType === 'multipart/form-data') {
70
- return convertToFormData(data);
77
+ return convertData(data, new FormData());
78
+ }
79
+
80
+ if (contentType === 'application/x-www-form-urlencoded') {
81
+ return convertData(data, new URLSearchParams());
71
82
  } // 'text/plain'
72
83
 
73
84
 
@@ -1,4 +1,4 @@
1
- import { requestContentType, requestHeadersForContentType, requestBodyForContentType, FORM_DATA_ERROR_MSG } from './requestContentType';
1
+ import { requestContentType, requestHeadersForContentType, requestBodyForContentType } from './requestContentType';
2
2
  describe('requestContentType', () => {
3
3
  it('returns "application/json" for a normal resource', () => {
4
4
  expect(requestContentType('create', {
@@ -85,7 +85,28 @@ describe('requestBodyForContentType', () => {
85
85
  type: 'text/plain'
86
86
  })
87
87
  });
88
- }).toThrow(new Error(FORM_DATA_ERROR_MSG));
88
+ }).toThrow(new Error('Could not convert data to FormData: object does not have own enumerable string-keyed properties'));
89
+ });
90
+ it('converts to URLSearchParams if contentType is "application/x-www-form-urlencoded"', () => {
91
+ const data = {
92
+ a: 'AAA'
93
+ };
94
+ const result = requestBodyForContentType('application/x-www-form-urlencoded', {
95
+ resource: 'test',
96
+ data
97
+ });
98
+ expect(result instanceof URLSearchParams).toEqual(true);
99
+ expect(result.get('a')).toEqual('AAA');
100
+ });
101
+ it('throws an error if contentType is "application/x-www-form-urlencoded" and data does have own string-keyd properties', () => {
102
+ expect(() => {
103
+ requestBodyForContentType('application/x-www-form-urlencoded', {
104
+ resource: 'test',
105
+ data: new File(['foo'], 'foo.txt', {
106
+ type: 'text/plain'
107
+ })
108
+ });
109
+ }).toThrow(new Error('Could not convert data to URLSearchParams: object does not have own enumerable string-keyed properties'));
89
110
  });
90
111
  it('returns the data as received if contentType is "text/plain"', () => {
91
112
  const data = 'Something';
@@ -0,0 +1,4 @@
1
+ // POST to convert an SVG file
2
+ export const isSvgConversion = (type, {
3
+ resource
4
+ }) => type === 'create' && (resource === 'svg.png' || resource === 'svg.pdf');
@@ -0,0 +1,18 @@
1
+ import { isSvgConversion } from './xWwwFormUrlencodedMatchers';
2
+ describe('isSvgConversion', () => {
3
+ it('returns true for a POST to "svg.png"', () => {
4
+ expect(isSvgConversion('create', {
5
+ resource: 'svg.png'
6
+ })).toEqual(true);
7
+ });
8
+ it('returns true for a POST to "svg.pdf"', () => {
9
+ expect(isSvgConversion('create', {
10
+ resource: 'svg.pdf'
11
+ })).toEqual(true);
12
+ });
13
+ it('retuns false for a POST to a different resource', () => {
14
+ expect(isSvgConversion('create', {
15
+ resource: 'notSvg'
16
+ })).toEqual(false);
17
+ });
18
+ });
@@ -56,7 +56,7 @@ const isAction = resource => resource.startsWith(actionPrefix);
56
56
  const makeActionPath = resource => joinPath('dhis-web-commons', "".concat(resource.substr(actionPrefix.length), ".action"));
57
57
 
58
58
  const skipApiVersion = (resource, config) => {
59
- if (resource === 'tracker') {
59
+ if (resource === 'tracker' || resource.startsWith('tracker/')) {
60
60
  var _config$serverVersion, _config$serverVersion2;
61
61
 
62
62
  if (!((_config$serverVersion = config.serverVersion) !== null && _config$serverVersion !== void 0 && _config$serverVersion.minor) || ((_config$serverVersion2 = config.serverVersion) === null || _config$serverVersion2 === void 0 ? void 0 : _config$serverVersion2.minor) < 38) {
@@ -150,6 +150,12 @@ describe('queryToResourcePath', () => {
150
150
  };
151
151
  expect(queryToResourcePath(link, query, 'read')).toBe("".concat(link.unversionedApiPath, "/tracker"));
152
152
  });
153
+ it('should return an unversioned endpoint sub-resources of the new tracker importer (in version 2.37)', () => {
154
+ const query = {
155
+ resource: 'tracker/test'
156
+ };
157
+ expect(queryToResourcePath(link, query, 'read')).toBe("".concat(link.unversionedApiPath, "/tracker/test"));
158
+ });
153
159
  it('should return a VERSIONED endpoint for the new tracker importer (in version 2.38)', () => {
154
160
  const query = {
155
161
  resource: 'tracker'
@@ -0,0 +1,23 @@
1
+ import { stableVariablesHash } from './stableVariablesHash';
2
+ export const mergeAndCompareVariables = (previousVariables, newVariables, previousHash) => {
3
+ if (!newVariables) {
4
+ return {
5
+ identical: true,
6
+ mergedVariablesHash: previousHash,
7
+ mergedVariables: previousVariables
8
+ };
9
+ } // Use cached hash if it exists
10
+
11
+
12
+ const currentHash = previousHash || stableVariablesHash(previousVariables);
13
+ const mergedVariables = { ...previousVariables,
14
+ ...newVariables
15
+ };
16
+ const mergedVariablesHash = stableVariablesHash(mergedVariables);
17
+ const identical = currentHash === mergedVariablesHash;
18
+ return {
19
+ identical,
20
+ mergedVariablesHash,
21
+ mergedVariables
22
+ };
23
+ };
@@ -0,0 +1,53 @@
1
+ import { mergeAndCompareVariables } from './mergeAndCompareVariables';
2
+ import { stableVariablesHash } from './stableVariablesHash';
3
+ jest.mock('./stableVariablesHash', () => ({
4
+ stableVariablesHash: object => JSON.stringify(object)
5
+ }));
6
+ const testVariables = {
7
+ question: 'What do you get when you multiply six by nine?',
8
+ answer: 42
9
+ };
10
+ const testHash = stableVariablesHash(testVariables);
11
+ describe('mergeAndCompareVariables', () => {
12
+ it('Should return previous variables and hash when no new variables are provided', () => {
13
+ expect(mergeAndCompareVariables(testVariables, undefined, undefined)).toMatchObject({
14
+ identical: true,
15
+ mergedVariables: testVariables,
16
+ mergedVariablesHash: undefined
17
+ });
18
+ });
19
+ it('Should return identical: true when merged variables are identical to old variables (without prev hash)', () => {
20
+ const newVariables = {
21
+ answer: testVariables.answer
22
+ };
23
+ expect(mergeAndCompareVariables(testVariables, newVariables, undefined)).toMatchObject({
24
+ identical: true,
25
+ mergedVariables: testVariables,
26
+ mergedVariablesHash: testHash
27
+ });
28
+ });
29
+ it('Should return identical: false with incorrect previous hash', () => {
30
+ const incorrectPreviousHash = 'IAmAHash';
31
+ const newVariables = {
32
+ answer: 42
33
+ };
34
+ expect(mergeAndCompareVariables(testVariables, newVariables, incorrectPreviousHash)).toMatchObject({
35
+ identical: false,
36
+ mergedVariables: testVariables,
37
+ mergedVariablesHash: testHash
38
+ });
39
+ });
40
+ it('Should return identical: false when merged variables are different than old variables', () => {
41
+ const newVariables = {
42
+ answer: 43
43
+ };
44
+ const expectedMergedVariables = { ...testVariables,
45
+ ...newVariables
46
+ };
47
+ expect(mergeAndCompareVariables(testVariables, newVariables, testHash)).toMatchObject({
48
+ identical: false,
49
+ mergedVariables: expectedMergedVariables,
50
+ mergedVariablesHash: stableVariablesHash(expectedMergedVariables)
51
+ });
52
+ });
53
+ });
@@ -1,6 +1,6 @@
1
- import { useState, useRef, useCallback } from 'react';
1
+ import { useState, useRef, useCallback, useDebugValue } from 'react';
2
2
  import { useQuery, setLogger } from 'react-query';
3
- import { stableVariablesHash } from './stableVariablesHash';
3
+ import { mergeAndCompareVariables } from './mergeAndCompareVariables';
4
4
  import { useDataEngine } from './useDataEngine';
5
5
  import { useStaticInput } from './useStaticInput';
6
6
 
@@ -23,24 +23,35 @@ export const useDataQuery = (query, {
23
23
  variables: initialVariables = {},
24
24
  lazy: initialLazy = false
25
25
  } = {}) => {
26
- const variablesHash = useRef(null);
27
- const [variables, setVariables] = useState(initialVariables);
28
- const [enabled, setEnabled] = useState(!initialLazy);
29
26
  const [staticQuery] = useStaticInput(query, {
30
27
  warn: true,
31
28
  name: 'query'
32
29
  });
30
+ const [variablesUpdateCount, setVariablesUpdateCount] = useState(0);
31
+ const queryState = useRef({
32
+ variables: initialVariables,
33
+ variablesHash: undefined,
34
+ enabled: !initialLazy,
35
+ refetchCallback: undefined
36
+ });
33
37
  /**
34
- * User callbacks and refetch handling
38
+ * Display current query state and refetch count in React DevTools
35
39
  */
36
40
 
37
- const refetchCallback = useRef(null);
41
+ useDebugValue({
42
+ variablesUpdateCount,
43
+ enabled: queryState.current.enabled,
44
+ variables: queryState.current.variables
45
+ }, debugValue => JSON.stringify(debugValue));
46
+ /**
47
+ * User callbacks and refetch handling
48
+ */
38
49
 
39
50
  const onSuccess = data => {
40
- if (refetchCallback.current) {
41
- refetchCallback.current(data);
42
- refetchCallback.current = null;
43
- }
51
+ var _queryState$current$r, _queryState$current;
52
+
53
+ (_queryState$current$r = (_queryState$current = queryState.current).refetchCallback) === null || _queryState$current$r === void 0 ? void 0 : _queryState$current$r.call(_queryState$current, data);
54
+ queryState.current.refetchCallback = undefined;
44
55
 
45
56
  if (userOnSuccess) {
46
57
  userOnSuccess(data);
@@ -49,9 +60,7 @@ export const useDataQuery = (query, {
49
60
 
50
61
  const onError = error => {
51
62
  // If we'd want to reject on errors we'd call the cb with the error here
52
- if (refetchCallback.current) {
53
- refetchCallback.current = null;
54
- }
63
+ queryState.current.refetchCallback = undefined;
55
64
 
56
65
  if (userOnError) {
57
66
  userOnError(error);
@@ -63,10 +72,10 @@ export const useDataQuery = (query, {
63
72
 
64
73
 
65
74
  const engine = useDataEngine();
66
- const queryKey = [staticQuery, variables];
75
+ const queryKey = [staticQuery, queryState.current.variables];
67
76
 
68
77
  const queryFn = () => engine.query(staticQuery, {
69
- variables
78
+ variables: queryState.current.variables
70
79
  });
71
80
 
72
81
  const {
@@ -77,7 +86,7 @@ export const useDataQuery = (query, {
77
86
  data,
78
87
  refetch: queryRefetch
79
88
  } = useQuery(queryKey, queryFn, {
80
- enabled,
89
+ enabled: queryState.current.enabled,
81
90
  onSuccess,
82
91
  onError
83
92
  });
@@ -91,11 +100,17 @@ export const useDataQuery = (query, {
91
100
  */
92
101
 
93
102
  const refetch = useCallback(newVariables => {
103
+ const {
104
+ identical,
105
+ mergedVariables,
106
+ mergedVariablesHash
107
+ } = mergeAndCompareVariables(queryState.current.variables, newVariables, queryState.current.variablesHash);
94
108
  /**
95
109
  * If there are no updates that will trigger an automatic refetch
96
110
  * we'll need to call react-query's refetch directly
97
111
  */
98
- if (enabled && !newVariables) {
112
+
113
+ if (queryState.current.enabled && identical) {
99
114
  return queryRefetch({
100
115
  cancelRefetch: true,
101
116
  throwOnError: false
@@ -104,44 +119,19 @@ export const useDataQuery = (query, {
104
119
  }) => data);
105
120
  }
106
121
 
107
- if (newVariables) {
108
- // Use cached hash if it exists
109
- const currentHash = variablesHash.current || stableVariablesHash(variables);
110
- const mergedVariables = { ...variables,
111
- ...newVariables
112
- };
113
- const mergedHash = stableVariablesHash(mergedVariables);
114
- const identical = currentHash === mergedHash;
115
-
116
- if (identical && enabled) {
117
- /**
118
- * If the variables are identical and the query is enabled
119
- * we'll need to trigger the refetch manually
120
- */
121
- return queryRefetch({
122
- cancelRefetch: true,
123
- throwOnError: false
124
- }).then(({
125
- data
126
- }) => data);
127
- } else {
128
- variablesHash.current = mergedHash;
129
- setVariables(mergedVariables);
130
- }
131
- } // Enable the query after the variables have been set to prevent extra request
132
-
133
-
134
- if (!enabled) {
135
- setEnabled(true);
136
- } // This promise does not currently reject on errors
137
-
138
-
139
- return new Promise(resolve => {
140
- refetchCallback.current = data => {
122
+ queryState.current.variables = mergedVariables;
123
+ queryState.current.variablesHash = mergedVariablesHash;
124
+ queryState.current.enabled = true; // This promise does not currently reject on errors
125
+
126
+ const refetchPromise = new Promise(resolve => {
127
+ queryState.current.refetchCallback = data => {
141
128
  resolve(data);
142
129
  };
143
- });
144
- }, [enabled, queryRefetch, variables]);
130
+ }); // Trigger a react-query refetch by incrementing variablesUpdateCount state
131
+
132
+ setVariablesUpdateCount(prevCount => prevCount + 1);
133
+ return refetchPromise;
134
+ }, [queryRefetch]);
145
135
  /**
146
136
  * react-query returns null or an error, but we return undefined
147
137
  * or an error, so this ensures consistency with the other types.
@@ -497,6 +497,65 @@ describe('useDataQuery', () => {
497
497
  });
498
498
  });
499
499
  describe('return values: refetch', () => {
500
+ it('Should be stable if the query variables change', async () => {
501
+ let count = 0;
502
+ const spy = jest.fn(() => {
503
+ count++;
504
+ return count;
505
+ });
506
+ const data = {
507
+ answer: spy
508
+ };
509
+ const query = {
510
+ x: {
511
+ resource: 'answer'
512
+ }
513
+ };
514
+
515
+ const wrapper = ({
516
+ children
517
+ }) => /*#__PURE__*/React.createElement(CustomDataProvider, {
518
+ data: data
519
+ }, children);
520
+
521
+ const {
522
+ result,
523
+ waitFor
524
+ } = renderHook(() => useDataQuery(query, {
525
+ lazy: true
526
+ }), {
527
+ wrapper
528
+ });
529
+ expect(spy).not.toHaveBeenCalled();
530
+ const initialRefetch = result.current.refetch;
531
+ act(() => {
532
+ initialRefetch();
533
+ });
534
+ await waitFor(() => {
535
+ expect(result.current).toMatchObject({
536
+ loading: false,
537
+ called: true,
538
+ data: {
539
+ x: 1
540
+ }
541
+ });
542
+ });
543
+ expect(spy).toHaveBeenCalledTimes(1);
544
+ act(() => {
545
+ initialRefetch();
546
+ });
547
+ await waitFor(() => {
548
+ expect(result.current).toMatchObject({
549
+ loading: false,
550
+ called: true,
551
+ data: {
552
+ x: 2
553
+ }
554
+ });
555
+ });
556
+ expect(spy).toHaveBeenCalledTimes(2);
557
+ expect(initialRefetch).toBe(result.current.refetch);
558
+ });
500
559
  it('Should only trigger a single request when refetch is called on a lazy query with new variables', async () => {
501
560
  const spy = jest.fn((type, query) => {
502
561
  if (query.id === '1') {
@@ -1,10 +1,11 @@
1
- import { useState, useEffect, useRef } from 'react';
1
+ import { useState, useEffect, useRef, useDebugValue } from 'react';
2
2
  export const useStaticInput = (staticValue, {
3
3
  warn = false,
4
4
  name = 'input'
5
5
  } = {}) => {
6
6
  const originalValue = useRef(staticValue);
7
7
  const [value, setValue] = useState(() => originalValue.current);
8
+ useDebugValue(value, debugValue => "".concat(name, ": ").concat(JSON.stringify(debugValue)));
8
9
  useEffect(() => {
9
10
  if (warn && originalValue.current !== staticValue) {
10
11
  console.warn("The ".concat(name, " should be static, don't create it within the render loop!"));
@@ -4,4 +4,3 @@ export declare const isFileResourceUpload: (type: FetchType, { resource }: Resol
4
4
  export declare const isMessageConversationAttachment: (type: FetchType, { resource }: ResolvedResourceQuery) => boolean;
5
5
  export declare const isStaticContentUpload: (type: FetchType, { resource }: ResolvedResourceQuery) => boolean;
6
6
  export declare const isAppInstall: (type: FetchType, { resource }: ResolvedResourceQuery) => boolean;
7
- export declare const isSvgConversion: (type: FetchType, { resource }: ResolvedResourceQuery) => boolean;
@@ -1,7 +1,7 @@
1
1
  import { ResolvedResourceQuery, FetchType } from '../../../engine';
2
- declare type RequestContentType = 'application/json' | 'application/json-patch+json' | 'text/plain' | 'multipart/form-data' | null;
3
- export declare const FORM_DATA_ERROR_MSG = "Could not convert data to FormData: object does not have own enumerable string-keyed properties";
2
+ declare type RequestContentType = 'application/json' | 'application/json-patch+json' | 'text/plain' | 'multipart/form-data' | 'application/x-www-form-urlencoded' | null;
3
+ export declare const getConversionErrorMessage: (outputType: string) => string;
4
4
  export declare const requestContentType: (type: FetchType, query: ResolvedResourceQuery) => null | RequestContentType;
5
5
  export declare const requestHeadersForContentType: (contentType: RequestContentType) => undefined | Record<'Content-Type', string>;
6
- export declare const requestBodyForContentType: (contentType: RequestContentType, { data }: ResolvedResourceQuery) => undefined | string | FormData;
6
+ export declare const requestBodyForContentType: (contentType: RequestContentType, { data }: ResolvedResourceQuery) => undefined | string | FormData | URLSearchParams;
7
7
  export {};
@@ -0,0 +1,2 @@
1
+ import { ResolvedResourceQuery, FetchType } from '../../../engine';
2
+ export declare const isSvgConversion: (type: FetchType, { resource }: ResolvedResourceQuery) => boolean;
@@ -0,0 +1,6 @@
1
+ import type { QueryVariables } from '../../engine';
2
+ export declare const mergeAndCompareVariables: (previousVariables?: QueryVariables | undefined, newVariables?: QueryVariables | undefined, previousHash?: string | undefined) => {
3
+ identical: boolean;
4
+ mergedVariablesHash: string | undefined;
5
+ mergedVariables: QueryVariables | undefined;
6
+ };
@@ -1,3 +1,3 @@
1
- import { Query, QueryOptions } from '../../engine';
2
- import { QueryRenderInput } from '../../types';
1
+ import type { Query, QueryOptions } from '../../engine';
2
+ import type { QueryRenderInput } from '../../types';
3
3
  export declare const useDataQuery: (query: Query, { onComplete: userOnSuccess, onError: userOnError, variables: initialVariables, lazy: initialLazy, }?: QueryOptions) => QueryRenderInput;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhis2/app-service-data",
3
- "version": "3.4.1",
3
+ "version": "3.4.4",
4
4
  "main": "./build/cjs/index.js",
5
5
  "module": "./build/es/index.js",
6
6
  "types": "build/types/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "build/**"
23
23
  ],
24
24
  "peerDependencies": {
25
- "@dhis2/app-service-config": "3.4.1",
25
+ "@dhis2/app-service-config": "3.4.4",
26
26
  "@dhis2/cli-app-scripts": "^7.1.1",
27
27
  "prop-types": "^15.7.2",
28
28
  "react": "^16.8",