@dhis2/app-service-data 3.2.8 → 3.4.0

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 (25) hide show
  1. package/build/cjs/engine/helpers/getMutationFetchType.test.js +5 -0
  2. package/build/cjs/engine/helpers/validate.js +5 -1
  3. package/build/cjs/engine/helpers/validate.test.js +8 -0
  4. package/build/cjs/links/CustomDataLink.js +1 -1
  5. package/build/cjs/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.js +10 -2
  6. package/build/cjs/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.test.js +17 -0
  7. package/build/cjs/links/RestAPILink/queryToRequestOptions/requestContentType.js +5 -1
  8. package/build/cjs/links/RestAPILink/queryToRequestOptions/requestContentType.test.js +6 -0
  9. package/build/cjs/links/RestAPILink/queryToRequestOptions.js +4 -0
  10. package/build/cjs/links/RestAPILink/queryToRequestOptions.test.js +10 -0
  11. package/build/es/engine/helpers/getMutationFetchType.test.js +5 -0
  12. package/build/es/engine/helpers/validate.js +5 -1
  13. package/build/es/engine/helpers/validate.test.js +8 -0
  14. package/build/es/links/CustomDataLink.js +1 -1
  15. package/build/es/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.js +6 -1
  16. package/build/es/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.test.js +18 -1
  17. package/build/es/links/RestAPILink/queryToRequestOptions/requestContentType.js +5 -1
  18. package/build/es/links/RestAPILink/queryToRequestOptions/requestContentType.test.js +6 -0
  19. package/build/es/links/RestAPILink/queryToRequestOptions.js +4 -0
  20. package/build/es/links/RestAPILink/queryToRequestOptions.test.js +10 -0
  21. package/build/types/engine/types/ExecuteOptions.d.ts +1 -1
  22. package/build/types/engine/types/Mutation.d.ts +2 -2
  23. package/build/types/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.d.ts +1 -0
  24. package/build/types/links/RestAPILink/queryToRequestOptions/requestContentType.d.ts +4 -6
  25. package/package.json +2 -2
@@ -14,6 +14,11 @@ describe('getMutationFetchType', () => {
14
14
  resource: 'test',
15
15
  id: 'id'
16
16
  })).toBe('delete');
17
+ expect((0, _getMutationFetchType.getMutationFetchType)({
18
+ type: 'json-patch',
19
+ resource: 'test',
20
+ data: {}
21
+ })).toBe('json-patch');
17
22
  });
18
23
  it('should return `replace` for non-partial `update`', () => {
19
24
  expect((0, _getMutationFetchType.getMutationFetchType)({
@@ -8,7 +8,7 @@ exports.validateResourceQuery = exports.validateResourceQueries = exports.getRes
8
8
  var _InvalidQueryError = require("../types/InvalidQueryError");
9
9
 
10
10
  const validQueryKeys = ['resource', 'id', 'params', 'data'];
11
- const validTypes = ['read', 'create', 'update', 'replace', 'delete'];
11
+ const validTypes = ['read', 'create', 'update', 'replace', 'delete', 'json-patch'];
12
12
 
13
13
  const getResourceQueryErrors = (type, query) => {
14
14
  if (!validTypes.includes(type)) {
@@ -41,6 +41,10 @@ const getResourceQueryErrors = (type, query) => {
41
41
  errors.push("Mutation type 'delete' does not support property 'data'");
42
42
  }
43
43
 
44
+ if (type === 'json-patch' && !Array.isArray(query.data)) {
45
+ errors.push("Mutation type 'json-patch' requires property 'data' to be of type Array");
46
+ }
47
+
44
48
  const invalidKeys = Object.keys(query).filter(k => !validQueryKeys.includes(k));
45
49
  invalidKeys.forEach(k => {
46
50
  errors.push("Property ".concat(k, " is not supported"));
@@ -76,6 +76,14 @@ describe('query validation', () => {
76
76
  expect(errors).toHaveLength(1);
77
77
  expect(errors).toMatchInlineSnapshot("\n Array [\n \"Mutation type 'delete' does not support property 'data'\",\n ]\n ");
78
78
  });
79
+ it('should fail if query is json-patch mutation with non-array data prop', () => {
80
+ const errors = (0, _validate.getResourceQueryErrors)('json-patch', {
81
+ resource: 'metadata',
82
+ data: {}
83
+ });
84
+ expect(errors).toHaveLength(1);
85
+ expect(errors).toMatchInlineSnapshot("\n Array [\n \"Mutation type 'json-patch' requires property 'data' to be of type Array\",\n ]\n ");
86
+ });
79
87
  it('should fail if unrecognized keys are passed to query', () => {
80
88
  const errors = (0, _validate.getResourceQueryErrors)('update', {
81
89
  resource: 'indicators',
@@ -30,7 +30,7 @@ class CustomDataLink {
30
30
 
31
31
  const customResource = this.data[query.resource];
32
32
 
33
- if (!customResource) {
33
+ if (customResource === undefined) {
34
34
  if (this.failOnMiss) {
35
35
  throw new Error("No data provided for resource type ".concat(query.resource, "!"));
36
36
  }
@@ -3,14 +3,22 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.isSvgConversion = exports.isAppInstall = exports.isStaticContentUpload = exports.isMessageConversationAttachment = exports.isFileResourceUpload = void 0;
6
+ exports.isSvgConversion = 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
10
10
  * the developer documentation:
11
11
  * https://docs.dhis2.org/master/en/developer/html/dhis2_developer_manual_full.html
12
12
  */
13
- // POST to 'fileResources' (upload a file resource)
13
+ // Post to 'dataValues' (send/update a data value; endpoint doesn't support JSON)
14
+ // For file-uploads too
15
+ const isDataValue = (type, {
16
+ resource
17
+ }) => type === 'create' && (resource === 'dataValues' || resource === 'dataValues/file'); // POST to 'fileResources' (upload a file resource)
18
+
19
+
20
+ exports.isDataValue = isDataValue;
21
+
14
22
  const isFileResourceUpload = (type, {
15
23
  resource
16
24
  }) => type === 'create' && resource === 'fileResources'; // POST to 'messageConversations/attachments' (upload a message conversation attachment)
@@ -2,6 +2,23 @@
2
2
 
3
3
  var _multipartFormDataMatchers = require("./multipartFormDataMatchers");
4
4
 
5
+ describe('isDataValue', () => {
6
+ it('returns true for a POST to "dataValues"', () => {
7
+ expect((0, _multipartFormDataMatchers.isDataValue)('create', {
8
+ resource: 'dataValues'
9
+ })).toEqual(true);
10
+ });
11
+ it('returns true for a POST to "dataValues/file"', () => {
12
+ expect((0, _multipartFormDataMatchers.isDataValue)('create', {
13
+ resource: 'dataValues/file'
14
+ })).toEqual(true);
15
+ });
16
+ it('returns false for a POST to a different resource', () => {
17
+ expect((0, _multipartFormDataMatchers.isDataValue)('create', {
18
+ resource: 'somethingElse'
19
+ })).toEqual(false);
20
+ });
21
+ });
5
22
  describe('isFileResourceUpload', () => {
6
23
  it('returns true for a POST to "fileResources"', () => {
7
24
  expect((0, _multipartFormDataMatchers.isFileResourceUpload)('create', {
@@ -38,6 +38,10 @@ const requestContentType = (type, query) => {
38
38
  return null;
39
39
  }
40
40
 
41
+ if (type === 'json-patch') {
42
+ return 'application/json-patch+json';
43
+ }
44
+
41
45
  if (resourceExpectsTextPlain(type, query)) {
42
46
  return 'text/plain';
43
47
  }
@@ -77,7 +81,7 @@ const requestBodyForContentType = (contentType, {
77
81
  return undefined;
78
82
  }
79
83
 
80
- if (contentType === 'application/json') {
84
+ if (contentType === 'application/json' || contentType === 'application/json-patch+json') {
81
85
  return JSON.stringify(data);
82
86
  }
83
87
 
@@ -9,6 +9,12 @@ describe('requestContentType', () => {
9
9
  data: 'test'
10
10
  })).toEqual('application/json');
11
11
  });
12
+ it('returns "application/json-patch+json" when the fetch type is "json-patch"', () => {
13
+ expect((0, _requestContentType.requestContentType)('json-patch', {
14
+ resource: 'test',
15
+ data: 'test'
16
+ })).toEqual('application/json-patch+json');
17
+ });
12
18
  it('returns "multipart/form-data" for a specific resource that expects it', () => {
13
19
  expect((0, _requestContentType.requestContentType)('create', {
14
20
  resource: 'fileResources',
@@ -16,6 +16,7 @@ const getMethod = type => {
16
16
  return 'GET';
17
17
 
18
18
  case 'update':
19
+ case 'json-patch':
19
20
  return 'PATCH';
20
21
 
21
22
  case 'replace':
@@ -23,6 +24,9 @@ const getMethod = type => {
23
24
 
24
25
  case 'delete':
25
26
  return 'DELETE';
27
+
28
+ default:
29
+ throw new Error("Unknown type ".concat(type));
26
30
  }
27
31
  };
28
32
 
@@ -29,6 +29,16 @@ describe('queryToRequestOptions', () => {
29
29
  });
30
30
  expect(options).toMatchInlineSnapshot("\n Object {\n \"body\": \"{\\\"answer\\\":42,\\\"foo\\\":\\\"bar\\\"}\",\n \"headers\": Object {\n \"Content-Type\": \"application/json\",\n },\n \"method\": \"PATCH\",\n \"signal\": undefined,\n }\n ");
31
31
  });
32
+ it('should return a valid Fetch option object for json-patch request', () => {
33
+ const options = (0, _queryToRequestOptions.queryToRequestOptions)('json-patch', {
34
+ resource: 'test',
35
+ data: {
36
+ answer: 42,
37
+ foo: 'bar'
38
+ }
39
+ });
40
+ expect(options).toMatchInlineSnapshot("\n Object {\n \"body\": \"{\\\"answer\\\":42,\\\"foo\\\":\\\"bar\\\"}\",\n \"headers\": Object {\n \"Content-Type\": \"application/json-patch+json\",\n },\n \"method\": \"PATCH\",\n \"signal\": undefined,\n }\n ");
41
+ });
32
42
  it('should return a valid Fetch option object for replace request', () => {
33
43
  const options = (0, _queryToRequestOptions.queryToRequestOptions)('replace', {
34
44
  resource: 'test',
@@ -11,6 +11,11 @@ describe('getMutationFetchType', () => {
11
11
  resource: 'test',
12
12
  id: 'id'
13
13
  })).toBe('delete');
14
+ expect(getMutationFetchType({
15
+ type: 'json-patch',
16
+ resource: 'test',
17
+ data: {}
18
+ })).toBe('json-patch');
14
19
  });
15
20
  it('should return `replace` for non-partial `update`', () => {
16
21
  expect(getMutationFetchType({
@@ -1,6 +1,6 @@
1
1
  import { InvalidQueryError } from '../types/InvalidQueryError';
2
2
  const validQueryKeys = ['resource', 'id', 'params', 'data'];
3
- const validTypes = ['read', 'create', 'update', 'replace', 'delete'];
3
+ const validTypes = ['read', 'create', 'update', 'replace', 'delete', 'json-patch'];
4
4
  export const getResourceQueryErrors = (type, query) => {
5
5
  if (!validTypes.includes(type)) {
6
6
  return ["Unknown query or mutation type ".concat(type)];
@@ -32,6 +32,10 @@ export const getResourceQueryErrors = (type, query) => {
32
32
  errors.push("Mutation type 'delete' does not support property 'data'");
33
33
  }
34
34
 
35
+ if (type === 'json-patch' && !Array.isArray(query.data)) {
36
+ errors.push("Mutation type 'json-patch' requires property 'data' to be of type Array");
37
+ }
38
+
35
39
  const invalidKeys = Object.keys(query).filter(k => !validQueryKeys.includes(k));
36
40
  invalidKeys.forEach(k => {
37
41
  errors.push("Property ".concat(k, " is not supported"));
@@ -73,6 +73,14 @@ describe('query validation', () => {
73
73
  expect(errors).toHaveLength(1);
74
74
  expect(errors).toMatchInlineSnapshot("\n Array [\n \"Mutation type 'delete' does not support property 'data'\",\n ]\n ");
75
75
  });
76
+ it('should fail if query is json-patch mutation with non-array data prop', () => {
77
+ const errors = getResourceQueryErrors('json-patch', {
78
+ resource: 'metadata',
79
+ data: {}
80
+ });
81
+ expect(errors).toHaveLength(1);
82
+ expect(errors).toMatchInlineSnapshot("\n Array [\n \"Mutation type 'json-patch' requires property 'data' to be of type Array\",\n ]\n ");
83
+ });
76
84
  it('should fail if unrecognized keys are passed to query', () => {
77
85
  const errors = getResourceQueryErrors('update', {
78
86
  resource: 'indicators',
@@ -23,7 +23,7 @@ export class CustomDataLink {
23
23
 
24
24
  const customResource = this.data[query.resource];
25
25
 
26
- if (!customResource) {
26
+ if (customResource === undefined) {
27
27
  if (this.failOnMiss) {
28
28
  throw new Error("No data provided for resource type ".concat(query.resource, "!"));
29
29
  }
@@ -3,7 +3,12 @@
3
3
  * the developer documentation:
4
4
  * https://docs.dhis2.org/master/en/developer/html/dhis2_developer_manual_full.html
5
5
  */
6
- // POST to 'fileResources' (upload a file resource)
6
+ // Post to 'dataValues' (send/update a data value; endpoint doesn't support JSON)
7
+ // For file-uploads too
8
+ export const isDataValue = (type, {
9
+ resource
10
+ }) => type === 'create' && (resource === 'dataValues' || resource === 'dataValues/file'); // POST to 'fileResources' (upload a file resource)
11
+
7
12
  export const isFileResourceUpload = (type, {
8
13
  resource
9
14
  }) => type === 'create' && resource === 'fileResources'; // POST to 'messageConversations/attachments' (upload a message conversation attachment)
@@ -1,4 +1,21 @@
1
- import { isFileResourceUpload, isMessageConversationAttachment, isStaticContentUpload, isAppInstall, isSvgConversion } from './multipartFormDataMatchers';
1
+ import { isFileResourceUpload, isMessageConversationAttachment, isStaticContentUpload, isAppInstall, isSvgConversion, isDataValue } from './multipartFormDataMatchers';
2
+ describe('isDataValue', () => {
3
+ it('returns true for a POST to "dataValues"', () => {
4
+ expect(isDataValue('create', {
5
+ resource: 'dataValues'
6
+ })).toEqual(true);
7
+ });
8
+ it('returns true for a POST to "dataValues/file"', () => {
9
+ expect(isDataValue('create', {
10
+ resource: 'dataValues/file'
11
+ })).toEqual(true);
12
+ });
13
+ it('returns false for a POST to a different resource', () => {
14
+ expect(isDataValue('create', {
15
+ resource: 'somethingElse'
16
+ })).toEqual(false);
17
+ });
18
+ });
2
19
  describe('isFileResourceUpload', () => {
3
20
  it('returns true for a POST to "fileResources"', () => {
4
21
  expect(isFileResourceUpload('create', {
@@ -25,6 +25,10 @@ export const requestContentType = (type, query) => {
25
25
  return null;
26
26
  }
27
27
 
28
+ if (type === 'json-patch') {
29
+ return 'application/json-patch+json';
30
+ }
31
+
28
32
  if (resourceExpectsTextPlain(type, query)) {
29
33
  return 'text/plain';
30
34
  }
@@ -58,7 +62,7 @@ export const requestBodyForContentType = (contentType, {
58
62
  return undefined;
59
63
  }
60
64
 
61
- if (contentType === 'application/json') {
65
+ if (contentType === 'application/json' || contentType === 'application/json-patch+json') {
62
66
  return JSON.stringify(data);
63
67
  }
64
68
 
@@ -6,6 +6,12 @@ describe('requestContentType', () => {
6
6
  data: 'test'
7
7
  })).toEqual('application/json');
8
8
  });
9
+ it('returns "application/json-patch+json" when the fetch type is "json-patch"', () => {
10
+ expect(requestContentType('json-patch', {
11
+ resource: 'test',
12
+ data: 'test'
13
+ })).toEqual('application/json-patch+json');
14
+ });
9
15
  it('returns "multipart/form-data" for a specific resource that expects it', () => {
10
16
  expect(requestContentType('create', {
11
17
  resource: 'fileResources',
@@ -9,6 +9,7 @@ const getMethod = type => {
9
9
  return 'GET';
10
10
 
11
11
  case 'update':
12
+ case 'json-patch':
12
13
  return 'PATCH';
13
14
 
14
15
  case 'replace':
@@ -16,6 +17,9 @@ const getMethod = type => {
16
17
 
17
18
  case 'delete':
18
19
  return 'DELETE';
20
+
21
+ default:
22
+ throw new Error("Unknown type ".concat(type));
19
23
  }
20
24
  };
21
25
 
@@ -26,6 +26,16 @@ describe('queryToRequestOptions', () => {
26
26
  });
27
27
  expect(options).toMatchInlineSnapshot("\n Object {\n \"body\": \"{\\\"answer\\\":42,\\\"foo\\\":\\\"bar\\\"}\",\n \"headers\": Object {\n \"Content-Type\": \"application/json\",\n },\n \"method\": \"PATCH\",\n \"signal\": undefined,\n }\n ");
28
28
  });
29
+ it('should return a valid Fetch option object for json-patch request', () => {
30
+ const options = queryToRequestOptions('json-patch', {
31
+ resource: 'test',
32
+ data: {
33
+ answer: 42,
34
+ foo: 'bar'
35
+ }
36
+ });
37
+ expect(options).toMatchInlineSnapshot("\n Object {\n \"body\": \"{\\\"answer\\\":42,\\\"foo\\\":\\\"bar\\\"}\",\n \"headers\": Object {\n \"Content-Type\": \"application/json-patch+json\",\n },\n \"method\": \"PATCH\",\n \"signal\": undefined,\n }\n ");
38
+ });
29
39
  it('should return a valid Fetch option object for replace request', () => {
30
40
  const options = queryToRequestOptions('replace', {
31
41
  resource: 'test',
@@ -1,6 +1,6 @@
1
1
  import { FetchError } from './FetchError';
2
2
  import { QueryVariables } from './Query';
3
- export declare type FetchType = 'create' | 'read' | 'update' | 'replace' | 'delete';
3
+ export declare type FetchType = 'create' | 'read' | 'update' | 'json-patch' | 'replace' | 'delete';
4
4
  export interface QueryExecuteOptions {
5
5
  variables?: QueryVariables;
6
6
  signal?: AbortSignal;
@@ -1,6 +1,6 @@
1
1
  import { FetchError } from './FetchError';
2
2
  import { ResourceQuery, QueryVariables } from './Query';
3
- export declare type MutationType = 'create' | 'update' | 'replace' | 'delete';
3
+ export declare type MutationType = 'create' | 'update' | 'json-patch' | 'replace' | 'delete';
4
4
  export interface MutationData {
5
5
  [key: string]: any;
6
6
  }
@@ -12,7 +12,7 @@ export interface CreateMutation extends BaseMutation {
12
12
  data: MutationData;
13
13
  }
14
14
  export interface UpdateMutation extends BaseMutation {
15
- type: 'update' | 'replace';
15
+ type: 'update' | 'replace' | 'json-patch';
16
16
  id: string;
17
17
  partial?: boolean;
18
18
  data: MutationData;
@@ -1,4 +1,5 @@
1
1
  import { ResolvedResourceQuery, FetchType } from '../../../engine';
2
+ export declare const isDataValue: (type: FetchType, { resource }: ResolvedResourceQuery) => boolean;
2
3
  export declare const isFileResourceUpload: (type: FetchType, { resource }: ResolvedResourceQuery) => boolean;
3
4
  export declare const isMessageConversationAttachment: (type: FetchType, { resource }: ResolvedResourceQuery) => boolean;
4
5
  export declare const isStaticContentUpload: (type: FetchType, { resource }: ResolvedResourceQuery) => boolean;
@@ -1,9 +1,7 @@
1
1
  import { ResolvedResourceQuery, FetchType } from '../../../engine';
2
- declare type RequestContentType = 'application/json' | 'text/plain' | 'multipart/form-data' | null;
2
+ declare type RequestContentType = 'application/json' | 'application/json-patch+json' | 'text/plain' | 'multipart/form-data' | null;
3
3
  export declare const FORM_DATA_ERROR_MSG = "Could not convert data to FormData: object does not have own enumerable string-keyed properties";
4
- export declare const requestContentType: (type: FetchType, query: ResolvedResourceQuery) => "application/json" | "text/plain" | "multipart/form-data" | null;
5
- export declare const requestHeadersForContentType: (contentType: RequestContentType) => {
6
- 'Content-Type': "application/json" | "text/plain";
7
- } | undefined;
8
- export declare const requestBodyForContentType: (contentType: RequestContentType, { data }: ResolvedResourceQuery) => any;
4
+ export declare const requestContentType: (type: FetchType, query: ResolvedResourceQuery) => null | RequestContentType;
5
+ export declare const requestHeadersForContentType: (contentType: RequestContentType) => undefined | Record<'Content-Type', string>;
6
+ export declare const requestBodyForContentType: (contentType: RequestContentType, { data }: ResolvedResourceQuery) => undefined | string | FormData;
9
7
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhis2/app-service-data",
3
- "version": "3.2.8",
3
+ "version": "3.4.0",
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.2.8",
25
+ "@dhis2/app-service-config": "3.4.0",
26
26
  "@dhis2/cli-app-scripts": "^7.1.1",
27
27
  "prop-types": "^15.7.2",
28
28
  "react": "^16.8",