@knpkv/jira-api-client 1.0.1 → 2.0.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.
- package/CHANGELOG.md +44 -0
- package/dist/JiraApiClient.d.ts +6 -6
- package/dist/JiraApiClient.d.ts.map +1 -1
- package/dist/JiraApiClient.js +24 -11
- package/dist/JiraApiClient.js.map +1 -1
- package/dist/JiraApiConfig.d.ts +30 -11
- package/dist/JiraApiConfig.d.ts.map +1 -1
- package/dist/JiraApiConfig.js +2 -0
- package/dist/JiraApiConfig.js.map +1 -1
- package/dist/OwnOptionalKey.d.ts +13 -0
- package/dist/OwnOptionalKey.d.ts.map +1 -0
- package/dist/OwnOptionalKey.js +28 -0
- package/dist/OwnOptionalKey.js.map +1 -0
- package/dist/generated/JiraApi.d.ts +21240 -77776
- package/dist/generated/JiraApi.d.ts.map +1 -1
- package/dist/generated/JiraApi.js +2403 -2234
- package/dist/generated/JiraApi.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/scripts/regenerate.ts +22 -9
- package/src/JiraApiClient.ts +45 -23
- package/src/JiraApiConfig.ts +25 -5
- package/src/OwnOptionalKey.ts +36 -0
- package/src/generated/JiraApi.ts +3277 -2894
- package/src/index.ts +4 -2
- package/test/JiraApiClient.test.ts +6 -6
- package/test/JiraApiCredential.test.ts +103 -0
- package/test/OwnOptionalKey.test.ts +24 -0
- package/test/RecursiveSchemas.test.ts +26 -0
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
|
-
export { JiraApiClient, type
|
|
7
|
-
export { JiraApiConfig, type
|
|
6
|
+
export { JiraApiClient, type JiraApiClientContract, layer, make, type UploadAttachmentInput } from "./JiraApiClient.js";
|
|
7
|
+
export { JiraApiConfig, type JiraApiConfigContract, type JiraApiCredential } from "./JiraApiConfig.js";
|
|
8
8
|
export * as JiraApi from "./generated/JiraApi.js";
|
|
9
|
+
export { ownOptionalKey, type PrototypeKey } from "./OwnOptionalKey.js";
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,KAAK,qBAAqB,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAEvH,OAAO,EAAE,aAAa,EAAE,KAAK,qBAAqB,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAEtG,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAA;AAEjD,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAA"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAA8B,KAAK,EAAE,IAAI,EAA8B,MAAM,oBAAoB,CAAA;AAEvH,OAAO,EAAE,aAAa,EAAsD,MAAM,oBAAoB,CAAA;AAEtG,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAA;AAEjD,OAAO,EAAE,cAAc,EAAqB,MAAM,qBAAqB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knpkv/jira-api-client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Effect-based Jira Cloud REST API v3 client",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "knpkv",
|
|
@@ -39,18 +39,18 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"effect": "4.0.0
|
|
42
|
+
"effect": "4.0.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@effect/openapi-generator": "4.0.0
|
|
46
|
-
"@effect/platform-node": "4.0.0
|
|
47
|
-
"@effect/vitest": "4.0.0
|
|
45
|
+
"@effect/openapi-generator": "4.0.0",
|
|
46
|
+
"@effect/platform-node": "4.0.0",
|
|
47
|
+
"@effect/vitest": "4.0.0",
|
|
48
48
|
"@types/node": "latest",
|
|
49
|
-
"effect": "4.0.0
|
|
50
|
-
"prettier": "^3.9.
|
|
51
|
-
"tsx": "^4.23.
|
|
52
|
-
"typescript": "
|
|
53
|
-
"vitest": "^
|
|
49
|
+
"effect": "4.0.0",
|
|
50
|
+
"prettier": "^3.9.9",
|
|
51
|
+
"tsx": "^4.23.15",
|
|
52
|
+
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
53
|
+
"vitest": "^5.0.3"
|
|
54
54
|
},
|
|
55
55
|
"keywords": [
|
|
56
56
|
"effect",
|
package/scripts/regenerate.ts
CHANGED
|
@@ -8,19 +8,19 @@
|
|
|
8
8
|
import * as OpenApiGenerator from "@effect/openapi-generator/OpenApiGenerator"
|
|
9
9
|
import * as OpenApiPatch from "@effect/openapi-generator/OpenApiPatch"
|
|
10
10
|
import { NodeHttpClient, NodeRuntime, NodeServices } from "@effect/platform-node"
|
|
11
|
+
import * as Command from "effect/cli/Command"
|
|
12
|
+
import * as Flag from "effect/cli/Flag"
|
|
11
13
|
import * as Console from "effect/Console"
|
|
12
14
|
import * as Data from "effect/Data"
|
|
13
15
|
import * as Effect from "effect/Effect"
|
|
14
16
|
import * as FileSystem from "effect/FileSystem"
|
|
17
|
+
import type { OpenAPISpec } from "effect/http-api/OpenApi"
|
|
18
|
+
import * as HttpClient from "effect/http/HttpClient"
|
|
15
19
|
import type * as JsonSchema from "effect/JsonSchema"
|
|
16
20
|
import * as Layer from "effect/Layer"
|
|
17
21
|
import * as Path from "effect/Path"
|
|
18
22
|
import * as Predicate from "effect/Predicate"
|
|
19
23
|
import * as Schema from "effect/Schema"
|
|
20
|
-
import * as Command from "effect/unstable/cli/Command"
|
|
21
|
-
import * as Flag from "effect/unstable/cli/Flag"
|
|
22
|
-
import * as HttpClient from "effect/unstable/http/HttpClient"
|
|
23
|
-
import type { OpenAPISpec } from "effect/unstable/httpapi/OpenApi"
|
|
24
24
|
import { format as formatSource } from "prettier"
|
|
25
25
|
|
|
26
26
|
const SPEC_URL = "https://dac-static.atlassian.com/cloud/jira/platform/swagger-v3.v3.json"
|
|
@@ -167,6 +167,17 @@ const normalizeResponses = (document: Schema.Json): Schema.Json => {
|
|
|
167
167
|
|
|
168
168
|
const stripTrailingWhitespace = (source: string): string => source.replace(/[ \t]+$/gm, "")
|
|
169
169
|
|
|
170
|
+
/** Route optional keys named after `Object.prototype` members through `ownOptionalKey`; see `src/OwnOptionalKey.ts`. */
|
|
171
|
+
const ownPrototypeKeys = (source: string): string => {
|
|
172
|
+
const rewritten = source.replace(/"(toString)": Schema\.optionalKey\(/g, "\"$1\": ownOptionalKey(\"$1\", ")
|
|
173
|
+
return rewritten === source
|
|
174
|
+
? source
|
|
175
|
+
: rewritten.replace(
|
|
176
|
+
/^(import \* as Schema from "effect\/Schema"\n)/m,
|
|
177
|
+
"$1import { ownOptionalKey } from \"../OwnOptionalKey.js\"\n"
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
170
181
|
const generate = Effect.fn("Jira.regenerate.generate")(function*(scriptPaths: ScriptPaths, upstream: Schema.Json) {
|
|
171
182
|
const fs = yield* FileSystem.FileSystem
|
|
172
183
|
const patch = yield* OpenApiPatch.parsePatchInput(scriptPaths.patchFile).pipe(
|
|
@@ -195,17 +206,19 @@ const generate = Effect.fn("Jira.regenerate.generate")(function*(scriptPaths: Sc
|
|
|
195
206
|
(warning) => Console.warn(`[${warning.code}] ${warning.method ?? ""} ${warning.path ?? ""}: ${warning.message}`),
|
|
196
207
|
{ discard: true }
|
|
197
208
|
)
|
|
198
|
-
yield* fs.writeFileString(scriptPaths.outputFile, stripTrailingWhitespace(generated)).pipe(
|
|
209
|
+
yield* fs.writeFileString(scriptPaths.outputFile, ownPrototypeKeys(stripTrailingWhitespace(generated))).pipe(
|
|
199
210
|
Effect.mapError((cause) => new RegenerateError({ message: "Could not write generated Jira client", cause }))
|
|
200
211
|
)
|
|
201
212
|
})
|
|
202
213
|
|
|
203
|
-
const check = Flag.
|
|
204
|
-
Flag.withDescription("Exit non-zero when the committed upstream spec differs from Jira")
|
|
214
|
+
const check = Flag.Boolean("check").pipe(
|
|
215
|
+
Flag.withDescription("Exit non-zero when the committed upstream spec differs from Jira"),
|
|
216
|
+
Flag.withDefault(false)
|
|
205
217
|
)
|
|
206
218
|
|
|
207
|
-
const local = Flag.
|
|
208
|
-
Flag.withDescription("Regenerate from the committed spec without contacting Jira")
|
|
219
|
+
const local = Flag.Boolean("local").pipe(
|
|
220
|
+
Flag.withDescription("Regenerate from the committed spec without contacting Jira"),
|
|
221
|
+
Flag.withDefault(false)
|
|
209
222
|
)
|
|
210
223
|
|
|
211
224
|
const root = Command.make("jira-api-regenerate", { check, local }).pipe(
|
package/src/JiraApiClient.ts
CHANGED
|
@@ -9,17 +9,17 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import * as Context from "effect/Context"
|
|
11
11
|
import * as Effect from "effect/Effect"
|
|
12
|
-
import
|
|
12
|
+
import { Base64 } from "effect/encoding"
|
|
13
13
|
import { flow } from "effect/Function"
|
|
14
|
+
import * as HttpClient from "effect/http/HttpClient"
|
|
15
|
+
import type * as HttpClientError from "effect/http/HttpClientError"
|
|
16
|
+
import * as HttpClientRequest from "effect/http/HttpClientRequest"
|
|
17
|
+
import * as HttpClientResponse from "effect/http/HttpClientResponse"
|
|
14
18
|
import * as Layer from "effect/Layer"
|
|
15
19
|
import * as Redacted from "effect/Redacted"
|
|
16
20
|
import type { SchemaError } from "effect/Schema"
|
|
17
|
-
import * as HttpClient from "effect/unstable/http/HttpClient"
|
|
18
|
-
import type * as HttpClientError from "effect/unstable/http/HttpClientError"
|
|
19
|
-
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
|
|
20
|
-
import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"
|
|
21
21
|
import * as Generated from "./generated/JiraApi.js"
|
|
22
|
-
import { JiraApiConfig, type
|
|
22
|
+
import { JiraApiConfig, type JiraApiConfigContract, type JiraApiCredential } from "./JiraApiConfig.js"
|
|
23
23
|
|
|
24
24
|
export interface UploadAttachmentInput {
|
|
25
25
|
readonly bytes: Uint8Array
|
|
@@ -27,37 +27,59 @@ export interface UploadAttachmentInput {
|
|
|
27
27
|
readonly mediaType?: string | undefined
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export interface
|
|
30
|
+
export interface JiraApiClientContract extends Generated.JiraApi {
|
|
31
31
|
readonly uploadAttachment: (
|
|
32
32
|
issueIdOrKey: string,
|
|
33
33
|
input: UploadAttachmentInput
|
|
34
34
|
) => Effect.Effect<Generated.AddAttachment200, HttpClientError.HttpClientError | SchemaError>
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const authorizationHeader = (
|
|
38
|
-
|
|
39
|
-
? `Basic ${
|
|
40
|
-
: `Bearer ${Redacted.value(
|
|
37
|
+
const authorizationHeader = (auth: JiraApiCredential): string =>
|
|
38
|
+
auth.type === "basic"
|
|
39
|
+
? `Basic ${Base64.encode(`${auth.email}:${Redacted.value(auth.apiToken)}`)}`
|
|
40
|
+
: `Bearer ${Redacted.value(auth.accessToken)}`
|
|
41
41
|
|
|
42
|
-
const apiBaseUrl = (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
const apiBaseUrl = (baseUrl: string, auth: JiraApiCredential): string =>
|
|
43
|
+
auth.type === "oauth2" ? `https://api.atlassian.com/ex/jira/${auth.cloudId}` : baseUrl
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Where the credential is turned into a header.
|
|
47
|
+
*
|
|
48
|
+
* Both the host and the `Authorization` header come from `auth`, so they are derived together from
|
|
49
|
+
* one value rather than separately from two reads that could disagree about which site is being
|
|
50
|
+
* addressed. When `resolveAuth` is present that pair is recomputed per request, which is the only
|
|
51
|
+
* way a token refreshed mid-run reaches the wire — `mapRequest` would capture the startup value.
|
|
52
|
+
*/
|
|
53
|
+
const addressAndAuthorize = (
|
|
54
|
+
config: JiraApiConfigContract
|
|
55
|
+
): (client: HttpClient.HttpClient) => HttpClient.HttpClient => {
|
|
56
|
+
const resolve = config.resolveAuth
|
|
57
|
+
if (resolve === undefined) {
|
|
58
|
+
return HttpClient.mapRequest(flow(
|
|
59
|
+
HttpClientRequest.prependUrl(apiBaseUrl(config.baseUrl, config.auth)),
|
|
60
|
+
HttpClientRequest.setHeader("Authorization", authorizationHeader(config.auth))
|
|
61
|
+
))
|
|
62
|
+
}
|
|
63
|
+
return HttpClient.mapRequestEffect((request) =>
|
|
64
|
+
Effect.map(resolve, (auth) =>
|
|
65
|
+
request.pipe(
|
|
66
|
+
HttpClientRequest.prependUrl(apiBaseUrl(config.baseUrl, auth)),
|
|
67
|
+
HttpClientRequest.setHeader("Authorization", authorizationHeader(auth))
|
|
68
|
+
))
|
|
69
|
+
)
|
|
70
|
+
}
|
|
46
71
|
|
|
47
72
|
export const make = (
|
|
48
73
|
httpClient: HttpClient.HttpClient,
|
|
49
|
-
config:
|
|
74
|
+
config: JiraApiConfigContract
|
|
50
75
|
): Generated.JiraApi =>
|
|
51
76
|
Generated.make(httpClient.pipe(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
HttpClientRequest.setHeader("Authorization", authorizationHeader(config)),
|
|
55
|
-
HttpClientRequest.setHeader("Accept", "application/json")
|
|
56
|
-
))
|
|
77
|
+
addressAndAuthorize(config),
|
|
78
|
+
HttpClient.mapRequest(HttpClientRequest.setHeader("Accept", "application/json"))
|
|
57
79
|
))
|
|
58
80
|
|
|
59
81
|
const makeUploadAttachment =
|
|
60
|
-
(api: Generated.JiraApi):
|
|
82
|
+
(api: Generated.JiraApi): JiraApiClientContract["uploadAttachment"] => (issueIdOrKey, input) => {
|
|
61
83
|
const buffer = new ArrayBuffer(input.bytes.byteLength)
|
|
62
84
|
new Uint8Array(buffer).set(input.bytes)
|
|
63
85
|
const form = new FormData()
|
|
@@ -77,7 +99,7 @@ const makeUploadAttachment =
|
|
|
77
99
|
)
|
|
78
100
|
}
|
|
79
101
|
|
|
80
|
-
export class JiraApiClient extends Context.Service<JiraApiClient,
|
|
102
|
+
export class JiraApiClient extends Context.Service<JiraApiClient, JiraApiClientContract>()(
|
|
81
103
|
"@knpkv/jira-api-client/JiraApiClient"
|
|
82
104
|
) {
|
|
83
105
|
static readonly layer: Layer.Layer<
|
package/src/JiraApiConfig.ts
CHANGED
|
@@ -7,22 +7,42 @@
|
|
|
7
7
|
* Basic auth uses email + Redacted API token; OAuth2 uses Redacted access token + cloud ID.
|
|
8
8
|
* - **Base URL routing**: For OAuth2, the base URL is derived from cloud ID (`api.atlassian.com/ex/jira/{cloudId}`);
|
|
9
9
|
* for basic auth, `baseUrl` is used directly.
|
|
10
|
+
* - **`auth` is a snapshot; `resolveAuth` is a subscription.** A credential that can expire needs the
|
|
11
|
+
* second one — see its own note.
|
|
10
12
|
*
|
|
11
13
|
* @module
|
|
12
14
|
*/
|
|
13
15
|
import * as Context from "effect/Context"
|
|
16
|
+
import type * as Effect from "effect/Effect"
|
|
14
17
|
import type * as Redacted from "effect/Redacted"
|
|
15
18
|
|
|
19
|
+
/** How a request proves who is asking. */
|
|
20
|
+
export type JiraApiCredential =
|
|
21
|
+
| { readonly type: "basic"; readonly email: string; readonly apiToken: Redacted.Redacted<string> }
|
|
22
|
+
| { readonly type: "oauth2"; readonly accessToken: Redacted.Redacted<string>; readonly cloudId: string }
|
|
23
|
+
|
|
16
24
|
/**
|
|
17
25
|
* Configuration shape for Jira API client.
|
|
18
26
|
*
|
|
19
27
|
* @category Config
|
|
20
28
|
*/
|
|
21
|
-
export interface
|
|
29
|
+
export interface JiraApiConfigContract {
|
|
22
30
|
readonly baseUrl: string
|
|
23
|
-
readonly auth:
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
readonly auth: JiraApiCredential
|
|
32
|
+
/**
|
|
33
|
+
* Re-read before every request, when supplied. Otherwise `auth` is used as given.
|
|
34
|
+
*
|
|
35
|
+
* A `Redacted` token is a value, so a client built from one is pinned to it for as long as the
|
|
36
|
+
* client lives. That is invisible in a command that exits in seconds and fatal in one that does
|
|
37
|
+
* not: an Atlassian access token lasts about an hour, after which every request 401s and no amount
|
|
38
|
+
* of retrying inside the process can recover, because the expired token is baked into the header.
|
|
39
|
+
* `jcf watch` is meant to run all day, so it supplies this and a refresh reaches the next request.
|
|
40
|
+
*
|
|
41
|
+
* Typed as infallible on purpose. This runs inside request preprocessing, where a new error would
|
|
42
|
+
* widen every operation's error channel; a resolver that cannot read its credential yields
|
|
43
|
+
* whatever it has, and the 401 that follows is the same one an absent login already produces.
|
|
44
|
+
*/
|
|
45
|
+
readonly resolveAuth?: Effect.Effect<JiraApiCredential> | undefined
|
|
26
46
|
}
|
|
27
47
|
|
|
28
48
|
/**
|
|
@@ -46,6 +66,6 @@ export interface JiraApiConfigShape {
|
|
|
46
66
|
*
|
|
47
67
|
* @category Config
|
|
48
68
|
*/
|
|
49
|
-
export class JiraApiConfig extends Context.Service<JiraApiConfig,
|
|
69
|
+
export class JiraApiConfig extends Context.Service<JiraApiConfig, JiraApiConfigContract>()(
|
|
50
70
|
"@knpkv/jira-api-client/JiraApiConfig"
|
|
51
71
|
) {}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional struct keys that shadow `Object.prototype` members.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import * as Option from "effect/Option"
|
|
7
|
+
import * as Predicate from "effect/Predicate"
|
|
8
|
+
import * as Schema from "effect/Schema"
|
|
9
|
+
import * as SchemaGetter from "effect/SchemaGetter"
|
|
10
|
+
|
|
11
|
+
/** `Object.prototype` members that Jira payloads use as ordinary field names. */
|
|
12
|
+
export type PrototypeKey = "toString"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Optional key for a field named after an `Object.prototype` member, such as Jira's `toString`.
|
|
16
|
+
*
|
|
17
|
+
* Effect resolves declared struct keys with `in`, so a JSON object that omits `toString` presents the inherited
|
|
18
|
+
* `Object.prototype.toString`. Only that exact inherited function decodes as an absent key; any other value must
|
|
19
|
+
* satisfy `schema`. Use it in place of `Schema.optionalKey(schema)` for these keys; the encoded and decoded types
|
|
20
|
+
* stay those of `Schema.optionalKey(schema)`.
|
|
21
|
+
*/
|
|
22
|
+
export const ownOptionalKey = <S extends Schema.Top>(key: PrototypeKey, schema: S) => {
|
|
23
|
+
const inherited = Object.prototype[key]
|
|
24
|
+
// Typed `never`: the inherited member is not a domain value and never survives decoding.
|
|
25
|
+
const isInherited = Predicate.compose(
|
|
26
|
+
Predicate.isFunction,
|
|
27
|
+
(member: Function): member is never => member === inherited
|
|
28
|
+
)
|
|
29
|
+
const InheritedMember = Schema.declare(isInherited, { expected: `the inherited Object.prototype.${key}` })
|
|
30
|
+
return Schema.optionalKey(Schema.Union([schema, InheritedMember])).pipe(
|
|
31
|
+
Schema.decodeTo(Schema.optionalKey(Schema.toType(schema)), {
|
|
32
|
+
decode: SchemaGetter.transformOptional(Option.filter(Predicate.not(isInherited))),
|
|
33
|
+
encode: SchemaGetter.passthrough()
|
|
34
|
+
})
|
|
35
|
+
)
|
|
36
|
+
}
|