@hitchy/plugin-odem-rest 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -156,6 +156,7 @@ module.exports = function() {
156
156
 
157
157
  // here comes the REST-compliant part
158
158
  routes.set( "GET " + resolve( modelUrl ), reqBadModel || reqFetchItems );
159
+ routes.set( "QUERY " + resolve( modelUrl ), reqBadModel || reqFetchItems );
159
160
  routes.set( "GET " + resolve( modelUrl, ":uuid" ), reqBadModel || reqFetchItem );
160
161
 
161
162
  routes.set( "HEAD " + resolve( modelUrl, ":uuid" ), reqBadModel || reqCheckItem );
@@ -314,7 +315,13 @@ module.exports = function() {
314
315
  return undefined;
315
316
  }
316
317
 
317
- return ( req.query.q ? reqListMatches : reqListAll ).call( this, req, res );
318
+ let handler = reqListAll;
319
+
320
+ if ( req.query.q || req.query.query || req.method === "QUERY" ) {
321
+ handler = reqListMatches;
322
+ }
323
+
324
+ return handler.call( this, req, res );
318
325
  }
319
326
 
320
327
  /**
@@ -356,30 +363,66 @@ module.exports = function() {
356
363
  * @param {Hitchy.Core.ServerResponse} res API for creating response
357
364
  * @returns {Promise|undefined} promises request processed successfully
358
365
  */
359
- function reqListMatches( req, res ) {
366
+ async function reqListMatches( req, res ) {
360
367
  logDebug( "got request listing matching items" );
361
368
 
362
369
  if ( !Schema.mayBeExposed( req, Model ) ) {
363
370
  res.status( 403 ).json( { error: "access forbidden by model" } );
364
- return undefined;
371
+ return;
365
372
  }
366
373
 
367
- const { q: query = "", offset = 0, limit = Infinity, sortBy = null, descending = false, loadRecords = true, count = false } = req.query;
368
-
369
- if ( !query ) {
374
+ const {
375
+ q: simpleQuery = "",
376
+ offset = 0,
377
+ limit = Infinity,
378
+ sortBy = null,
379
+ descending = false,
380
+ loadRecords = true,
381
+ count = false
382
+ } = req.query;
383
+ let parsedQuery;
384
+
385
+ if ( simpleQuery ) {
386
+ // support old-style simple queries with ?q=<query>
387
+ parsedQuery = parseQuery( simpleQuery );
388
+
389
+ if ( !parsedQuery ) {
390
+ res.status( 400 ).json( { error: "invalid query, e.g. use ?q=name:operation:value" } );
391
+ return;
392
+ }
393
+ } else if ( req.query.query ) {
394
+ // support JSON-encoded queries in URL with ?query=<json-query>
395
+ try {
396
+ parsedQuery = JSON.parse( req.query.query );
397
+ } catch ( cause ) {
398
+ res.status( 400 ).json( { error: 'invalid extended query, e.g. use ?query={"operation":{"name":value}}' } );
399
+ return;
400
+ }
401
+ } else if ( req.method === "QUERY" ) {
402
+ // support HTTP QUERY method (which is in draft state, currently)
403
+ const query = await req.fetchBody();
404
+
405
+ if ( typeof query === "string" ) {
406
+ try {
407
+ parsedQuery = JSON.parse( query );
408
+ } catch ( cause ) {
409
+ res.status( 400 ).json( { error: "invalid query in QUERY request payload" } );
410
+ return;
411
+ }
412
+ } else if ( query && typeof query === "object" && !Array.isArray( query ) ) {
413
+ parsedQuery = query;
414
+ } else {
415
+ res.status( 400 ).json( { error: "invalid query in QUERY request payload" } );
416
+ return;
417
+ }
418
+ } else {
370
419
  res.status( 400 ).json( { error: "missing query" } );
371
- return undefined;
372
- }
373
-
374
- const parsedQuery = parseQuery( query );
375
- if ( !parsedQuery ) {
376
- res.status( 400 ).json( { error: "invalid query, e.g. use ?q=name:operation:value" } );
377
- return undefined;
420
+ return;
378
421
  }
379
422
 
380
423
  const meta = count || req.headers["x-count"] ? {} : null;
381
424
 
382
- return Model.find( parsedQuery, { offset, limit, sortBy, sortAscendingly: !descending }, {
425
+ await Model.find( parsedQuery, { offset, limit, sortBy, sortAscendingly: !descending }, {
383
426
  metaCollector: meta,
384
427
  loadRecords
385
428
  } )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hitchy/plugin-odem-rest",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "HTTP REST API for Hitchy's ODM",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -19,14 +19,14 @@
19
19
  "homepage": "https://gitlab.com/hitchy/plugin-odem-rest#plugin-odem-rest",
20
20
  "peerDependencies": {
21
21
  "@hitchy/core": "^0.7.2",
22
- "@hitchy/plugin-odem": "^0.7.1"
22
+ "@hitchy/plugin-odem": "^0.7.4"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@hitchy/server-dev-tools": "^0.4.3",
26
- "c8": "^7.11.3",
27
- "eslint": "^8.15.0",
26
+ "c8": "^7.12.0",
27
+ "eslint": "^8.24.0",
28
28
  "eslint-config-cepharum": "^1.0.12",
29
- "eslint-plugin-promise": "^6.0.0",
29
+ "eslint-plugin-promise": "^6.0.1",
30
30
  "mocha": "^10.0.0",
31
31
  "should": "^13.2.3",
32
32
  "should-http": "^0.1.1"
@@ -6,6 +6,9 @@ HTTP REST API for [Hitchy's](https://core.hitchy.org/) [ODM](https://odem.hitchy
6
6
 
7
7
  This plugin is defining blueprint routes for accessing data managed in ODM using REST API.
8
8
 
9
+ ## License
10
+
11
+ [MIT](LICENSE)
9
12
 
10
13
  ## Installation
11
14
 
@@ -59,7 +62,7 @@ module.exports = {
59
62
 
60
63
  When starting your Hitchy-based application it will discover a model named **LocalEmployee** and expose it via REST API using URLs like `/api/local-employee` just because of this package and its dependencies mentioned before.
61
64
 
62
- #### Models of Hitchy Plugins
65
+ #### Models of Hitchy plugins
63
66
 
64
67
  Due to the way Hitchy is discovering plugins and compiling [components](https://core.hitchy.org/internals/components.html) defined there, this plugin is always covering models defined in installed plugins as well. Thus, any plugin is capable of defining additional models to be supported. In addition, some plugin may extend models defined by another plugin.
65
68
 
@@ -90,18 +93,19 @@ In Hitchy's ODM all model instances or items are uniquely addressable via UUIDs.
90
93
 
91
94
  The provided routes implement these actions:
92
95
 
93
- | Method | URL | Action |
94
- |---|---|---|
95
- | GET | `/api/model` | Lists items of selected model. |
96
- | GET | `/api/model/<uuid>` | Fetches properties of selected item. |
97
- | PUT | `/api/model/<uuid>` | Replaces all properties of selected item with those given in request body. Selected item is created when missing. |
98
- | PATCH | `/api/model/<uuid>` | Adjusts selected item by replacing values of properties given in request body (leaving those missing in request body untouched). |
99
- | POST | `/api/model` | Creates new item initialized with properties provided in request body. |
100
- | DELETE | `/api/model/<uuid>` | Removes selected item from model's collection. |
101
- | HEAD | `/api/model` | Tests if selected model exists. |
102
- | HEAD | `/api/model/<uuid>` | Tests if selected item exists. |
96
+ | Method | URL | Action |
97
+ |---|---|------------------------------------------------------------------------------------------------------------------------------------------|
98
+ | GET | `/api/model` | Lists items of selected model. |
99
+ | GET | `/api/model/<uuid>` | Fetches properties of selected item. |
100
+ | PUT | `/api/model/<uuid>` | Replaces all properties of selected item with those given in request body. Selected item is created when missing. |
101
+ | PATCH | `/api/model/<uuid>` | Adjusts selected item by replacing values of properties given in request body (leaving those missing in request body untouched). |
102
+ | POST | `/api/model` | Creates new item initialized with properties provided in request body. |
103
+ | DELETE | `/api/model/<uuid>` | Removes selected item from model's collection. |
104
+ | HEAD | `/api/model` | Tests if selected model exists. |
105
+ | HEAD | `/api/model/<uuid>` | Tests if selected item exists. |
106
+ | QUERY | `/api/model` | Fetches items of model matching JSON-encoded query in request body.<br><br>_Not working unless Node.js is supporting [HTTP QUERY method](https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/?include_text=1)._ |
103
107
 
104
- In addition following URLs are available for accessing schema information:
108
+ In addition, following URLs are available for accessing schema information:
105
109
 
106
110
  | Method | URL | Action |
107
111
  |---|---|---|
@@ -121,7 +125,7 @@ Response status code indicates basic result of either requests.
121
125
  | 405 | A given method isn't allowed on selected model or item. This is basically a more specific information related to performing some invalid request like trying to PATCH or DELETE a whole model instead of a single item. |
122
126
 
123
127
 
124
- ### Convenience Routes
128
+ ### Convenience routes
125
129
 
126
130
  By default, the module is exposing another set of routes for every model that enables requesting either supported action using GET-requests. This is assumed to be very useful in development e.g. to conveniently add or remove items using regular browser.
127
131
 
@@ -139,7 +143,7 @@ There are no extra routes following this pattern for actions that are exposed vi
139
143
 
140
144
  All request data is provided in query parameters instead of request body for GET requests don't have a body.
141
145
 
142
- #### Disabling Feature
146
+ #### Disabling feature
143
147
 
144
148
  Disable this feature in the configuration file **config/model.js**:
145
149
 
@@ -150,16 +154,16 @@ exports.model = {
150
154
  ```
151
155
 
152
156
 
153
- ### Extended Fetching of Items
157
+ ### Extended fetching of items
154
158
 
155
159
  Whenever fetching a list of items using GET request on a model's URL there are additional options for controlling the retrieved list.
156
160
 
157
161
 
158
162
  #### Filtering
159
163
 
160
- Using query parameter `q` the list of fetched items can be limited to those items matching criteria given in that query parameter. The abbreviated name `q` just refers to a _search query_.
164
+ Using query parameter `q` the list of fetched items can be limited to those items matching criteria given in that simple query. The abbreviated name `q` just refers to a _search query_.
161
165
 
162
- ##### Simple Comparisons
166
+ ##### Simple comparisons
163
167
 
164
168
  The search query may comply with the pattern `name:operation:value` to compare every item's property with a given value using one of these operations:
165
169
 
@@ -174,7 +178,7 @@ The search query may comply with the pattern `name:operation:value` to compare e
174
178
 
175
179
  For example, a GET-request for `/api/localEmployee?q=lastName:eq:Doe` will deliver all items of model **LocalEmployee** with property **lastName** equal given value **Doe**. The value may contain further colons.
176
180
 
177
- ##### Simple Unary Tests
181
+ ##### Unary tests
178
182
 
179
183
  Alternatively the search query may comply with the pattern `name:operation` for testing the named property using one of these supported operations:
180
184
 
@@ -185,7 +189,7 @@ Alternatively the search query may comply with the pattern `name:operation` for
185
189
 
186
190
  For example, a GET-request for `/api/localEmployee?q=lastName:null` will deliver all items of model **LocalEmployee** with unset property **lastName**.
187
191
 
188
- ##### Simple Ternary Tests
192
+ ##### Ternary tests
189
193
 
190
194
  A third type of test operations are ternary tests. This refers to operations consisting of three parameters: the property's name and two values instead of one to compare that property's values with. Related queries comply with the pattern `name:operation:value:value`, hence using colon in first given value is not supported.
191
195
 
@@ -195,9 +199,26 @@ A third type of test operations are ternary tests. This refers to operations con
195
199
 
196
200
  For example, a GET-request for `/api/localEmployee?q=salary:between:2000:4000` will deliver all items of model **LocalEmployee** with value of property **salary** in range from 2000 to 4000.
197
201
 
198
- ##### Complex Tests
202
+ ##### Complex tests <Bade type=info text=v0.5.2+></Badge>
199
203
 
200
- There will be more complex tests supported in future versions using different formats in query parameter `q`.
204
+ Hitchy ODM supports more complex queries that can't be encoded as such simple queries as described above. Thus, a different way of querying has been added.
205
+
206
+ When using parameter `query` instead of `q`, its value is assumed to be a JSON-encoded query complying with query syntax supported by [Model.find() method of Hitchy ODM](https://odem.hitchy.org/api/model.html#model-find).
207
+
208
+ ```http request
209
+ GET /api/user?query={"in":{"name":["john","jane","jason"]}}
210
+ ```
211
+
212
+ _This example omits proper URL encoding of value to `query` parameter for illustration purposes. You should always encode queries._
213
+
214
+ In addition, support for upcoming [HTTP QUERY method](https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/?include_text=1) has been prepared. However, this one does not work unless Node.js is accepting HTTP requests with method `QUERY`.
215
+
216
+ ```http request
217
+ QUERY /api/user
218
+ Content-Type: application/json
219
+
220
+ {"in":{"name":["john","jane","jason"]}}
221
+ ```
201
222
 
202
223
 
203
224
  #### Sorting