Skip to content

Commit

Permalink
feat: implement postQuery builder (emberjs#8925)
Browse files Browse the repository at this point in the history
  • Loading branch information
runspired authored and BoussonKarel committed Oct 6, 2023
1 parent aaecf4c commit fa777f7
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 3 deletions.
9 changes: 9 additions & 0 deletions ember-data-types/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ export type QueryRequestOptions = {
op: 'query';
};

export type PostQueryRequestOptions = {
url: string;
method: 'POST' | 'QUERY';
headers: Headers;
body: string;
cacheOptions: CacheOptions & { key: string };
op: 'query';
};

export type DeleteRequestOptions = {
url: string;
method: 'DELETE';
Expand Down
79 changes: 78 additions & 1 deletion packages/json-api/src/-private/builders/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
import { pluralize } from 'ember-inflector';

import { buildBaseURL, buildQueryParams, QueryParamsSource, type QueryUrlOptions } from '@ember-data/request-utils';
import type { ConstrainedRequestOptions, QueryRequestOptions } from '@ember-data/types/request';
import type {
CacheOptions,
ConstrainedRequestOptions,
PostQueryRequestOptions,
QueryRequestOptions,
} from '@ember-data/types/request';

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

Expand Down Expand Up @@ -85,3 +90,75 @@ export function query(
op: 'query',
};
}

/**
* Builds request options to query for resources, usually by a primary
* type, configured for the url and header expectations of most JSON:API APIs.
*
* ```ts
* import { postQuery } from '@ember-data/json-api/request';
*
* const options = query('person', { include: ['pets', 'friends'] });
* const data = await store.request(options);
* ```
*
* **Supplying Options to Modify the Request Behavior**
*
* The following options are supported:
*
* - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.
* - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.
* - `resourcePath` - The resource path to use for the request, defaults to pluralizing the supplied type
* - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this
* option will delegate to the store's lifetimes service, defaulting to `false` if none is configured.
* - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the
* promise with the cached value, not supplying this option will delegate to the store's lifetimes service,
* defaulting to `false` if none is configured.
* - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)
*
* ```ts
* import { query } from '@ember-data/json-api/request';
*
* const options = query('person', { include: ['pets', 'friends'] }, { reload: true });
* const data = await store.request(options);
* ```
*
* @method postQuery
* @public
* @static
* @for @ember-data/json-api/request
* @param identifier
* @param query
* @param options
*/
export function postQuery(
type: string,
// eslint-disable-next-line @typescript-eslint/no-shadow
query: QueryParamsSource = {},
options: ConstrainedRequestOptions = {}
): PostQueryRequestOptions {
const cacheOptions = extractCacheOptions(options);
const urlOptions: QueryUrlOptions = {
identifier: { type },
op: 'query',
resourcePath: options.resourcePath ?? pluralize(type),
};

copyForwardUrlOptions(urlOptions, options);

const url = buildBaseURL(urlOptions);
const headers = new Headers();
headers.append('Accept', 'application/vnd.api+json');

const queryData = structuredClone(query);
cacheOptions.key = cacheOptions.key ?? `${url}?${buildQueryParams(queryData, options.urlParamsSettings)}`;

return {
url,
method: 'POST',
body: JSON.stringify(query),
headers,
cacheOptions: cacheOptions as CacheOptions & { key: string },
op: 'query',
};
}
2 changes: 1 addition & 1 deletion packages/json-api/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,6 @@ URLs follow the most common JSON:API format (dasherized pluralized resource type
* @main @ember-data/json-api/request
*/
export { findRecord } from './-private/builders/find-record';
export { query } from './-private/builders/query';
export { query, postQuery } from './-private/builders/query';
export { deleteRecord, createRecord, updateRecord } from './-private/builders/save-record';
export { serializeResources, serializePatch } from './-private/serialize';
27 changes: 26 additions & 1 deletion tests/builders/tests/unit/json-api-builder-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { module, test } from 'qunit';

import { setupTest } from 'ember-qunit';

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

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

test('postQuery', function (assert) {
const result = postQuery(
'user-setting',
{ include: 'user,friends', sort: 'name:asc', search: ['zeta', 'beta'] },
{ reload: true, backgroundReload: false }
);
assert.deepEqual(
result,
{
url: 'https://api.example.com/api/v1/user-settings',
method: 'POST',
body: JSON.stringify({ include: 'user,friends', sort: 'name:asc', search: ['zeta', 'beta'] }),
headers: new Headers(JSON_API_HEADERS),
cacheOptions: {
reload: true,
backgroundReload: false,
key: 'https://api.example.com/api/v1/user-settings?include=friends%2Cuser&search=beta%2Czeta&sort=name%3Aasc',
},
op: 'query',
},
`query works with type and options`
);
assert.deepEqual(headersToObject(result.headers), JSON_API_HEADERS);
});

test('createRecord passing store record', function (assert) {
const store = this.owner.lookup('service:store') as Store;
const userSetting = store.createRecord('user-setting', {
Expand Down

0 comments on commit fa777f7

Please sign in to comment.