can-query-logic
Advanced tools
Comparing version 0.2.0 to 0.3.0
@@ -144,2 +144,8 @@ require("./src/comparators/enum-test"); | ||
index = algebra.index( | ||
{sort: "-name"}, | ||
[{id: 1, name:"g"}, {id: 2, name:"j"}, {id: 3, name:"m"}, {id: 4, name:"s"}].reverse(), | ||
{name: "k"}); | ||
equal(index, 2); | ||
index = algebra.index( | ||
{}, | ||
@@ -185,1 +191,10 @@ [{id: 1, name:"g"}, {id: 2, name:"j"}, {id: 3, name:"m"}, {id: 4, name:"s"}], | ||
}); | ||
QUnit.test("filterMembers with reverse sort", function(){ | ||
var sortedMembers = algebra.filterMembers( | ||
{sort: "-name"}, | ||
[{id: 1, name:"a"}, {id: 2, name:"z"}, {id: 3, name:"f"}, {id: 4, name:"s"}]); | ||
QUnit.deepEqual(sortedMembers, | ||
[{id: 2, name:"z"}, {id: 4, name:"s"}, {id: 3, name:"f"}, {id: 1, name:"a"}]); | ||
}); |
// for can-set compat | ||
var Query = require("../can-query-logic"); | ||
var canReflect = require("can-reflect"); | ||
var transform = require("can-get/transform/transform"); | ||
var transform = require("can-key/transform/transform"); | ||
var deleteKey = require("can-key/delete/delete"); | ||
var getKey = require("can-key/get/get"); | ||
var makeEnum = require("../src/types/make-enum"); | ||
var SET = require("../src/set"); | ||
var helpers = require("../src/helpers"); | ||
@@ -44,2 +47,13 @@ var IsBoolean = function(){ | ||
function convertToJSONAPISort(sortPropValue){ | ||
var parts = sortPropValue.split(' '); | ||
var isDesc = (parts[1] || '').toLowerCase() === 'desc'; | ||
return isDesc ? "-"+parts[0] : parts[0]; | ||
} | ||
function convertToLegacySort(value) { | ||
var result = helpers.sortData(value); | ||
return result.desc ? "-"+result.prop : result.prop; | ||
} | ||
var defaultAlgebra; | ||
@@ -270,2 +284,9 @@ | ||
sort: function(prop, sortFunc){ | ||
/** | ||
* var parts = sortPropValue.split(' '); | ||
return { | ||
prop: parts[0], | ||
desc: (parts[1] || '').toLowerCase() === 'desc' | ||
}; | ||
*/ | ||
if(!prop) { | ||
@@ -277,15 +298,22 @@ prop = "sort"; | ||
} | ||
var hydrateTransfomer = {}; | ||
hydrateTransfomer["filter."+prop] = "sort"; | ||
var serializeTransformer = { | ||
"sort": prop | ||
}; | ||
return { | ||
hydrate: function(raw){ | ||
return transform(raw, hydrateTransfomer); | ||
var clone = canReflect.serialize(raw); | ||
var sort = getKey(clone,"filter."+prop); | ||
if(sort !== undefined) { | ||
deleteKey(clone,"filter."+prop); | ||
clone.sort = convertToJSONAPISort(sort); | ||
} | ||
return clone; | ||
}, | ||
serialize: function(raw){ | ||
// TODO: fix bug in transform so it doesn't delete props | ||
return prop === "sort" ? raw : transform(raw, serializeTransformer); | ||
var clone = canReflect.serialize(raw); | ||
var sort = clone.sort; | ||
if(sort !== undefined) { | ||
delete clone.sort; | ||
clone[prop] = convertToLegacySort(sort); | ||
} | ||
return clone; | ||
} | ||
@@ -292,0 +320,0 @@ }; |
@@ -53,3 +53,3 @@ @module {function} can-query-logic | ||
}, | ||
sort: "name desc", | ||
sort: "-name", | ||
page: {start: 0, end: 19} | ||
@@ -73,3 +73,3 @@ },[ | ||
// Sort the results of the selection | ||
sort: "name desc", | ||
sort: "-name", | ||
// Selects a range of the sorted result | ||
@@ -97,3 +97,3 @@ page: {start: 0, end: 19} | ||
```js | ||
new Query({ | ||
new QueryLogic({ | ||
// keys that uniquely represent this type | ||
@@ -110,5 +110,5 @@ identity: ["id"], | ||
By default, filter properties like `status` in `{filter: {status: "complete"}}` | ||
are hydrated to one of the [can-query/types/comparisons comparison types] like | ||
are used to create to one of the [can-query/types/comparisons comparison instances] like | ||
`GreaterThan`. A matching schema key will overwrite this behavior. How this | ||
works is explained in the _Special Comparison Logic_ section below. | ||
works is explained in the [Defining filter properties with special logic](#Definingfilterpropertieswithspeciallogic) section below. | ||
@@ -123,4 +123,3 @@ @param {Object} [options] The following _optional_ options are used to translate | ||
The _Special Comparison Logic_ section below describes how to use these | ||
options to match your query's logic to your servers. | ||
The [Changing the query structure](#Changingthequerystructure) section below describes how to use these options to match your query's logic to your servers. | ||
@@ -143,6 +142,7 @@ | ||
``` | ||
/api/todos?filter[complete]=true&sort=name+asc | ||
/api/todos?filter[complete]=true&sort=name | ||
``` | ||
The values after the `?` are used to control the data that comes back. Those values [can-deparam]-ed into | ||
The values after the `?` are used to control the data that comes back. Those values are | ||
[can-deparam deserialized] into | ||
a query object look like this: | ||
@@ -153,8 +153,7 @@ | ||
filter: {complete: true}, | ||
sort: "name asc" | ||
sort: "name" | ||
} | ||
``` | ||
This object represents a [can-query-logic/query Query]. This specific query indicates to | ||
request completed todos and have the todos sorted by their _name_. | ||
This object represents a [can-query-logic/query Query]. This specific query is for requesting completed todos and have the todos sorted by their _name_. | ||
@@ -183,6 +182,23 @@ A `QueryLogic` instance _understands_ what a `Query` represents. For example, it can filter items | ||
A [can-query-logic.prototype.isMember] to see if a particular item | ||
belongs to a query and [can-query-logic.prototype.index] to get the location where that | ||
item should be inserted. This is particularly useful for creating real-time behaviors. | ||
The [can-query-logic.prototype.filterMembers] method allows `QueryLogic` to be used similar to a database. `QueryLogic` instances methods help solve other problems too: | ||
- __real-time__ - [can-query-logic.prototype.isMember] returns if a particular item | ||
belongs to a query and [can-query-logic.prototype.index] returns the location where that item belongs. | ||
- __caching__ - [can-query-logic.prototype.isSubset] can tell you if you've already loaded | ||
data you are looking for. [can-query-logic.prototype.difference] can tell you what data | ||
you need to load that already isn't in your cache. | ||
In fact, `can-query-logic`'s most unique ability is to be able to directly compare | ||
queries that represent sets of data instead of having to compare | ||
the data itself. For example, if you already loaded all completed todos, | ||
`can-query-logic` can tell you how to get all remaining todos: | ||
```js | ||
var completedTodosQuery = {filter: {complete: false}}; | ||
var allTodosQuery = {}; | ||
var remainingTodosQuery = queryLogic.difference(allTodosQuery, completedTodosQuery); | ||
remainingTodosQuery //-> {filter: {complete: {$ne: false}}} | ||
``` | ||
## Use | ||
@@ -214,6 +230,6 @@ | ||
filter: { | ||
complete: false | ||
complete: {$in: [false, null]} | ||
}, | ||
// Sort the results of the selection | ||
sort: "name desc", | ||
sort: "-name", | ||
// Selects a range of the sorted result | ||
@@ -224,8 +240,13 @@ page: {start: 0, end: 19} | ||
This structures follows the [Fetching Data JSONAPI specification](http://jsonapi.org/format/#fetching). | ||
There's: | ||
- a `filter` property for filtering records, | ||
- a `sort` property for specifying the order to sort records, and | ||
- a `page` property that selects a range of the sorted result. | ||
- a [filter](http://jsonapi.org/format/#fetching-filtering) property for filtering records, | ||
- a [sort](http://jsonapi.org/format/#fetching-sorting) property for specifying the order to sort records, and | ||
- a [page](http://jsonapi.org/format/#fetching-pagination) property that selects a range of the sorted result. _The range indexes are inclusive_. | ||
> __NOTE__: [can-connect] does not follow the rest of the JSONAPI specification. Specifically | ||
> [can-connect] expects your server to send back JSON data in a different format. | ||
If you control the service layer, we __encourage__ you to make it match the default | ||
@@ -307,6 +328,9 @@ [can-query-logic/query]. The default query structure also supports the following [can-query-logic/comparison-operators]: `$eq`, `$gt`, `$gte`, `$in`, `$lt`, `$lte`, `$ne`, `$nin`. | ||
```js | ||
// 1. DEFINE YOUR TYPE | ||
// DEFINE YOUR TYPE | ||
const Todo = DefineMap.extend({...}); | ||
// CREATE YOUR QUERY LOGIC | ||
var todoQueryLogic = new QueryLogic(Todo, { | ||
// Takes what your service expects: {where: {...}} | ||
// Returns what QueryLogic expects: {filter: {...}} | ||
toQuery(params){ | ||
@@ -318,2 +342,4 @@ var where = params.where; | ||
}, | ||
// Takes what QueryLogic expects: {filter: {...}} | ||
// Returns what your service expects: {where: {...}} | ||
toParams(query){ | ||
@@ -327,3 +353,3 @@ var where = query.filter; | ||
// 2. CREATE YOUR CONNECTION | ||
// PASS YOUR QueryLogic TO YOUR CONNECTION | ||
realTimeRest({ | ||
@@ -340,3 +366,3 @@ url: "/todos", | ||
If the logic of the [can-query-logic/query default query] is not adequate to represent | ||
the behavior of your service layer queries, you can define special behaviors called `SetType`s to | ||
the behavior of your service layer queries, you can define special classes called `SetType`s to | ||
provide the additional logic. | ||
@@ -348,6 +374,4 @@ | ||
Before reading the following sections, it's useful to have some background information on | ||
how `can-query-logic` works. | ||
how `can-query-logic` works. We suggest reading the [How it works](#Howitworks) section. | ||
`can-query-logic` uses [set theory](https://en.wikipedia.org/wiki/Set_theory) | ||
#### Built-in special types | ||
@@ -373,8 +397,13 @@ | ||
const todoLogic = new QueryLogic(Todo); | ||
todoLogic.union( | ||
var unionQuery = todoLogic.union( | ||
{filter: {status: ["new","assigned"] }}, | ||
{filter: {status: "complete" }} | ||
) //-> {} | ||
) | ||
unionQuery //-> {} | ||
``` | ||
> NOTE: `unionQuery` is empty because if we loaded all todos that | ||
> are new, assigned, and complete, we've loaded every todo. | ||
> The `{}` query would load every todo. | ||
@@ -507,24 +536,36 @@ #### Custom types that work with the comparison operators | ||
QueryLogic.defineComparison(SearchableStringSet,SearchableStringSet,{ | ||
// Return a set that would load all items in searchA and searchB. | ||
union(searchA, searchB){ | ||
// if searchA's text contains searchB's text, then | ||
// If searchA's text contains searchB's text, then | ||
// searchB will include searchA's results. | ||
if(searchA.value.includes(searchB.value)) { | ||
// A:`food` ∪ B:`foo` => `foo` | ||
return searchB; | ||
} | ||
if(searchB.value.includes(searchA.value)) { | ||
// A:`foo` ∪ B:`food` => `foo` | ||
return searchA; | ||
} | ||
// A:`ice` ∪ B:`cream` => `ice` || `cream` | ||
return new QueryLogic.Or([searchA, searchB]); | ||
}, | ||
// Return a set that would load items shared by searchA and searchB. | ||
intersection(searchA, searchB){ | ||
// if searchA's text contains searchB's text, then | ||
// If searchA's text contains searchB's text, then | ||
// searchA is the shared search results. | ||
if(searchA.value.includes(searchB.value)) { | ||
// A:`food` ∩ B:`foo` => `food` | ||
return searchA; | ||
} | ||
if(searchB.value.includes(searchA.value)) { | ||
// A:`foo` ∩ B:`food` => `food` | ||
return searchB; | ||
} | ||
// A:`ice` ∩ B:`cream` => `ice` && `cream` | ||
// But there is no `QueryLogic.AndValues`, | ||
// So we return `UNDEFINABLE`. | ||
return QueryLogic.UNDEFINABLE; | ||
}, | ||
// Return a set that would load items in searchA that are not in | ||
// searchB. | ||
difference(searchA, searchB){ | ||
@@ -534,2 +575,3 @@ // if searchA's text contains searchB's text, then | ||
if(searchA.value.includes(searchB.value)) { | ||
// A:`food` \ B:`foo` => ∅ | ||
return QueryLogic.EMPTY; | ||
@@ -541,7 +583,11 @@ } | ||
if(searchB.value.includes(searchA.value)) { | ||
// A:`foo` \ B:`food` => UNDEFINABLE | ||
return QueryLogic.UNDEFINABLE; | ||
} | ||
// A:`ice` \ B:`cream` => `ice` && !`cream` | ||
// If there's another situation, we | ||
// aren't able to tell if there is a difference. | ||
return QueryLogic.UNKNOWABLE; | ||
// aren't able to express the difference | ||
// so we return UNDEFINABLE. | ||
return QueryLogic.UNDEFINABLE; | ||
} | ||
@@ -632,3 +678,3 @@ }); | ||
[can-define/map/map]'s expose this type information as a schema: | ||
[can-define/map/map]s expose this type information as a schema: | ||
@@ -661,3 +707,3 @@ ```js | ||
The queries (ex: `{ filter: {name: "assigned"} }`) are hydrated to set types like: | ||
The queries (ex: `{ filter: {name: "assigned"} }`) are hydrated to `SetType`s like: | ||
@@ -672,2 +718,6 @@ ```js | ||
> NOTE: __hydrated__ is the opposite of serialization. It means we take | ||
> a plain JavaScript object like `{ filter: {name: "assigned"} }` and | ||
> create instances of types with it. | ||
The following is a more complex query and what it gets hydrated to: | ||
@@ -681,3 +731,3 @@ | ||
}, | ||
sort: "name desc", | ||
sort: "-name", | ||
page: {start: 0, end: 9} | ||
@@ -691,3 +741,3 @@ } | ||
}), | ||
sort: "name desc", | ||
sort: "-name", | ||
page: new RealNumberRangeInclusive(0,9) | ||
@@ -721,2 +771,6 @@ }); | ||
`can-query-logic`s methods reflect [set theory](https://en.wikipedia.org/wiki/Set_theory) | ||
operations. That's why most types need a `union`, `intersection`, and `difference` | ||
method. With that, other methods like `isEqual` and `isSubset` can be derived. | ||
In this case, `set.union` will call `BasicQuery`'s union with | ||
@@ -754,2 +808,2 @@ itself. This will see that the `sort` and `page` results match | ||
And this is what is returned as a result of the union. | ||
The serialized output above is what is returned as a result of the union. |
{ | ||
"name": "can-query-logic", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "query data", | ||
@@ -49,3 +49,3 @@ "homepage": "", | ||
"can-define-lazy-value": "^1.0.2", | ||
"can-get": "<2.0.0", | ||
"can-key": "<2.0.0", | ||
"can-reflect": "^1.13.3", | ||
@@ -52,0 +52,0 @@ "can-symbol": "^1.6.1" |
@@ -46,7 +46,7 @@ var helpers = { | ||
sortData: function (sortPropValue) { | ||
var parts = sortPropValue.split(' '); | ||
return { | ||
prop: parts[0], | ||
desc: (parts[1] || '').toLowerCase() === 'desc' | ||
}; | ||
if(sortPropValue[0] === "-") { | ||
return {prop: sortPropValue.slice(1), desc: true}; | ||
} else { | ||
return {prop: sortPropValue, desc: false}; | ||
} | ||
}, | ||
@@ -53,0 +53,0 @@ sorter: function (sortPropValue) { |
@@ -109,3 +109,3 @@ var canSymbol = require("can-symbol"); | ||
if(basicQuery.sort !== id+" ASC") { | ||
if(basicQuery.sort !== id) { | ||
res.sort = basicQuery.sort; | ||
@@ -149,3 +149,3 @@ } | ||
} else { | ||
query.sort = id+" ASC"; | ||
query.sort = id; | ||
} | ||
@@ -152,0 +152,0 @@ return new BasicQuery(query); |
@@ -5,3 +5,3 @@ var set = require("../set"); | ||
var canReflect = require("can-reflect"); | ||
var canGet = require("can-get"); | ||
var canGet = require("can-key/get/get"); | ||
var canSymbol = require("can-symbol"); | ||
@@ -8,0 +8,0 @@ |
@@ -71,3 +71,3 @@ var BasicQuery = require("./basic-query"); | ||
page: new BasicQuery.RecordRange(1,3), | ||
sort: 'note AsC' | ||
sort: 'note' | ||
}); | ||
@@ -98,3 +98,3 @@ var res = query.filterFrom(items); | ||
page: new BasicQuery.RecordRange(1,3), | ||
sort: 'note deSc' | ||
sort: '-note' | ||
}); | ||
@@ -101,0 +101,0 @@ var res = query.filterFrom(items); |
@@ -28,3 +28,3 @@ var set = require("../set"); | ||
if(!this.sort) { | ||
this.sort = "id ASC"; | ||
this.sort = "id"; | ||
} | ||
@@ -31,0 +31,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
230056
5880
+ Addedcan-key@<2.0.0
+ Addedcan-key@1.2.1(transitive)
- Removedcan-get@<2.0.0
- Removedcan-get@0.2.1(transitive)