From 006b834862fcb76646f7f67898c5c496276d4c3f Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Thu, 18 Jun 2026 16:59:16 -0700 Subject: [PATCH 01/20] wip remove rawResponse from packages --- .../arcgis-rest-feature-service/src/query.ts | 28 ++++++-- .../test/query.test.ts | 8 +-- packages/arcgis-rest-geocoding/src/bulk.ts | 66 ++++++++++++++----- packages/arcgis-rest-geocoding/src/geocode.ts | 54 +++++++++++++-- .../arcgis-rest-geocoding/test/bulk.test.ts | 9 ++- .../test/geocode.test.ts | 9 ++- packages/arcgis-rest-portal/src/items/get.ts | 53 +++++++++++++-- .../arcgis-rest-portal/test/items/get.test.ts | 16 ++--- 8 files changed, 185 insertions(+), 58 deletions(-) diff --git a/packages/arcgis-rest-feature-service/src/query.ts b/packages/arcgis-rest-feature-service/src/query.ts index 5b26e0f3b..e0c1edb88 100644 --- a/packages/arcgis-rest-feature-service/src/query.ts +++ b/packages/arcgis-rest-feature-service/src/query.ts @@ -267,12 +267,12 @@ export function queryPbfAsGeoJSONOrArcGIS( * }); * ``` * - * @param requestOptions - Options for the request - * @returns A Promise that resolves with the feature by default, or with the native Response when `rawResponse` is `true`. + * @param requestOptions - Options for the request. + * @returns A Promise that resolves with the feature. */ export function getFeature( requestOptions: IGetFeatureOptions -): Promise { +): Promise { const url = `${cleanUrl(requestOptions.url)}/${requestOptions.id}`; // default to a GET request @@ -280,12 +280,28 @@ export function getFeature( ...{ httpMethod: "GET" }, ...requestOptions }; - if (options.rawResponse) { - return rawRequest(url, options); - } return request(url, options).then((response: any) => response.feature); } +/** + * Get a feature by id and return the native response. + * + * @param requestOptions - Options for the request. + * @returns A Promise that resolves with the native response. + */ +export function rawGetFeature( + requestOptions: IGetFeatureOptions +): Promise { + const url = `${cleanUrl(requestOptions.url)}/${requestOptions.id}`; + + // default to a GET request + const options: IGetFeatureOptions = { + ...{ httpMethod: "GET" }, + ...requestOptions + }; + return rawRequest(url, options); +} + /** * Query a feature service. See [REST Documentation](https://developers.arcgis.com/rest/services-reference/query-feature-service-layer-.htm) for more information. * diff --git a/packages/arcgis-rest-feature-service/test/query.test.ts b/packages/arcgis-rest-feature-service/test/query.test.ts index a58f4a204..d6ae64831 100644 --- a/packages/arcgis-rest-feature-service/test/query.test.ts +++ b/packages/arcgis-rest-feature-service/test/query.test.ts @@ -5,6 +5,7 @@ import { describe, afterEach, test, expect, vi, beforeEach } from "vitest"; import fetchMock from "fetch-mock"; import { getFeature, + rawGetFeature, queryFeatures, queryAllFeatures, queryRelated, @@ -51,15 +52,14 @@ describe("getFeature() and queryFeatures()", () => { expect(response.attributes.FID).toBe(42); }); - test("return rawResponse when getting a feature", async () => { + test("returns raw response when getting a feature", async () => { const requestOptions = { url: serviceUrl, - id: 42, - rawResponse: true + id: 42 }; fetchMock.once("*", featureResponse); - const response: any = await getFeature(requestOptions); + const response: any = await rawGetFeature(requestOptions); expect(fetchMock.called()).toBeTruthy(); const [url, options] = fetchMock.lastCall("*"); diff --git a/packages/arcgis-rest-geocoding/src/bulk.ts b/packages/arcgis-rest-geocoding/src/bulk.ts index 2dcfd0359..27d2ce25e 100644 --- a/packages/arcgis-rest-geocoding/src/bulk.ts +++ b/packages/arcgis-rest-geocoding/src/bulk.ts @@ -68,7 +68,7 @@ export interface IBulkGeocodeResponse { * ``` * * @param requestOptions - Request options to pass to the geocoder, including an array of addresses and authentication session. - * @returns A Promise that will resolve with the data from the response. The spatial reference will be added to address locations unless `rawResponse: true` was passed. + * @returns A Promise that will resolve with the data from the response. The spatial reference will be added to address locations. */ export function bulkGeocode( requestOptions: IBulkGeocodeOptions // must POST, which is the default @@ -85,33 +85,63 @@ export function bulkGeocode( }) }; + const endpoint = options.endpoint; + // the SAS service does not support anonymous requests if ( !requestOptions.authentication && - options.endpoint === ARCGIS_ONLINE_BULK_GEOCODING_URL + endpoint === ARCGIS_ONLINE_BULK_GEOCODING_URL ) { return Promise.reject( "bulk geocoding using the ArcGIS service requires authentication" ); } - if (options.rawResponse) { - return rawRequest( - `${cleanUrl(options.endpoint)}/geocodeAddresses`, - options + return request(`${cleanUrl(endpoint)}/geocodeAddresses`, options).then( + (response) => { + const sr = response.spatialReference; + response.locations.forEach(function (address: { location: IPoint }) { + if (address.location) { + address.location.spatialReference = sr; + } + }); + return response; + } + ); +} + +/** + * Used to geocode a batch of addresses and return the native response. + * + * @param requestOptions - Request options to pass to the geocoder, including an array of addresses and authentication session. + * @returns A Promise that resolves with the native response. + */ +export function rawBulkGeocode( + requestOptions: IBulkGeocodeOptions +): Promise { + const options: IBulkGeocodeOptions = { + endpoint: ARCGIS_ONLINE_BULK_GEOCODING_URL, + params: {}, + ...requestOptions + }; + + options.params.addresses = { + records: requestOptions.addresses.map((address) => { + return { attributes: address }; + }) + }; + + const endpoint = options.endpoint; + + // the SAS service does not support anonymous requests + if ( + !requestOptions.authentication && + endpoint === ARCGIS_ONLINE_BULK_GEOCODING_URL + ) { + return Promise.reject( + "bulk geocoding using the ArcGIS service requires authentication" ); } - return request( - `${cleanUrl(options.endpoint)}/geocodeAddresses`, - options - ).then((response) => { - const sr = response.spatialReference; - response.locations.forEach(function (address: { location: IPoint }) { - if (address.location) { - address.location.spatialReference = sr; - } - }); - return response; - }); + return rawRequest(`${cleanUrl(endpoint)}/geocodeAddresses`, options); } diff --git a/packages/arcgis-rest-geocoding/src/geocode.ts b/packages/arcgis-rest-geocoding/src/geocode.ts index 97afc98af..567b9620f 100644 --- a/packages/arcgis-rest-geocoding/src/geocode.ts +++ b/packages/arcgis-rest-geocoding/src/geocode.ts @@ -94,7 +94,7 @@ export interface IGeocodeResponse { * ``` * * @param address String representing the address or point of interest or RequestOptions to pass to the endpoint. - * @returns A Promise that will resolve with address candidates for the request. The spatial reference will be added to candidate locations and extents unless `rawResponse: true` was passed. + * @returns A Promise that will resolve with address candidates for the request. The spatial reference will be added to candidate locations and extents. */ export function geocode( address: string | IGeocodeOptions @@ -135,10 +135,6 @@ export function geocode( } } - if (typeof address !== "string" && address.rawResponse) { - return rawRequest(`${cleanUrl(endpoint)}/findAddressCandidates`, options); - } - // add spatialReference property to individual matches return request(`${cleanUrl(endpoint)}/findAddressCandidates`, options).then( (response) => { @@ -179,3 +175,51 @@ export function geocode( } ); } + +/** + * Used to determine the location of a single address or point of interest and return the native response. + * + * @param address String representing the address or point of interest or RequestOptions to pass to the endpoint. + * @returns A Promise that will resolve with the native response. + */ +export function rawGeocode( + address: string | IGeocodeOptions +): Promise { + let options: IGeocodeOptions = {}; + let endpoint: string; + + if (typeof address === "string") { + options.params = { singleLine: address }; + endpoint = ARCGIS_ONLINE_GEOCODING_URL; + } else { + endpoint = address.endpoint || ARCGIS_ONLINE_GEOCODING_URL; + options = appendCustomParams( + address, + [ + "singleLine", + "address", + "address2", + "address3", + "neighborhood", + "city", + "subregion", + "region", + "postal", + "postalExt", + "countryCode", + "outFields", + "magicKey" + ], + { params: { ...address.params } } + ); + + if (options.params.postal && typeof options.params.postal === "number") { + warn( + "The postal code should be a string. " + + "Issues can arise when using it as a number, especially if they start with zero." + ); + } + } + + return rawRequest(`${cleanUrl(endpoint)}/findAddressCandidates`, options); +} diff --git a/packages/arcgis-rest-geocoding/test/bulk.test.ts b/packages/arcgis-rest-geocoding/test/bulk.test.ts index e9a1a451a..b9f591101 100644 --- a/packages/arcgis-rest-geocoding/test/bulk.test.ts +++ b/packages/arcgis-rest-geocoding/test/bulk.test.ts @@ -3,7 +3,7 @@ import { describe, test, afterEach, expect } from "vitest"; import fetchMock from "fetch-mock"; -import { bulkGeocode } from "../src/bulk.js"; +import { bulkGeocode, rawBulkGeocode } from "../src/bulk.js"; import { GeocodeAddresses } from "./mocks/responses.js"; const addresses = [ @@ -180,7 +180,7 @@ describe("geocode", () => { expect(response.spatialReference.latestWkid).toEqual(4326); }); - test("should support rawResponse", async () => { + test("should return raw response from rawBulkGeocode", async () => { fetchMock.once("*", GeocodeAddresses); const MOCK_AUTH = { @@ -190,10 +190,9 @@ describe("geocode", () => { portal: "https://mapsdev.arcgis.com" }; - const response: any = await bulkGeocode({ + const response: any = await rawBulkGeocode({ addresses, - authentication: MOCK_AUTH, - rawResponse: true + authentication: MOCK_AUTH }); expect(fetchMock.called()).toEqual(true); const [url, options] = fetchMock.lastCall("*"); diff --git a/packages/arcgis-rest-geocoding/test/geocode.test.ts b/packages/arcgis-rest-geocoding/test/geocode.test.ts index ed69ecd99..31bcd36bb 100644 --- a/packages/arcgis-rest-geocoding/test/geocode.test.ts +++ b/packages/arcgis-rest-geocoding/test/geocode.test.ts @@ -3,7 +3,7 @@ import { describe, test, afterEach, expect } from "vitest"; import fetchMock from "fetch-mock"; -import { geocode } from "../src/geocode.js"; +import { geocode, rawGeocode } from "../src/geocode.js"; import { FindAddressCandidates, FindAddressCandidates3857, @@ -177,12 +177,11 @@ describe("geocode", () => { ).toBe(true); }); - test("should support rawResponse", async () => { + test("should return raw response from rawGeocode", async () => { fetchMock.once("*", FindAddressCandidates); - const response: any = await geocode({ + const response: any = await rawGeocode({ address: "1600 Pennsylvania Avenue", - city: "Washington D.C.", - rawResponse: true + city: "Washington D.C." }); expect(fetchMock.called()).toEqual(true); const [url, options] = fetchMock.lastCall("*"); diff --git a/packages/arcgis-rest-portal/src/items/get.ts b/packages/arcgis-rest-portal/src/items/get.ts index fe5d6db46..cdf205cef 100644 --- a/packages/arcgis-rest-portal/src/items/get.ts +++ b/packages/arcgis-rest-portal/src/items/get.ts @@ -248,7 +248,7 @@ export interface IGetItemResourceOptions extends IRequestOptions { * .then(resourceContents => {}); * * // Get the response object instead - * getItemResource("3ef",{ rawResponse: true, fileName: "resource.json" }) + * rawGetItemResource("3ef", { fileName: "resource.json" }) * .then(response => {}) * ``` * @@ -268,6 +268,28 @@ export function getItemResource( ); } +/** + * Fetches an item resource and returns the native response. + * + * @param itemId - The item id. + * @param requestOptions - Options for the request. + * @returns A Promise that resolves with the native response. + */ +export function rawGetItemResource( + itemId: string, + requestOptions: IGetItemResourceOptions +): Promise { + const url = `${getItemBaseUrl(itemId, requestOptions)}/resources/${ + requestOptions.fileName + }`; + const options: IRequestOptions = { + params: {}, + ...requestOptions + }; + options.params.f = null; + return rawRequest(url, options); +} + /** * Lists the groups of which the item is a part, only showing the groups that the calling user can access. See the [REST Documentation](https://developers.arcgis.com/rest/users-groups-and-items/groups.htm) for more information. * @@ -417,6 +439,28 @@ export function getItemInfo( return getItemFile(id, `/info/${fileName}`, readAs, options); } +/** + * Get an info file for an item and return the native response. + * + * @param id - Item Id. + * @param requestOptions - Options for the request, including the file name which defaults to `iteminfo.xml`. + * @returns A Promise that resolves with the native response. + */ +export function rawGetItemInfo( + id: string, + requestOptions?: IGetItemInfoOptions +): Promise { + const { fileName = "iteminfo.xml" } = requestOptions || {}; + const options: IRequestOptions = { + params: {}, + httpMethod: "GET", + ...requestOptions + }; + options.params.f = null; + const url = `${getItemBaseUrl(id, options)}/info/${fileName}`; + return rawRequest(url, options); +} + /** * ``` * import { getItemMetadata } from "@esri/arcgis-rest-portal"; @@ -454,18 +498,13 @@ function getItemFile( requestOptions?: IRequestOptions ): Promise { const url = `${getItemBaseUrl(id, requestOptions)}${fileName}`; - // preserve escape hatch to let the consumer read the response - // and ensure the f param is not appended to the query string + // ensure f param is not appended to the query string for file endpoints const options: IRequestOptions = { params: {}, ...requestOptions }; options.params.f = null; - if (options.rawResponse) { - return rawRequest(url, options); - } - return rawRequest(url, options).then((response) => { return readMethod !== "json" ? response[readMethod]() diff --git a/packages/arcgis-rest-portal/test/items/get.test.ts b/packages/arcgis-rest-portal/test/items/get.test.ts index 25c5e895b..65b8d61a8 100644 --- a/packages/arcgis-rest-portal/test/items/get.test.ts +++ b/packages/arcgis-rest-portal/test/items/get.test.ts @@ -14,8 +14,10 @@ import { getItemParts, getRelatedItems, getItemInfo, + rawGetItemInfo, getItemMetadata, - getItemResource + getItemResource, + rawGetItemResource } from "../../src/items/get.js"; import { @@ -175,11 +177,10 @@ describe("get", () => { expect(options.method).toBe("GET"); }); - test("should return raw response item info if desired", async () => { + test("should return raw response item info", async () => { fetchMock.once("*", ItemFormJsonResponse); - const response = await getItemInfo("3ef", { - fileName: "form.json", - rawResponse: true + const response = await rawGetItemInfo("3ef", { + fileName: "form.json" } as IGetItemInfoOptions); const formJson = await response.json(); expect(formJson).toEqual(ItemFormJsonResponse); @@ -404,13 +405,12 @@ describe("get", () => { expect(resource.foo).toEqual("foobarbaz"); }); - test("respects rawResponse setting with JSON resource", async () => { + test("returns raw response for a JSON resource", async () => { const badJsonString = '{"foo":"foobarbaz"}'; fetchMock.once("*", badJsonString); - const response = await getItemResource("3ef", { + const response = await rawGetItemResource("3ef", { fileName: "resource.json", - rawResponse: true, ...MOCK_USER_REQOPTS }); const [url, options] = fetchMock.lastCall("*"); From 711cec5fa582132429e5a41729cb2c48a1ac8f21 Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Tue, 23 Jun 2026 14:59:32 -0700 Subject: [PATCH 02/20] refactor(developer-credentials): migrate request method shape and align tests --- .../src/createApiKey.ts | 5 ++++- .../src/createOAuthApp.ts | 5 ++++- .../src/shared/getRegisteredAppInfo.ts | 5 ++++- .../src/shared/helpers.ts | 10 ++-------- .../src/shared/unregisterApp.ts | 5 ++++- .../src/updateApiKey.ts | 5 ++++- .../src/updateOAuthApp.ts | 5 ++++- .../test/getApiKey.test.ts | 2 +- .../test/shared/getRegisteredAppInfo.test.ts | 2 +- .../test/shared/registerApp.test.ts | 2 +- 10 files changed, 29 insertions(+), 17 deletions(-) diff --git a/packages/arcgis-rest-developer-credentials/src/createApiKey.ts b/packages/arcgis-rest-developer-credentials/src/createApiKey.ts index 4f3c16281..2b1ba500d 100644 --- a/packages/arcgis-rest-developer-credentials/src/createApiKey.ts +++ b/packages/arcgis-rest-developer-credentials/src/createApiKey.ts @@ -62,7 +62,10 @@ import { getRegisteredAppInfo } from "./shared/getRegisteredAppInfo.js"; export async function createApiKey( requestOptions: ICreateApiKeyOptions ): Promise { - requestOptions.httpMethod = "POST"; + requestOptions.fetchOptions = { + ...requestOptions.fetchOptions, + method: "POST" + }; // filter param buckets: const baseRequestOptions = extractBaseRequestOptions(requestOptions); // snapshot of basic IRequestOptions before customized params being built into it diff --git a/packages/arcgis-rest-developer-credentials/src/createOAuthApp.ts b/packages/arcgis-rest-developer-credentials/src/createOAuthApp.ts index a53f35654..efff7b7ae 100644 --- a/packages/arcgis-rest-developer-credentials/src/createOAuthApp.ts +++ b/packages/arcgis-rest-developer-credentials/src/createOAuthApp.ts @@ -48,7 +48,10 @@ import { ICreateOAuthAppOption, IOAuthApp } from "./shared/types/oAuthType.js"; export async function createOAuthApp( requestOptions: ICreateOAuthAppOption ): Promise { - requestOptions.httpMethod = "POST"; + requestOptions.fetchOptions = { + ...requestOptions.fetchOptions, + method: "POST" + }; // filter param buckets: diff --git a/packages/arcgis-rest-developer-credentials/src/shared/getRegisteredAppInfo.ts b/packages/arcgis-rest-developer-credentials/src/shared/getRegisteredAppInfo.ts index 2f4ca60b4..688ded4bb 100644 --- a/packages/arcgis-rest-developer-credentials/src/shared/getRegisteredAppInfo.ts +++ b/packages/arcgis-rest-developer-credentials/src/shared/getRegisteredAppInfo.ts @@ -42,7 +42,10 @@ export async function getRegisteredAppInfo( const url = getPortalUrl(requestOptions) + `/content/users/${userName}/items/${requestOptions.itemId}/registeredAppInfo`; - requestOptions.httpMethod = "POST"; + requestOptions.fetchOptions = { + ...requestOptions.fetchOptions, + method: "POST" + }; const registeredAppResponse: IRegisteredAppResponse = await request(url, { ...requestOptions, diff --git a/packages/arcgis-rest-developer-credentials/src/shared/helpers.ts b/packages/arcgis-rest-developer-credentials/src/shared/helpers.ts index 48bb313d5..a22937f53 100644 --- a/packages/arcgis-rest-developer-credentials/src/shared/helpers.ts +++ b/packages/arcgis-rest-developer-credentials/src/shared/helpers.ts @@ -87,15 +87,9 @@ export function extractBaseRequestOptions( options: T ): Partial { const requestOptionsProperties: Array = [ - "credentials", - "headers", - "hideToken", - "httpMethod", - "maxUrlLength", "portal", - "rawResponse", - "signal", - "suppressWarnings" + "requestFlags", + "fetchOptions" ]; return filterKeys(options, requestOptionsProperties); diff --git a/packages/arcgis-rest-developer-credentials/src/shared/unregisterApp.ts b/packages/arcgis-rest-developer-credentials/src/shared/unregisterApp.ts index 45b89ce8d..fb4fc05db 100644 --- a/packages/arcgis-rest-developer-credentials/src/shared/unregisterApp.ts +++ b/packages/arcgis-rest-developer-credentials/src/shared/unregisterApp.ts @@ -36,7 +36,10 @@ import { request } from "@esri/arcgis-rest-request"; export async function unregisterApp( requestOptions: IUnregisterAppOptions ): Promise { - requestOptions.httpMethod = "POST"; + requestOptions.fetchOptions = { + ...requestOptions.fetchOptions, + method: "POST" + }; // get app const baseRequestOptions = extractBaseRequestOptions(requestOptions); diff --git a/packages/arcgis-rest-developer-credentials/src/updateApiKey.ts b/packages/arcgis-rest-developer-credentials/src/updateApiKey.ts index 55b364145..af1e4dc70 100644 --- a/packages/arcgis-rest-developer-credentials/src/updateApiKey.ts +++ b/packages/arcgis-rest-developer-credentials/src/updateApiKey.ts @@ -63,7 +63,10 @@ import { export async function updateApiKey( requestOptions: IUpdateApiKeyOptions ): Promise { - requestOptions.httpMethod = "POST"; + requestOptions.fetchOptions = { + ...requestOptions.fetchOptions, + method: "POST" + }; const baseRequestOptions = extractBaseRequestOptions(requestOptions); // get base requestOptions snapshot /** diff --git a/packages/arcgis-rest-developer-credentials/src/updateOAuthApp.ts b/packages/arcgis-rest-developer-credentials/src/updateOAuthApp.ts index a3778f843..5699d4791 100644 --- a/packages/arcgis-rest-developer-credentials/src/updateOAuthApp.ts +++ b/packages/arcgis-rest-developer-credentials/src/updateOAuthApp.ts @@ -50,7 +50,10 @@ import { getRegisteredAppInfo } from "./shared/getRegisteredAppInfo.js"; export async function updateOAuthApp( requestOptions: IUpdateOAuthOptions ): Promise { - requestOptions.httpMethod = "POST"; + requestOptions.fetchOptions = { + ...requestOptions.fetchOptions, + method: "POST" + }; // get app const baseRequestOptions = extractBaseRequestOptions(requestOptions); // get base requestOptions snapshot diff --git a/packages/arcgis-rest-developer-credentials/test/getApiKey.test.ts b/packages/arcgis-rest-developer-credentials/test/getApiKey.test.ts index ad69abf49..6f15d1432 100644 --- a/packages/arcgis-rest-developer-credentials/test/getApiKey.test.ts +++ b/packages/arcgis-rest-developer-credentials/test/getApiKey.test.ts @@ -211,7 +211,7 @@ describe("getApiKey()", () => { const apiKeyResponse = await getApiKey({ itemId: "cddcacee5848488bb981e6c6ff91ab79", authentication: authOnline, - httpMethod: "GET" + fetchOptions: { method: "GET" } }); // verify first fetch diff --git a/packages/arcgis-rest-developer-credentials/test/shared/getRegisteredAppInfo.test.ts b/packages/arcgis-rest-developer-credentials/test/shared/getRegisteredAppInfo.test.ts index 9f37c6b53..95748e2b1 100644 --- a/packages/arcgis-rest-developer-credentials/test/shared/getRegisteredAppInfo.test.ts +++ b/packages/arcgis-rest-developer-credentials/test/shared/getRegisteredAppInfo.test.ts @@ -151,7 +151,7 @@ describe("registerApp()", () => { const requestOptions: IGetAppInfoOptions = { itemId: "fake-itemID", authentication: authOnline, - httpMethod: "GET" + fetchOptions: { method: "GET" } }; const appResponse = await getRegisteredAppInfo(requestOptions); diff --git a/packages/arcgis-rest-developer-credentials/test/shared/registerApp.test.ts b/packages/arcgis-rest-developer-credentials/test/shared/registerApp.test.ts index c205b0990..0ff15825d 100644 --- a/packages/arcgis-rest-developer-credentials/test/shared/registerApp.test.ts +++ b/packages/arcgis-rest-developer-credentials/test/shared/registerApp.test.ts @@ -170,7 +170,7 @@ describe("registerApp()", () => { httpReferrers: ["https://www.esri.com/en-us/home"], privileges: [], authentication: authOnline, - httpMethod: "GET" + fetchOptions: { method: "GET" } }; const appResponse = await registerApp(requestOptions); From c08ddd2298aadf082be8629ed7d2a46e195c4d4a Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Tue, 23 Jun 2026 14:59:38 -0700 Subject: [PATCH 03/20] refactor(elevation): migrate request method shape --- .../arcgis-rest-elevation/src/findElevationAtManyPoints.ts | 6 +++++- packages/arcgis-rest-elevation/src/findElevationAtPoint.ts | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/arcgis-rest-elevation/src/findElevationAtManyPoints.ts b/packages/arcgis-rest-elevation/src/findElevationAtManyPoints.ts index d1b0e9017..382bfaaa2 100644 --- a/packages/arcgis-rest-elevation/src/findElevationAtManyPoints.ts +++ b/packages/arcgis-rest-elevation/src/findElevationAtManyPoints.ts @@ -27,7 +27,7 @@ export interface IFindElevationAtManyPointsResponse extends successResponse {} * Options for {@linkcode findElevationAtPoint}. */ export interface IFindElevationAtManyPointsOptions - extends Omit, + extends Omit, queryParams {} /** @@ -74,6 +74,10 @@ export function findElevationAtManyPoints( ); options.params.coordinates = JSON.stringify(requestOptions.coordinates); + options.fetchOptions = { + ...options.fetchOptions, + method: "GET" + }; return ( request(`${baseUrl}/elevation/at-many-points`, { diff --git a/packages/arcgis-rest-elevation/src/findElevationAtPoint.ts b/packages/arcgis-rest-elevation/src/findElevationAtPoint.ts index 0111cfbf4..ce53f2264 100644 --- a/packages/arcgis-rest-elevation/src/findElevationAtPoint.ts +++ b/packages/arcgis-rest-elevation/src/findElevationAtPoint.ts @@ -27,7 +27,7 @@ export interface IFindElevationAtPointResponse extends successResponse {} * Options for {@linkcode findElevationAtPoint}. */ export interface IFindElevationAtPointOptions - extends Omit, + extends Omit, queryParams {} /** @@ -68,7 +68,10 @@ export function findElevationAtPoint( return ( request(`${baseUrl}/elevation/at-point`, { ...options, - httpMethod: "GET" + fetchOptions: { + ...options.fetchOptions, + method: "GET" + } }) as Promise ).then((response) => { const r: IFindElevationAtPointResponse = { From ab05b41c763ba02c226706fadd6820dd2a7fd9ee Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Tue, 23 Jun 2026 14:59:40 -0700 Subject: [PATCH 04/20] refactor(feature-service): migrate request method shape and align tests --- .../src/getAttachments.ts | 7 +++-- .../arcgis-rest-feature-service/src/query.ts | 30 ++++++++++++++----- .../src/queryRelated.ts | 7 ++++- .../test/query.test.ts | 2 +- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/packages/arcgis-rest-feature-service/src/getAttachments.ts b/packages/arcgis-rest-feature-service/src/getAttachments.ts index ba7f7517e..8a21d036a 100644 --- a/packages/arcgis-rest-feature-service/src/getAttachments.ts +++ b/packages/arcgis-rest-feature-service/src/getAttachments.ts @@ -44,8 +44,11 @@ export function getAttachments( requestOptions: IGetAttachmentsOptions ): Promise<{ attachmentInfos: IAttachmentInfo[] }> { const options: IGetAttachmentsOptions = { - httpMethod: "GET", - ...requestOptions + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions.fetchOptions + } }; // pass through diff --git a/packages/arcgis-rest-feature-service/src/query.ts b/packages/arcgis-rest-feature-service/src/query.ts index e0c1edb88..63680abeb 100644 --- a/packages/arcgis-rest-feature-service/src/query.ts +++ b/packages/arcgis-rest-feature-service/src/query.ts @@ -277,8 +277,11 @@ export function getFeature( // default to a GET request const options: IGetFeatureOptions = { - ...{ httpMethod: "GET" }, - ...requestOptions + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions.fetchOptions + } }; return request(url, options).then((response: any) => response.feature); } @@ -296,8 +299,11 @@ export function rawGetFeature( // default to a GET request const options: IGetFeatureOptions = { - ...{ httpMethod: "GET" }, - ...requestOptions + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions.fetchOptions + } }; return rawRequest(url, options); } @@ -362,7 +368,7 @@ export function queryFeatures( "f" ], { - httpMethod: "GET", + fetchOptions: { method: "GET" }, params: { // set default query parameters where: "1=1", @@ -372,6 +378,11 @@ export function queryFeatures( } ); + queryOptions.fetchOptions = { + method: "GET", + ...queryOptions.fetchOptions + }; + if ( queryOptions.params?.f === "pbf-as-geojson" || queryOptions.params?.f === "pbf-as-arcgis" @@ -432,7 +443,7 @@ export async function queryAllFeatures( } else { // retrieve the maxRecordCount for the service only if user did not provide resultRecordCount const pageSizeResponse = await request(requestOptions.url, { - httpMethod: "GET", + fetchOptions: { method: "GET" }, authentication: requestOptions.authentication }); // default the pageSize to 2000 if it is not provided @@ -487,7 +498,7 @@ export async function queryAllFeatures( "f" ], { - httpMethod: "GET", + fetchOptions: { method: "GET" }, params: { where: "1=1", outFields: "*", @@ -497,6 +508,11 @@ export async function queryAllFeatures( } ); + queryOptions.fetchOptions = { + method: "GET", + ...queryOptions.fetchOptions + }; + let response: IQueryAllFeaturesResponse; if ( queryOptions.params?.f === "pbf-as-geojson" || diff --git a/packages/arcgis-rest-feature-service/src/queryRelated.ts b/packages/arcgis-rest-feature-service/src/queryRelated.ts index 7a61a6912..7614bd0da 100644 --- a/packages/arcgis-rest-feature-service/src/queryRelated.ts +++ b/packages/arcgis-rest-feature-service/src/queryRelated.ts @@ -68,7 +68,7 @@ export function queryRelated( requestOptions, ["objectIds", "relationshipId", "definitionExpression", "outFields"], { - httpMethod: "GET", + fetchOptions: { method: "GET" }, params: { // set default query parameters definitionExpression: "1=1", @@ -79,6 +79,11 @@ export function queryRelated( } ); + options.fetchOptions = { + method: "GET", + ...options.fetchOptions + }; + return request( `${cleanUrl(requestOptions.url)}/queryRelatedRecords`, options diff --git a/packages/arcgis-rest-feature-service/test/query.test.ts b/packages/arcgis-rest-feature-service/test/query.test.ts index d6ae64831..8973855c6 100644 --- a/packages/arcgis-rest-feature-service/test/query.test.ts +++ b/packages/arcgis-rest-feature-service/test/query.test.ts @@ -129,7 +129,7 @@ describe("getFeature() and queryFeatures()", () => { relationshipId: 1, definitionExpression: "APPROXACRE<10000", outFields: ["APPROXACRE", "FIELD_NAME"], - httpMethod: "POST" + fetchOptions: { method: "POST" } }; fetchMock.once("*", queryRelatedResponse); const response = await queryRelated(requestOptions); From ec5de03ce52d105a7dafe233f575af60175c8643 Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Tue, 23 Jun 2026 14:59:48 -0700 Subject: [PATCH 05/20] refactor(geocoding): migrate request method shape and align tests --- packages/arcgis-rest-geocoding/src/helpers.ts | 8 +++++--- packages/arcgis-rest-geocoding/test/geocode.test.ts | 2 +- packages/arcgis-rest-geocoding/test/helpers.test.ts | 4 +++- packages/arcgis-rest-geocoding/test/reverse.test.ts | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/arcgis-rest-geocoding/src/helpers.ts b/packages/arcgis-rest-geocoding/src/helpers.ts index 360f9d466..9e89f7151 100644 --- a/packages/arcgis-rest-geocoding/src/helpers.ts +++ b/packages/arcgis-rest-geocoding/src/helpers.ts @@ -47,9 +47,11 @@ export function getGeocodeService( (requestOptions && requestOptions.endpoint) || ARCGIS_ONLINE_GEOCODING_URL; const options: IEndpointOptions = { - httpMethod: "GET", - maxUrlLength: 2000, - ...requestOptions + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions?.fetchOptions + } }; return request(url, options); diff --git a/packages/arcgis-rest-geocoding/test/geocode.test.ts b/packages/arcgis-rest-geocoding/test/geocode.test.ts index 31bcd36bb..112e3b875 100644 --- a/packages/arcgis-rest-geocoding/test/geocode.test.ts +++ b/packages/arcgis-rest-geocoding/test/geocode.test.ts @@ -146,7 +146,7 @@ describe("geocode", () => { address: "380 New York St", postal: 92373 }, - httpMethod: "GET" + fetchOptions: { method: "GET" } }); expect(fetchMock.called()).toEqual(true); const [url, options] = fetchMock.lastCall("*"); diff --git a/packages/arcgis-rest-geocoding/test/helpers.test.ts b/packages/arcgis-rest-geocoding/test/helpers.test.ts index 010cb3ad9..9b1159413 100644 --- a/packages/arcgis-rest-geocoding/test/helpers.test.ts +++ b/packages/arcgis-rest-geocoding/test/helpers.test.ts @@ -34,7 +34,9 @@ describe("geocode", () => { test("should make POST request for metadata from the World Geocoding Service", async () => { fetchMock.once("*", SharingInfo); - const response = await getGeocodeService({ httpMethod: "POST" }); + const response = await getGeocodeService({ + fetchOptions: { method: "POST" } + }); expect(fetchMock.called()).toEqual(true); const [url, options] = fetchMock.lastCall("*"); expect(url).toEqual( diff --git a/packages/arcgis-rest-geocoding/test/reverse.test.ts b/packages/arcgis-rest-geocoding/test/reverse.test.ts index 272ef7049..5685dc807 100644 --- a/packages/arcgis-rest-geocoding/test/reverse.test.ts +++ b/packages/arcgis-rest-geocoding/test/reverse.test.ts @@ -33,7 +33,7 @@ describe("geocode", () => { const response = await reverseGeocode( { x: -118.409, y: 33.9425, spatialReference: { wkid: 4326 } }, - { httpMethod: "GET" } + { fetchOptions: { method: "GET" } } ); expect(fetchMock.called()).toEqual(true); const [url, options] = fetchMock.lastCall("*"); From 28650736ec1c80b4abe9d553c927b272b9ce2aed Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Tue, 23 Jun 2026 14:59:51 -0700 Subject: [PATCH 06/20] refactor(places): migrate request method shape --- packages/arcgis-rest-places/src/findPlacesNearPoint.ts | 7 +++++-- packages/arcgis-rest-places/src/findPlacesWithinExtent.ts | 7 +++++-- packages/arcgis-rest-places/src/getCategories.ts | 7 +++++-- packages/arcgis-rest-places/src/getCategory.ts | 7 +++++-- packages/arcgis-rest-places/src/getPlaceDetails.ts | 7 +++++-- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/arcgis-rest-places/src/findPlacesNearPoint.ts b/packages/arcgis-rest-places/src/findPlacesNearPoint.ts index 80f668312..46477c4ce 100644 --- a/packages/arcgis-rest-places/src/findPlacesNearPoint.ts +++ b/packages/arcgis-rest-places/src/findPlacesNearPoint.ts @@ -30,7 +30,7 @@ export interface IFindPlacesNearPointResponse extends successResponse { * Options for {@linkcode findPlacesNearPoint}. */ export interface IFindPlacesNearPointOptions - extends Omit, + extends Omit, queryParams { /** * Override the URL. This should be the full URL to the API endpoint you want to call. Used internally by Esri staff for testing. @@ -99,7 +99,10 @@ export function findPlacesNearPoint( return ( request(requestOptions.endpoint || `${baseUrl}/places/near-point`, { ...options, - httpMethod: "GET" + fetchOptions: { + ...options.fetchOptions, + method: "GET" + } }) as Promise ).then((response) => { const r: IFindPlacesNearPointResponse = { diff --git a/packages/arcgis-rest-places/src/findPlacesWithinExtent.ts b/packages/arcgis-rest-places/src/findPlacesWithinExtent.ts index 3948920ba..8fbc81265 100644 --- a/packages/arcgis-rest-places/src/findPlacesWithinExtent.ts +++ b/packages/arcgis-rest-places/src/findPlacesWithinExtent.ts @@ -37,7 +37,7 @@ export interface IFindPlacesWithinExtentResponse extends successResponse { * Options for {@linkcode findPlacesNearPoint}. */ export interface IFindPlaceWithinExtentOptions - extends Omit, + extends Omit, queryParams { /** * Override the URL. This should be the full URL to the API endpoint you want to call. Used internally by Esri staff for testing. @@ -108,7 +108,10 @@ export function findPlacesWithinExtent( return ( request(requestOptions.endpoint || `${baseUrl}/places/within-extent`, { ...options, - httpMethod: "GET" + fetchOptions: { + ...options.fetchOptions, + method: "GET" + } }) as Promise ).then((response) => { const r: IFindPlacesWithinExtentResponse = { diff --git a/packages/arcgis-rest-places/src/getCategories.ts b/packages/arcgis-rest-places/src/getCategories.ts index 1d4938485..e25af2e59 100644 --- a/packages/arcgis-rest-places/src/getCategories.ts +++ b/packages/arcgis-rest-places/src/getCategories.ts @@ -28,7 +28,7 @@ export interface IGetCategoriesResponse extends successResponse {} * Options for {@linkcode getCategories}. */ export interface IGetCategoriesOptions - extends Omit, + extends Omit, queryParams { /** * Override the URL. This should be the full URL to the API endpoint you want to call. Used internally by Esri staff for testing. @@ -73,6 +73,9 @@ export function getCategories( return request(requestOptions.endpoint || `${baseUrl}/categories`, { ...options, - httpMethod: "GET" + fetchOptions: { + ...options.fetchOptions, + method: "GET" + } }); } diff --git a/packages/arcgis-rest-places/src/getCategory.ts b/packages/arcgis-rest-places/src/getCategory.ts index 76daf5112..0f0e17938 100644 --- a/packages/arcgis-rest-places/src/getCategory.ts +++ b/packages/arcgis-rest-places/src/getCategory.ts @@ -25,7 +25,7 @@ export interface IGetCategoryResponse extends successResponse {} * Options for {@linkcode getCategory}. */ export interface IGetCategoryOptions - extends Omit, + extends Omit, queryParams { categoryId: string; icon?: IconOptions; @@ -68,7 +68,10 @@ export function getCategory( requestOptions.endpoint || `${baseUrl}/categories/${categoryId}`, { ...options, - httpMethod: "GET" + fetchOptions: { + ...options.fetchOptions, + method: "GET" + } } ); } diff --git a/packages/arcgis-rest-places/src/getPlaceDetails.ts b/packages/arcgis-rest-places/src/getPlaceDetails.ts index 89ff51d7b..62b28608b 100644 --- a/packages/arcgis-rest-places/src/getPlaceDetails.ts +++ b/packages/arcgis-rest-places/src/getPlaceDetails.ts @@ -28,7 +28,7 @@ export interface IGetPlaceResponse extends successResponse {} * Options for {@linkcode getPlaceDetails}. */ export interface IGetPlaceOptions - extends Omit, + extends Omit, queryParams { placeId: string; /** @@ -82,6 +82,9 @@ export function getPlaceDetails( return request(requestOptions.endpoint || `${baseUrl}/places/${placeId}`, { ...options, - httpMethod: "GET" + fetchOptions: { + ...options.fetchOptions, + method: "GET" + } }); } From ddd4ba2fc67a1260b72f3301b1c7e9551a78831d Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Tue, 23 Jun 2026 14:59:54 -0700 Subject: [PATCH 07/20] refactor(portal): migrate request method shape and align tests --- packages/arcgis-rest-portal/src/groups/get.ts | 37 +++++++++++++++---- .../arcgis-rest-portal/src/items/content.ts | 2 +- .../arcgis-rest-portal/src/items/export.ts | 2 +- packages/arcgis-rest-portal/src/items/get.ts | 34 ++++++++++++----- .../src/services/is-service-name-available.ts | 2 +- .../src/sharing/is-item-shared-with-group.ts | 2 +- .../src/users/get-user-properties.ts | 8 +++- .../arcgis-rest-portal/src/users/get-user.ts | 7 +++- .../src/users/invitation.ts | 24 ++++++++++-- .../src/users/notification.ts | 12 +++++- .../src/users/set-user-properties.ts | 2 +- .../src/util/generic-search.ts | 9 ++++- .../src/util/get-portal-settings.ts | 7 +++- .../arcgis-rest-portal/src/util/get-portal.ts | 7 +++- .../src/util/get-subscription-info.ts | 7 +++- .../test/items/search.test.ts | 4 +- 16 files changed, 125 insertions(+), 41 deletions(-) diff --git a/packages/arcgis-rest-portal/src/groups/get.ts b/packages/arcgis-rest-portal/src/groups/get.ts index 926f42d6f..775380e64 100644 --- a/packages/arcgis-rest-portal/src/groups/get.ts +++ b/packages/arcgis-rest-portal/src/groups/get.ts @@ -61,8 +61,11 @@ export function getGroup( const url = `${getPortalUrl(requestOptions)}/community/groups/${id}`; // default to a GET request const options: IRequestOptions = { - ...{ httpMethod: "GET" }, - ...requestOptions + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions?.fetchOptions + } }; return request(url, options); } @@ -85,8 +88,11 @@ export function getGroupCategorySchema( // default to a GET request const options: IRequestOptions = { - ...{ httpMethod: "GET" }, - ...requestOptions + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions?.fetchOptions + } }; return request(url, options); } @@ -106,11 +112,19 @@ export function getGroupContent( // default to a GET request const options: IRequestOptions = { - ...{ httpMethod: "GET" }, + fetchOptions: { + method: "GET", + ...requestOptions?.fetchOptions + }, params: { start: 1, num: 100 }, ...requestOptions } as IGetGroupContentOptions; + options.fetchOptions = { + method: "GET", + ...requestOptions?.fetchOptions + }; + // is this the most concise way to mixin with the defaults above? if (requestOptions && requestOptions.paging) { options.params = { ...requestOptions.paging }; @@ -133,8 +147,11 @@ export function getGroupUsers( const url = `${getPortalUrl(requestOptions)}/community/groups/${id}/users`; // default to a GET request const options: IRequestOptions = { - ...{ httpMethod: "GET" }, - ...requestOptions + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions?.fetchOptions + } }; return request(url, options); } @@ -182,8 +199,12 @@ export function searchGroupUsers( searchOptions || {}, ["name", "num", "start", "sortField", "sortOrder", "joined", "memberType"], { - httpMethod: "GET" + fetchOptions: { method: "GET" } } ); + options.fetchOptions = { + method: "GET", + ...options.fetchOptions + }; return request(url, options); } diff --git a/packages/arcgis-rest-portal/src/items/content.ts b/packages/arcgis-rest-portal/src/items/content.ts index ad8731dc3..c279d9b9a 100644 --- a/packages/arcgis-rest-portal/src/items/content.ts +++ b/packages/arcgis-rest-portal/src/items/content.ts @@ -58,7 +58,7 @@ export const getUserContent = ( ) .then((url) => request(url, { - httpMethod: "GET", + fetchOptions: { method: "GET" }, authentication, params: { start, diff --git a/packages/arcgis-rest-portal/src/items/export.ts b/packages/arcgis-rest-portal/src/items/export.ts index d0cd7ff28..7f8c645ce 100644 --- a/packages/arcgis-rest-portal/src/items/export.ts +++ b/packages/arcgis-rest-portal/src/items/export.ts @@ -80,7 +80,7 @@ export const exportItem = ( ) .then((url) => request(url, { - httpMethod: "POST", + fetchOptions: { method: "POST" }, authentication, params: { itemId, diff --git a/packages/arcgis-rest-portal/src/items/get.ts b/packages/arcgis-rest-portal/src/items/get.ts index cdf205cef..5450dd544 100644 --- a/packages/arcgis-rest-portal/src/items/get.ts +++ b/packages/arcgis-rest-portal/src/items/get.ts @@ -45,8 +45,11 @@ export function getItem( // default to a GET request const options: IRequestOptions = { - ...{ httpMethod: "GET" }, - ...requestOptions + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions?.fetchOptions + } }; return request(url, options).then(async (item: IItem) => { @@ -105,8 +108,12 @@ export function getItemData( const url = `${getItemBaseUrl(id, requestOptions)}/data`; // default to a GET request const options: IItemDataOptions = { - ...{ httpMethod: "GET", params: {} }, - ...requestOptions + ...requestOptions, + params: {}, + fetchOptions: { + method: "GET", + ...requestOptions?.fetchOptions + } }; if (options.file) { @@ -155,7 +162,10 @@ export function getRelatedItems( )}/relatedItems`; const options: IItemRelationshipOptions = { - httpMethod: "GET", + fetchOptions: { + method: "GET", + ...requestOptions.fetchOptions + }, params: { direction: requestOptions.direction }, @@ -433,8 +443,11 @@ export function getItemInfo( ): Promise { const { fileName = "iteminfo.xml", readAs = "text" } = requestOptions || {}; const options: IRequestOptions = { - httpMethod: "GET", - ...requestOptions + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions?.fetchOptions + } }; return getItemFile(id, `/info/${fileName}`, readAs, options); } @@ -453,8 +466,11 @@ export function rawGetItemInfo( const { fileName = "iteminfo.xml" } = requestOptions || {}; const options: IRequestOptions = { params: {}, - httpMethod: "GET", - ...requestOptions + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions?.fetchOptions + } }; options.params.f = null; const url = `${getItemBaseUrl(id, options)}/info/${fileName}`; diff --git a/packages/arcgis-rest-portal/src/services/is-service-name-available.ts b/packages/arcgis-rest-portal/src/services/is-service-name-available.ts index 0e775ec7b..85927499d 100644 --- a/packages/arcgis-rest-portal/src/services/is-service-name-available.ts +++ b/packages/arcgis-rest-portal/src/services/is-service-name-available.ts @@ -22,7 +22,7 @@ export function isServiceNameAvailable( name, type }, - httpMethod: "GET", + fetchOptions: { method: "GET" }, authentication: session }); } diff --git a/packages/arcgis-rest-portal/src/sharing/is-item-shared-with-group.ts b/packages/arcgis-rest-portal/src/sharing/is-item-shared-with-group.ts index b9a678776..2cd542fd4 100644 --- a/packages/arcgis-rest-portal/src/sharing/is-item-shared-with-group.ts +++ b/packages/arcgis-rest-portal/src/sharing/is-item-shared-with-group.ts @@ -29,7 +29,7 @@ export function isItemSharedWithGroup( num: 10, sortField: "title", authentication: requestOptions.authentication, - httpMethod: "POST" + fetchOptions: { method: "POST" } } as ISearchOptions; return searchItems(searchOpts).then((searchResponse) => { diff --git a/packages/arcgis-rest-portal/src/users/get-user-properties.ts b/packages/arcgis-rest-portal/src/users/get-user-properties.ts index 79eec0708..59ff0a01a 100644 --- a/packages/arcgis-rest-portal/src/users/get-user-properties.ts +++ b/packages/arcgis-rest-portal/src/users/get-user-properties.ts @@ -34,7 +34,13 @@ export async function getUserProperties( const url = `${getPortalUrl( requestOptions )}/community/users/${encodeURIComponent(username)}/properties`; - const response = await request(url, { httpMethod: "GET", ...requestOptions }); + const response = await request(url, { + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions.fetchOptions + } + }); if (!response.properties.mapViewer) { response.properties.mapViewer = "modern"; } diff --git a/packages/arcgis-rest-portal/src/users/get-user.ts b/packages/arcgis-rest-portal/src/users/get-user.ts index eee8252df..34a8e4903 100644 --- a/packages/arcgis-rest-portal/src/users/get-user.ts +++ b/packages/arcgis-rest-portal/src/users/get-user.ts @@ -41,7 +41,7 @@ export async function getUser( requestOptions?: string | IGetUserOptions ): Promise { let url; - let options = { httpMethod: "GET" } as IGetUserOptions; + let options = { fetchOptions: { method: "GET" } } as IGetUserOptions; // if a username is passed, assume ArcGIS Online if (typeof requestOptions === "string") { @@ -52,7 +52,10 @@ export async function getUser( url = `${getPortalUrl(requestOptions)}/community/users/${username}`; options = { ...requestOptions, - ...options + fetchOptions: { + method: "GET", + ...requestOptions?.fetchOptions + } }; } // send the request diff --git a/packages/arcgis-rest-portal/src/users/invitation.ts b/packages/arcgis-rest-portal/src/users/invitation.ts index a7de0e146..5a524088d 100644 --- a/packages/arcgis-rest-portal/src/users/invitation.ts +++ b/packages/arcgis-rest-portal/src/users/invitation.ts @@ -52,11 +52,19 @@ export interface IInvitationResult { export async function getUserInvitations( requestOptions: IAuthenticatedRequestOptions ): Promise { - let options = { httpMethod: "GET" } as IAuthenticatedRequestOptions; + let options = { + fetchOptions: { method: "GET" } + } as IAuthenticatedRequestOptions; const username = await determineUsername(requestOptions); const portalUrl = getPortalUrl(requestOptions); const url = `${portalUrl}/community/users/${username}/invitations`; - options = { ...requestOptions, ...options }; + options = { + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions.fetchOptions + } + }; // send the request return request(url, options); @@ -90,8 +98,16 @@ export async function getUserInvitation( const portalUrl = getPortalUrl(requestOptions); const url = `${portalUrl}/community/users/${username}/invitations/${requestOptions.invitationId}`; - let options = { httpMethod: "GET" } as IGetUserInvitationOptions; - options = { ...requestOptions, ...options }; + let options = { + fetchOptions: { method: "GET" } + } as IGetUserInvitationOptions; + options = { + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions.fetchOptions + } + }; // send the request return request(url, options); diff --git a/packages/arcgis-rest-portal/src/users/notification.ts b/packages/arcgis-rest-portal/src/users/notification.ts index 120877cf9..ee27c70fb 100644 --- a/packages/arcgis-rest-portal/src/users/notification.ts +++ b/packages/arcgis-rest-portal/src/users/notification.ts @@ -46,12 +46,20 @@ export interface INotificationResult { export async function getUserNotifications( requestOptions: IAuthenticatedRequestOptions ): Promise { - let options = { httpMethod: "GET" } as IAuthenticatedRequestOptions; + let options = { + fetchOptions: { method: "GET" } + } as IAuthenticatedRequestOptions; const username = await determineUsername(requestOptions); const portalUrl = getPortalUrl(requestOptions); const url = `${portalUrl}/community/users/${username}/notifications`; - options = { ...requestOptions, ...options }; + options = { + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions.fetchOptions + } + }; // send the request return request(url, options); diff --git a/packages/arcgis-rest-portal/src/users/set-user-properties.ts b/packages/arcgis-rest-portal/src/users/set-user-properties.ts index 01f61c96c..fe2647510 100644 --- a/packages/arcgis-rest-portal/src/users/set-user-properties.ts +++ b/packages/arcgis-rest-portal/src/users/set-user-properties.ts @@ -24,7 +24,7 @@ export async function setUserProperties( requestOptions )}/community/users/${encodeURIComponent(username)}/setProperties`; const options: IAuthenticatedRequestOptions = { - httpMethod: "POST", + fetchOptions: { method: "POST" }, params: { properties }, ...requestOptions }; diff --git a/packages/arcgis-rest-portal/src/util/generic-search.ts b/packages/arcgis-rest-portal/src/util/generic-search.ts index 03fae12df..8491fbb68 100644 --- a/packages/arcgis-rest-portal/src/util/generic-search.ts +++ b/packages/arcgis-rest-portal/src/util/generic-search.ts @@ -29,7 +29,7 @@ export function genericSearch( let options: IRequestOptions; if (typeof search === "string" || search instanceof SearchQueryBuilder) { options = { - httpMethod: "GET", + fetchOptions: { method: "GET" }, params: { q: search } @@ -53,11 +53,16 @@ export function genericSearch( "categoryFilters" ], { - httpMethod: "GET" + fetchOptions: { method: "GET" } } ); } + options.fetchOptions = { + method: "GET", + ...options.fetchOptions + }; + let path; switch (searchType) { case "item": diff --git a/packages/arcgis-rest-portal/src/util/get-portal-settings.ts b/packages/arcgis-rest-portal/src/util/get-portal-settings.ts index 2461e811e..ea3c84e56 100644 --- a/packages/arcgis-rest-portal/src/util/get-portal-settings.ts +++ b/packages/arcgis-rest-portal/src/util/get-portal-settings.ts @@ -37,8 +37,11 @@ export function getPortalSettings( // default to a GET request const options: IRequestOptions = { - ...{ httpMethod: "GET" }, - ...requestOptions + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions?.fetchOptions + } }; // send the request diff --git a/packages/arcgis-rest-portal/src/util/get-portal.ts b/packages/arcgis-rest-portal/src/util/get-portal.ts index e8bb08f83..da4177dd0 100644 --- a/packages/arcgis-rest-portal/src/util/get-portal.ts +++ b/packages/arcgis-rest-portal/src/util/get-portal.ts @@ -47,8 +47,11 @@ export function getPortal( // default to a GET request const options: IRequestOptions = { - ...{ httpMethod: "GET" }, - ...requestOptions + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions?.fetchOptions + } }; // send the request diff --git a/packages/arcgis-rest-portal/src/util/get-subscription-info.ts b/packages/arcgis-rest-portal/src/util/get-subscription-info.ts index 45b20f264..66d4fa31b 100644 --- a/packages/arcgis-rest-portal/src/util/get-subscription-info.ts +++ b/packages/arcgis-rest-portal/src/util/get-subscription-info.ts @@ -36,8 +36,11 @@ export function getSubscriptionInfo( // default to a GET request const options: IRequestOptions = { - ...{ httpMethod: "GET" }, - ...requestOptions + ...requestOptions, + fetchOptions: { + method: "GET", + ...requestOptions?.fetchOptions + } }; // send the request diff --git a/packages/arcgis-rest-portal/test/items/search.test.ts b/packages/arcgis-rest-portal/test/items/search.test.ts index 79bfe935c..07e5cec7d 100644 --- a/packages/arcgis-rest-portal/test/items/search.test.ts +++ b/packages/arcgis-rest-portal/test/items/search.test.ts @@ -140,7 +140,7 @@ describe("search", () => { start: 22, sortField: "title", sortOrder: "desc", - httpMethod: "POST" + fetchOptions: { method: "POST" } }); expect(fetchMock.called()).toEqual(true); const [url, options] = fetchMock.lastCall("*"); @@ -165,7 +165,7 @@ describe("search", () => { start: 22, sortField: "title", sortOrder: "desc", - httpMethod: "POST" + fetchOptions: { method: "POST" } }); expect(fetchMock.called()).toEqual(true); const [url, options] = fetchMock.lastCall("*"); From 762af37408452ae3a6ab8a7710db99485d5ee756 Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Tue, 23 Jun 2026 15:00:00 -0700 Subject: [PATCH 08/20] refactor(request): migrate request method shape --- packages/arcgis-rest-request/src/ArcGISIdentityManager.ts | 8 ++++++-- .../arcgis-rest-request/src/AuthenticationManagerBase.ts | 6 +++++- packages/arcgis-rest-request/src/request.ts | 2 +- packages/arcgis-rest-request/src/revoke-token.ts | 5 ++++- .../arcgis-rest-request/src/utils/ITokenRequestOptions.ts | 6 ++---- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/arcgis-rest-request/src/ArcGISIdentityManager.ts b/packages/arcgis-rest-request/src/ArcGISIdentityManager.ts index e7fe89201..f2a99ad0b 100644 --- a/packages/arcgis-rest-request/src/ArcGISIdentityManager.ts +++ b/packages/arcgis-rest-request/src/ArcGISIdentityManager.ts @@ -672,7 +672,7 @@ export class ArcGISIdentityManager // exchange our auth code for a token + refresh token return fetchToken(tokenEndpoint, { - httpMethod: "POST", + fetchOptions: { method: "POST" }, params: { client_id: clientId, code_verifier: codeVerifier, @@ -1171,11 +1171,15 @@ export class ArcGISIdentityManager const url = `${this.portal}/portals/self`; const options = { - httpMethod: "GET", authentication: this, ...requestOptions } as IRequestOptions; + options.fetchOptions = { + method: "GET", + ...requestOptions?.fetchOptions + }; + this._pendingPortalRequest = request(url, options).then((response) => { this._portalInfo = response; this._pendingPortalRequest = null; diff --git a/packages/arcgis-rest-request/src/AuthenticationManagerBase.ts b/packages/arcgis-rest-request/src/AuthenticationManagerBase.ts index d6c9d24cf..223f99f16 100644 --- a/packages/arcgis-rest-request/src/AuthenticationManagerBase.ts +++ b/packages/arcgis-rest-request/src/AuthenticationManagerBase.ts @@ -86,11 +86,15 @@ class AuthenticationManagerBase { const url = `${this.portal}/community/self`; const options = { - httpMethod: "GET", authentication: this, ...requestOptions } as IRequestOptions; + options.fetchOptions = { + method: "GET", + ...requestOptions?.fetchOptions + }; + this._pendingUserRequest = request(url, options).then((response) => { this._user = response; this._pendingUserRequest = null; diff --git a/packages/arcgis-rest-request/src/request.ts b/packages/arcgis-rest-request/src/request.ts index 0e9668f5f..3a30dd198 100644 --- a/packages/arcgis-rest-request/src/request.ts +++ b/packages/arcgis-rest-request/src/request.ts @@ -656,7 +656,7 @@ export async function rawRequest( * .then(response) // response.currentVersion === 5.2 * * request('https://www.arcgis.com/sharing/rest', { - * httpMethod: "GET" + * fetchOptions: { method: "GET" } * }) * * request('https://www.arcgis.com/sharing/rest/search', { diff --git a/packages/arcgis-rest-request/src/revoke-token.ts b/packages/arcgis-rest-request/src/revoke-token.ts index 639ffa4d9..559c2d11a 100644 --- a/packages/arcgis-rest-request/src/revoke-token.ts +++ b/packages/arcgis-rest-request/src/revoke-token.ts @@ -48,7 +48,10 @@ export function revokeToken( const options: IRequestOptions = { ...requestOptions, - httpMethod: "POST", + fetchOptions: { + ...requestOptions.fetchOptions, + method: "POST" + }, params: { client_id: clientId, auth_token: token diff --git a/packages/arcgis-rest-request/src/utils/ITokenRequestOptions.ts b/packages/arcgis-rest-request/src/utils/ITokenRequestOptions.ts index 158094bcd..1dd5dab49 100644 --- a/packages/arcgis-rest-request/src/utils/ITokenRequestOptions.ts +++ b/packages/arcgis-rest-request/src/utils/ITokenRequestOptions.ts @@ -1,9 +1,7 @@ -import { HTTPMethods } from "./HTTPMethods.js"; +import { IRequestOptions } from "./IRequestOptions.js"; import { IGenerateTokenParams } from "./IGenerateTokenParams.js"; import { IFetchTokenParams } from "./IFetchTokenParams.js"; -export interface ITokenRequestOptions { +export interface ITokenRequestOptions extends IRequestOptions { params?: IGenerateTokenParams | IFetchTokenParams; - httpMethod?: HTTPMethods; - fetch?: (input: RequestInfo, init?: RequestInit) => Promise; } From b6dea54d074e758ed5769dae83174623214056c0 Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Tue, 23 Jun 2026 15:00:02 -0700 Subject: [PATCH 09/20] refactor(basemap-sessions): migrate request method shape --- .../arcgis-rest-basemap-sessions/src/utils/startNewSession.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/arcgis-rest-basemap-sessions/src/utils/startNewSession.ts b/packages/arcgis-rest-basemap-sessions/src/utils/startNewSession.ts index c37ff43ad..e1adf0839 100644 --- a/packages/arcgis-rest-basemap-sessions/src/utils/startNewSession.ts +++ b/packages/arcgis-rest-basemap-sessions/src/utils/startNewSession.ts @@ -23,7 +23,7 @@ export function startNewSession({ duration = DEFAULT_DURATION }: IRequestNewSessionParams): Promise { return request(startSessionUrl, { - httpMethod: "GET", + fetchOptions: { method: "GET" }, authentication: authentication, params: { styleFamily, durationSeconds: duration } }); From ead9c35baf1a32c5b2730ea3c1d6476225db8c13 Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Fri, 26 Jun 2026 10:59:15 -0700 Subject: [PATCH 10/20] remove raw geocode --- packages/arcgis-rest-geocoding/src/geocode.ts | 120 ++++++------------ .../test/geocode.test.ts | 31 +---- 2 files changed, 37 insertions(+), 114 deletions(-) diff --git a/packages/arcgis-rest-geocoding/src/geocode.ts b/packages/arcgis-rest-geocoding/src/geocode.ts index 567b9620f..6e87a0926 100644 --- a/packages/arcgis-rest-geocoding/src/geocode.ts +++ b/packages/arcgis-rest-geocoding/src/geocode.ts @@ -96,7 +96,7 @@ export interface IGeocodeResponse { * @param address String representing the address or point of interest or RequestOptions to pass to the endpoint. * @returns A Promise that will resolve with address candidates for the request. The spatial reference will be added to candidate locations and extents. */ -export function geocode( +export async function geocode( address: string | IGeocodeOptions ): Promise { let options: IGeocodeOptions = {}; @@ -135,91 +135,43 @@ export function geocode( } } - // add spatialReference property to individual matches - return request(`${cleanUrl(endpoint)}/findAddressCandidates`, options).then( - (response) => { - const sr: ISpatialReference = response.spatialReference; - response.candidates.forEach(function (candidate: { - location: IPoint; - extent?: IExtent; - }) { - candidate.location.spatialReference = sr; - if (candidate.extent) { - candidate.extent.spatialReference = sr; - } - }); - - // geoJson - if (sr.wkid === 4326) { - const features = response.candidates.map((candidate: any) => { - return { - type: "Feature", - geometry: arcgisToGeoJSON(candidate.location), - properties: Object.assign( - { - address: candidate.address, - score: candidate.score - }, - candidate.attributes - ) - }; - }); - - response.geoJson = { - type: "FeatureCollection", - features - }; - } - - return response; - } + const response = await request( + `${cleanUrl(endpoint)}/findAddressCandidates`, + options ); -} - -/** - * Used to determine the location of a single address or point of interest and return the native response. - * - * @param address String representing the address or point of interest or RequestOptions to pass to the endpoint. - * @returns A Promise that will resolve with the native response. - */ -export function rawGeocode( - address: string | IGeocodeOptions -): Promise { - let options: IGeocodeOptions = {}; - let endpoint: string; - - if (typeof address === "string") { - options.params = { singleLine: address }; - endpoint = ARCGIS_ONLINE_GEOCODING_URL; - } else { - endpoint = address.endpoint || ARCGIS_ONLINE_GEOCODING_URL; - options = appendCustomParams( - address, - [ - "singleLine", - "address", - "address2", - "address3", - "neighborhood", - "city", - "subregion", - "region", - "postal", - "postalExt", - "countryCode", - "outFields", - "magicKey" - ], - { params: { ...address.params } } - ); - - if (options.params.postal && typeof options.params.postal === "number") { - warn( - "The postal code should be a string. " + - "Issues can arise when using it as a number, especially if they start with zero." - ); + const sr: ISpatialReference = response.spatialReference; + // add spatialReference property to individual matches + response.candidates.forEach(function (candidate: { + location: IPoint; + extent?: IExtent; + }) { + candidate.location.spatialReference = sr; + if (candidate.extent) { + candidate.extent.spatialReference = sr; } + }); + + // geoJson + if (sr.wkid === 4326) { + const features = response.candidates.map((candidate: any) => { + return { + type: "Feature", + geometry: arcgisToGeoJSON(candidate.location), + properties: Object.assign( + { + address: candidate.address, + score: candidate.score + }, + candidate.attributes + ) + }; + }); + + response.geoJson = { + type: "FeatureCollection", + features + }; } - return rawRequest(`${cleanUrl(endpoint)}/findAddressCandidates`, options); + return response; } diff --git a/packages/arcgis-rest-geocoding/test/geocode.test.ts b/packages/arcgis-rest-geocoding/test/geocode.test.ts index 112e3b875..b8c62da97 100644 --- a/packages/arcgis-rest-geocoding/test/geocode.test.ts +++ b/packages/arcgis-rest-geocoding/test/geocode.test.ts @@ -3,7 +3,7 @@ import { describe, test, afterEach, expect } from "vitest"; import fetchMock from "fetch-mock"; -import { geocode, rawGeocode } from "../src/geocode.js"; +import { geocode } from "../src/geocode.js"; import { FindAddressCandidates, FindAddressCandidates3857, @@ -177,35 +177,6 @@ describe("geocode", () => { ).toBe(true); }); - test("should return raw response from rawGeocode", async () => { - fetchMock.once("*", FindAddressCandidates); - const response: any = await rawGeocode({ - address: "1600 Pennsylvania Avenue", - city: "Washington D.C." - }); - expect(fetchMock.called()).toEqual(true); - const [url, options] = fetchMock.lastCall("*"); - expect(url).toEqual( - "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates" - ); - expect(options.method).toBe("POST"); - expect(options.body).toContain("f=json"); - expect(options.body).toContain( - `address=${encodeURIComponent("1600 Pennsylvania Avenue")}` - ); - expect(options.body).toContain( - `city=${encodeURIComponent("Washington D.C.")}` - ); - expect(options.method).toBe("POST"); - expect(response.status).toBe(200); - expect(response.ok).toBe(true); - expect(response.body.Readable).not.toBe(null); - const raw = await response.json(); - expect(raw).toEqual(FindAddressCandidates); - // this used to work with isomorphic-fetch - // expect(response instanceof Response).toBe(true); - }); - test("should make a single geocoding request with a postal code as a string", async () => { fetchMock.once("*", FindAddressCandidates); From 68c64f9ec800c78e5743e2f3dd0b932e8a159863 Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Fri, 26 Jun 2026 11:00:38 -0700 Subject: [PATCH 11/20] remove raw bulk geocode --- packages/arcgis-rest-geocoding/src/bulk.ts | 66 ++++--------------- .../arcgis-rest-geocoding/test/bulk.test.ts | 31 +-------- 2 files changed, 15 insertions(+), 82 deletions(-) diff --git a/packages/arcgis-rest-geocoding/src/bulk.ts b/packages/arcgis-rest-geocoding/src/bulk.ts index 27d2ce25e..77d447bb8 100644 --- a/packages/arcgis-rest-geocoding/src/bulk.ts +++ b/packages/arcgis-rest-geocoding/src/bulk.ts @@ -70,7 +70,7 @@ export interface IBulkGeocodeResponse { * @param requestOptions - Request options to pass to the geocoder, including an array of addresses and authentication session. * @returns A Promise that will resolve with the data from the response. The spatial reference will be added to address locations. */ -export function bulkGeocode( +export async function bulkGeocode( requestOptions: IBulkGeocodeOptions // must POST, which is the default ): Promise { const options: IBulkGeocodeOptions = { @@ -85,63 +85,25 @@ export function bulkGeocode( }) }; - const endpoint = options.endpoint; - // the SAS service does not support anonymous requests if ( !requestOptions.authentication && - endpoint === ARCGIS_ONLINE_BULK_GEOCODING_URL + options.endpoint === ARCGIS_ONLINE_BULK_GEOCODING_URL ) { - return Promise.reject( - "bulk geocoding using the ArcGIS service requires authentication" + throw new Error( + "bulk geocoding using the ArcGIS service requires authentication." ); } - return request(`${cleanUrl(endpoint)}/geocodeAddresses`, options).then( - (response) => { - const sr = response.spatialReference; - response.locations.forEach(function (address: { location: IPoint }) { - if (address.location) { - address.location.spatialReference = sr; - } - }); - return response; - } + const response = await request( + `${cleanUrl(options.endpoint)}/geocodeAddresses`, + options ); -} - -/** - * Used to geocode a batch of addresses and return the native response. - * - * @param requestOptions - Request options to pass to the geocoder, including an array of addresses and authentication session. - * @returns A Promise that resolves with the native response. - */ -export function rawBulkGeocode( - requestOptions: IBulkGeocodeOptions -): Promise { - const options: IBulkGeocodeOptions = { - endpoint: ARCGIS_ONLINE_BULK_GEOCODING_URL, - params: {}, - ...requestOptions - }; - - options.params.addresses = { - records: requestOptions.addresses.map((address) => { - return { attributes: address }; - }) - }; - - const endpoint = options.endpoint; - - // the SAS service does not support anonymous requests - if ( - !requestOptions.authentication && - endpoint === ARCGIS_ONLINE_BULK_GEOCODING_URL - ) { - return Promise.reject( - "bulk geocoding using the ArcGIS service requires authentication" - ); - } - - return rawRequest(`${cleanUrl(endpoint)}/geocodeAddresses`, options); + const sr = response.spatialReference; + response.locations.forEach(function (address: { location: IPoint }) { + if (address.location) { + address.location.spatialReference = sr; + } + }); + return response; } diff --git a/packages/arcgis-rest-geocoding/test/bulk.test.ts b/packages/arcgis-rest-geocoding/test/bulk.test.ts index b9f591101..c38076b08 100644 --- a/packages/arcgis-rest-geocoding/test/bulk.test.ts +++ b/packages/arcgis-rest-geocoding/test/bulk.test.ts @@ -3,7 +3,7 @@ import { describe, test, afterEach, expect } from "vitest"; import fetchMock from "fetch-mock"; -import { bulkGeocode, rawBulkGeocode } from "../src/bulk.js"; +import { bulkGeocode } from "../src/bulk.js"; import { GeocodeAddresses } from "./mocks/responses.js"; const addresses = [ @@ -179,33 +179,4 @@ describe("geocode", () => { ); expect(response.spatialReference.latestWkid).toEqual(4326); }); - - test("should return raw response from rawBulkGeocode", async () => { - fetchMock.once("*", GeocodeAddresses); - - const MOCK_AUTH = { - getToken() { - return Promise.resolve("token"); - }, - portal: "https://mapsdev.arcgis.com" - }; - - const response: any = await rawBulkGeocode({ - addresses, - authentication: MOCK_AUTH - }); - expect(fetchMock.called()).toEqual(true); - const [url, options] = fetchMock.lastCall("*"); - expect(url).toEqual( - "https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/geocodeAddresses" - ); - expect(options.method).toBe("POST"); - expect(response.status).toBe(200); - expect(response.ok).toBe(true); - expect(response.body.Readable).not.toBe(null); - const raw = await response.json(); - expect(raw).toEqual(GeocodeAddresses); - // this used to work with isomorphic-fetch - // expect(response instanceof Response).toBe(true); - }); }); From 80b2f77fb956f71b9227e293be739f9014604dcc Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Fri, 26 Jun 2026 11:03:38 -0700 Subject: [PATCH 12/20] remove raw get feature --- .../arcgis-rest-feature-service/src/query.ts | 22 ------------------- .../test/query.test.ts | 22 ------------------- 2 files changed, 44 deletions(-) diff --git a/packages/arcgis-rest-feature-service/src/query.ts b/packages/arcgis-rest-feature-service/src/query.ts index 63680abeb..1ded6cd12 100644 --- a/packages/arcgis-rest-feature-service/src/query.ts +++ b/packages/arcgis-rest-feature-service/src/query.ts @@ -286,28 +286,6 @@ export function getFeature( return request(url, options).then((response: any) => response.feature); } -/** - * Get a feature by id and return the native response. - * - * @param requestOptions - Options for the request. - * @returns A Promise that resolves with the native response. - */ -export function rawGetFeature( - requestOptions: IGetFeatureOptions -): Promise { - const url = `${cleanUrl(requestOptions.url)}/${requestOptions.id}`; - - // default to a GET request - const options: IGetFeatureOptions = { - ...requestOptions, - fetchOptions: { - method: "GET", - ...requestOptions.fetchOptions - } - }; - return rawRequest(url, options); -} - /** * Query a feature service. See [REST Documentation](https://developers.arcgis.com/rest/services-reference/query-feature-service-layer-.htm) for more information. * diff --git a/packages/arcgis-rest-feature-service/test/query.test.ts b/packages/arcgis-rest-feature-service/test/query.test.ts index 8973855c6..7f2d2e55c 100644 --- a/packages/arcgis-rest-feature-service/test/query.test.ts +++ b/packages/arcgis-rest-feature-service/test/query.test.ts @@ -5,7 +5,6 @@ import { describe, afterEach, test, expect, vi, beforeEach } from "vitest"; import fetchMock from "fetch-mock"; import { getFeature, - rawGetFeature, queryFeatures, queryAllFeatures, queryRelated, @@ -52,27 +51,6 @@ describe("getFeature() and queryFeatures()", () => { expect(response.attributes.FID).toBe(42); }); - test("returns raw response when getting a feature", async () => { - const requestOptions = { - url: serviceUrl, - id: 42 - }; - fetchMock.once("*", featureResponse); - - const response: any = await rawGetFeature(requestOptions); - - expect(fetchMock.called()).toBeTruthy(); - const [url, options] = fetchMock.lastCall("*"); - expect(url).toBe(`${requestOptions.url}/42?f=json`); - expect(options.method).toBe("GET"); - expect(response.status).toBe(200); - expect(response.ok).toBe(true); - expect(response.body.Readable).not.toBe(null); - - const raw = await response.json(); - expect(raw).toEqual(featureResponse); - }); - test("should supply default query parameters", async () => { const requestOptions: IQueryFeaturesOptions = { url: serviceUrl From a4f1ec21303c8ab3cbe8d297ef065b79917014e3 Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Fri, 26 Jun 2026 11:16:13 -0700 Subject: [PATCH 13/20] remove raw get item resource and info --- packages/arcgis-rest-portal/src/items/get.ts | 65 +++---------------- .../arcgis-rest-portal/test/items/get.test.ts | 36 +--------- 2 files changed, 10 insertions(+), 91 deletions(-) diff --git a/packages/arcgis-rest-portal/src/items/get.ts b/packages/arcgis-rest-portal/src/items/get.ts index 5450dd544..647a670e3 100644 --- a/packages/arcgis-rest-portal/src/items/get.ts +++ b/packages/arcgis-rest-portal/src/items/get.ts @@ -258,7 +258,7 @@ export interface IGetItemResourceOptions extends IRequestOptions { * .then(resourceContents => {}); * * // Get the response object instead - * rawGetItemResource("3ef", { fileName: "resource.json" }) + * getItemResource("3ef", { fileName: "resource.json" }) * .then(response => {}) * ``` * @@ -278,28 +278,6 @@ export function getItemResource( ); } -/** - * Fetches an item resource and returns the native response. - * - * @param itemId - The item id. - * @param requestOptions - Options for the request. - * @returns A Promise that resolves with the native response. - */ -export function rawGetItemResource( - itemId: string, - requestOptions: IGetItemResourceOptions -): Promise { - const url = `${getItemBaseUrl(itemId, requestOptions)}/resources/${ - requestOptions.fileName - }`; - const options: IRequestOptions = { - params: {}, - ...requestOptions - }; - options.params.f = null; - return rawRequest(url, options); -} - /** * Lists the groups of which the item is a part, only showing the groups that the calling user can access. See the [REST Documentation](https://developers.arcgis.com/rest/users-groups-and-items/groups.htm) for more information. * @@ -452,31 +430,6 @@ export function getItemInfo( return getItemFile(id, `/info/${fileName}`, readAs, options); } -/** - * Get an info file for an item and return the native response. - * - * @param id - Item Id. - * @param requestOptions - Options for the request, including the file name which defaults to `iteminfo.xml`. - * @returns A Promise that resolves with the native response. - */ -export function rawGetItemInfo( - id: string, - requestOptions?: IGetItemInfoOptions -): Promise { - const { fileName = "iteminfo.xml" } = requestOptions || {}; - const options: IRequestOptions = { - params: {}, - ...requestOptions, - fetchOptions: { - method: "GET", - ...requestOptions?.fetchOptions - } - }; - options.params.f = null; - const url = `${getItemBaseUrl(id, options)}/info/${fileName}`; - return rawRequest(url, options); -} - /** * ``` * import { getItemMetadata } from "@esri/arcgis-rest-portal"; @@ -506,7 +459,7 @@ export function getItemMetadata( // overrides request()'s default behavior for reading the response // which is based on `params.f` and defaults to JSON // Also adds JSON parse error protection by sanitizing out any unescaped control characters before parsing -function getItemFile( +async function getItemFile( id: string, // NOTE: fileName should include any folder/subfolders fileName: string, @@ -521,11 +474,11 @@ function getItemFile( }; options.params.f = null; - return rawRequest(url, options).then((response) => { - return readMethod !== "json" - ? response[readMethod]() - : response - .text() - .then((text: string) => JSON.parse(scrubControlChars(text))); - }); + const response = await rawRequest(url, options); + + return readMethod !== "json" + ? response[readMethod]() + : response + .text() + .then((text: string) => JSON.parse(scrubControlChars(text))); } diff --git a/packages/arcgis-rest-portal/test/items/get.test.ts b/packages/arcgis-rest-portal/test/items/get.test.ts index 65b8d61a8..b2eb7de48 100644 --- a/packages/arcgis-rest-portal/test/items/get.test.ts +++ b/packages/arcgis-rest-portal/test/items/get.test.ts @@ -14,10 +14,8 @@ import { getItemParts, getRelatedItems, getItemInfo, - rawGetItemInfo, getItemMetadata, - getItemResource, - rawGetItemResource + getItemResource } from "../../src/items/get.js"; import { @@ -177,21 +175,6 @@ describe("get", () => { expect(options.method).toBe("GET"); }); - test("should return raw response item info", async () => { - fetchMock.once("*", ItemFormJsonResponse); - const response = await rawGetItemInfo("3ef", { - fileName: "form.json" - } as IGetItemInfoOptions); - const formJson = await response.json(); - expect(formJson).toEqual(ItemFormJsonResponse); - expect(fetchMock.called()).toEqual(true); - const [url, options] = fetchMock.lastCall("*"); - expect(url).toEqual( - "https://www.arcgis.com/sharing/rest/content/items/3ef/info/form.json" - ); - expect(options.method).toBe("GET"); - }); - test("should return item info JSON files", async () => { fetchMock.once("*", ItemFormJsonResponse); const formJson = await getItemInfo("3ef", { @@ -404,23 +387,6 @@ describe("get", () => { expect(options.method).toBe("POST"); expect(resource.foo).toEqual("foobarbaz"); }); - - test("returns raw response for a JSON resource", async () => { - const badJsonString = '{"foo":"foobarbaz"}'; - fetchMock.once("*", badJsonString); - - const response = await rawGetItemResource("3ef", { - fileName: "resource.json", - ...MOCK_USER_REQOPTS - }); - const [url, options] = fetchMock.lastCall("*"); - expect(url).toEqual( - "https://myorg.maps.arcgis.com/sharing/rest/content/items/3ef/resources/resource.json" - ); - expect(options.method).toBe("POST"); - expect(response.json).toBeDefined(); - await expect(response.json()).rejects.toBeDefined(); - }); }); test("get item groups anonymously", async () => { From 2e70e57625e3f701c147c9c163ded0df3b5c46b9 Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Tue, 30 Jun 2026 11:54:12 -0700 Subject: [PATCH 14/20] handle queryFeatures and queryFeaturesRaw --- .../arcgis-rest-feature-service/src/query.ts | 149 ++++++++++-------- .../test/query.test.live.ts | 16 +- .../test/query.test.ts | 82 +++++++++- 3 files changed, 171 insertions(+), 76 deletions(-) diff --git a/packages/arcgis-rest-feature-service/src/query.ts b/packages/arcgis-rest-feature-service/src/query.ts index 1ded6cd12..4121ee166 100644 --- a/packages/arcgis-rest-feature-service/src/query.ts +++ b/packages/arcgis-rest-feature-service/src/query.ts @@ -80,7 +80,7 @@ export interface IQueryFeaturesOptions extends ISharedQueryOptions { /** * Response format. Defaults to "json". */ - f?: "json" | "geojson" | "pbf" | "pbf-as-geojson" | "pbf-as-arcgis"; + f?: "json" | "geojson" | "pbf-as-geojson" | "pbf-as-arcgis"; /** * someday... * @@ -133,7 +133,7 @@ export interface IQueryAllFeaturesOptions extends ISharedQueryOptions { returnExceededLimitFeatures?: true; /** * Response format. Defaults to "json" - * NOTE: for "pbf" you must use the method `rawRequest()` + * NOTE: for "f=pbf" you must use the method `queryFeaturesRaw()` * and parse the response yourself using `response.arrayBuffer()` */ f?: "json" | "geojson" | "pbf-as-geojson" | "pbf-as-arcgis"; @@ -156,6 +156,76 @@ export interface IQueryResponse { objectIds?: number[]; } +export interface IQueryFeaturesRawOptions + extends Omit { + /** + * Response format for raw queries. Includes "pbf" for callers that need direct binary handling. + */ + f?: IQueryFeaturesOptions["f"] | "pbf"; +} + +function prepareQueryFeaturesOptions( + requestOptions: IQueryFeaturesRawOptions | IQueryFeaturesOptions +): IRequestOptions { + const queryOptions = appendCustomParams( + requestOptions, + [ + "where", + "objectIds", + "relationParam", + "time", + "distance", + "units", + "outFields", + "geometry", + "geometryType", + "spatialRel", + "returnGeometry", + "maxAllowableOffset", + "geometryPrecision", + "inSR", + "outSR", + "gdbVersion", + "returnDistinctValues", + "returnIdsOnly", + "returnCountOnly", + "returnExtentOnly", + "orderByFields", + "groupByFieldsForStatistics", + "outStatistics", + "returnZ", + "returnM", + "multipatchOption", + "resultOffset", + "resultRecordCount", + "quantizationParameters", + "returnCentroid", + "resultType", + "historicMoment", + "returnTrueCurves", + "sqlFormat", + "returnExceededLimitFeatures", + "f" + ], + { + fetchOptions: { method: "GET" }, + params: { + // set default query parameters + where: "1=1", + outFields: "*", + ...requestOptions.params + } + } + ); + + queryOptions.fetchOptions = { + method: "GET", + ...queryOptions.fetchOptions + }; + + return queryOptions; +} + /** * Query and decode pbf features on the client. Improves performance on slow networks and large queries. * Handles both f=pbf-as-geojson and f=pbf-as-arcgis format query params and handles errors. @@ -305,76 +375,29 @@ export function getFeature( export function queryFeatures( requestOptions: IQueryFeaturesOptions ): Promise { - const queryOptions = appendCustomParams( - requestOptions, - [ - "where", - "objectIds", - "relationParam", - "time", - "distance", - "units", - "outFields", - "geometry", - "geometryType", - "spatialRel", - "returnGeometry", - "maxAllowableOffset", - "geometryPrecision", - "inSR", - "outSR", - "gdbVersion", - "returnDistinctValues", - "returnIdsOnly", - "returnCountOnly", - "returnExtentOnly", - "orderByFields", - "groupByFieldsForStatistics", - "outStatistics", - "returnZ", - "returnM", - "multipatchOption", - "resultOffset", - "resultRecordCount", - "quantizationParameters", - "returnCentroid", - "resultType", - "historicMoment", - "returnTrueCurves", - "sqlFormat", - "returnExceededLimitFeatures", - "f" - ], - { - fetchOptions: { method: "GET" }, - params: { - // set default query parameters - where: "1=1", - outFields: "*", - ...requestOptions.params - } - } - ); - - queryOptions.fetchOptions = { - method: "GET", - ...queryOptions.fetchOptions - }; - + const queryOptions = prepareQueryFeaturesOptions(requestOptions); if ( queryOptions.params?.f === "pbf-as-geojson" || queryOptions.params?.f === "pbf-as-arcgis" ) { return queryPbfAsGeoJSONOrArcGIS(requestOptions.url, queryOptions); - } else if (queryOptions.params?.f === "pbf") { - return rawRequest( - `${cleanUrl(requestOptions.url)}/query`, - queryOptions - ) as Promise; } return request(`${cleanUrl(requestOptions.url)}/query`, queryOptions); } +/** + * Query a feature service and return the native response. + * + * @param requestOptions - Options for the request + * @returns A Promise that resolves with the native response. + */ +export function queryFeaturesRaw( + requestOptions: IQueryFeaturesRawOptions +): Promise { + const queryOptions = prepareQueryFeaturesOptions(requestOptions); + return rawRequest(`${cleanUrl(requestOptions.url)}/query`, queryOptions); +} + /** * Query a feature service to retrieve all features. See [REST Documentation](https://developers.arcgis.com/rest/services-reference/query-feature-service-layer-.htm) for more information. * diff --git a/packages/arcgis-rest-feature-service/test/query.test.live.ts b/packages/arcgis-rest-feature-service/test/query.test.live.ts index c881cc6f2..448fcef3f 100644 --- a/packages/arcgis-rest-feature-service/test/query.test.live.ts +++ b/packages/arcgis-rest-feature-service/test/query.test.live.ts @@ -3,9 +3,11 @@ import { describe, afterEach, test, expect } from "vitest"; import { IQueryAllFeaturesOptions, IQueryFeaturesOptions, + IQueryFeaturesRawOptions, IQueryFeaturesResponse, queryAllFeatures, - queryFeatures + queryFeatures, + queryFeaturesRaw } from "../src/index.js"; import pbfToArcGIS from "../src/pbf-parser/arcGISPbfParser.js"; import { readEnvironmentFileToJSON } from "./utils/readFileArrayBuffer.js"; @@ -442,14 +444,14 @@ describe("queryFeatures() and queryAllFeatures() live tests", () => { }); test("LIVE TEST: should decode POINT pbf to arcgis", async () => { - const zipCodePointsPbfOptions: IQueryFeaturesOptions = { + const zipCodePointsPbfOptions: IQueryFeaturesRawOptions = { url: `https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/USA_ZIP_Code_Points_analysis/FeatureServer/0`, f: "pbf", where: "1=1", outFields: ["*"], resultRecordCount: 1 }; - const response = await queryFeatures(zipCodePointsPbfOptions); + const response = await queryFeaturesRaw(zipCodePointsPbfOptions); const arrBuffer = await (response as any).arrayBuffer(); const arcgis = pbfToArcGIS(arrBuffer); @@ -483,14 +485,14 @@ describe("queryFeatures() and queryAllFeatures() live tests", () => { }); test("LIVE TEST: should decode LINE pbf to arcgis", async () => { - const trailsLinesPbfOptions: IQueryFeaturesOptions = { + const trailsLinesPbfOptions: IQueryFeaturesRawOptions = { url: `https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/Trails/FeatureServer/0`, f: "pbf", where: "1=1", outFields: ["*"], resultRecordCount: 1 }; - const response = await queryFeatures(trailsLinesPbfOptions); + const response = await queryFeaturesRaw(trailsLinesPbfOptions); const arrBuffer = await (response as any).arrayBuffer(); const arcgis = pbfToArcGIS(arrBuffer); @@ -516,14 +518,14 @@ describe("queryFeatures() and queryAllFeatures() live tests", () => { }); test("LIVE TEST: should decode POLYGON pbf to arcgis", async () => { - const parksPolygonsPbfOptions: IQueryFeaturesOptions = { + const parksPolygonsPbfOptions: IQueryFeaturesRawOptions = { url: `https://services3.arcgis.com/GVgbJbqm8hXASVYi/ArcGIS/rest/services/Parks_and_Open_Space_Styled/FeatureServer/0`, f: "pbf", where: "1=1", outFields: ["*"], resultRecordCount: 1 }; - const response = await queryFeatures(parksPolygonsPbfOptions); + const response = await queryFeaturesRaw(parksPolygonsPbfOptions); const arrBuffer = await (response as any).arrayBuffer(); const arcgis = pbfToArcGIS(arrBuffer); // required properties diff --git a/packages/arcgis-rest-feature-service/test/query.test.ts b/packages/arcgis-rest-feature-service/test/query.test.ts index 7f2d2e55c..1ecfaa8e3 100644 --- a/packages/arcgis-rest-feature-service/test/query.test.ts +++ b/packages/arcgis-rest-feature-service/test/query.test.ts @@ -6,9 +6,11 @@ import fetchMock from "fetch-mock"; import { getFeature, queryFeatures, + queryFeaturesRaw, queryAllFeatures, queryRelated, IQueryFeaturesOptions, + IQueryFeaturesRawOptions, IQueryRelatedOptions, IQueryAllFeaturesOptions, IQueryFeaturesResponse @@ -86,6 +88,31 @@ describe("getFeature() and queryFeatures()", () => { expect(options.method).toBe("GET"); }); + test("queryFeaturesRaw should return raw response for default json queries", async () => { + const requestOptions: IQueryFeaturesOptions = { + url: serviceUrl, + where: "1=1", + outFields: ["*"] + }; + fetchMock.once("*", queryResponse); + + const response: any = await queryFeaturesRaw(requestOptions); + + expect(fetchMock.called()).toBeTruthy(); + const [url, options] = fetchMock.lastCall("*"); + expect(url).toEqual( + `${requestOptions.url}/query?f=json&where=1%3D1&outFields=*` + ); + expect(options.method).toBe("GET"); + + expect(response.status).toBe(200); + expect(response.ok).toBe(true); + + // convert the raw response to json and verify the json is as expected + const json = await response.json(); + expect(json.features[0].attributes.FID).toBe(1); + }); + test("should supply default query related parameters", async () => { const requestOptions: IQueryRelatedOptions = { url: serviceUrl @@ -1285,12 +1312,12 @@ describe("queryAllFeatures (custom pagination)", () => { }); }); -describe("queryFeatures(): pbf", () => { +describe("queryFeaturesRaw() and queryFeatures(): pbf", () => { afterEach(() => { fetchMock.restore(); }); - test("should return raw response for f=pbf without decoding", async () => { + test("queryFeaturesRaw should return raw response for f=pbf without decoding", async () => { const arrayBuffer = await readEnvironmentFileToArrayBuffer( "./packages/arcgis-rest-feature-service/test/mocks/pbf/CRS4326/PBFPointResponseCRS4326.pbf" ); @@ -1305,7 +1332,7 @@ describe("queryFeatures(): pbf", () => { { sendAsJson: false } ); - const requestOptions: IQueryFeaturesOptions = { + const requestOptions: IQueryFeaturesRawOptions = { url: serviceUrl, f: "pbf", where: "1=1", @@ -1313,7 +1340,7 @@ describe("queryFeatures(): pbf", () => { resultRecordCount: 1 }; - const response: any = await queryFeatures(requestOptions); + const response: any = await queryFeaturesRaw(requestOptions); expect(fetchMock.called()).toBeTruthy(); const [url, options] = fetchMock.lastCall("*"); @@ -1326,7 +1353,50 @@ describe("queryFeatures(): pbf", () => { expect(response.status).toBe(200); expect(response.ok).toBe(true); - const rawBuffer = await response.arrayBuffer(); - expect(rawBuffer.byteLength).toBeGreaterThan(0); + const rawBuffer = (await response.arrayBuffer()) as ArrayBuffer; + // expect the raw buffer to have a byte length of 443, which is the length of the mock pbf file + expect(rawBuffer.byteLength).toBe(443); + }); + + test("queryFeatures with f=pbf should warn, but will query json and return typed json for f=pbf queries", async () => { + // create console spy to check if warning is logged for f=pbf queries + const consoleWarn = console.warn; + const warnSpy = vi.fn(); + console.warn = warnSpy; + + fetchMock.once("*", { + features: [ + { + attributes: { name: "Feature 1" } + } + ], + exceededTransferLimit: true + }); + + const requestOptions: IQueryFeaturesRawOptions = { + url: serviceUrl, + f: "pbf", + where: "1=1", + outFields: ["*"], + resultRecordCount: 1 + }; + + const response = (await queryFeatures( + // typescript should warn here if user passes in f=pbf, but this test is asserting the behavior in which case they try and override it + requestOptions as any + )) as IQueryFeaturesResponse; + + expect(warnSpy).toHaveBeenCalledTimes(1); + // expect warn spy with message that includes + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining( + "request() only supports 'json' formats and responses. Provided value 'pbf' will be defaulted to 'json'. Use 'rawRequest()' to support special 'f' parameter values." + ) + ); + expect(response.features.length).toBe(1); + expect(response.features[0].attributes.name).toBe("Feature 1"); // dummy assertion to ensure test passes if no error is thrown + + // reset console.warn to default behavior + console.warn = consoleWarn; }); }); From 1ea4b0e1b72c9a7d1d72bfcd3e50259573bd2fd7 Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Tue, 30 Jun 2026 12:47:45 -0700 Subject: [PATCH 15/20] fix bulk test to test for error object instead of string --- packages/arcgis-rest-geocoding/test/bulk.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/arcgis-rest-geocoding/test/bulk.test.ts b/packages/arcgis-rest-geocoding/test/bulk.test.ts index c38076b08..2bf166883 100644 --- a/packages/arcgis-rest-geocoding/test/bulk.test.ts +++ b/packages/arcgis-rest-geocoding/test/bulk.test.ts @@ -66,8 +66,8 @@ describe("geocode", () => { test("should throw an error when a bulk geocoding request is made without a token", async () => { fetchMock.once("*", GeocodeAddresses); - await expect(bulkGeocode({ addresses })).rejects.toEqual( - "bulk geocoding using the ArcGIS service requires authentication" + await expect(bulkGeocode({ addresses })).rejects.toThrowError( + "bulk geocoding using the ArcGIS service requires authentication." ); }); From 1a8f3e54a1d63e12adf0e6c1c1b107d26f2eb310 Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Tue, 30 Jun 2026 16:09:58 -0700 Subject: [PATCH 16/20] handle getItemData and getItemDataRaw --- packages/arcgis-rest-portal/src/items/get.ts | 48 +++++++++++++++---- .../arcgis-rest-portal/src/items/helpers.ts | 2 +- .../arcgis-rest-portal/test/items/get.test.ts | 30 +++++++++--- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/packages/arcgis-rest-portal/src/items/get.ts b/packages/arcgis-rest-portal/src/items/get.ts index 647a670e3..85ec2a6e3 100644 --- a/packages/arcgis-rest-portal/src/items/get.ts +++ b/packages/arcgis-rest-portal/src/items/get.ts @@ -13,7 +13,6 @@ import { IItem } from "../helpers.js"; import { getPortalUrl } from "../util/get-portal-url.js"; import { scrubControlChars } from "../util/scrub-control-chars.js"; import { - IItemDataOptions, IItemRelationshipOptions, IUserItemOptions, determineOwner, @@ -103,11 +102,11 @@ export const getItemBaseUrl = ( */ export function getItemData( id: string, - requestOptions?: IItemDataOptions + requestOptions?: IRequestOptions ): Promise { const url = `${getItemBaseUrl(id, requestOptions)}/data`; // default to a GET request - const options: IItemDataOptions = { + const options: IRequestOptions = { ...requestOptions, params: {}, fetchOptions: { @@ -116,11 +115,6 @@ export function getItemData( } }; - if (options.file) { - options.params.f = null; - return rawRequest(url, options).then((response) => response.blob()); - } - return request(url, options).catch((err) => { /* if the item doesn't include data, the response will be empty and the internal call to response.json() will fail */ @@ -133,6 +127,44 @@ export function getItemData( }); } +/** + * ``` + * import { getItemDataRaw } from "@esri/arcgis-rest-portal"; + * + * const response = await getItemDataRaw("ae7"); + * const data = await response.json(); + * // or + * const data = await response.text(); + * // or + * const data = await response.blob(); + * // or + * const data = await response.arrayBuffer(); + * ``` + * Get the native response for an item's /data resource so callers can parse + * JSON, text, blobs, or array buffers themselves. + * + * @param id - Item Id + * @param requestOptions - Options for the request + * @returns A Promise that will resolve with the native response. + */ +export function getItemDataRaw( + id: string, + requestOptions?: IRequestOptions +): Promise { + const url = `${getItemBaseUrl(id, requestOptions)}/data`; + const options: IRequestOptions = { + ...requestOptions, + params: {}, + fetchOptions: { + method: "GET", + ...requestOptions?.fetchOptions + } + }; + + options.params.f = null; + return rawRequest(url, options); +} + export interface IGetRelatedItemsResponse { total: number; relatedItems: IItem[]; diff --git a/packages/arcgis-rest-portal/src/items/helpers.ts b/packages/arcgis-rest-portal/src/items/helpers.ts index 5bbcdb712..66a1eae75 100644 --- a/packages/arcgis-rest-portal/src/items/helpers.ts +++ b/packages/arcgis-rest-portal/src/items/helpers.ts @@ -180,7 +180,7 @@ export interface ICreateUpdateItemOptions extends IAuthenticatedRequestOptions { export interface IItemDataOptions extends IRequestOptions { /** - * Used to request binary data. + * @deprecated Use getItemDataRaw() to retrieve native responses or binary item data. */ file?: boolean; } diff --git a/packages/arcgis-rest-portal/test/items/get.test.ts b/packages/arcgis-rest-portal/test/items/get.test.ts index b2eb7de48..b3354a9c7 100644 --- a/packages/arcgis-rest-portal/test/items/get.test.ts +++ b/packages/arcgis-rest-portal/test/items/get.test.ts @@ -8,6 +8,7 @@ import { getItemBaseUrl, getItem, getItemData, + getItemDataRaw, getItemResources, getItemGroups, getItemStatus, @@ -36,8 +37,8 @@ import { } from "@esri/arcgis-rest-request"; import { - isBrowser, isNode, + isBrowser, TOMORROW } from "../../../../scripts/test-helpers.js"; @@ -77,7 +78,7 @@ describe("get", () => { expect(options.method).toBe("GET"); }); - test("should return binary item data by id", async () => { + test("should return raw item data by id", async () => { fetchMock.once( "*", { @@ -88,24 +89,39 @@ describe("get", () => { sendAsJson: false } ); - const response = await getItemData("3ef", { file: true }); + const response = await getItemDataRaw("3ef"); expect(fetchMock.called()).toEqual(true); const [url, options] = fetchMock.lastCall("*"); expect(url).toEqual( "https://www.arcgis.com/sharing/rest/content/items/3ef/data" ); expect(options.method).toBe("GET"); - expect(response).toBeDefined(); + expect(response.ok).toBe(true); + expect(response.status).toBe(200); + const blob = await response.blob(); if (isBrowser) { - expect(response).toBeInstanceOf(Blob); + expect(blob).toBeInstanceOf(Blob); } if (isNode) { - expect((response as Blob).size).toBe(4); - const bytes = new Uint8Array(await (response as Blob).arrayBuffer()); + expect(blob.size).toBe(4); + const bytes = new Uint8Array(await blob.arrayBuffer()); expect(Array.from(bytes)).toEqual([97, 98, 99, 100]); } }); + test("should return parsed json item data even when file is requested", async () => { + fetchMock.once("*", ItemDataResponse); + + const response = await getItemData("3ef", { file: true } as any); + expect(fetchMock.called()).toEqual(true); + const [url, options] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://www.arcgis.com/sharing/rest/content/items/3ef/data?f=json" + ); + expect(options.method).toBe("GET"); + expect(response).toEqual(ItemDataResponse); + }); + test("should return a valid response even when no data is retrieved", async () => { fetchMock.once( "*", From 17f88cb650249e7de95fe4d4965d3001a4860473 Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Wed, 1 Jul 2026 11:52:21 -0700 Subject: [PATCH 17/20] adjust spread order and consolidate http method sets --- .../src/findElevationAtManyPoints.ts | 13 ++++++++----- .../src/findElevationAtPoint.ts | 9 ++++++++- .../arcgis-rest-feature-service/src/query.ts | 2 -- .../src/queryRelated.ts | 1 - packages/arcgis-rest-portal/src/groups/get.ts | 16 ++++------------ packages/arcgis-rest-portal/src/items/get.ts | 9 +++++---- .../src/users/get-user-properties.ts | 10 ++++++---- .../arcgis-rest-portal/src/users/get-user.ts | 4 ++-- .../arcgis-rest-portal/src/users/invitation.ts | 18 ++++++------------ .../src/users/notification.ts | 10 +++------- 10 files changed, 42 insertions(+), 50 deletions(-) diff --git a/packages/arcgis-rest-elevation/src/findElevationAtManyPoints.ts b/packages/arcgis-rest-elevation/src/findElevationAtManyPoints.ts index 382bfaaa2..8aeec5fa6 100644 --- a/packages/arcgis-rest-elevation/src/findElevationAtManyPoints.ts +++ b/packages/arcgis-rest-elevation/src/findElevationAtManyPoints.ts @@ -23,11 +23,18 @@ type successResponse = */ export interface IFindElevationAtManyPointsResponse extends successResponse {} +type IRequestOptionsWithoutHttpMethod = Omit< + IRequestOptions, + "fetchOptions" +> & { + fetchOptions?: Omit; +}; + /** * Options for {@linkcode findElevationAtPoint}. */ export interface IFindElevationAtManyPointsOptions - extends Omit, + extends IRequestOptionsWithoutHttpMethod, queryParams {} /** @@ -74,10 +81,6 @@ export function findElevationAtManyPoints( ); options.params.coordinates = JSON.stringify(requestOptions.coordinates); - options.fetchOptions = { - ...options.fetchOptions, - method: "GET" - }; return ( request(`${baseUrl}/elevation/at-many-points`, { diff --git a/packages/arcgis-rest-elevation/src/findElevationAtPoint.ts b/packages/arcgis-rest-elevation/src/findElevationAtPoint.ts index ce53f2264..6dd5f96a5 100644 --- a/packages/arcgis-rest-elevation/src/findElevationAtPoint.ts +++ b/packages/arcgis-rest-elevation/src/findElevationAtPoint.ts @@ -23,11 +23,18 @@ type successResponse = */ export interface IFindElevationAtPointResponse extends successResponse {} +type IRequestOptionsWithoutHttpMethod = Omit< + IRequestOptions, + "fetchOptions" +> & { + fetchOptions?: Omit; +}; + /** * Options for {@linkcode findElevationAtPoint}. */ export interface IFindElevationAtPointOptions - extends Omit, + extends IRequestOptionsWithoutHttpMethod, queryParams {} /** diff --git a/packages/arcgis-rest-feature-service/src/query.ts b/packages/arcgis-rest-feature-service/src/query.ts index 4121ee166..2dc841cd7 100644 --- a/packages/arcgis-rest-feature-service/src/query.ts +++ b/packages/arcgis-rest-feature-service/src/query.ts @@ -208,7 +208,6 @@ function prepareQueryFeaturesOptions( "f" ], { - fetchOptions: { method: "GET" }, params: { // set default query parameters where: "1=1", @@ -499,7 +498,6 @@ export async function queryAllFeatures( "f" ], { - fetchOptions: { method: "GET" }, params: { where: "1=1", outFields: "*", diff --git a/packages/arcgis-rest-feature-service/src/queryRelated.ts b/packages/arcgis-rest-feature-service/src/queryRelated.ts index 7614bd0da..61f960182 100644 --- a/packages/arcgis-rest-feature-service/src/queryRelated.ts +++ b/packages/arcgis-rest-feature-service/src/queryRelated.ts @@ -68,7 +68,6 @@ export function queryRelated( requestOptions, ["objectIds", "relationshipId", "definitionExpression", "outFields"], { - fetchOptions: { method: "GET" }, params: { // set default query parameters definitionExpression: "1=1", diff --git a/packages/arcgis-rest-portal/src/groups/get.ts b/packages/arcgis-rest-portal/src/groups/get.ts index 775380e64..0437bba82 100644 --- a/packages/arcgis-rest-portal/src/groups/get.ts +++ b/packages/arcgis-rest-portal/src/groups/get.ts @@ -112,19 +112,14 @@ export function getGroupContent( // default to a GET request const options: IRequestOptions = { + params: { start: 1, num: 100 }, + ...requestOptions, fetchOptions: { method: "GET", ...requestOptions?.fetchOptions - }, - params: { start: 1, num: 100 }, - ...requestOptions + } } as IGetGroupContentOptions; - options.fetchOptions = { - method: "GET", - ...requestOptions?.fetchOptions - }; - // is this the most concise way to mixin with the defaults above? if (requestOptions && requestOptions.paging) { options.params = { ...requestOptions.paging }; @@ -197,10 +192,7 @@ export function searchGroupUsers( const url = `${getPortalUrl(searchOptions)}/community/groups/${id}/userlist`; const options = appendCustomParams( searchOptions || {}, - ["name", "num", "start", "sortField", "sortOrder", "joined", "memberType"], - { - fetchOptions: { method: "GET" } - } + ["name", "num", "start", "sortField", "sortOrder", "joined", "memberType"] ); options.fetchOptions = { method: "GET", diff --git a/packages/arcgis-rest-portal/src/items/get.ts b/packages/arcgis-rest-portal/src/items/get.ts index 85ec2a6e3..04425ffc5 100644 --- a/packages/arcgis-rest-portal/src/items/get.ts +++ b/packages/arcgis-rest-portal/src/items/get.ts @@ -107,8 +107,8 @@ export function getItemData( const url = `${getItemBaseUrl(id, requestOptions)}/data`; // default to a GET request const options: IRequestOptions = { - ...requestOptions, params: {}, + ...requestOptions, fetchOptions: { method: "GET", ...requestOptions?.fetchOptions @@ -153,8 +153,8 @@ export function getItemDataRaw( ): Promise { const url = `${getItemBaseUrl(id, requestOptions)}/data`; const options: IRequestOptions = { - ...requestOptions, params: {}, + ...requestOptions, fetchOptions: { method: "GET", ...requestOptions?.fetchOptions @@ -194,14 +194,15 @@ export function getRelatedItems( )}/relatedItems`; const options: IItemRelationshipOptions = { + ...requestOptions, fetchOptions: { method: "GET", ...requestOptions.fetchOptions }, params: { + ...requestOptions.params, direction: requestOptions.direction - }, - ...requestOptions + } }; if (typeof requestOptions.relationshipType === "string") { diff --git a/packages/arcgis-rest-portal/src/users/get-user-properties.ts b/packages/arcgis-rest-portal/src/users/get-user-properties.ts index 59ff0a01a..ba806cbaa 100644 --- a/packages/arcgis-rest-portal/src/users/get-user-properties.ts +++ b/packages/arcgis-rest-portal/src/users/get-user-properties.ts @@ -34,13 +34,15 @@ export async function getUserProperties( const url = `${getPortalUrl( requestOptions )}/community/users/${encodeURIComponent(username)}/properties`; - const response = await request(url, { + + const options = { ...requestOptions, fetchOptions: { - method: "GET", - ...requestOptions.fetchOptions + ...requestOptions.fetchOptions, + method: "GET" } - }); + }; + const response = await request(url, options); if (!response.properties.mapViewer) { response.properties.mapViewer = "modern"; } diff --git a/packages/arcgis-rest-portal/src/users/get-user.ts b/packages/arcgis-rest-portal/src/users/get-user.ts index 34a8e4903..bfe0f6872 100644 --- a/packages/arcgis-rest-portal/src/users/get-user.ts +++ b/packages/arcgis-rest-portal/src/users/get-user.ts @@ -53,8 +53,8 @@ export async function getUser( options = { ...requestOptions, fetchOptions: { - method: "GET", - ...requestOptions?.fetchOptions + ...requestOptions?.fetchOptions, + method: "GET" } }; } diff --git a/packages/arcgis-rest-portal/src/users/invitation.ts b/packages/arcgis-rest-portal/src/users/invitation.ts index 5a524088d..995a2adcc 100644 --- a/packages/arcgis-rest-portal/src/users/invitation.ts +++ b/packages/arcgis-rest-portal/src/users/invitation.ts @@ -52,17 +52,14 @@ export interface IInvitationResult { export async function getUserInvitations( requestOptions: IAuthenticatedRequestOptions ): Promise { - let options = { - fetchOptions: { method: "GET" } - } as IAuthenticatedRequestOptions; const username = await determineUsername(requestOptions); const portalUrl = getPortalUrl(requestOptions); const url = `${portalUrl}/community/users/${username}/invitations`; - options = { + const options = { ...requestOptions, fetchOptions: { - method: "GET", - ...requestOptions.fetchOptions + ...requestOptions.fetchOptions, + method: "GET" } }; @@ -98,14 +95,11 @@ export async function getUserInvitation( const portalUrl = getPortalUrl(requestOptions); const url = `${portalUrl}/community/users/${username}/invitations/${requestOptions.invitationId}`; - let options = { - fetchOptions: { method: "GET" } - } as IGetUserInvitationOptions; - options = { + const options = { ...requestOptions, fetchOptions: { - method: "GET", - ...requestOptions.fetchOptions + ...requestOptions.fetchOptions, + method: "GET" } }; diff --git a/packages/arcgis-rest-portal/src/users/notification.ts b/packages/arcgis-rest-portal/src/users/notification.ts index ee27c70fb..3f72550c2 100644 --- a/packages/arcgis-rest-portal/src/users/notification.ts +++ b/packages/arcgis-rest-portal/src/users/notification.ts @@ -46,18 +46,14 @@ export interface INotificationResult { export async function getUserNotifications( requestOptions: IAuthenticatedRequestOptions ): Promise { - let options = { - fetchOptions: { method: "GET" } - } as IAuthenticatedRequestOptions; - const username = await determineUsername(requestOptions); const portalUrl = getPortalUrl(requestOptions); const url = `${portalUrl}/community/users/${username}/notifications`; - options = { + const options = { ...requestOptions, fetchOptions: { - method: "GET", - ...requestOptions.fetchOptions + ...requestOptions.fetchOptions, + method: "GET" } }; From 1ac926cf1d86280103cfab361c7ad6162014b433 Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Wed, 1 Jul 2026 13:59:19 -0700 Subject: [PATCH 18/20] make method change disallow more explicit --- .../arcgis-rest-places/src/findPlacesWithinExtent.ts | 9 ++++++++- packages/arcgis-rest-places/src/getCategories.ts | 9 ++++++++- packages/arcgis-rest-places/src/getCategory.ts | 9 ++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/arcgis-rest-places/src/findPlacesWithinExtent.ts b/packages/arcgis-rest-places/src/findPlacesWithinExtent.ts index 8fbc81265..71cdf5d9c 100644 --- a/packages/arcgis-rest-places/src/findPlacesWithinExtent.ts +++ b/packages/arcgis-rest-places/src/findPlacesWithinExtent.ts @@ -33,11 +33,18 @@ export interface IFindPlacesWithinExtentResponse extends successResponse { nextPage?: () => Promise; } +type IRequestOptionsWithoutHttpMethod = Omit< + IRequestOptions, + "fetchOptions" +> & { + fetchOptions?: Omit; +}; + /** * Options for {@linkcode findPlacesNearPoint}. */ export interface IFindPlaceWithinExtentOptions - extends Omit, + extends IRequestOptionsWithoutHttpMethod, queryParams { /** * Override the URL. This should be the full URL to the API endpoint you want to call. Used internally by Esri staff for testing. diff --git a/packages/arcgis-rest-places/src/getCategories.ts b/packages/arcgis-rest-places/src/getCategories.ts index e25af2e59..8382c91f5 100644 --- a/packages/arcgis-rest-places/src/getCategories.ts +++ b/packages/arcgis-rest-places/src/getCategories.ts @@ -24,11 +24,18 @@ type successResponse = */ export interface IGetCategoriesResponse extends successResponse {} +type IRequestOptionsWithoutHttpMethod = Omit< + IRequestOptions, + "fetchOptions" +> & { + fetchOptions?: Omit; +}; + /** * Options for {@linkcode getCategories}. */ export interface IGetCategoriesOptions - extends Omit, + extends IRequestOptionsWithoutHttpMethod, queryParams { /** * Override the URL. This should be the full URL to the API endpoint you want to call. Used internally by Esri staff for testing. diff --git a/packages/arcgis-rest-places/src/getCategory.ts b/packages/arcgis-rest-places/src/getCategory.ts index 0f0e17938..080464b66 100644 --- a/packages/arcgis-rest-places/src/getCategory.ts +++ b/packages/arcgis-rest-places/src/getCategory.ts @@ -21,11 +21,18 @@ type successResponse = */ export interface IGetCategoryResponse extends successResponse {} +type IRequestOptionsWithoutHttpMethod = Omit< + IRequestOptions, + "fetchOptions" +> & { + fetchOptions?: Omit; +}; + /** * Options for {@linkcode getCategory}. */ export interface IGetCategoryOptions - extends Omit, + extends IRequestOptionsWithoutHttpMethod, queryParams { categoryId: string; icon?: IconOptions; From b694710bea750422fbece2522127baf30311895c Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Wed, 1 Jul 2026 14:09:37 -0700 Subject: [PATCH 19/20] found two more --- packages/arcgis-rest-places/src/findPlacesNearPoint.ts | 9 ++++++++- packages/arcgis-rest-places/src/getPlaceDetails.ts | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/arcgis-rest-places/src/findPlacesNearPoint.ts b/packages/arcgis-rest-places/src/findPlacesNearPoint.ts index 46477c4ce..d487023d6 100644 --- a/packages/arcgis-rest-places/src/findPlacesNearPoint.ts +++ b/packages/arcgis-rest-places/src/findPlacesNearPoint.ts @@ -26,11 +26,18 @@ export interface IFindPlacesNearPointResponse extends successResponse { nextPage?: () => Promise; } +type IRequestOptionsWithoutHttpMethod = Omit< + IRequestOptions, + "fetchOptions" +> & { + fetchOptions?: Omit; +}; + /** * Options for {@linkcode findPlacesNearPoint}. */ export interface IFindPlacesNearPointOptions - extends Omit, + extends IRequestOptionsWithoutHttpMethod, queryParams { /** * Override the URL. This should be the full URL to the API endpoint you want to call. Used internally by Esri staff for testing. diff --git a/packages/arcgis-rest-places/src/getPlaceDetails.ts b/packages/arcgis-rest-places/src/getPlaceDetails.ts index 62b28608b..f6feaefc3 100644 --- a/packages/arcgis-rest-places/src/getPlaceDetails.ts +++ b/packages/arcgis-rest-places/src/getPlaceDetails.ts @@ -24,11 +24,18 @@ type successResponse = */ export interface IGetPlaceResponse extends successResponse {} +type IRequestOptionsWithoutHttpMethod = Omit< + IRequestOptions, + "fetchOptions" +> & { + fetchOptions?: Omit; +}; + /** * Options for {@linkcode getPlaceDetails}. */ export interface IGetPlaceOptions - extends Omit, + extends IRequestOptionsWithoutHttpMethod, queryParams { placeId: string; /** From a26812734d9b8e3010678c310984e9da52794281 Mon Sep 17 00:00:00 2001 From: Dixon Yant Date: Wed, 1 Jul 2026 15:35:07 -0700 Subject: [PATCH 20/20] add changeset --- .changeset/cold-teams-sit.md | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .changeset/cold-teams-sit.md diff --git a/.changeset/cold-teams-sit.md b/.changeset/cold-teams-sit.md new file mode 100644 index 000000000..75586c730 --- /dev/null +++ b/.changeset/cold-teams-sit.md @@ -0,0 +1,45 @@ +--- +"@esri/arcgis-rest-basemap-sessions": major +"@esri/arcgis-rest-developer-credentials": major +"@esri/arcgis-rest-elevation": major +"@esri/arcgis-rest-feature-service": major +"@esri/arcgis-rest-geocoding": major +"@esri/arcgis-rest-places": major +"@esri/arcgis-rest-portal": major +"@esri/arcgis-rest-request": major +--- + +remove support for rawResponse and update packages to new IRequestOptions shape + +### Summary + +This change drops support for `rawResponse` from all packages, updates all packages to use the new `IRequestOptions` shape, and adds support for raw requests with new methods. + +- `rawResponse` support removed from REST JS +- fetch methods now return JSON only. Non-JSON data can be fetched using `rawRequest()` or new wrapper methods that implement it. +- added `queryFeaturesRaw()` and `getItemDataRaw()` +- converted all `httpMethod` instances to the new `fetchOptions.method` + +### Why these changes were made + +- REST JS methods are being moved to return JSON only in most cases. +- Generic types will be easier to implement and improve our TypeScript commitment. +- Improve API surface area and our testing landscape. +- Separate default function from more advanced uses. + +### Breaking Changes + +- `getFeature()` no longer supports raw response. +- `queryFeatures()` can no longer be used to query features as pbf with `f=pbf`. +- `bulkGeocode()` no longer supports raw response. +- `geocode()` no longer supports raw response. +- `getItemData()` no longer supports raw, file, or binary responses, use getItemDataRaw()instead. + deprecated IItemDataOptions since we don't support the file property in getItemData() anymore. Use getItemDataRaw() to get the native response instead. +- `getItemInfo()` no longer supports rawResponse as an option and uses rawRequest() as default behavior. +- `getItemResource()` no longer supports rawResponse as an option and uses rawRequest() as default behavior. + +### Migration guide + +- To get a raw response, construct your own request and use `rawRequest()` to fetch a native response. +- For querying features as `pbf`, use `queryFeaturesRaw()` and supply `f=pbf` in the query options. +- To manually set an HTTP method, supply `fetchOptions.method` in IRequestOptions instead of top-level `httpMethod`.