moonflower 0.9.2 → 0.10.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 (41) hide show
  1. package/dist/src/hooks/useRequestBody.d.ts +1 -1
  2. package/dist/src/hooks/useRequestBody.d.ts.map +1 -1
  3. package/dist/src/hooks/useReturnValue.d.ts +7 -0
  4. package/dist/src/hooks/useReturnValue.d.ts.map +1 -0
  5. package/dist/src/hooks/useReturnValue.js +12 -0
  6. package/dist/src/openapi/analyzerModule/nodeParsers.d.ts.map +1 -1
  7. package/dist/src/openapi/analyzerModule/nodeParsers.js +9 -0
  8. package/dist/src/openapi/analyzerModule/parseEndpoint.js +50 -6
  9. package/dist/src/openapi/analyzerModule/test/openApiAnalyzer.spec.data.js +27 -0
  10. package/dist/src/openapi/analyzerModule/types.d.ts +6 -1
  11. package/dist/src/openapi/analyzerModule/types.d.ts.map +1 -1
  12. package/dist/src/openapi/generatorModule/generatePaths.d.ts.map +1 -1
  13. package/dist/src/openapi/generatorModule/generatePaths.js +5 -6
  14. package/dist/src/openapi/generatorModule/test/openApiGenerator.spec.data.d.ts.map +1 -1
  15. package/dist/src/openapi/generatorModule/test/openApiGenerator.spec.data.js +27 -20
  16. package/dist/src/openapi/types.d.ts +5 -6
  17. package/dist/src/openapi/types.d.ts.map +1 -1
  18. package/dist/src/router/Router.d.ts.map +1 -1
  19. package/dist/src/router/Router.js +19 -13
  20. package/dist/src/router/parseEndpointReturnValue.d.ts +8 -0
  21. package/dist/src/router/parseEndpointReturnValue.d.ts.map +1 -0
  22. package/dist/src/router/parseEndpointReturnValue.js +33 -0
  23. package/dist/tsconfig.build.tsbuildinfo +1 -1
  24. package/package.json +1 -1
  25. package/src/hooks/useRequestBody.spec.ts +25 -0
  26. package/src/hooks/useRequestBody.ts +1 -1
  27. package/src/hooks/useReturnValue.spec.ts +16 -0
  28. package/src/hooks/useReturnValue.ts +12 -0
  29. package/src/openapi/analyzerModule/nodeParsers.ts +10 -0
  30. package/src/openapi/analyzerModule/parseEndpoint.ts +59 -6
  31. package/src/openapi/analyzerModule/test/openApiAnalyzer.spec.data.ts +31 -0
  32. package/src/openapi/analyzerModule/test/openApiAnalyzer.spec.ts +31 -0
  33. package/src/openapi/analyzerModule/types.ts +7 -0
  34. package/src/openapi/generatorModule/generatePaths.ts +5 -2
  35. package/src/openapi/generatorModule/test/openApiGenerator.spec.data.ts +27 -20
  36. package/src/openapi/generatorModule/test/openApiGenerator.spec.ts +162 -0
  37. package/src/openapi/types.ts +5 -3
  38. package/src/router/Router.ts +19 -13
  39. package/src/router/parseEndpointReturnValue.spec.ts +35 -0
  40. package/src/router/parseEndpointReturnValue.ts +31 -0
  41. package/src/router/responseValueToJson.ts +0 -6
@@ -4,6 +4,7 @@ export type ShapeOfType =
4
4
  | ShapeOfNumberLiteral
5
5
  | ShapeOfUnion
6
6
  | ShapeOfUnionEntry
7
+ | ShapeOfBuffer
7
8
  | ShapeOfRecord
8
9
  | ShapeOfArray
9
10
  | ShapeOfRef
@@ -41,6 +42,12 @@ export type ShapeOfUnionEntry = {
41
42
  optional: boolean
42
43
  }
43
44
 
45
+ export type ShapeOfBuffer = {
46
+ role: 'buffer'
47
+ shape: string
48
+ optional: boolean
49
+ }
50
+
44
51
  export type ShapeOfRecord = {
45
52
  role: 'record'
46
53
  shape: string | ShapeOfType[]
@@ -92,16 +92,19 @@ export const generatePaths = (endpoints: EndpointData[], preferences: ApiDocsPre
92
92
  endpoint.responses.forEach((response) => {
93
93
  const status = String(response.status)
94
94
 
95
- const existingSchemas = responses[status]?.['content']?.['application/json']['schema']['oneOf'] ?? []
95
+ const existingSchemas =
96
+ responses[status]?.['content']?.[response.contentType]?.['schema']['oneOf'] ?? []
96
97
 
97
98
  const responseSchema = getSchema(response.signature)
99
+
98
100
  const content = (() => {
99
101
  if ('type' in responseSchema && (responseSchema.type === 'void' || responseSchema.type === 'null')) {
100
102
  return undefined
101
103
  }
102
104
 
103
105
  return {
104
- 'application/json': {
106
+ ...responses[status]?.['content'],
107
+ [response.contentType]: {
105
108
  schema: {
106
109
  oneOf: [...existingSchemas, getSchema(response.signature)],
107
110
  },
@@ -10,7 +10,7 @@ export const manyEndpointsData: EndpointData[] = [
10
10
  requestHeaders: [],
11
11
  rawBody: undefined,
12
12
  objectBody: [],
13
- responses: [{ status: 200, signature: 'void' }],
13
+ responses: [{ status: 200, signature: 'void', contentType: 'application/json' }],
14
14
  name: 'Test endpoint name',
15
15
  summary: 'Test endpoint summary',
16
16
  description: 'Test endpoint description',
@@ -40,7 +40,7 @@ export const manyEndpointsData: EndpointData[] = [
40
40
  requestHeaders: [],
41
41
  rawBody: undefined,
42
42
  objectBody: [],
43
- responses: [{ status: 200, signature: 'void' }],
43
+ responses: [{ status: 200, signature: 'void', contentType: 'application/json' }],
44
44
  name: undefined,
45
45
  summary: undefined,
46
46
  description: undefined,
@@ -70,7 +70,7 @@ export const manyEndpointsData: EndpointData[] = [
70
70
  requestHeaders: [],
71
71
  rawBody: undefined,
72
72
  objectBody: [],
73
- responses: [{ status: 200, signature: 'void' }],
73
+ responses: [{ status: 200, signature: 'void', contentType: 'application/json' }],
74
74
  name: undefined,
75
75
  summary: undefined,
76
76
  description: undefined,
@@ -121,7 +121,7 @@ export const manyEndpointsData: EndpointData[] = [
121
121
  requestHeaders: [],
122
122
  rawBody: undefined,
123
123
  objectBody: [],
124
- responses: [{ status: 200, signature: 'void' }],
124
+ responses: [{ status: 200, signature: 'void', contentType: 'application/json' }],
125
125
  name: undefined,
126
126
  summary: undefined,
127
127
  description: undefined,
@@ -160,7 +160,7 @@ export const manyEndpointsData: EndpointData[] = [
160
160
  requestHeaders: [],
161
161
  rawBody: undefined,
162
162
  objectBody: [],
163
- responses: [{ status: 200, signature: 'void' }],
163
+ responses: [{ status: 200, signature: 'void', contentType: 'application/json' }],
164
164
  name: undefined,
165
165
  summary: undefined,
166
166
  description: undefined,
@@ -233,7 +233,7 @@ export const manyEndpointsData: EndpointData[] = [
233
233
  requestHeaders: [],
234
234
  rawBody: undefined,
235
235
  objectBody: [],
236
- responses: [{ status: 200, signature: 'void' }],
236
+ responses: [{ status: 200, signature: 'void', contentType: 'application/json' }],
237
237
  name: undefined,
238
238
  summary: undefined,
239
239
  description: undefined,
@@ -259,7 +259,7 @@ export const manyEndpointsData: EndpointData[] = [
259
259
  requestHeaders: [],
260
260
  rawBody: undefined,
261
261
  objectBody: [],
262
- responses: [{ status: 200, signature: 'void' }],
262
+ responses: [{ status: 200, signature: 'void', contentType: 'application/json' }],
263
263
  name: undefined,
264
264
  summary: undefined,
265
265
  description: undefined,
@@ -289,7 +289,7 @@ export const manyEndpointsData: EndpointData[] = [
289
289
  optional: false,
290
290
  },
291
291
  objectBody: [],
292
- responses: [{ status: 200, signature: 'void' }],
292
+ responses: [{ status: 200, signature: 'void', contentType: 'application/json' }],
293
293
  name: undefined,
294
294
  summary: undefined,
295
295
  description: undefined,
@@ -319,7 +319,7 @@ export const manyEndpointsData: EndpointData[] = [
319
319
  optional: false,
320
320
  },
321
321
  objectBody: [],
322
- responses: [{ status: 200, signature: 'void' }],
322
+ responses: [{ status: 200, signature: 'void', contentType: 'application/json' }],
323
323
  name: undefined,
324
324
  summary: undefined,
325
325
  description: undefined,
@@ -349,7 +349,7 @@ export const manyEndpointsData: EndpointData[] = [
349
349
  optional: true,
350
350
  },
351
351
  objectBody: [],
352
- responses: [{ status: 200, signature: 'void' }],
352
+ responses: [{ status: 200, signature: 'void', contentType: 'application/json' }],
353
353
  name: undefined,
354
354
  summary: undefined,
355
355
  description: undefined,
@@ -363,7 +363,7 @@ export const manyEndpointsData: EndpointData[] = [
363
363
  requestHeaders: [],
364
364
  rawBody: { signature: 'boolean', optional: true },
365
365
  objectBody: [],
366
- responses: [{ status: 200, signature: 'void' }],
366
+ responses: [{ status: 200, signature: 'void', contentType: 'application/json' }],
367
367
  name: undefined,
368
368
  summary: undefined,
369
369
  description: undefined,
@@ -389,7 +389,7 @@ export const manyEndpointsData: EndpointData[] = [
389
389
  },
390
390
  { identifier: 'thirdParam', signature: 'number', optional: true },
391
391
  ],
392
- responses: [{ status: 200, signature: 'void' }],
392
+ responses: [{ status: 200, signature: 'void', contentType: 'application/json' }],
393
393
  name: undefined,
394
394
  summary: undefined,
395
395
  description: undefined,
@@ -415,7 +415,7 @@ export const manyEndpointsData: EndpointData[] = [
415
415
  },
416
416
  { identifier: 'thirdParam', signature: 'number', optional: true },
417
417
  ],
418
- responses: [{ status: 200, signature: 'void' }],
418
+ responses: [{ status: 200, signature: 'void', contentType: 'application/json' }],
419
419
  name: undefined,
420
420
  summary: undefined,
421
421
  description: undefined,
@@ -441,7 +441,7 @@ export const manyEndpointsData: EndpointData[] = [
441
441
  },
442
442
  { identifier: 'thirdParam', signature: 'number', optional: true },
443
443
  ],
444
- responses: [{ status: 200, signature: 'void' }],
444
+ responses: [{ status: 200, signature: 'void', contentType: 'application/json' }],
445
445
  name: undefined,
446
446
  summary: undefined,
447
447
  description: undefined,
@@ -455,7 +455,7 @@ export const manyEndpointsData: EndpointData[] = [
455
455
  requestHeaders: [],
456
456
  rawBody: undefined,
457
457
  objectBody: [],
458
- responses: [{ status: 200, signature: 'string' }],
458
+ responses: [{ status: 200, signature: 'string', contentType: 'application/json' }],
459
459
  name: undefined,
460
460
  summary: undefined,
461
461
  description: undefined,
@@ -470,9 +470,9 @@ export const manyEndpointsData: EndpointData[] = [
470
470
  rawBody: undefined,
471
471
  objectBody: [],
472
472
  responses: [
473
- { status: 200, signature: 'boolean' },
474
- { status: 200, signature: 'string' },
475
- { status: 200, signature: 'number' },
473
+ { status: 200, signature: 'boolean', contentType: 'application/json' },
474
+ { status: 200, signature: 'string', contentType: 'application/json' },
475
+ { status: 200, signature: 'number', contentType: 'application/json' },
476
476
  ],
477
477
  name: undefined,
478
478
  summary: undefined,
@@ -490,6 +490,7 @@ export const manyEndpointsData: EndpointData[] = [
490
490
  responses: [
491
491
  {
492
492
  status: 200,
493
+ contentType: 'application/json',
493
494
  signature: [
494
495
  {
495
496
  role: 'property',
@@ -522,6 +523,7 @@ export const manyEndpointsData: EndpointData[] = [
522
523
  responses: [
523
524
  {
524
525
  status: 200,
526
+ contentType: 'application/json',
525
527
  signature: [
526
528
  {
527
529
  role: 'property',
@@ -554,6 +556,7 @@ export const manyEndpointsData: EndpointData[] = [
554
556
  responses: [
555
557
  {
556
558
  status: 200,
559
+ contentType: 'application/json',
557
560
  signature: [
558
561
  {
559
562
  role: 'property',
@@ -602,6 +605,7 @@ export const manyEndpointsData: EndpointData[] = [
602
605
  responses: [
603
606
  {
604
607
  status: 200,
608
+ contentType: 'application/json',
605
609
  signature: [
606
610
  {
607
611
  role: 'property',
@@ -640,6 +644,7 @@ export const manyEndpointsData: EndpointData[] = [
640
644
  responses: [
641
645
  {
642
646
  status: 200,
647
+ contentType: 'application/json',
643
648
  signature: [
644
649
  {
645
650
  role: 'property',
@@ -690,6 +695,7 @@ export const manyEndpointsData: EndpointData[] = [
690
695
  responses: [
691
696
  {
692
697
  status: 200,
698
+ contentType: 'application/json',
693
699
  signature: [
694
700
  {
695
701
  role: 'property',
@@ -701,6 +707,7 @@ export const manyEndpointsData: EndpointData[] = [
701
707
  },
702
708
  {
703
709
  status: 200,
710
+ contentType: 'application/json',
704
711
  signature: [
705
712
  {
706
713
  role: 'property',
@@ -736,7 +743,7 @@ export const manyEndpointsData: EndpointData[] = [
736
743
  requestHeaders: [],
737
744
  rawBody: undefined,
738
745
  objectBody: [],
739
- responses: [{ status: 200, signature: 'object' }],
746
+ responses: [{ status: 200, signature: 'object', contentType: 'application/json' }],
740
747
  name: undefined,
741
748
  summary: undefined,
742
749
  description: undefined,
@@ -750,7 +757,7 @@ export const manyEndpointsData: EndpointData[] = [
750
757
  requestHeaders: [],
751
758
  rawBody: undefined,
752
759
  objectBody: [],
753
- responses: [{ status: 204, signature: 'void' }],
760
+ responses: [{ status: 204, signature: 'void', contentType: 'application/json' }],
754
761
  name: undefined,
755
762
  summary: undefined,
756
763
  description: undefined,
@@ -77,6 +77,7 @@ describe('OpenApi Generator', () => {
77
77
  {
78
78
  status: 204,
79
79
  signature: 'void',
80
+ contentType: 'application/json',
80
81
  },
81
82
  ],
82
83
  },
@@ -94,6 +95,7 @@ describe('OpenApi Generator', () => {
94
95
  {
95
96
  status: 200,
96
97
  signature: 'bigint',
98
+ contentType: 'application/json',
97
99
  },
98
100
  ],
99
101
  },
@@ -121,6 +123,7 @@ describe('OpenApi Generator', () => {
121
123
  responses: [
122
124
  {
123
125
  status: 200,
126
+ contentType: 'application/json',
124
127
  signature: [
125
128
  {
126
129
  role: 'record',
@@ -179,6 +182,7 @@ describe('OpenApi Generator', () => {
179
182
  responses: [
180
183
  {
181
184
  status: 200,
185
+ contentType: 'application/json',
182
186
  signature: [
183
187
  {
184
188
  role: 'property',
@@ -225,6 +229,7 @@ describe('OpenApi Generator', () => {
225
229
  responses: [
226
230
  {
227
231
  status: 200,
232
+ contentType: 'application/json',
228
233
  signature: [
229
234
  {
230
235
  role: 'array',
@@ -270,6 +275,7 @@ describe('OpenApi Generator', () => {
270
275
  responses: [
271
276
  {
272
277
  status: 200,
278
+ contentType: 'application/json',
273
279
  signature: [
274
280
  {
275
281
  identifier: 'foo',
@@ -316,6 +322,7 @@ describe('OpenApi Generator', () => {
316
322
  responses: [
317
323
  {
318
324
  status: 200,
325
+ contentType: 'application/json',
319
326
  signature: [
320
327
  {
321
328
  role: 'union',
@@ -369,6 +376,7 @@ describe('OpenApi Generator', () => {
369
376
  responses: [
370
377
  {
371
378
  status: 200,
379
+ contentType: 'application/json',
372
380
  signature: [{ role: 'literal_string', shape: 'hello world', optional: false }],
373
381
  },
374
382
  ],
@@ -496,6 +504,7 @@ describe('OpenApi Generator', () => {
496
504
  responses: [
497
505
  {
498
506
  status: 200,
507
+ contentType: 'application/json',
499
508
  signature: 'string',
500
509
  description: 'Test description',
501
510
  },
@@ -730,6 +739,7 @@ describe('OpenApi Generator', () => {
730
739
  responses: [
731
740
  {
732
741
  status: 200,
742
+ contentType: 'application/json',
733
743
  signature: [
734
744
  {
735
745
  identifier: 'foo',
@@ -780,4 +790,156 @@ describe('OpenApi Generator', () => {
780
790
  responses: {},
781
791
  })
782
792
  })
793
+
794
+ describe('responses with multiple content types', () => {
795
+ it('respects contentType in single response', () => {
796
+ const manager = createManagerWithEndpoints([
797
+ {
798
+ ...minimalEndpointData,
799
+ responses: [
800
+ {
801
+ status: 200,
802
+ contentType: 'text/plain',
803
+ signature: 'string',
804
+ },
805
+ ],
806
+ },
807
+ ])
808
+ const spec = generateOpenApiSpec(manager)
809
+
810
+ expect(spec.paths['/test/path'].get?.responses[200].content).toEqual({
811
+ 'text/plain': {
812
+ schema: {
813
+ oneOf: [{ type: 'string' }],
814
+ },
815
+ },
816
+ })
817
+ })
818
+
819
+ it('includes multiple responses with different content types', () => {
820
+ const manager = createManagerWithEndpoints([
821
+ {
822
+ ...minimalEndpointData,
823
+ responses: [
824
+ {
825
+ status: 200,
826
+ contentType: 'text/plain',
827
+ signature: 'string',
828
+ },
829
+ {
830
+ status: 200,
831
+ contentType: 'application/json',
832
+ signature: 'string',
833
+ },
834
+ {
835
+ status: 200,
836
+ contentType: 'content/custom',
837
+ signature: 'string',
838
+ },
839
+ ],
840
+ },
841
+ ])
842
+ const spec = generateOpenApiSpec(manager)
843
+
844
+ expect(spec.paths['/test/path'].get?.responses[200].content).toEqual({
845
+ 'text/plain': {
846
+ schema: {
847
+ oneOf: [{ type: 'string' }],
848
+ },
849
+ },
850
+ 'application/json': {
851
+ schema: {
852
+ oneOf: [{ type: 'string' }],
853
+ },
854
+ },
855
+ 'content/custom': {
856
+ schema: {
857
+ oneOf: [{ type: 'string' }],
858
+ },
859
+ },
860
+ })
861
+ })
862
+
863
+ it('combines responses with the same content type and status code', () => {
864
+ const manager = createManagerWithEndpoints([
865
+ {
866
+ ...minimalEndpointData,
867
+ responses: [
868
+ {
869
+ status: 200,
870
+ contentType: 'text/plain',
871
+ signature: 'number',
872
+ },
873
+ {
874
+ status: 200,
875
+ contentType: 'text/plain',
876
+ signature: 'string',
877
+ },
878
+ {
879
+ status: 200,
880
+ contentType: 'text/plain',
881
+ signature: 'boolean',
882
+ },
883
+ ],
884
+ },
885
+ ])
886
+ const spec = generateOpenApiSpec(manager)
887
+
888
+ expect(spec.paths['/test/path'].get?.responses[200].content).toEqual({
889
+ 'text/plain': {
890
+ schema: {
891
+ oneOf: [{ type: 'number' }, { type: 'string' }, { type: 'boolean' }],
892
+ },
893
+ },
894
+ })
895
+ })
896
+
897
+ it('keeps content types separate for different status codes', () => {
898
+ const manager = createManagerWithEndpoints([
899
+ {
900
+ ...minimalEndpointData,
901
+ responses: [
902
+ {
903
+ status: 200,
904
+ contentType: 'text/plain',
905
+ signature: 'number',
906
+ },
907
+ {
908
+ status: 204,
909
+ contentType: 'text/plain',
910
+ signature: 'string',
911
+ },
912
+ {
913
+ status: 418,
914
+ contentType: 'text/plain',
915
+ signature: 'boolean',
916
+ },
917
+ ],
918
+ },
919
+ ])
920
+ const spec = generateOpenApiSpec(manager)
921
+
922
+ expect(spec.paths['/test/path'].get?.responses[200].content).toEqual({
923
+ 'text/plain': {
924
+ schema: {
925
+ oneOf: [{ type: 'number' }],
926
+ },
927
+ },
928
+ })
929
+ expect(spec.paths['/test/path'].get?.responses[204].content).toEqual({
930
+ 'text/plain': {
931
+ schema: {
932
+ oneOf: [{ type: 'string' }],
933
+ },
934
+ },
935
+ })
936
+ expect(spec.paths['/test/path'].get?.responses[418].content).toEqual({
937
+ 'text/plain': {
938
+ schema: {
939
+ oneOf: [{ type: 'boolean' }],
940
+ },
941
+ },
942
+ })
943
+ })
944
+ })
783
945
  })
@@ -19,13 +19,14 @@ export type PathDefinition = {
19
19
  string,
20
20
  {
21
21
  description: string
22
- content?: {
23
- 'application/json': {
22
+ content?: Record<
23
+ string,
24
+ {
24
25
  schema: {
25
26
  oneOf: SchemaType[]
26
27
  }
27
28
  }
28
- }
29
+ >
29
30
  }
30
31
  >
31
32
  }
@@ -80,6 +81,7 @@ export type EndpointData = {
80
81
  responses: {
81
82
  status: number
82
83
  signature: string | ShapeOfType[]
84
+ contentType: string
83
85
  description?: string
84
86
  errorMessage?: string
85
87
  }[]
@@ -4,7 +4,7 @@ import Koa from 'koa'
4
4
 
5
5
  import { OpenApiManager } from '../openapi/manager/OpenApiManager'
6
6
  import { ExtractedRequestParams } from '../utils/TypeUtils'
7
- import { responseValueToJson } from './responseValueToJson'
7
+ import { parseEndpointReturnValue } from './parseEndpointReturnValue'
8
8
 
9
9
  type Props = {
10
10
  skipOpenApiAnalysis: boolean
@@ -46,10 +46,11 @@ export class Router<StateT = Koa.DefaultState, ContextT = Koa.DefaultContext> {
46
46
  callback: KoaRouter.Middleware<StateT, ContextT & ExtractedRequestParams<P>>
47
47
  ) {
48
48
  this.koaRouter.get(path, async (ctx) => {
49
- ctx.set('Content-Type', 'application/json; charset=utf-8')
50
49
  // @ts-ignore
51
50
  const responseValue = await callback(ctx, undefined)
52
- ctx.body = responseValueToJson(responseValue)
51
+ const { value, contentType } = parseEndpointReturnValue(responseValue)
52
+ ctx.body = value
53
+ ctx.set('Content-Type', contentType)
53
54
  })
54
55
  return this
55
56
  }
@@ -59,10 +60,11 @@ export class Router<StateT = Koa.DefaultState, ContextT = Koa.DefaultContext> {
59
60
  callback: KoaRouter.Middleware<StateT, ContextT & ExtractedRequestParams<P>>
60
61
  ) {
61
62
  this.koaRouter.post(path, async (ctx) => {
62
- ctx.set('Content-Type', 'application/json; charset=utf-8')
63
63
  // @ts-ignore
64
64
  const responseValue = await callback(ctx, undefined)
65
- ctx.body = responseValueToJson(responseValue)
65
+ const { value, contentType } = parseEndpointReturnValue(responseValue)
66
+ ctx.body = value
67
+ ctx.set('Content-Type', contentType)
66
68
  })
67
69
  return this
68
70
  }
@@ -72,10 +74,11 @@ export class Router<StateT = Koa.DefaultState, ContextT = Koa.DefaultContext> {
72
74
  callback: KoaRouter.Middleware<StateT, ContextT & ExtractedRequestParams<P>>
73
75
  ) {
74
76
  this.koaRouter.put(path, async (ctx) => {
75
- ctx.set('Content-Type', 'application/json; charset=utf-8')
76
77
  // @ts-ignore
77
78
  const responseValue = await callback(ctx, undefined)
78
- ctx.body = responseValueToJson(responseValue)
79
+ const { value, contentType } = parseEndpointReturnValue(responseValue)
80
+ ctx.body = value
81
+ ctx.set('Content-Type', contentType)
79
82
  })
80
83
  return this
81
84
  }
@@ -85,10 +88,11 @@ export class Router<StateT = Koa.DefaultState, ContextT = Koa.DefaultContext> {
85
88
  callback: KoaRouter.Middleware<StateT, ContextT & ExtractedRequestParams<P>>
86
89
  ) {
87
90
  this.koaRouter.delete(path, async (ctx) => {
88
- ctx.set('Content-Type', 'application/json; charset=utf-8')
89
91
  // @ts-ignore
90
92
  const responseValue = await callback(ctx, undefined)
91
- ctx.body = responseValueToJson(responseValue)
93
+ const { value, contentType } = parseEndpointReturnValue(responseValue)
94
+ ctx.body = value
95
+ ctx.set('Content-Type', contentType)
92
96
  })
93
97
  return this
94
98
  }
@@ -98,10 +102,11 @@ export class Router<StateT = Koa.DefaultState, ContextT = Koa.DefaultContext> {
98
102
  callback: KoaRouter.Middleware<StateT, ContextT & ExtractedRequestParams<P>>
99
103
  ) {
100
104
  this.koaRouter.del(path, async (ctx) => {
101
- ctx.set('Content-Type', 'application/json; charset=utf-8')
102
105
  // @ts-ignore
103
106
  const responseValue = await callback(ctx, undefined)
104
- ctx.body = responseValueToJson(responseValue)
107
+ const { value, contentType } = parseEndpointReturnValue(responseValue)
108
+ ctx.body = value
109
+ ctx.set('Content-Type', contentType)
105
110
  })
106
111
  return this
107
112
  }
@@ -111,10 +116,11 @@ export class Router<StateT = Koa.DefaultState, ContextT = Koa.DefaultContext> {
111
116
  callback: KoaRouter.Middleware<StateT, ContextT & ExtractedRequestParams<P>>
112
117
  ) {
113
118
  this.koaRouter.patch(path, async (ctx) => {
114
- ctx.set('Content-Type', 'application/json; charset=utf-8')
115
119
  // @ts-ignore
116
120
  const responseValue = await callback(ctx, undefined)
117
- ctx.body = responseValueToJson(responseValue)
121
+ const { value, contentType } = parseEndpointReturnValue(responseValue)
122
+ ctx.body = value
123
+ ctx.set('Content-Type', contentType)
118
124
  })
119
125
  return this
120
126
  }
@@ -0,0 +1,35 @@
1
+ import { parseEndpointReturnValue } from './parseEndpointReturnValue'
2
+
3
+ describe('parseEndpointReturnValue', () => {
4
+ it('returns string as plain text data', () => {
5
+ const value = 'foo'
6
+ expect(parseEndpointReturnValue(value)).toEqual({
7
+ value,
8
+ contentType: 'text/plain',
9
+ })
10
+ })
11
+
12
+ it('returns Buffer as raw data', () => {
13
+ const value = Buffer.from('foo')
14
+ expect(parseEndpointReturnValue(value)).toEqual({
15
+ value,
16
+ contentType: 'application/octet-stream',
17
+ })
18
+ })
19
+
20
+ it('transforms object into JSON', () => {
21
+ const value = { foo: 'bar' }
22
+ expect(parseEndpointReturnValue(value)).toEqual({
23
+ value: JSON.stringify(value),
24
+ contentType: 'application/json; charset=utf-8',
25
+ })
26
+ })
27
+
28
+ it('keeps custom content type', () => {
29
+ const value = {
30
+ value: 'foo',
31
+ contentType: 'application/custom',
32
+ }
33
+ expect(parseEndpointReturnValue(value)).toEqual(value)
34
+ })
35
+ })
@@ -0,0 +1,31 @@
1
+ export const parseEndpointReturnValue = (response: unknown) => {
2
+ if (
3
+ typeof response === 'object' &&
4
+ response &&
5
+ 'value' in response &&
6
+ typeof response['value'] === 'string' &&
7
+ 'contentType' in response &&
8
+ typeof response['contentType'] === 'string'
9
+ ) {
10
+ return {
11
+ value: response.value,
12
+ contentType: response.contentType,
13
+ }
14
+ }
15
+ if (typeof response === 'string') {
16
+ return {
17
+ value: response,
18
+ contentType: 'text/plain',
19
+ }
20
+ }
21
+ if (Buffer.isBuffer(response)) {
22
+ return {
23
+ value: response,
24
+ contentType: 'application/octet-stream',
25
+ }
26
+ }
27
+ return {
28
+ value: JSON.stringify(response, (_, value) => (typeof value === 'bigint' ? value.toString() : value)),
29
+ contentType: 'application/json; charset=utf-8',
30
+ }
31
+ }
@@ -1,6 +0,0 @@
1
- export const responseValueToJson = (value: any) => {
2
- if (typeof value === 'string') {
3
- return value
4
- }
5
- return JSON.stringify(value, (_, value) => (typeof value === 'bigint' ? value.toString() : value))
6
- }