Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
adbf710
Merge branch 'requestoptionsv5changes' into rawrequest-replace-rawres…
dixonyant Apr 21, 2026
290c292
add v2 request file for comparison
dixonyant Apr 22, 2026
bfb6539
refactor to async await syntax
dixonyant Apr 22, 2026
ecf1c25
regrouping request constructions
dixonyant May 6, 2026
71eb068
revert url naming
dixonyant May 6, 2026
ef0e0c5
pre rawrequest extraction parity state
dixonyant May 8, 2026
60988fc
remove rawResponse altogether form request
dixonyant May 11, 2026
224e2b6
remove params from checkForErrors
dixonyant May 11, 2026
bd4812c
remove unused vars
dixonyant May 11, 2026
c6358f0
support geojson as json in request
dixonyant May 11, 2026
0fa2486
log warning change
dixonyant May 11, 2026
6197005
fix broken test
dixonyant May 11, 2026
35d84fb
wip centralize fetchOptions
dixonyant May 12, 2026
0baa47e
remove redundant assignment
dixonyant May 12, 2026
43e9f61
destructure requestFlags and remove double self platform call
dixonyant May 13, 2026
bf96460
destructure request context
dixonyant May 13, 2026
dfb644b
wrap rawRequest in retry logic for parity
dixonyant May 13, 2026
6ca8013
remove error retry in rawResponse
dixonyant May 13, 2026
1602de6
restruct build auth logic
dixonyant May 14, 2026
3974367
wip centralize fetchOptions
dixonyant May 12, 2026
825536f
Merge remote-tracking branch 'origin/rawrequest-replace-rawresponse' …
dixonyant May 15, 2026
1ffaca5
wip normalize compatibility requestOptions
dixonyant May 15, 2026
141b65f
warn unsupported on maxUrlLength and rawResponse usage
dixonyant May 15, 2026
d1675a5
use old shape
dixonyant May 15, 2026
7f70ec6
update requestOptions shape
dixonyant May 15, 2026
e37a060
undo test change
dixonyant May 15, 2026
18e4da4
ignoreMaxUrlLength related tests
dixonyant May 16, 2026
d5529a1
replace request with requestV2 refactor
dixonyant May 28, 2026
ca6ce04
wip replace all rawResponse with rawRequest
dixonyant May 28, 2026
af87f91
wip get test fix with blob files
dixonyant May 28, 2026
d4470a0
fixed breaking tests that no longer fulfill new request options shape
dixonyant May 28, 2026
6c3ab3f
change from whitelist to blacklist normalization
dixonyant May 28, 2026
f6612a6
add pbf test for when a user wants to request raw pbf for some reason
dixonyant May 28, 2026
f634a0c
fix redundancies and coverage issues
dixonyant May 28, 2026
83eee25
add test case for raw request with no options
dixonyant May 28, 2026
c4c714b
update maxUrlLength refactor to ignoreMaxUrlLength test cases
dixonyant May 31, 2026
0c6c675
Merge branch 'v5.0.0' into rawrequest-replace-rawresponse
dixonyant Jun 5, 2026
92bb7a0
install playwright version to remove hanging ci issue
dixonyant Jun 5, 2026
bea9adf
update pr feedback
dixonyant Jun 5, 2026
2bd2aa3
add changeset
dixonyant Jun 8, 2026
a362469
Update packages/arcgis-rest-request/src/utils/warn-deprecated-request…
dixonyant Jun 9, 2026
b2fccda
pull GET to POST workflow out into helper
dixonyant Jun 12, 2026
940990a
add mergeHeaders until we get to the point or removing legacy options
dixonyant Jun 12, 2026
436dca5
update global defaults to use normalized and updated IRequestOptions
dixonyant Jun 12, 2026
2d0f684
force ceorce json unconditionally
dixonyant Jun 12, 2026
dd42707
don't warn when f param is not passed in
dixonyant Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions .changeset/polite-dragons-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
"@esri/arcgis-rest-developer-credentials": major
"@esri/arcgis-rest-basemap-sessions": major
"@esri/arcgis-rest-feature-service": major
"@esri/arcgis-rest-demographics": major
"@esri/arcgis-rest-elevation": major
"@esri/arcgis-rest-geocoding": major
"@esri/arcgis-rest-request": major
"@esri/arcgis-rest-routing": major
"@esri/arcgis-rest-places": major
"@esri/arcgis-rest-portal": major
"@esri/arcgis-rest-auth": major
---

This release changes how HTTP responses are accessed and redefines top-level request options.

## Summary

This release introduces rawRequest() for direct HTTP response access and removes legacy request option patterns.

- `request()` returns JSON only. Non-JSON `f` values are coerced to `json` with a warning.
- `rawResponse` is no longer supported. Use `rawRequest()` instead.
- `maxUrlLength` is no longer supported. Use requestFlags.ignoreMaxUrlLength with GET requests to avoid REST JS coercing the request to POST for large URLs.

## Breaking changes

- `request()` now only supports JSON payload handling by coercing `f=json`.

- If you pass `params.f` values like `text` or `html` to `request()`, the value is coerced to `json` and a warning is emitted.
- Use `rawRequest()` when you need custom response formats or direct access to the native Response.

- `rawResponse` as an `IRequestOptions` property is no longer supported for `request()`.

- Previous pattern: `request(url, { rawResponse: true, ... })`
- New pattern: `rawRequest(url, options)`

- `maxUrlLength` as an `IRequestOptions` property is no longer supported.
- Previous pattern: { maxUrlLength: 3000 }
- New pattern: { requestFlags: { ignoreMaxUrlLength: true } }

## Why these changes were made

- Improve options handling and remove legacy properties.
- Make generic return types feasible.
- Contain HTTP-related fetch options and ArcGIS request flags within their own respective objects for a clearer API surface.

## Migration guide

1. Replace request(..., { rawResponse: true, ...options }) with rawRequest(..., options).

```ts
// Before
const response = await request(url, {
rawResponse: true,
params: { f: "pbf" }
});
const bytes = await response.arrayBuffer();

// After
const response = await rawRequest(url, { params: { f: "pbf" } });
const bytes = await response.arrayBuffer();
```

2. If you need text/html/blob/pbf from an endpoint, switch from request() to rawRequest().

```ts
// Before
const text = await request(url, { params: { f: "html" } });

// After
const response = await rawRequest(url, { params: { f: "html" } });
const text = await response.text();
```

3. Move top-level maxUrlLength usage to requestFlags.ignoreMaxUrlLength.

Before, you could specify a GET request, and it would return so long as the max URL length was < 2000 or < the user-specified `maxUrlLength`.
Now, if the max URL length surpasses 2000, `request()` will change request method from GET to POST unless the user specifies `ignoreMaxUrlLength` true in `requestFlags.ignoreMaxUrlLength`.

```ts
// Before
await request(url, { httpMethod: "GET", maxUrlLength: 5000 });

// After
await request(url, {
fetchOptions: { method: "GET" },
requestFlags: { ignoreMaxUrlLength: true }
});
```
18 changes: 12 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"@changesets/cli": "^2.29.8",
"@eslint/compat": "^2.0.0",
"@eslint/js": "^9.39.1",
"@playwright/test": "^1.56.1",
"@playwright/test": "^1.60.0",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.3.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,16 +172,15 @@ export interface ICreateServiceResult {
* });
* ```
*
* @param requestOptions - Options for the request. NOTE: `rawResponse` is not supported by this operation.
* @param requestOptions - Options for the request.
* @returns A Promise that resolves with service details once the service has been created
*/
export function createFeatureService(
requestOptions: ICreateServiceOptions
): Promise<ICreateServiceResult> {
return determineOwner(requestOptions).then((owner) => {
const options: ICreateServiceOptions = {
...requestOptions,
rawResponse: false
...requestOptions
};
const baseUrl = `${getPortalUrl(requestOptions)}/content/users/${owner}`;
const folder =
Expand Down
35 changes: 18 additions & 17 deletions packages/arcgis-rest-feature-service/src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
IExtent,
ArcGISRequestError,
ArcGISAuthError,
IRequestOptions
IRequestOptions,
rawRequest
} from "@esri/arcgis-rest-request";

import {
Expand Down Expand Up @@ -77,9 +78,7 @@ export interface IQueryFeaturesOptions extends ISharedQueryOptions {
sqlFormat?: "none" | "standard" | "native";
returnExceededLimitFeatures?: boolean;
/**
* Response format. Defaults to "json"
* NOTE: for "pbf" you must also supply `rawResponse: true`
* and parse the response yourself using `response.arrayBuffer()`
* Response format. Defaults to "json".
*/
f?: "json" | "geojson" | "pbf" | "pbf-as-geojson" | "pbf-as-arcgis";
/**
Expand Down Expand Up @@ -134,7 +133,7 @@ export interface IQueryAllFeaturesOptions extends ISharedQueryOptions {
returnExceededLimitFeatures?: true;
/**
* Response format. Defaults to "json"
* NOTE: for "pbf" you must also supply `rawResponse: true`
* NOTE: for "pbf" you must use the method `rawRequest()`
* and parse the response yourself using `response.arrayBuffer()`
*/
f?: "json" | "geojson" | "pbf-as-geojson" | "pbf-as-arcgis";
Expand Down Expand Up @@ -188,17 +187,16 @@ export function queryPbfAsGeoJSONOrArcGIS(
// default pbf request to EPSG:4326 if requesting pbf-as-geojson to satisfy geojson crs standard
const geoJSONSpatialReference =
queryOptions.params.f === "pbf-as-geojson" ? { outSR: "4326" } : {};
// query with f=pbf and rawResponse:true on behalf of the user to fetch metadata with the pbf response
// query with f=pbf and rawRequest on behalf of the user to fetch metadata with the pbf response
const customOptions = {
...queryOptions,
params: {
...queryOptions.params,
...geoJSONSpatialReference,
f: "pbf"
} as any,
rawResponse: true
} as any
};
return request(`${cleanUrl(url)}/query`, customOptions).then(
return rawRequest(`${cleanUrl(url)}/query`, customOptions).then(
async (response: any) => {
// if pbf request to service returns a json format, there is an error
if (response.headers.get("content-type")?.includes("application/json")) {
Expand Down Expand Up @@ -270,24 +268,22 @@ export function queryPbfAsGeoJSONOrArcGIS(
* ```
*
* @param requestOptions - Options for the request
* @returns A Promise that will resolve with the feature or the [response](https://developer.mozilla.org/en-US/docs/Web/API/Response) itself if `rawResponse: true` was passed in.
* @returns A Promise that resolves with the feature by default, or with the native Response when `rawResponse` is `true`.
Comment thread
patrickarlt marked this conversation as resolved.
*/
export function getFeature(
requestOptions: IGetFeatureOptions
Comment thread
dixonyant marked this conversation as resolved.
): Promise<IFeature> {
): Promise<IFeature | Response> {
const url = `${cleanUrl(requestOptions.url)}/${requestOptions.id}`;

// default to a GET request
const options: IGetFeatureOptions = {
...{ httpMethod: "GET" },
...requestOptions
};
Comment thread
dixonyant marked this conversation as resolved.
return request(url, options).then((response: any) => {
if (options.rawResponse) {
return response;
}
return response.feature;
});
if (options.rawResponse) {
return rawRequest(url, options);
}
return request(url, options).then((response: any) => response.feature);
}

/**
Expand Down Expand Up @@ -365,6 +361,11 @@ export function queryFeatures(
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<any>;
Comment on lines +364 to +368

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dixonyant this also seems off. The goal is that all methods should return JSON by default and this breaks the pattern by returning Promise<any> if f=pbf so if someone really wants f=pbf they will neet to use a new rawQueryFeatures() that will need to be added at v5

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is off. This in-between behavior shouldn't even see the light of day since the follow-up PR is going to be all the removing of rawRequest calls from all functions that should be only returning JSON. They either get a replacement rawFunction call after v5 or with v5. Just the purpose of this PR is to replace the guts of all calls to request({rawResponse}) with rawRequest for immediate backwards compatibility internally, and then I was going to remove fully and assert types in the next follow-up PR.

}
Comment thread
dixonyant marked this conversation as resolved.
return request(`${cleanUrl(requestOptions.url)}/query`, queryOptions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ describe("add to feature service", () => {
params: expect.objectContaining({
addToDefinition: { layers: [layerDescriptionFail] }
}),
httpMethod: "POST"
fetchOptions: expect.objectContaining({
method: "POST"
})
})
});
});
Expand All @@ -258,7 +260,9 @@ describe("add to feature service", () => {
params: expect.objectContaining({
addToDefinition: { tables: [tableDescriptionFail] }
}),
httpMethod: "POST"
fetchOptions: expect.objectContaining({
method: "POST"
})
})
});
});
Expand Down Expand Up @@ -286,7 +290,9 @@ describe("add to feature service", () => {
layers: [layerDescriptionFail]
}
}),
httpMethod: "POST"
fetchOptions: expect.objectContaining({
method: "POST"
})
})
});
});
Expand Down
9 changes: 3 additions & 6 deletions packages/arcgis-rest-feature-service/test/query.test.live.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,8 +447,7 @@ describe("queryFeatures() and queryAllFeatures() live tests", () => {
f: "pbf",
where: "1=1",
outFields: ["*"],
resultRecordCount: 1,
rawResponse: true
resultRecordCount: 1
};
const response = await queryFeatures(zipCodePointsPbfOptions);
const arrBuffer = await (response as any).arrayBuffer();
Expand Down Expand Up @@ -489,8 +488,7 @@ describe("queryFeatures() and queryAllFeatures() live tests", () => {
f: "pbf",
where: "1=1",
outFields: ["*"],
resultRecordCount: 1,
rawResponse: true
resultRecordCount: 1
};
const response = await queryFeatures(trailsLinesPbfOptions);
const arrBuffer = await (response as any).arrayBuffer();
Expand Down Expand Up @@ -523,8 +521,7 @@ describe("queryFeatures() and queryAllFeatures() live tests", () => {
f: "pbf",
where: "1=1",
outFields: ["*"],
resultRecordCount: 1,
rawResponse: true
resultRecordCount: 1
};
const response = await queryFeatures(parksPolygonsPbfOptions);
const arrBuffer = await (response as any).arrayBuffer();
Expand Down
46 changes: 46 additions & 0 deletions packages/arcgis-rest-feature-service/test/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1306,3 +1306,49 @@ describe("queryAllFeatures (custom pagination)", () => {
}
});
});

describe("queryFeatures(): pbf", () => {
afterEach(() => {
fetchMock.restore();
});

test("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"
);

fetchMock.once(
"*",
{
status: 200,
headers: { "content-type": "application/x-protobuf" },
body: arrayBuffer
},
{ sendAsJson: false }
);

const requestOptions: IQueryFeaturesOptions = {
url: serviceUrl,
f: "pbf",
where: "1=1",
outFields: ["*"],
resultRecordCount: 1
};

const response: any = await queryFeatures(requestOptions);

expect(fetchMock.called()).toBeTruthy();
const [url, options] = fetchMock.lastCall("*");
expect(url).toBe(
`${serviceUrl}/query?f=pbf&where=1%3D1&outFields=*&resultRecordCount=1`
);
expect(options.method).toBe("GET");

// rawRequest path should return a Response-like object
expect(response.status).toBe(200);
expect(response.ok).toBe(true);

const rawBuffer = await response.arrayBuffer();
expect(rawBuffer.byteLength).toBeGreaterThan(0);
});
});
Loading