vite-plugin-openapi-codegen 1.1.3 → 1.2.1
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/README.md +2 -0
- package/dist/cli.mjs +58 -16
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +58 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -225,6 +225,8 @@ The generated client shape depends on the OpenAPI operation:
|
|
|
225
225
|
- path parameters become `options.path`
|
|
226
226
|
- query parameters become `options.query`
|
|
227
227
|
- JSON request bodies become `options.body`
|
|
228
|
+
- `application/octet-stream` request bodies become `options.body` with `contentType: "application/octet-stream"`
|
|
229
|
+
- `multipart/form-data` request bodies become `options.body` as `FormData`
|
|
228
230
|
- JSON responses become typed `Promise<T>`
|
|
229
231
|
- empty responses use the configured void request function
|
|
230
232
|
|
package/dist/cli.mjs
CHANGED
|
@@ -146,11 +146,21 @@ function createClientChannelField(key, channel) {
|
|
|
146
146
|
function createClientFunctionDeclaration(operation) {
|
|
147
147
|
const requestProperties = [ts.factory.createSpreadAssignment(ts.factory.createIdentifier("requestOptions")), ts.factory.createPropertyAssignment(ts.factory.createIdentifier("method"), ts.factory.createStringLiteral(operation.methodUpper))];
|
|
148
148
|
if (operation.queryChannel.present) requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("searchParams"), ts.factory.createCallExpression(ts.factory.createIdentifier("buildSearchParams"), void 0, [ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "query")])));
|
|
149
|
-
if (operation.bodyChannel.present)
|
|
149
|
+
if (operation.bodyChannel.present) {
|
|
150
|
+
const bodyPropertyName = operation.bodyContentType === "json" ? "json" : "body";
|
|
151
|
+
const bodyAssignment = ts.factory.createObjectLiteralExpression([ts.factory.createPropertyAssignment(ts.factory.createIdentifier(bodyPropertyName), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body"))], false);
|
|
152
|
+
if (operation.bodyContentType === "json") requestProperties.push(createBodyRequestProperty(operation.bodyChannel, bodyAssignment));
|
|
153
|
+
else requestProperties.push(createBodyRequestProperty(operation.bodyChannel, bodyAssignment));
|
|
154
|
+
if (operation.bodyContentType === "binary") requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("contentType"), ts.factory.createStringLiteral("application/octet-stream")));
|
|
155
|
+
}
|
|
150
156
|
requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("signal"), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "signal")));
|
|
151
157
|
const requestCall = ts.factory.createCallExpression(ts.factory.createIdentifier(operation.requestFunction), operation.responseTypeExpr ? [createTypeNodeFromText(operation.responseTypeExpr)] : void 0, [parseExpression(operation.pathInvocationExpr), ts.factory.createObjectLiteralExpression(requestProperties, true)]);
|
|
152
158
|
return ts.factory.createFunctionDeclaration([createExportModifier()], void 0, ts.factory.createIdentifier(operation.funcName), void 0, [ts.factory.createParameterDeclaration(void 0, void 0, ts.factory.createIdentifier("options"), void 0, ts.factory.createTypeReferenceNode(operation.optionTypeName)), ts.factory.createParameterDeclaration(void 0, void 0, ts.factory.createIdentifier("requestOptions"), void 0, ts.factory.createTypeReferenceNode("RuntimeRequestOptions"), ts.factory.createObjectLiteralExpression())], createTypeNodeFromText(operation.returnTypeExpr), ts.factory.createBlock([ts.factory.createReturnStatement(requestCall)], true));
|
|
153
159
|
}
|
|
160
|
+
function createBodyRequestProperty(bodyChannel, bodyAssignment) {
|
|
161
|
+
if (bodyChannel.required) return bodyAssignment.properties[0];
|
|
162
|
+
return ts.factory.createSpreadAssignment(ts.factory.createParenthesizedExpression(ts.factory.createConditionalExpression(ts.factory.createBinaryExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body"), ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createIdentifier("undefined")), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createObjectLiteralExpression([], false), ts.factory.createToken(ts.SyntaxKind.ColonToken), bodyAssignment)));
|
|
163
|
+
}
|
|
154
164
|
function createBuildSearchParamsFunction() {
|
|
155
165
|
return ts.factory.createFunctionDeclaration(void 0, void 0, ts.factory.createIdentifier("buildSearchParams"), void 0, [ts.factory.createParameterDeclaration(void 0, void 0, ts.factory.createIdentifier("query"), void 0, createTypeNodeFromText("Record<string, unknown> | undefined"))], createTypeNodeFromText("URLSearchParams | undefined"), ts.factory.createBlock([
|
|
156
166
|
ts.factory.createIfStatement(ts.factory.createBinaryExpression(ts.factory.createIdentifier("query"), ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createIdentifier("undefined")), ts.factory.createReturnStatement(ts.factory.createIdentifier("undefined"))),
|
|
@@ -242,13 +252,6 @@ function getParametersByLocation(operation, location) {
|
|
|
242
252
|
function hasRequiredChannel(parameters) {
|
|
243
253
|
return parameters.some((parameter) => parameter.required);
|
|
244
254
|
}
|
|
245
|
-
function getJsonRequestBody(operation) {
|
|
246
|
-
const requestBody = operation.requestBody;
|
|
247
|
-
if (!requestBody) return void 0;
|
|
248
|
-
const jsonBody = requestBody.content?.["application/json"];
|
|
249
|
-
if (!jsonBody) throw new Error(`Operation "${operation.operationId ?? "unknown"}" has a requestBody but no application/json content`);
|
|
250
|
-
return jsonBody;
|
|
251
|
-
}
|
|
252
255
|
function getSuccessResponseInfo(operation) {
|
|
253
256
|
const successResponses = Object.entries(operation.responses ?? {}).filter(([statusKey]) => isSuccessStatus(statusKey)).sort(([left], [right]) => Number(left) - Number(right));
|
|
254
257
|
if (successResponses.length === 0) throw new Error(`Operation "${operation.operationId ?? "unknown"}" has no 2xx success response`);
|
|
@@ -335,10 +338,11 @@ function normalizeOperation(entry, context, requestFunctionNames) {
|
|
|
335
338
|
const builderAlias = getBuilderAlias(entry.funcName);
|
|
336
339
|
const pathChannel = normalizeParameterChannel(entry, context, "path");
|
|
337
340
|
const queryChannel = normalizeParameterChannel(entry, context, "query");
|
|
338
|
-
const
|
|
341
|
+
const bodyResolution = normalizeBodyChannel(entry, context);
|
|
339
342
|
const responseTypeRef = resolveResponseTypeReference(entry, context, successResponse);
|
|
340
343
|
return {
|
|
341
|
-
bodyChannel,
|
|
344
|
+
bodyChannel: bodyResolution.channel,
|
|
345
|
+
bodyContentType: bodyResolution.contentType,
|
|
342
346
|
builderAlias,
|
|
343
347
|
entry,
|
|
344
348
|
optionTypeName: getClientOptionTypeName(entry.funcName),
|
|
@@ -364,7 +368,35 @@ function normalizeParameterChannel(entry, context, location) {
|
|
|
364
368
|
};
|
|
365
369
|
}
|
|
366
370
|
function normalizeBodyChannel(entry, context) {
|
|
367
|
-
const
|
|
371
|
+
const requestBody = entry.operation.requestBody;
|
|
372
|
+
if (!requestBody) return {
|
|
373
|
+
channel: {
|
|
374
|
+
present: false,
|
|
375
|
+
required: false,
|
|
376
|
+
typeRef: null
|
|
377
|
+
},
|
|
378
|
+
contentType: null
|
|
379
|
+
};
|
|
380
|
+
const content = requestBody.content ?? {};
|
|
381
|
+
const jsonBody = content["application/json"];
|
|
382
|
+
if (jsonBody) return {
|
|
383
|
+
channel: createRequestBodyChannel(entry, context, jsonBody, "json"),
|
|
384
|
+
contentType: "json"
|
|
385
|
+
};
|
|
386
|
+
const binaryBody = content["application/octet-stream"];
|
|
387
|
+
if (binaryBody) return {
|
|
388
|
+
channel: createRequestBodyChannel(entry, context, binaryBody, "binary"),
|
|
389
|
+
contentType: "binary"
|
|
390
|
+
};
|
|
391
|
+
const multipartBody = content["multipart/form-data"];
|
|
392
|
+
if (multipartBody) return {
|
|
393
|
+
channel: createRequestBodyChannel(entry, context, multipartBody, "formData"),
|
|
394
|
+
contentType: "formData"
|
|
395
|
+
};
|
|
396
|
+
throw new Error(`Operation "${entry.operationId ?? "unknown"}" has a requestBody but no supported content type`);
|
|
397
|
+
}
|
|
398
|
+
function createRequestBodyChannel(entry, context, body, kind) {
|
|
399
|
+
const typeRef = resolveRequestBodyTypeReference(entry, context, body, kind);
|
|
368
400
|
if (!typeRef) return {
|
|
369
401
|
present: false,
|
|
370
402
|
required: false,
|
|
@@ -372,14 +404,22 @@ function normalizeBodyChannel(entry, context) {
|
|
|
372
404
|
};
|
|
373
405
|
return {
|
|
374
406
|
present: true,
|
|
375
|
-
required: entry.operation.requestBody?.required
|
|
407
|
+
required: entry.operation.requestBody?.required === true,
|
|
376
408
|
typeRef
|
|
377
409
|
};
|
|
378
410
|
}
|
|
379
|
-
function resolveRequestBodyTypeReference(entry, context) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
411
|
+
function resolveRequestBodyTypeReference(entry, context, body, kind) {
|
|
412
|
+
if (kind === "formData") return {
|
|
413
|
+
aliasDefinitionExpr: "FormData",
|
|
414
|
+
sourceExpr: "FormData",
|
|
415
|
+
typeName: allocateOperationTypeName(context, entry.funcName, "Request")
|
|
416
|
+
};
|
|
417
|
+
if (kind === "binary") return {
|
|
418
|
+
aliasDefinitionExpr: "Blob | File | ArrayBuffer | string",
|
|
419
|
+
sourceExpr: "Blob | File | ArrayBuffer | string",
|
|
420
|
+
typeName: allocateOperationTypeName(context, entry.funcName, "Request")
|
|
421
|
+
};
|
|
422
|
+
const schemaTypeRef = resolveSchemaTypeReference(context, body.schema);
|
|
383
423
|
if (schemaTypeRef) return schemaTypeRef;
|
|
384
424
|
return {
|
|
385
425
|
aliasDefinitionExpr: `operations['${entry.operationId}']['requestBody']['content']['application/json']`,
|
|
@@ -526,6 +566,7 @@ function createApiEntries(normalizedOps, useTypeAliases) {
|
|
|
526
566
|
return normalizedOps.map((op) => ({
|
|
527
567
|
funcName: op.entry.funcName,
|
|
528
568
|
group: op.entry.group,
|
|
569
|
+
bodyContentType: op.bodyContentType,
|
|
529
570
|
pathTypeExpr: op.pathChannel.typeRef == null ? null : useTypeAliases ? op.pathChannel.typeRef.typeName : op.pathChannel.typeRef.sourceExpr,
|
|
530
571
|
strippedPath: op.entry.strippedPath
|
|
531
572
|
}));
|
|
@@ -540,6 +581,7 @@ function createClientRenderModel(model, useTypeAliases) {
|
|
|
540
581
|
needsSearchParamsHelper: model.needsSearchParamsHelper,
|
|
541
582
|
operations: model.operations.map((operation) => ({
|
|
542
583
|
bodyChannel: resolveChannel(operation.bodyChannel, useTypeAliases),
|
|
584
|
+
bodyContentType: operation.bodyContentType,
|
|
543
585
|
builderAlias: operation.builderAlias,
|
|
544
586
|
funcName: operation.entry.funcName,
|
|
545
587
|
group: operation.entry.group,
|
package/dist/index.d.mts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -144,11 +144,21 @@ function createClientChannelField(key, channel) {
|
|
|
144
144
|
function createClientFunctionDeclaration(operation) {
|
|
145
145
|
const requestProperties = [ts.factory.createSpreadAssignment(ts.factory.createIdentifier("requestOptions")), ts.factory.createPropertyAssignment(ts.factory.createIdentifier("method"), ts.factory.createStringLiteral(operation.methodUpper))];
|
|
146
146
|
if (operation.queryChannel.present) requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("searchParams"), ts.factory.createCallExpression(ts.factory.createIdentifier("buildSearchParams"), void 0, [ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "query")])));
|
|
147
|
-
if (operation.bodyChannel.present)
|
|
147
|
+
if (operation.bodyChannel.present) {
|
|
148
|
+
const bodyPropertyName = operation.bodyContentType === "json" ? "json" : "body";
|
|
149
|
+
const bodyAssignment = ts.factory.createObjectLiteralExpression([ts.factory.createPropertyAssignment(ts.factory.createIdentifier(bodyPropertyName), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body"))], false);
|
|
150
|
+
if (operation.bodyContentType === "json") requestProperties.push(createBodyRequestProperty(operation.bodyChannel, bodyAssignment));
|
|
151
|
+
else requestProperties.push(createBodyRequestProperty(operation.bodyChannel, bodyAssignment));
|
|
152
|
+
if (operation.bodyContentType === "binary") requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("contentType"), ts.factory.createStringLiteral("application/octet-stream")));
|
|
153
|
+
}
|
|
148
154
|
requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("signal"), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "signal")));
|
|
149
155
|
const requestCall = ts.factory.createCallExpression(ts.factory.createIdentifier(operation.requestFunction), operation.responseTypeExpr ? [createTypeNodeFromText(operation.responseTypeExpr)] : void 0, [parseExpression(operation.pathInvocationExpr), ts.factory.createObjectLiteralExpression(requestProperties, true)]);
|
|
150
156
|
return ts.factory.createFunctionDeclaration([createExportModifier()], void 0, ts.factory.createIdentifier(operation.funcName), void 0, [ts.factory.createParameterDeclaration(void 0, void 0, ts.factory.createIdentifier("options"), void 0, ts.factory.createTypeReferenceNode(operation.optionTypeName)), ts.factory.createParameterDeclaration(void 0, void 0, ts.factory.createIdentifier("requestOptions"), void 0, ts.factory.createTypeReferenceNode("RuntimeRequestOptions"), ts.factory.createObjectLiteralExpression())], createTypeNodeFromText(operation.returnTypeExpr), ts.factory.createBlock([ts.factory.createReturnStatement(requestCall)], true));
|
|
151
157
|
}
|
|
158
|
+
function createBodyRequestProperty(bodyChannel, bodyAssignment) {
|
|
159
|
+
if (bodyChannel.required) return bodyAssignment.properties[0];
|
|
160
|
+
return ts.factory.createSpreadAssignment(ts.factory.createParenthesizedExpression(ts.factory.createConditionalExpression(ts.factory.createBinaryExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body"), ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createIdentifier("undefined")), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createObjectLiteralExpression([], false), ts.factory.createToken(ts.SyntaxKind.ColonToken), bodyAssignment)));
|
|
161
|
+
}
|
|
152
162
|
function createBuildSearchParamsFunction() {
|
|
153
163
|
return ts.factory.createFunctionDeclaration(void 0, void 0, ts.factory.createIdentifier("buildSearchParams"), void 0, [ts.factory.createParameterDeclaration(void 0, void 0, ts.factory.createIdentifier("query"), void 0, createTypeNodeFromText("Record<string, unknown> | undefined"))], createTypeNodeFromText("URLSearchParams | undefined"), ts.factory.createBlock([
|
|
154
164
|
ts.factory.createIfStatement(ts.factory.createBinaryExpression(ts.factory.createIdentifier("query"), ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createIdentifier("undefined")), ts.factory.createReturnStatement(ts.factory.createIdentifier("undefined"))),
|
|
@@ -240,13 +250,6 @@ function getParametersByLocation(operation, location) {
|
|
|
240
250
|
function hasRequiredChannel(parameters) {
|
|
241
251
|
return parameters.some((parameter) => parameter.required);
|
|
242
252
|
}
|
|
243
|
-
function getJsonRequestBody(operation) {
|
|
244
|
-
const requestBody = operation.requestBody;
|
|
245
|
-
if (!requestBody) return void 0;
|
|
246
|
-
const jsonBody = requestBody.content?.["application/json"];
|
|
247
|
-
if (!jsonBody) throw new Error(`Operation "${operation.operationId ?? "unknown"}" has a requestBody but no application/json content`);
|
|
248
|
-
return jsonBody;
|
|
249
|
-
}
|
|
250
253
|
function getSuccessResponseInfo(operation) {
|
|
251
254
|
const successResponses = Object.entries(operation.responses ?? {}).filter(([statusKey]) => isSuccessStatus(statusKey)).sort(([left], [right]) => Number(left) - Number(right));
|
|
252
255
|
if (successResponses.length === 0) throw new Error(`Operation "${operation.operationId ?? "unknown"}" has no 2xx success response`);
|
|
@@ -333,10 +336,11 @@ function normalizeOperation(entry, context, requestFunctionNames) {
|
|
|
333
336
|
const builderAlias = getBuilderAlias(entry.funcName);
|
|
334
337
|
const pathChannel = normalizeParameterChannel(entry, context, "path");
|
|
335
338
|
const queryChannel = normalizeParameterChannel(entry, context, "query");
|
|
336
|
-
const
|
|
339
|
+
const bodyResolution = normalizeBodyChannel(entry, context);
|
|
337
340
|
const responseTypeRef = resolveResponseTypeReference(entry, context, successResponse);
|
|
338
341
|
return {
|
|
339
|
-
bodyChannel,
|
|
342
|
+
bodyChannel: bodyResolution.channel,
|
|
343
|
+
bodyContentType: bodyResolution.contentType,
|
|
340
344
|
builderAlias,
|
|
341
345
|
entry,
|
|
342
346
|
optionTypeName: getClientOptionTypeName(entry.funcName),
|
|
@@ -362,7 +366,35 @@ function normalizeParameterChannel(entry, context, location) {
|
|
|
362
366
|
};
|
|
363
367
|
}
|
|
364
368
|
function normalizeBodyChannel(entry, context) {
|
|
365
|
-
const
|
|
369
|
+
const requestBody = entry.operation.requestBody;
|
|
370
|
+
if (!requestBody) return {
|
|
371
|
+
channel: {
|
|
372
|
+
present: false,
|
|
373
|
+
required: false,
|
|
374
|
+
typeRef: null
|
|
375
|
+
},
|
|
376
|
+
contentType: null
|
|
377
|
+
};
|
|
378
|
+
const content = requestBody.content ?? {};
|
|
379
|
+
const jsonBody = content["application/json"];
|
|
380
|
+
if (jsonBody) return {
|
|
381
|
+
channel: createRequestBodyChannel(entry, context, jsonBody, "json"),
|
|
382
|
+
contentType: "json"
|
|
383
|
+
};
|
|
384
|
+
const binaryBody = content["application/octet-stream"];
|
|
385
|
+
if (binaryBody) return {
|
|
386
|
+
channel: createRequestBodyChannel(entry, context, binaryBody, "binary"),
|
|
387
|
+
contentType: "binary"
|
|
388
|
+
};
|
|
389
|
+
const multipartBody = content["multipart/form-data"];
|
|
390
|
+
if (multipartBody) return {
|
|
391
|
+
channel: createRequestBodyChannel(entry, context, multipartBody, "formData"),
|
|
392
|
+
contentType: "formData"
|
|
393
|
+
};
|
|
394
|
+
throw new Error(`Operation "${entry.operationId ?? "unknown"}" has a requestBody but no supported content type`);
|
|
395
|
+
}
|
|
396
|
+
function createRequestBodyChannel(entry, context, body, kind) {
|
|
397
|
+
const typeRef = resolveRequestBodyTypeReference(entry, context, body, kind);
|
|
366
398
|
if (!typeRef) return {
|
|
367
399
|
present: false,
|
|
368
400
|
required: false,
|
|
@@ -370,14 +402,22 @@ function normalizeBodyChannel(entry, context) {
|
|
|
370
402
|
};
|
|
371
403
|
return {
|
|
372
404
|
present: true,
|
|
373
|
-
required: entry.operation.requestBody?.required
|
|
405
|
+
required: entry.operation.requestBody?.required === true,
|
|
374
406
|
typeRef
|
|
375
407
|
};
|
|
376
408
|
}
|
|
377
|
-
function resolveRequestBodyTypeReference(entry, context) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
409
|
+
function resolveRequestBodyTypeReference(entry, context, body, kind) {
|
|
410
|
+
if (kind === "formData") return {
|
|
411
|
+
aliasDefinitionExpr: "FormData",
|
|
412
|
+
sourceExpr: "FormData",
|
|
413
|
+
typeName: allocateOperationTypeName(context, entry.funcName, "Request")
|
|
414
|
+
};
|
|
415
|
+
if (kind === "binary") return {
|
|
416
|
+
aliasDefinitionExpr: "Blob | File | ArrayBuffer | string",
|
|
417
|
+
sourceExpr: "Blob | File | ArrayBuffer | string",
|
|
418
|
+
typeName: allocateOperationTypeName(context, entry.funcName, "Request")
|
|
419
|
+
};
|
|
420
|
+
const schemaTypeRef = resolveSchemaTypeReference(context, body.schema);
|
|
381
421
|
if (schemaTypeRef) return schemaTypeRef;
|
|
382
422
|
return {
|
|
383
423
|
aliasDefinitionExpr: `operations['${entry.operationId}']['requestBody']['content']['application/json']`,
|
|
@@ -524,6 +564,7 @@ function createApiEntries(normalizedOps, useTypeAliases) {
|
|
|
524
564
|
return normalizedOps.map((op) => ({
|
|
525
565
|
funcName: op.entry.funcName,
|
|
526
566
|
group: op.entry.group,
|
|
567
|
+
bodyContentType: op.bodyContentType,
|
|
527
568
|
pathTypeExpr: op.pathChannel.typeRef == null ? null : useTypeAliases ? op.pathChannel.typeRef.typeName : op.pathChannel.typeRef.sourceExpr,
|
|
528
569
|
strippedPath: op.entry.strippedPath
|
|
529
570
|
}));
|
|
@@ -538,6 +579,7 @@ function createClientRenderModel(model, useTypeAliases) {
|
|
|
538
579
|
needsSearchParamsHelper: model.needsSearchParamsHelper,
|
|
539
580
|
operations: model.operations.map((operation) => ({
|
|
540
581
|
bodyChannel: resolveChannel(operation.bodyChannel, useTypeAliases),
|
|
582
|
+
bodyContentType: operation.bodyContentType,
|
|
541
583
|
builderAlias: operation.builderAlias,
|
|
542
584
|
funcName: operation.entry.funcName,
|
|
543
585
|
group: operation.entry.group,
|