Skip to content

Commit d0061f4

Browse files
committed
feat: implement postQuery builder
1 parent 4867771 commit d0061f4

File tree

4 files changed

+114
-3
lines changed

4 files changed

+114
-3
lines changed

ember-data-types/request.ts

+9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ export type QueryRequestOptions = {
2424
op: 'query';
2525
};
2626

27+
export type PostQueryRequestOptions = {
28+
url: string;
29+
method: 'POST' | 'QUERY';
30+
headers: Headers;
31+
body: string;
32+
cacheOptions: CacheOptions & { key: string };
33+
op: 'query';
34+
};
35+
2736
export type DeleteRequestOptions = {
2837
url: string;
2938
method: 'DELETE';

packages/json-api/src/-private/builders/query.ts

+78-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
import { pluralize } from 'ember-inflector';
55

66
import { buildBaseURL, buildQueryParams, QueryParamsSource, type QueryUrlOptions } from '@ember-data/request-utils';
7-
import type { ConstrainedRequestOptions, QueryRequestOptions } from '@ember-data/types/request';
7+
import type {
8+
CacheOptions,
9+
ConstrainedRequestOptions,
10+
PostQueryRequestOptions,
11+
QueryRequestOptions,
12+
} from '@ember-data/types/request';
813

914
import { copyForwardUrlOptions, extractCacheOptions } from './-utils';
1015

@@ -85,3 +90,75 @@ export function query(
8590
op: 'query',
8691
};
8792
}
93+
94+
/**
95+
* Builds request options to query for resources, usually by a primary
96+
* type, configured for the url and header expectations of most JSON:API APIs.
97+
*
98+
* ```ts
99+
* import { postQuery } from '@ember-data/json-api/request';
100+
*
101+
* const options = query('person', { include: ['pets', 'friends'] });
102+
* const data = await store.request(options);
103+
* ```
104+
*
105+
* **Supplying Options to Modify the Request Behavior**
106+
*
107+
* The following options are supported:
108+
*
109+
* - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.
110+
* - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.
111+
* - `resourcePath` - The resource path to use for the request, defaults to pluralizing the supplied type
112+
* - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this
113+
* option will delegate to the store's lifetimes service, defaulting to `false` if none is configured.
114+
* - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the
115+
* promise with the cached value, not supplying this option will delegate to the store's lifetimes service,
116+
* defaulting to `false` if none is configured.
117+
* - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)
118+
*
119+
* ```ts
120+
* import { query } from '@ember-data/json-api/request';
121+
*
122+
* const options = query('person', { include: ['pets', 'friends'] }, { reload: true });
123+
* const data = await store.request(options);
124+
* ```
125+
*
126+
* @method postQuery
127+
* @public
128+
* @static
129+
* @for @ember-data/json-api/request
130+
* @param identifier
131+
* @param query
132+
* @param options
133+
*/
134+
export function postQuery(
135+
type: string,
136+
// eslint-disable-next-line @typescript-eslint/no-shadow
137+
query: QueryParamsSource = {},
138+
options: ConstrainedRequestOptions = {}
139+
): PostQueryRequestOptions {
140+
const cacheOptions = extractCacheOptions(options);
141+
const urlOptions: QueryUrlOptions = {
142+
identifier: { type },
143+
op: 'query',
144+
resourcePath: options.resourcePath ?? pluralize(type),
145+
};
146+
147+
copyForwardUrlOptions(urlOptions, options);
148+
149+
const url = buildBaseURL(urlOptions);
150+
const headers = new Headers();
151+
headers.append('Accept', 'application/vnd.api+json');
152+
153+
const queryData = structuredClone(query);
154+
cacheOptions.key = cacheOptions.key ?? `${url}?${buildQueryParams(queryData, options.urlParamsSettings)}`;
155+
156+
return {
157+
url,
158+
method: 'POST',
159+
body: JSON.stringify(query),
160+
headers,
161+
cacheOptions: cacheOptions as CacheOptions & { key: string },
162+
op: 'query',
163+
};
164+
}

packages/json-api/src/request.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,6 @@ URLs follow the most common JSON:API format (dasherized pluralized resource type
6464
* @main @ember-data/json-api/request
6565
*/
6666
export { findRecord } from './-private/builders/find-record';
67-
export { query } from './-private/builders/query';
67+
export { query, postQuery } from './-private/builders/query';
6868
export { deleteRecord, createRecord, updateRecord } from './-private/builders/save-record';
6969
export { serializeResources, serializePatch } from './-private/serialize';

tests/builders/tests/unit/json-api-builder-test.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { module, test } from 'qunit';
22

33
import { setupTest } from 'ember-qunit';
44

5-
import { createRecord, deleteRecord, findRecord, query, updateRecord } from '@ember-data/json-api/request';
5+
import { createRecord, deleteRecord, findRecord, postQuery, query, updateRecord } from '@ember-data/json-api/request';
66
import { setBuildURLConfig } from '@ember-data/request-utils';
77
import Store, { recordIdentifierFor } from '@ember-data/store';
88

@@ -116,6 +116,31 @@ module('JSON:API | Request Builders', function (hooks) {
116116
assert.deepEqual(headersToObject(result.headers), JSON_API_HEADERS);
117117
});
118118

119+
test('postQuery', function (assert) {
120+
const result = postQuery(
121+
'user-setting',
122+
{ include: 'user,friends', sort: 'name:asc', search: ['zeta', 'beta'] },
123+
{ reload: true, backgroundReload: false }
124+
);
125+
assert.deepEqual(
126+
result,
127+
{
128+
url: 'https://api.example.com/api/v1/user-settings',
129+
method: 'POST',
130+
body: JSON.stringify({ include: 'user,friends', sort: 'name:asc', search: ['zeta', 'beta'] }),
131+
headers: new Headers(JSON_API_HEADERS),
132+
cacheOptions: {
133+
reload: true,
134+
backgroundReload: false,
135+
key: 'https://api.example.com/api/v1/user-settings?include=friends%2Cuser&search=beta%2Czeta&sort=name%3Aasc',
136+
},
137+
op: 'query',
138+
},
139+
`query works with type and options`
140+
);
141+
assert.deepEqual(headersToObject(result.headers), JSON_API_HEADERS);
142+
});
143+
119144
test('createRecord passing store record', function (assert) {
120145
const store = this.owner.lookup('service:store') as Store;
121146
const userSetting = store.createRecord('user-setting', {

0 commit comments

Comments
 (0)