Skip to content

Commit fdee569

Browse files
committed
Add support for sending queries via POST
- Add `usePost()` to EntityQuery - Add post handling to AbstractDataServiceAdapter - Add a test to query-misc (but I also tested other queries using POST) - Update version to 2.1.7 (maybe should have been 2.2.0)
1 parent 3409a19 commit fdee569

File tree

6 files changed

+115
-45
lines changed

6 files changed

+115
-45
lines changed

mjs/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "breeze-client",
3-
"version": "2.1.6-mjs",
3+
"version": "2.1.7-mjs",
44
"private": true,
55
"description": "Breeze data management for JavaScript clients",
66
"repository": "https://github.com/Breeze/breeze-client.git",
@@ -19,7 +19,7 @@
1919
"scripts": {
2020
"build": "npm run clean && npm run packagr && npm run vermjs && npm pack ../dist",
2121
"clean": "rimraf temp && rimraf ../src/*.js* && rimraf ../src/*.d.ts && rimraf ../*.d.ts && rimraf ../dist",
22-
"vermjs": "cd ../dist && npm --no-git-tag-version version 2.1.6-mjs",
22+
"vermjs": "cd ../dist && npm --no-git-tag-version version 2.1.7-mjs",
2323
"publish": "node ../tools/unprivate.js && npm publish --tag mjs ../dist",
2424
"packagr": "ng-packagr -p ../ng-package.json"
2525
},

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "breeze-client",
3-
"version": "2.1.6",
3+
"version": "2.1.7",
44
"private": true,
55
"description": "Breeze data management for JavaScript clients",
66
"repository": "https://github.com/Breeze/breeze-client.git",
@@ -24,8 +24,8 @@
2424
"scripts": {
2525
"build": "npm run clean && npm run packagr && rimraf dist/spec && npm run downlevel-dts && npm pack ./dist && npm run install-to-spec",
2626
"clean": "rimraf temp && rimraf src/*.js* && rimraf src/*.d.ts && rimraf ./*.d.ts && rimraf dist && rimraf node_modules/breeze-client.js",
27-
"install-to-spec": "cd spec && npm install ../breeze-client-2.1.6.tgz",
28-
"publish": "node tools/unprivate.js && npm publish --tag latest ./dist && npm dist-tag add [email protected].6 cjs",
27+
"install-to-spec": "cd spec && npm install ../breeze-client-2.1.7.tgz",
28+
"publish": "node tools/unprivate.js && npm publish --tag latest ./dist && npm dist-tag add [email protected].7 cjs",
2929
"tsc": "tsc",
3030
"watch-tsc": "tsc -w",
3131
"downlevel-dts": "downlevel-dts ./dist ./dist",

spec/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"description": "Breeze tests",
66
"repository": "https://github.com/Breeze/breeze-client.git",
77
"dependencies": {
8-
"breeze-client": "file:../breeze-client-2.1.6.tgz",
8+
"breeze-client": "file:../breeze-client-2.1.7.tgz",
99
"node-fetch": "^2.6.0"
1010
},
1111
"devDependencies": {

spec/query-misc.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,25 @@ describe("Query Misc", () => {
172172
const em1 = TestFns.newEntityManager();
173173
const query = EntityQuery.from("Customers").where("companyName", "startsWith", "Alfreds");
174174
const qr1 = await em1.executeQuery(query);
175+
expect(qr1.results.length).toEqual(1);
175176
const alfred = qr1.results[0];
176177
const alfredsID = alfred.getProperty(TestFns.wellKnownData.keyNames.customer).toLowerCase();
177178
expect(alfredsID).toEqual(TestFns.wellKnownData.alfredsID);
178179
});
179180

181+
test("getAlfred - POST", async () => {
182+
expect.hasAssertions();
183+
const em1 = TestFns.newEntityManager();
184+
let query = EntityQuery.from("Customers").where("companyName", "startsWith", "Alfreds");
185+
query = query.usePost();
186+
const qr1 = await em1.executeQuery(query);
187+
expect(qr1.results.length).toEqual(1);
188+
const alfred = qr1.results[0];
189+
const alfredsID = alfred.getProperty(TestFns.wellKnownData.keyNames.customer).toLowerCase();
190+
expect(alfredsID).toEqual(TestFns.wellKnownData.alfredsID);
191+
}, 99000);
192+
193+
180194
// "odata", "has not yet implemented server side interception").
181195
skipTestIf(TestFns.isODataServer, "insure that query is Not a duration query even without type mapping", async function () {
182196
expect.hasAssertions();

src/abstract-data-service-adapter.ts

Lines changed: 70 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Entity } from './entity-aspect';
66
import { MappingContext } from './mapping-context';
77
import { DataService, JsonResultsAdapter } from './data-service';
88
import { HttpResponse, SaveContext, SaveBundle, ServerError, SaveResult, SaveErrorFromServer, QueryResult } from './entity-manager';
9-
import { MetadataStore } from './entity-metadata';
9+
import { EntityType, MetadataStore } from './entity-metadata';
1010

1111
/** For use by breeze plugin authors only. The class is used as the base class for most [[IDataServiceAdapter]] implementations
1212
@adapter (see [[IDataServiceAdapter]])
@@ -79,52 +79,86 @@ export abstract class AbstractDataServiceAdapter implements DataServiceAdapter {
7979
return promise;
8080
}
8181

82+
/** Execute the query in the mappingContext using the ajaxImpl. */
8283
executeQuery(mappingContext: MappingContext) {
8384
mappingContext.adapter = this;
84-
let promise = new Promise<QueryResult>((resolve, reject) => {
85-
let url = mappingContext.getUrl();
8685

87-
let params = {
88-
type: "GET",
89-
url: url,
90-
params: (mappingContext.query as EntityQuery).parameters,
91-
dataType: 'json',
92-
success: function (httpResponse: HttpResponse) {
93-
let data = httpResponse.data;
94-
try {
95-
let rData: QueryResult;
96-
let results = data && (data.results || data.Results);
97-
if (results) {
98-
rData = { results: results, inlineCount: data.inlineCount || data.InlineCount,
99-
httpResponse: httpResponse, query: mappingContext.query };
100-
} else {
101-
rData = { results: data, httpResponse: httpResponse, query: mappingContext.query };
102-
}
103-
104-
resolve(rData);
105-
} catch (e) {
106-
if (e instanceof Error) {
107-
reject(e);
108-
} else {
109-
handleHttpError(reject, httpResponse);
110-
}
86+
const usePost = (mappingContext.query as EntityQuery).usePostEnabled;
87+
const params = usePost ? this._makeQueryPostParams(mappingContext) : this._makeQueryGetParams(mappingContext) as any;
88+
89+
let promise = new Promise<QueryResult>((resolve, reject) => {
90+
params.success = function (httpResponse: HttpResponse) {
91+
let data = httpResponse.data;
92+
try {
93+
let rData: QueryResult;
94+
let results = data && (data.results || data.Results);
95+
if (results) {
96+
rData = { results: results, inlineCount: data.inlineCount || data.InlineCount,
97+
httpResponse: httpResponse, query: mappingContext.query };
98+
} else {
99+
rData = { results: data, httpResponse: httpResponse, query: mappingContext.query };
111100
}
112101

113-
},
114-
error: function (httpResponse: HttpResponse) {
115-
handleHttpError(reject, httpResponse);
116-
},
117-
crossDomain: false
102+
resolve(rData);
103+
} catch (e) {
104+
if (e instanceof Error) {
105+
reject(e);
106+
} else {
107+
handleHttpError(reject, httpResponse);
108+
}
109+
}
110+
};
111+
params.error = function (httpResponse: HttpResponse) {
112+
handleHttpError(reject, httpResponse);
118113
};
119-
if (mappingContext.dataService.useJsonp) {
120-
params.dataType = 'jsonp';
121-
params.crossDomain = true;
122-
}
123114
this.ajaxImpl.ajax(params);
124115
});
125116
return promise;
126117
}
127118

119+
/** Set up ajax parameters for query GET. This puts the query into the request querystring in either JSON or OData-style syntax, depending upon the UriBuilder. */
120+
_makeQueryGetParams(mappingContext: MappingContext) {
121+
const url = mappingContext.getUrl();
122+
123+
const params = {
124+
type: "GET",
125+
url: url,
126+
params: (mappingContext.query as EntityQuery).parameters,
127+
dataType: 'json',
128+
crossDomain: false,
129+
};
130+
if (mappingContext.dataService.useJsonp) {
131+
params.dataType = 'jsonp';
132+
params.crossDomain = true;
133+
}
134+
return params;
135+
}
136+
137+
/** Set up ajax parameters for query POST. This put the query into the request body as JSON. */
138+
_makeQueryPostParams(mappingContext: MappingContext) {
139+
const entityQuery = mappingContext.query as EntityQuery;
140+
const metadataStore = mappingContext.entityManager.metadataStore;
141+
const url = mappingContext.dataService.qualifyUrl(entityQuery.resourceName);
142+
143+
let entityType = entityQuery._getFromEntityType(metadataStore, false);
144+
if (!entityType) { entityType = new EntityType(metadataStore); }
145+
const json = entityQuery.toJSONExt({ entityType: entityType, toNameOnServer: true}) as any;
146+
json.from = undefined;
147+
json.queryOptions = undefined;
148+
149+
const params = {
150+
type: "POST",
151+
url: url,
152+
params: (mappingContext.query as EntityQuery).parameters,
153+
dataType: 'json',
154+
processData: false, // don't let JQuery form-encode it
155+
contentType: "application/json; charset=UTF-8",
156+
data: JSON.stringify(json),
157+
crossDomain: false,
158+
};
159+
return params;
160+
}
161+
128162
saveChanges(saveContext: SaveContext, saveBundle: SaveBundle) {
129163
let adapter = saveContext.adapter = this;
130164

src/entity-query.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export class EntityQuery {
5454
declare inlineCountEnabled: boolean;
5555
/** Whether entity tracking has been disabled for this query. __Read Only__ */
5656
declare noTrackingEnabled: boolean;
57+
/** Whether to send query as the body of a POST request. (Server needs to accomodate POST). __Read Only__ */
58+
declare usePostEnabled: boolean;
5759
/** The [[QueryOptions]] for this query. __Read Only__ **/
5860
// default is to get queryOptions and dataService from the entityManager.
5961
declare queryOptions?: QueryOptions;
@@ -91,6 +93,7 @@ export class EntityQuery {
9193
this.parameters = {};
9294
this.inlineCountEnabled = false;
9395
this.noTrackingEnabled = false;
96+
this.usePostEnabled = false;
9497
// default is to get queryOptions and dataService from the entityManager.
9598
// this.queryOptions = new QueryOptions();
9699
// this.dataService = new DataService();
@@ -389,15 +392,15 @@ export class EntityQuery {
389392
}
390393

391394
/**
392-
Returns a query with the 'inlineCount' capability either enabled or disabled. With 'inlineCount' enabled, an additional 'inlineCount' property
395+
Returns a query with the `inlineCount` capability either enabled or disabled. With `inlineCount` enabled, an additional 'inlineCount' property
393396
will be returned with the query results that will contain the number of entities that would have been returned by this
394397
query with only the 'where'/'filter' clauses applied, i.e. without any 'skip'/'take' operators applied. For local queries this clause is ignored.
395398
> let query = new EntityQuery("Customers")
396399
> .take(20)
397400
> .orderBy("CompanyName")
398401
> .inlineCount(true);
399402
400-
will return the first 20 customers as well as a count of all of the customers in the remote store.
403+
will return the first 20 customers as well as a count of _all_ of the customers in the remote store.
401404
@param enabled - (default = true) Whether or not inlineCount capability should be enabled. If this parameter is omitted, true is assumed.
402405
**/
403406
inlineCount(enabled?: boolean) {
@@ -413,7 +416,7 @@ export class EntityQuery {
413416
}
414417

415418
/**
416-
Returns a query with the 'noTracking' capability either enabled or disabled. With 'noTracking' enabled, the results of this query
419+
Returns a query with the `noTracking` capability either enabled or disabled. With `noTracking` enabled, the results of this query
417420
will not be coerced into entities but will instead look like raw javascript projections. i.e. simple javascript objects.
418421
> let query = new EntityQuery("Customers")
419422
> .take(20)
@@ -427,6 +430,23 @@ export class EntityQuery {
427430
return clone(this, "noTrackingEnabled", enabled);
428431
}
429432

433+
/**
434+
Returns a query with the `usePost` capability either enabled or disabled. With `usePost` enabled, the query is sent
435+
as a POST request (instead of GET) and the query expression will be sent as JSON in the body of the post.
436+
Note that the server must be able to parse the body of the request; otherwise the query expression will be ignored.
437+
> let query = new EntityQuery("Customers")
438+
> .where("companyId", "eq", 1)
439+
> .usePost(true);
440+
results in a POST request to `{host}/{path}/Customers`
441+
with body `{"where": {"companyId":{"eq":1}}}`
442+
@param enabled - (default = true) Whether or not usePost should be enabled. If this parameter is omitted, true is assumed.
443+
**/
444+
usePost(enabled?: boolean) {
445+
assertParam(enabled, "enabled").isBoolean().isOptional().check();
446+
enabled = (enabled === undefined) ? true : !!enabled;
447+
return clone(this, "usePostEnabled", enabled);
448+
}
449+
430450
using(obj: EntityManager): EntityQuery;
431451
using(obj: DataService): EntityQuery;
432452
using(obj: JsonResultsAdapter): EntityQuery;
@@ -762,6 +782,7 @@ function fromJSON(eq: EntityQuery, json: Object) {
762782
},
763783
"inlineCountEnabled,inlineCount": false,
764784
"noTrackingEnabled,noTracking": false,
785+
"usePostEnabled,usePost": false,
765786
queryOptions: function (v: any) {
766787
return v ? QueryOptions.fromJSON(v) : undefined;
767788
}
@@ -786,6 +807,7 @@ function clone(eq: EntityQuery, propName?: string, value?: any) {
786807
"expandClause",
787808
"inlineCountEnabled",
788809
"noTrackingEnabled",
810+
"usePostEnabled",
789811
"usesNameOnServer",
790812
"queryOptions",
791813
"entityManager",

0 commit comments

Comments
 (0)