sharetribe-flex-sdk 1.14.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 (87) hide show
  1. package/.circleci/config.yml +22 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc.js +19 -0
  4. package/CHANGELOG.md +231 -0
  5. package/LICENSE +201 -0
  6. package/README.md +76 -0
  7. package/build/sharetribe-flex-sdk-node.js +13592 -0
  8. package/build/sharetribe-flex-sdk-web.js +1 -0
  9. package/docs/README.md +20 -0
  10. package/docs/authentication.md +179 -0
  11. package/docs/calling-the-api.md +217 -0
  12. package/docs/configurations.md +95 -0
  13. package/docs/developing-sdk.md +131 -0
  14. package/docs/docpress.json +14 -0
  15. package/docs/features.md +14 -0
  16. package/docs/keep-alive.md +48 -0
  17. package/docs/object-query-parameters.md +36 -0
  18. package/docs/scripts.js +41 -0
  19. package/docs/serializing-types-to-json.md +40 -0
  20. package/docs/sharing-session-between-client-and-server.md +19 -0
  21. package/docs/styles.css +95 -0
  22. package/docs/token-store.md +114 -0
  23. package/docs/try-it-in-browser.md +32 -0
  24. package/docs/try-it-in-the-playground.md +153 -0
  25. package/docs/types.md +27 -0
  26. package/docs/writing-your-own-token-store.md +29 -0
  27. package/docs/your-own-types.md +61 -0
  28. package/examples/README.md +5 -0
  29. package/examples/getting-started-browser/README.md +22 -0
  30. package/examples/getting-started-browser/index.html +89 -0
  31. package/examples/getting-started-browser/index.js +156 -0
  32. package/examples/getting-started-browser/screenshots/screenshot1.png +0 -0
  33. package/examples/getting-started-browser/screenshots/screenshot2.png +0 -0
  34. package/examples/getting-started-node/README.md +23 -0
  35. package/examples/getting-started-node/index.js +139 -0
  36. package/examples/getting-started-node/screenshots/screenshot.png +0 -0
  37. package/package.json +83 -0
  38. package/playground.js +295 -0
  39. package/src/browser_cookie_store.js +26 -0
  40. package/src/context_runner.js +151 -0
  41. package/src/context_runner.test.js +185 -0
  42. package/src/detect.js +11 -0
  43. package/src/express_cookie_store.js +57 -0
  44. package/src/fake/adapter.js +130 -0
  45. package/src/fake/api.js +137 -0
  46. package/src/fake/auth.js +84 -0
  47. package/src/fake/token_store.js +231 -0
  48. package/src/index.js +25 -0
  49. package/src/interceptors/.eslintrc.js +5 -0
  50. package/src/interceptors/add_auth_header.js +32 -0
  51. package/src/interceptors/add_auth_header.test.js +50 -0
  52. package/src/interceptors/add_auth_token_response.js +16 -0
  53. package/src/interceptors/add_client_id_to_params.js +12 -0
  54. package/src/interceptors/add_client_secret_to_params.js +15 -0
  55. package/src/interceptors/add_grant_type_to_params.js +23 -0
  56. package/src/interceptors/add_idp_client_id_to_params.js +17 -0
  57. package/src/interceptors/add_idp_id_to_params.js +17 -0
  58. package/src/interceptors/add_idp_token_to_params.js +17 -0
  59. package/src/interceptors/add_scope_to_params.js +18 -0
  60. package/src/interceptors/add_subject_token_to_params.js +18 -0
  61. package/src/interceptors/add_token_exchange_grant_type_to_params.js +12 -0
  62. package/src/interceptors/auth_info.js +50 -0
  63. package/src/interceptors/clear_token_after_revoke.js +45 -0
  64. package/src/interceptors/default_params.js +12 -0
  65. package/src/interceptors/fetch_auth_token_from_api.js +33 -0
  66. package/src/interceptors/fetch_auth_token_from_store.js +27 -0
  67. package/src/interceptors/fetch_refresh_token_for_revoke.js +24 -0
  68. package/src/interceptors/multipart_request.js +35 -0
  69. package/src/interceptors/retry_with_anon_token.js +58 -0
  70. package/src/interceptors/retry_with_refresh_token.js +70 -0
  71. package/src/interceptors/save_token.js +20 -0
  72. package/src/interceptors/transit_request.js +27 -0
  73. package/src/interceptors/transit_request.test.js +58 -0
  74. package/src/interceptors/transit_response.js +27 -0
  75. package/src/memory_store.js +19 -0
  76. package/src/params_serializer.js +65 -0
  77. package/src/params_serializer.test.js +58 -0
  78. package/src/sdk.js +894 -0
  79. package/src/sdk.test.js +908 -0
  80. package/src/serializer.js +279 -0
  81. package/src/serializer.test.js +229 -0
  82. package/src/token_store.js +15 -0
  83. package/src/types.js +108 -0
  84. package/src/types.test.js +75 -0
  85. package/src/utils.js +68 -0
  86. package/src/utils.test.js +85 -0
  87. package/webpack.config.babel.js +47 -0
@@ -0,0 +1,50 @@
1
+ import AddAuthHeader from './add_auth_header';
2
+
3
+ describe('AddAuthHeader', () => {
4
+ it('adds the auth token to the header', () => {
5
+ const ctx = {
6
+ authToken: {
7
+ token_type: 'Bearer',
8
+ access_token: 'secret-123',
9
+ },
10
+ };
11
+
12
+ const addAuthHeader = new AddAuthHeader();
13
+
14
+ const newCtx = addAuthHeader.enter(ctx);
15
+
16
+ expect(newCtx).toEqual(
17
+ expect.objectContaining({
18
+ headers: {
19
+ Authorization: 'Bearer secret-123',
20
+ },
21
+ })
22
+ );
23
+ });
24
+
25
+ it('merges the new header, and does not override existing headers, except the auth header', () => {
26
+ const ctx = {
27
+ authToken: {
28
+ token_type: 'Bearer',
29
+ access_token: 'secret-123',
30
+ },
31
+ headers: {
32
+ Authorization: 'Bearer old-invalid-token',
33
+ 'Content-Type': 'application/transit+json',
34
+ },
35
+ };
36
+
37
+ const addAuthHeader = new AddAuthHeader();
38
+
39
+ const newCtx = addAuthHeader.enter(ctx);
40
+
41
+ expect(newCtx).toEqual(
42
+ expect.objectContaining({
43
+ headers: {
44
+ Authorization: 'Bearer secret-123',
45
+ 'Content-Type': 'application/transit+json',
46
+ },
47
+ })
48
+ );
49
+ });
50
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ Take `authToken` from `res` and add it to `ctx` top-level.
3
+
4
+ Changes to `ctx`:
5
+
6
+ - add `authToken` (from res)
7
+ */
8
+ export default class AddAuthTokenResponse {
9
+ leave(ctx) {
10
+ const {
11
+ res: { data: authToken },
12
+ } = ctx;
13
+
14
+ return { ...ctx, authToken };
15
+ }
16
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ Read `clientId` from `ctx` and add it to `params`
3
+
4
+ Changes to `ctx`:
5
+
6
+ - add `params.clientId`
7
+ */
8
+ export default class AddClientIdToParams {
9
+ enter({ clientId, params, ...ctx }) {
10
+ return { ...ctx, clientId, params: { ...params, client_id: clientId } };
11
+ }
12
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ Read `clientSecret` from `ctx` and add it to `params`
3
+
4
+ Changes to `ctx`:
5
+
6
+ - add `params.client_secret`
7
+ */
8
+ export default class AddClientSecretToParams {
9
+ enter({ clientSecret, params, ...ctx }) {
10
+ if (!clientSecret) {
11
+ throw new Error('SDK instance is missing the clientSecret config.');
12
+ }
13
+ return { ...ctx, clientSecret, params: { ...params, client_secret: clientSecret } };
14
+ }
15
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ See what credentials (`username`, `password`, and `authorizationCode`) are
3
+ passed in params and set the grant_type based on those.
4
+
5
+ Changes to `ctx`:
6
+
7
+ - add `params.grant_type`
8
+ */
9
+ export default class AddGrantTypeToParams {
10
+ enter({ params, ...ctx }) {
11
+ const { username, password, code } = params;
12
+
13
+ if (username && password) {
14
+ return { ...ctx, params: { grant_type: 'password', ...params } };
15
+ }
16
+
17
+ if (code) {
18
+ return { ...ctx, params: { grant_type: 'authorization_code', ...params } };
19
+ }
20
+
21
+ return { ...ctx, params: { ...params } };
22
+ }
23
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ Read `idpClientId` from `ctx` and add it to `params`
3
+
4
+ Changes to `ctx`:
5
+
6
+ - add `params.idpClientId`
7
+ */
8
+ export default class AddIdpClientIdToParams {
9
+ enter({ params, ...ctx }) {
10
+ const { idpClientId } = params;
11
+ return {
12
+ ...ctx,
13
+ idpClientId,
14
+ params: { ...params, idp_client_id: idpClientId },
15
+ };
16
+ }
17
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ Read `idpId` from `ctx` and add it to `params`
3
+
4
+ Changes to `ctx`:
5
+
6
+ - add `params.idpId`
7
+ */
8
+ export default class AddIdpIdToParams {
9
+ enter({ params, ...ctx }) {
10
+ const { idpId } = params;
11
+ return {
12
+ ...ctx,
13
+ idpId,
14
+ params: { ...params, idp_id: idpId },
15
+ };
16
+ }
17
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ Read `idpToken` from `ctx` and add it to `params`
3
+
4
+ Changes to `ctx`:
5
+
6
+ - add `params.idpToken`
7
+ */
8
+ export default class AddIdpTokenToParams {
9
+ enter({ params, ...ctx }) {
10
+ const { idpToken } = params;
11
+ return {
12
+ ...ctx,
13
+ idpToken,
14
+ params: { ...params, idp_token: idpToken },
15
+ };
16
+ }
17
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ Set the scope for a token request based on the params.
3
+
4
+ Changes to `ctx`:
5
+
6
+ - add `params.scope`
7
+ */
8
+ export default class AddScopeToParams {
9
+ enter({ params, ...ctx }) {
10
+ const { username, password } = params;
11
+
12
+ if (username && password) {
13
+ return { ...ctx, params: { scope: 'user', ...params } };
14
+ }
15
+
16
+ return { ...ctx, params: { ...params } };
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ Read `authToken.access_token` from `ctx` and adds it as
3
+ `subject_token` in params
4
+
5
+ Changes to `ctx`:
6
+
7
+ - add `params.subject_token`
8
+
9
+ */
10
+ export default class AddSubjectTokenToParams {
11
+ enter(ctx) {
12
+ const { authToken, params } = ctx;
13
+ if (authToken && authToken.access_token) {
14
+ return { ...ctx, params: { ...params, subject_token: authToken.access_token } };
15
+ }
16
+ return ctx;
17
+ }
18
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ Add "token_exchange" as the `grant_type` in `params`
3
+
4
+ Changes to `ctx`:
5
+
6
+ - add `params.grant_type`
7
+ */
8
+ export default class AddTokenExchangeGrantTypeToParams {
9
+ enter({ params, ...ctx }) {
10
+ return { ...ctx, params: { ...params, grant_type: 'token_exchange' } };
11
+ }
12
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ Reads current authentication token from the tokenStore and returns
3
+ the following information:
4
+
5
+ - scopes: list of scopes associated with the access token in store
6
+ - isAnonymous: boolean value indicating if the access token only grants
7
+ access to publicly read data from API
8
+
9
+
10
+ Changes to `ctx`:
11
+
12
+ - add `res`
13
+
14
+ */
15
+ export default class AuthInfo {
16
+ enter(ctx) {
17
+ const { tokenStore } = ctx;
18
+
19
+ if (tokenStore) {
20
+ return Promise.resolve()
21
+ .then(tokenStore.getToken)
22
+ .then(storedToken => {
23
+ if (storedToken) {
24
+ const tokenScope = storedToken.scope;
25
+
26
+ if (tokenScope) {
27
+ const scopes = tokenScope.split(' ');
28
+ const isAnonymous = tokenScope === 'public-read';
29
+
30
+ // Deprecated attribute, maintained here for client implementations
31
+ // that rely on this attribute
32
+ const grantType = isAnonymous ? 'client_credentials' : 'refresh_token';
33
+
34
+ return { ...ctx, res: { scopes, isAnonymous, grantType } };
35
+ }
36
+
37
+ // Support old tokens that are stored in the client's token store
38
+ // and possibly do not have the scope attribute
39
+ const isAnonymous = !storedToken.refresh_token;
40
+ const grantType = isAnonymous ? 'client_credentials' : 'refresh_token';
41
+ return { ...ctx, res: { isAnonymous, grantType } };
42
+ }
43
+
44
+ return { ...ctx, res: {} };
45
+ });
46
+ }
47
+
48
+ return { ...ctx, res: {} };
49
+ }
50
+ }
@@ -0,0 +1,45 @@
1
+ import _ from 'lodash';
2
+
3
+ /**
4
+ Clears token after revoke.
5
+
6
+ If the `revoke` call was successful, clear token.
7
+
8
+ If the `revoke` call was unsuccessful, and the reason
9
+ was that we we're not authorized (401), rescue the chain
10
+ and clear token
11
+
12
+ Otherwise, do nothing.
13
+
14
+ Changes to `ctx`:
15
+
16
+ - Remove `error`, if 401
17
+ */
18
+ export default class ClearTokenAfterRevoke {
19
+ static clearTokenAndResque(ctx) {
20
+ const { tokenStore } = ctx;
21
+
22
+ if (tokenStore) {
23
+ return Promise.resolve()
24
+ .then(tokenStore.removeToken)
25
+ .then(() => ({ ...ctx, error: null }));
26
+ }
27
+
28
+ return { ...ctx, error: null };
29
+ }
30
+
31
+ leave(ctx) {
32
+ return ClearTokenAfterRevoke.clearTokenAndResque(ctx);
33
+ }
34
+
35
+ error(ctx) {
36
+ const { status } = ctx.res || {};
37
+ const retryStatus = _.get(ctx, ['refreshTokenRetry', 'res', 'status']);
38
+
39
+ if (status === 401 && retryStatus === 401) {
40
+ return ClearTokenAfterRevoke.clearTokenAndResque(ctx);
41
+ }
42
+
43
+ return ctx;
44
+ }
45
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ Add given params to the `ctx.params`
3
+
4
+ Changes to `ctx`:
5
+
6
+ - Modify `ctx.params`
7
+ */
8
+ const defaultParams = (params = {}) => ({
9
+ enter: ({ params: ctxParams, ...ctx }) => ({ ...ctx, params: { ...params, ...ctxParams } }),
10
+ });
11
+
12
+ export default defaultParams;
@@ -0,0 +1,33 @@
1
+ import contextRunner from '../context_runner';
2
+ import SaveToken from './save_token';
3
+ import AddAuthTokenResponse from './add_auth_token_response';
4
+
5
+ /**
6
+ If there's no `authToken` stored to the `ctx`, try to fetch new auth token from the API.
7
+
8
+ Changes to the `ctx`:
9
+
10
+ - add `authToken`
11
+ */
12
+ export default class FetchAuthTokenFromApi {
13
+ enter(ctx) {
14
+ const { tokenStore, authToken, endpointInterceptors, clientId } = ctx;
15
+
16
+ if (authToken) {
17
+ return ctx;
18
+ }
19
+
20
+ return contextRunner([
21
+ new SaveToken(),
22
+ new AddAuthTokenResponse(),
23
+ ...endpointInterceptors.auth.token,
24
+ ])({
25
+ params: {
26
+ client_id: clientId,
27
+ grant_type: 'client_credentials',
28
+ scope: 'public-read',
29
+ },
30
+ tokenStore,
31
+ }).then(({ authToken: newAuthToken }) => ({ ...ctx, authToken: newAuthToken }));
32
+ }
33
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ Fetches the auth token from tokenStore and adds it to the context.
3
+
4
+ Changes to `ctx`:
5
+
6
+ - add `authToken`
7
+
8
+ */
9
+ export default class FetchAuthTokenFromStore {
10
+ enter(enterCtx) {
11
+ const { tokenStore } = enterCtx;
12
+
13
+ if (!tokenStore) {
14
+ return enterCtx;
15
+ }
16
+
17
+ return Promise.resolve()
18
+ .then(tokenStore.getToken)
19
+ .then(storedToken => {
20
+ if (storedToken) {
21
+ return { ...enterCtx, authToken: storedToken };
22
+ }
23
+
24
+ return enterCtx;
25
+ });
26
+ }
27
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ Fetch `refresh_token` from `authToken` in `ctx`. If
3
+ `refresh_token` doesn't exist, clear the `enterQueue`, because we
4
+ don't need to revoke the refresh token we don't have.
5
+
6
+ Changes to `ctx`:
7
+
8
+ - Add `params.token`
9
+ - Clear `enterQueue` (if no need to revoke)
10
+ */
11
+ export default class FetchRefreshTokenForRevoke {
12
+ enter(ctx) {
13
+ const { authToken: { refresh_token: token } = {} } = ctx;
14
+
15
+ if (token) {
16
+ return { ...ctx, params: { token } };
17
+ }
18
+
19
+ // No need to call `revoke` endpoint, because we don't have
20
+ // refresh_token.
21
+ // Clear the enterQueue
22
+ return { ...ctx, enterQueue: [] };
23
+ }
24
+ }
@@ -0,0 +1,35 @@
1
+ import _ from 'lodash';
2
+
3
+ /**
4
+ Takes `params` from `ctx` and converts to `FormData`
5
+
6
+ Changes to `ctx`:
7
+
8
+ - Modify `ctx.params`
9
+ */
10
+ export default class MultipartRequest {
11
+ enter({ params, ...ctx }) {
12
+ if (_.isPlainObject(params)) {
13
+ /* eslint-disable no-undef */
14
+ if (typeof FormData === 'undefined') {
15
+ throw new Error(
16
+ "Don't know how to create multipart request from Object, when the FormData is undefined"
17
+ );
18
+ }
19
+
20
+ const formDataObj = _.reduce(
21
+ params,
22
+ (fd, val, key) => {
23
+ fd.append(key, val);
24
+ return fd;
25
+ },
26
+ new FormData()
27
+ );
28
+ /* eslint-enable no-undef */
29
+
30
+ return { params: formDataObj, ...ctx };
31
+ }
32
+
33
+ return { params, ...ctx };
34
+ }
35
+ }
@@ -0,0 +1,58 @@
1
+ import contextRunner from '../context_runner';
2
+ import SaveToken from './save_token';
3
+ import AddAuthTokenResponse from './add_auth_token_response';
4
+
5
+ /**
6
+ Retries with a fresh anon token.
7
+
8
+ `enter`: Save current `enterQueue` to `retryQueue` and save current `attempts` count
9
+
10
+ `error`: Try to fetch new anon token. If successful, save it to `ctx`
11
+
12
+ Changes to `ctx`:
13
+
14
+ - add `anonTokenRetry`
15
+ - add `authToken`
16
+ */
17
+ export default class RetryWithAnonToken {
18
+ enter(enterCtx) {
19
+ const { enterQueue, anonTokenRetry: { attempts = 0 } = {} } = enterCtx;
20
+ return {
21
+ ...enterCtx,
22
+ anonTokenRetry: {
23
+ retryQueue: [...enterQueue, new RetryWithAnonToken()],
24
+ attempts: attempts + 1,
25
+ },
26
+ };
27
+ }
28
+
29
+ error(errorCtx) {
30
+ const {
31
+ clientId,
32
+ tokenStore,
33
+ endpointInterceptors,
34
+ anonTokenRetry: { retryQueue, attempts },
35
+ } = errorCtx;
36
+
37
+ if (attempts > 1) {
38
+ return errorCtx;
39
+ }
40
+
41
+ if (errorCtx.res && errorCtx.res.status === 401) {
42
+ return contextRunner([
43
+ new SaveToken(),
44
+ new AddAuthTokenResponse(),
45
+ ...endpointInterceptors.auth.token,
46
+ ])({
47
+ params: {
48
+ client_id: clientId,
49
+ grant_type: 'client_credentials',
50
+ scope: 'public-read',
51
+ },
52
+ tokenStore,
53
+ }).then(({ authToken }) => ({ ...errorCtx, authToken, enterQueue: retryQueue, error: null }));
54
+ }
55
+
56
+ return errorCtx;
57
+ }
58
+ }
@@ -0,0 +1,70 @@
1
+ import contextRunner from '../context_runner';
2
+ import SaveToken from './save_token';
3
+ import AddAuthTokenResponse from './add_auth_token_response';
4
+
5
+ /**
6
+ Retries with a fresh password token.
7
+
8
+
9
+ `enter`: Save current `enterQueue` to `retryQueue` and save current `attempts` count
10
+
11
+ `error`: Try to fetch new password token. If successful, save it to `ctx`
12
+
13
+ Changes to `ctx`:
14
+
15
+ - add `anonTokenRetry`
16
+ - add `authToken`
17
+ */
18
+ export default class RetryWithRefreshToken {
19
+ enter(enterCtx) {
20
+ const { enterQueue, refreshTokenRetry: { attempts = 0 } = {} } = enterCtx;
21
+ return {
22
+ ...enterCtx,
23
+ refreshTokenRetry: {
24
+ retryQueue: [...enterQueue, new RetryWithRefreshToken()],
25
+ attempts: attempts + 1,
26
+ },
27
+ };
28
+ }
29
+
30
+ error(errorCtx) {
31
+ const {
32
+ authToken,
33
+ clientId,
34
+ tokenStore,
35
+ endpointInterceptors,
36
+ refreshTokenRetry: { retryQueue, attempts },
37
+ } = errorCtx;
38
+
39
+ if (attempts > 1) {
40
+ return errorCtx;
41
+ }
42
+
43
+ if (errorCtx.res && errorCtx.res.status === 401 && authToken.refresh_token) {
44
+ return contextRunner([
45
+ new SaveToken(),
46
+ new AddAuthTokenResponse(),
47
+ ...endpointInterceptors.auth.token,
48
+ ])({
49
+ params: {
50
+ client_id: clientId,
51
+ grant_type: 'refresh_token',
52
+ refresh_token: authToken.refresh_token,
53
+ },
54
+ tokenStore,
55
+ })
56
+ .then(({ authToken: newAuthToken }) => ({
57
+ ...errorCtx,
58
+ authToken: newAuthToken,
59
+ enterQueue: retryQueue,
60
+ error: null,
61
+ }))
62
+ .catch(e => ({
63
+ ...errorCtx,
64
+ refreshTokenRetry: { retryQueue, attempts, res: e.response },
65
+ }));
66
+ }
67
+
68
+ return errorCtx;
69
+ }
70
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ On `leave` phase, take `authToken` from `ctx` and save it to tokenStore
3
+
4
+ Changes to `ctx`:
5
+
6
+ - None
7
+ */
8
+ export default class SaveToken {
9
+ leave(ctx) {
10
+ const { authToken, tokenStore } = ctx;
11
+
12
+ if (tokenStore) {
13
+ return Promise.resolve()
14
+ .then(() => tokenStore.setToken(authToken))
15
+ .then(() => ctx);
16
+ }
17
+
18
+ return ctx;
19
+ }
20
+ }
@@ -0,0 +1,27 @@
1
+ import { createTransitConverters } from '../serializer';
2
+
3
+ /**
4
+ Transit encode the request
5
+ */
6
+ export default class TransitRequest {
7
+ enter(ctx) {
8
+ const { params, headers = {}, typeHandlers, transitVerbose, ...restCtx } = ctx;
9
+
10
+ if (headers['Content-Type'] === 'application/transit+json') {
11
+ return ctx;
12
+ }
13
+
14
+ const { writer } = createTransitConverters(typeHandlers, { verbose: transitVerbose });
15
+
16
+ return {
17
+ params: writer.write(params),
18
+ headers: {
19
+ ...headers,
20
+ 'Content-Type': 'application/transit+json',
21
+ },
22
+ typeHandlers,
23
+ transitVerbose,
24
+ ...restCtx,
25
+ };
26
+ }
27
+ }