techniq / odata-query Goto Github PK
View Code? Open in Web Editor NEWOData v4 query builder
Home Page: https://www.npmjs.com/package/odata-query
License: MIT License
OData v4 query builder
Home Page: https://www.npmjs.com/package/odata-query
License: MIT License
I have a a following buildQueryTest.mjs
:
import buildQuery from "odata-query";
console.log(buildQuery({ top: 5 }));
It fails the following way:
michal@dzieni dataverse-poc % node buildQueryTest.mjs
file:///Users/michal/Projects/dataverse-poc/buildQueryTest.mjs:3
console.log(buildQuery({ top: 5 }));
^
TypeError: buildQuery is not a function
at file:///Users/michal/Projects/dataverse-poc/buildQueryTest.mjs:3:13
at ModuleJob.run (node:internal/modules/esm/module_job:193:25)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:541:24)
at async loadESM (node:internal/process/esm_loader:83:5)
at async handleMainPromise (node:internal/modules/run_main:65:12)
Node.js v18.6.0
Workaround is to use a following piece of code:
import _buildQuery from "odata-query";
const buildQuery = _buildQuery.default;
console.log(buildQuery({ top: 5 }));
Although it is incorrect according to what TS types say:
To sum it up: to properly import odata-query
in ESM-powered app with TypeScript, I have to perform following gymnastics:
import _buildQuery from 'odata-query'
const buildQuery = (_buildQuery as unknown as {default: typeof _buildQuery}).default
Request
"filter": {
or: [{
PropName: { gt: 5 },
and: [
{ SomeProp: 1 },
{ AnotherProp: 2 }
]
}
Expected
$filter=((PropName gt 5 or ((SomeProp eq 1) and (AnotherProp eq 2))))
Response
$filter=((PropName gt 5 and ((SomeProp eq 1) and (AnotherProp eq 2))))
Hi Team ,
I have installed this library as per instruction, But when i am using it, its giving issue odataQuery is not defined.
Below are my code for test. I tested it on 6.9 and 8.0 both.
var odataQuery = require("odata-query")
const filter = [{ SomeProp: 1 }, { AnotherProp: 2 }, 'startswith(Name, "foo")'];
oDataQuery({ filter })
console.log('oDataQuery..'+oDataQuery);.
Thanks ,
When passing filter functions such as contains
, startswith
, endswith
, etc. there's a space being added between column name and value.
With updates to Odata it might have a stricter check to avoid spacing inside the function.
startswith(Name, "foo") should be
startswith(Name,"foo") <<<<<<< No space inside
Hi,
As far as I can see the $search query parameter is not supported. Do you plan to implement that?
http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752364
Thanks!
Extracted from #20 (comment)
Regarding dates and times they do not appear to qualify the values with a type except optionally with a duration.
DateValue eq 2012-12-03
DateTimeOffsetValue eq 2012-12-03T07:16:23Z
DurationValue eq duration'P12DT23H59M59.999999999999S'
DurationValue eq 'P12DT23H59M59.999999999999S'
TimeOfDayValue eq 07:59:59.999
We could support the following in handleValue
{ type: 'datetimeoffset', value: date }
and a Date
instance (like we do now):
date.toISOString()
=> 2012-12-03T07:16:23Z
{ type: 'date', value: date }
:
date.toISOString().split('T')[0]
=> 2012-12-03
We should also support date
as either a Date
instance or a string (ex. 2018-01-02
):
const dateValue = (typeof date === 'string') ? new Date(date) : date instanceof Date ? date : null
const value = dateValue && dateValue.toISOString();
As proposed here
In order to verify builds and runs tests as part of a pull request, we can add the default Github Actions Node Workflow.
Hi I am trying to implement 'and' and 'in' in conjunction and it's not working because of the placement of () around the 'in'.
filter= {
and: [
{prop1: `${prop1}`}, { prop2:`${prop2}`}, ({prop3: {'in': ['value1','value2'] }})]
};
The issue is it is doing
$filter=(prop1 eq prop1 and prop2 eq prop2 or prop3 eq value1 or prop3 eq value2)
what I want
$filter=(prop1 eq prop1 and prop2 eq prop2 and (prop3 eq value1 or prop3 eq value2 or...)
Where am I going wrong?
I've tried to use the new 'not' operator for some of my logic while I noticed it doesn't get applied correctly - it is applied 'not' to the first condition instead of the whole group
It loolks like the example presents the issue as well:
const filter = {
not: {
and:[
{SomeProp: 1},
{AnotherProp: 2}
]
}
};
buildQuery({ filter })
=> '?$filter=(not (SomeProp eq 1) and (AnotherProp eq 2))'
It should be
=> '?$filter=(not ((SomeProp eq 1) and (AnotherProp eq 2)))'
as otherwise it would be considered as
(not SomeProp eq 1)
and (AnotherProp eq 2)
I would like to submit a feature request.
At present there is no support for cast
operator (search for "cast" here). For example, if I want to filter my collection based on some binary data, I would write the query as follows.
https://awesome.service/odata/v4/AwesomeEntities?$filter=Files/any(a:a/Hash eq cast('S0mEhAsH', Edm.Binary))&$expand=Files
However, currently I can't pass an object to query builder that can sufficiently represent this clause, like I can do it for other operators like eq
, such as {PropertyName: {eq: "SomeValue"}}
. I need to pass the string instead to the query builder. It would be much prettier if I can pass objects like below to the query builder for this purpose.
{
PropertyName: { eq: { cast: "Edm.TYPE", ValueExpr } }
}
// produces --> PropertyName eq cast(ValueExpr, "Edm.TYPE")
And similar behavior is naturally expected for navigational properties (including collections). What are your thoughts on this?
Lastly, thank you for this cool package. ๐
Per this StackOverflow answer we need to url encode the json for the array and use a parameter alias
builder.EntityType<TestEntity>().Collection
.Function("TestFunction2")
.ReturnsCollectionFromEntitySet<TestEntity>("TestEntities")
.CollectionParameter<person>("ArrayHere");
http://yourRestService/API/TestEntities/NS.TestFunction2(ArrayHere=@ArrayData)?@ArrayData=%5B%7B%22FirstName%22%3A%22Bob%22%2C+%22LastName%22%3A%22Dole%22%7D%2C%7B%22FirstName%22%3A%22Bill%22%2C+%22LastName%22%3A%22Clinton%22%7D%5D
Input:
{
Item: {
any: {
indexof(tolower(Name), 'gead'): {
eq: -1
}
}
}
}
Outputs -> Item/any(p:indexof(p/tolower(Name), 'adf') eq -1)
Expecting -> Item/any(p:indexof(tolower(p/Name), 'adf') eq -1)
Is there a syntax to achieve the above output? I tried switching indexof
with tolower
but it caused the lambda operator to not output. Any help would be appreciated! Thanks.
Originally posted by @fabio-b in #12 (comment)
This query works fine:
$apply=groupby(mfs/ID, aggregate(mfs(im_foo with average as Value)))
But odata-query can't produce that nested 'mfs' part wrapped in the aggregate ()
$apply=groupby(mfs/ID, aggregate(mfs(im_foo with average as Value)))
I tried transform with array, or objects, and nothing can produce that string:
const transform = {
groupBy: {
properties: ['mfs/ID'],
transform: {
aggregate: [
'mfs',
{
'mfs/im_Jitter': {
with: 'average',
as: 'Value',
},
'mfs/im_Latench': {
with: 'average',
as: 'Value1',
},
'mfs/im_PacketLossPercentage': {
with: 'average',
as: 'Value2',
},
},
],
},
},
};
Need to be able to create a filter like the following:
contains(cast(id,'String'),'medib') or contains(obj, 'medib')
Is there anyway to do this using this library? Read through the docs but can't find anything.
Hello,
I am using this library via https://github.com/diegomvh/angular-odata and have run into something that isn't currently supported. (I think)
My API has some derived types that I am trying to expand. It seems that odata-query is not able to describe the cast to the derived type in object form and manually entering the cast as a string value is interpreted as a nested expand. I understand the benefit of the string valued shorthand, but it precludes the ability to cast to the derived type.
Here is a contrived scenario:
Namespace: Example
Entities:
Endpoints:
If I want to query all of the Shapes but expand the Vertices for any Square objects, the query would be:
[root]/Shapes?$expand=Example.Square/Vertices
The object form doesn't have the ability to specify type, which isn't too big a deal since it is a more complex scenario. However, passing the string of "Example.Square/Vertices" to the expand property results in the this query:
[root]/Shapes?$expand=Example.Square($expand=Vertices)
Obviously, the above isn't correct given the model.
I realize that removing or modifying the string shorthand would be a large breaking change for many. Given that, is there a workaround for this scenario? Would it be possible to add this ability within the object form? Other ideas?
Quick Edit: OData v4
Hi i need to access this js library from a ts file in angular2.
Hi,
The library works great but I have an issue in combination with AWS API Gateway. It is choking on some characters { } ^
because they are not encoded.
So it would be nice to pass an option to the buildQuery
function that makes it possible to extends escapeIllegalChars
or pass a custom escapeIllegalChars
function. I'm also wondering if it is possible to use encodeURIComponent
instead of the current implementation? Is there a specific reason why it is not used?
I there an approach you prefer?
I'm happy to help you out by creating a pull request.
Kind regards
I think this is an oData v4 thing. Sample code: https://github.com/devnixs/ODataAngularResources/blob/master/src/odatavalue.js
When converting from ISO String to Date object, the library leaves off the milliseconds before the timezone string.
Ex.
Convert 2017-06-05T00:00:00.000Z to date object and pass to filter.
Filter creates URL string with 2017-06-05T00:00:00Z
The URL string should be 2017-06-05T00:00:00.000Z
Inside the filter object, I'm passing this date object:
Sun Jun 04 2017 20:00:00 GMT-0400 (EDT)
Let me know if you need me to provide anything else
Hello,
First of all, thanks for the great tool!
Have a question - can v6 be considered stable?
I'm going to use it in the TypeScript project, so I need type definitions, which are missing in the latest one, which is 5.7.0
.
Hi,
buildTransforms method firstly takes "aggregate" value and pushes to the result and only after it takes "filter" value and pushes to the result.
For example, we have the following query object:
{
"transform": {
"filter": [
{
"project": "some value"
}
],
"aggregate": {
"id": {
"with": "countdistinct",
"as": "count"
}
}
}
}
And result is:
$apply=aggregate(id with countdistinct as count)/filter(project eq 'some value')
And it's a wrong expression because first of all, we have to apply filter transformation.
Here:
Lines 496 to 500 in f38eb3d
params[key]
is 0
so is a falsy value, but it would be nice to make requests with $top=0
and $skip=0
(Right know it is ignored)
buildQuery({ filter: { SomeProp: 1 } } ); // top is undefined skip is undefined
// Expected: ?$filter=SomeProp eq 1
// Actual: ?$filter=SomeProp eq 1 โ
buildQuery({ filter: { SomeProp: 1 }, top: 0, skip: 0 } );
// Expected: ?$filter=SomeProp eq 1&$top=0&skip=0
// Actual: ?$filter=SomeProp eq 1 โ
buildQuery({ filter: { SomeProp: 1 }, top: 3 } ); //skip is undefined
// Expected: ?$filter=SomeProp eq 1&$top=3
// Actual: ?$filter=SomeProp eq 1&$top=3 โ
buildQuery({ filter: { SomeProp: 1 }, top: 3, skip: 0 } );
// Expected: ?$filter=SomeProp eq 1&$top=3&skip=0
// Actual: ?$filter=SomeProp eq 1&$top=3 โ
buildQuery({ filter: { SomeProp: 1 }, skip: 3 } ); // top is undefined
// Expected: ?$filter=SomeProp eq 1&$skip=3
// Actual: ?$filter=SomeProp eq 1&$skip=3 โ
buildQuery({ filter: { SomeProp: 1 }, top: 0, skip: 3 } );
// Expected: ?$filter=SomeProp eq 1&$top=0&skip=3
// Actual: ?$filter=SomeProp eq 1&$skip=3 โ
Hi Sean,
First of all, thanks for your package, it saves a lot of time creating those queries. I'm facing an issue that deeper objects result in double queries. Please consider the following object:
{
"filter": {
"Nomination": {
"ScheduledAt": {
"ge": "2021-12-12T23:00:00.000Z",
"le": "2021-12-13T22:59:59.999Z"
},
"NominationStatusValues": {
"any": {
"NominationStatusID": 3
}
}
}
},
"orderBy": "id",
"count": true,
"top": 15
}
this results in the following querystring (with some enters for readability):
$filter=
Nomination/ScheduledAt ge 2021-12-12T23:00:00.000Z and
Nomination/ScheduledAt le 2021-12-13T22:59:59.999Z and
Nomination/NominationStatusValues/any(nominationstatusvalues:nominationstatusvalues/NominationStatusID eq 3) and
Nomination/ScheduledAt ge 2021-12-12T23:00:00.000Z and
Nomination/ScheduledAt le 2021-12-13T22:59:59.999Z and
Nomination/NominationStatusValues/any(nominationstatusvalues:nominationstatusvalues/NominationStatusID eq 3)
&$orderby=id&$count=true&$top=15
Am I doing something wrong or did I stumble upon a bug? I've tried several versions (6.7.1, 6.7.0, 6.6.0) but to no avail.
Thanks in advance
Is just for ask about the posiblity of use zero value as key
Line 65 in a67e7f7
The if
not allow use that value
Can this be used with nodejs / express? If so, how?
The following results in: TypeError: buildQuery is not a function
const buildQuery = require('odata-query')
;
const filter = { PropName: 1 };
const query = buildQuery({filter});
I have the following scenario:
class User {
id: string;
[...]
teams: Member[];
}
class Member {
userId: string;
user: User;
teamId: string;
team: Team;
}
class Team {
id: string;
title: string;
[...]
}
and doing:
import buildQuery from "odata-query";
let query = buildQuery({
filter: {
Teams: {
all: {
and: [
{
"Team/Title": {
contains: "team1",
},
},
{
not: {
"Team/Title": {
contains: "team2",
},
},
},
],
},
},
},
});
console.log(query);
I expect:
?$filter=Teams/all(teams:((contains(teams/Team/Title,'team1')) and (not contains(teams/Team/Title,'team2'))))
but odata-query
produce an invalid query:
?$filter=Teams/all(teams:((contains(teams/Team/Title,'team1')) and (contains(teams/not/Team/Title,'team2'))))
as you can see teams/not/Team/Title
is wrong
Is this an issue or is there a way to achieve the expected query?
thanks in advance
Hi
Can you share odata-query sample express project for newbies ?
When using the in
-Operator with following filter the query doesn't reflect the implied and
filter: {
bar: 1,
foo: {
in: ['a', 'b'],
contains: 'foo'
}
}
}
?$filter=bar eq 1 and foo eq 'a' or foo eq 'b' and contains(foo,'foo')
?$filter=bar eq 1 and (foo eq 'a' or foo eq 'b' and contains(foo,'foo'))
Hi all.
Let me start with a "thank you for this simple but great piece of code". It saves me hours and hours.
Right now the lib has two compilation targets: commonjs and esm. Its enough by far if you use the library in a compiled (i.e webpack) project. Today i found myself in the need of "importing" the library on a legacy, almost vanillajs, es5 web project. It's not possible to 'import' or 'require' anything, so i cannot consume the library in this kind of project.
FEATURE REQUEST:
Add a new build target to bundle the lib for browsers (using browserify) so it creates a property buildQuery
in the global 'window' object of the browsers.
NOTE: I opened a local brach with the new functionallity; everything seems to work fine. I would PR the changes but I cannot push the branch.
I was wondering if there is a way to parse the query string and "recreate" the queryOptions
.
Could be useful to receive the query in a node Mocking-Server and return some values based on a given filter for example, like this:
// frontend
const query = buildQuery(queryOptions);
// backend
const queryOptions = parseBackIntoObjectSomehow(query)
When importing this package into a basic create-react-app you will get the following error:
Could not find dependency: 'tslib' relative to '/node_modules/odata-query/dist/commonjs/index.js'
The work around is to install tslib
along side this package.
My recommendation would be to install tslib
as a dependency rather than a devDependency, it seems tslib
is recommending that anyway.
Hi,
thank you for your amazing query builder.
I ran into this problem: if I pass a filter object with this structure:
{ "Module1/Module_CategoryModule": { "any": { "Module_CategoryId": { "eq": 2 } } } }
I get this query, which throws a Syntax error in Restier OData:
$filter=((Module1/Module_CategoryModule/any(module1/module_categorymodule:module1/module_categorymodule/Module_CategoryId%20eq%202)))
but actually the correct conversion would be this:
$filter=((Module1/Module_CategoryModule/any(c:c/Module_CategoryId%20eq%202)))
Did I do something wrong in the filter syntax?
Thank you
Marco
Great library and very helpful in working with OData! However, there is one small pain point I have encountered in use.
In some instances, I'd like to perform HTML encoding on Unicode chars via encodeURIComponent. When doing that in conjunction with buildQuery, some of the chars get encoded twice.
Would you consider adding the option of passing in an argument to forego character escaping (defaulting to performing the escape of course, so as not to affect existing users).
Perhaps implemented as a second positional parameter that destructures into possible configuration options. Something like below for instance:
export default function({
select,
filter,
search,
groupBy,
transform,
orderBy,
top,
skip,
key,
count,
expand,
action,
func,
format
} = {},
{
illegalCharEscape = true,
} = {}) {
An option like that would be very helpful for me, and I imagine other users as well.
I found raw usage in one of the PR - #9
{ SomeProp: { type: 'raw', value: datetime'${date.toISOString()}'
}
Can you please add it's details in the documentation?
Is there any way to create query using custom operator?
Something like dob relative today
This comment (60f825b) mistakenly broke checking for null values.
For example, the following now returns nothing
const filter = { Foo: null };
buildQuery({ filter })
// => ""
A workaround is to specify the eq
operator
const filter = { Foo: { eq: null };
buildQuery({ filter })
// => "?$filter=Foo eq null"
I think we should only check for undefined
which will fix this regression but still support easier conditional filters
There doesn't seem to be an easy way of comparing two columns using this library, with raw odata we can do simple
?$filter=SomeProp eq AnotherProp
but if we do
const filter = { SomeProp: 'AnotherProp' };
then we would get
?$filter=SomeProp eq 'AnotherProp'
which would do string comparison with string 'AnotherProp' and not the column of that name.
I was thinking if there is no better way if we could expand data types with type 'column' to achieve this
const filter = { "someProp": { eq: { type: 'column', value: 'AnotherProp' } } };
Is there maybe a better way to do it, that I have missed?
Support all transforms defined within spec
Currently only aggregate
, groupby
, and filter
are supported by the odata.net's UriQueryExpressionParser so supporting additional transforms are not a priority for a while (unless another backend shows support for these)
This filter:
const filter = {
Results: {
any: {
ResultsProperties: {
any: {
SomeProp: 1
}
}
}
}
}
Create next query string:
?$filter=Results/any(r:r/ResultsProperties/any(r:r/SomeProp eq 1))
OData failed by this query with next exception:
The range variable 'r' has already been declared.
After some discussion in the PR about how to handle in
and parens, I think we should implement a breaking change to make array ([ ]
) usage more consistent and easier to reason about.
Currently an array wraps each items within it in their own parens. (code)
} else if (Array.isArray(filters)) {
const builtFilters = filters.map(f => buildFilter(f, propPrefix)).filter(f => f !== undefined);
if (builtFilters.length) {
return `${builtFilters.map(f => `(${f})`).join(` and `)}`
}
which produces:
buildFilter({ filter: [{ SomeProp: 1 }, { AnotherProp: 2 }, "startswith(Name, 'R')"] });
=> "?$filter=(SomeProp eq 1) and (AnotherProp eq 2) and (startswith(Name, 'R'))";
The proposal is to change the code to:
} else if (Array.isArray(filters)) {
const builtFilters = filters.map(f => buildFilter(f, propPrefix)).filter(f => f !== undefined);
if (builtFilters.length) {
return `(${builtFilters.join(` and `)})`
}
which would produce:
buildFilter({ filter: [{ SomeProp: 1 }, { AnotherProp: 2 }, "startswith(Name, 'R')"] });
=> "?$filter=(SomeProp eq 1 and AnotherProp eq 2 and startswith(Name, 'R'))";
You also do not need the individual objects for the first two (not a change, just a simplification of the example).
buildFilter({ filter: [{ SomeProp: 1, AnotherProp: 2 }, "startswith(Name, 'R')"] });
=> "?$filter=(SomeProp eq 1 and AnotherProp eq 2 and startswith(Name, 'R'))";
If you wanted to reproduce the original results (albeit with an extra wrapping parens around it all), you would wrap each item in an array:
buildFilter({ filter: [[{ SomeProp: 1 }], [{ AnotherProp: 2 }], ["startswith(Name, 'R')"]] });
=> "?$filter=((SomeProp eq 1) and (AnotherProp eq 2) and (startswith(Name, 'R')))";
In summary, the [ ]
would literally translate as ( )
in the query. If this change is made, I would release it as a new major version since it could be breaking for some users.
If my query is bigger than 50 KB, does it still work? :) (please assume server-side has no limits)
So to better enforce type safety in a project - a developer may want to say let's only allow a string that adheres to keyof some TYPE for OData operations $orderBy or $filter- the issue is that keys of some TYPE are usually camelCased, but OData wants prop names in TitleCase format. Does it make sense to enhance buildQuery to properly format a camelCased "key" into a valid odata prop name that is TitleCased?
Hi!
I'm trying to figure out how to supply a date as an argument to a filter.
My code:
StatusHistory: { filter: { StartDateTime: { le: '2019-06-08T00:00:00Z' }, EndDateTime: { gt: '2019-06-08T00:00:00Z' }, ServiceStatusEnum: 'supplied' } }
This generates a query with both the dates and 'supplied' in quotes, but i don't want the dates in quotes because my backend thinks theyre string and not dates so i get invalid request.
I saw another issue about datetime object but didn't really understand the result of that post or if it has really anything to do with this problem
Any idea on how I could achieve this? Changing the backend is no option. Sorry for any ignorance i'm pretty new to odata and its queries, thanks
Say I want to have a case insensitive search on a property in a nested collection of items, where what I'm filtering is formed like:
DTO:
name: "<name>"
items: "<object array with property to search on 'searchProp'>"
So I form a filter like (where search is a search string):
const filter = {
or: {
"toupper(name)": {
contains: search.toUpperCase()
},
"items": {
any: {
"toupper(searchProp)": {
contains: search.toUpperCase()
}
}
}
}
}
buildQuery({filter});
Currently the second toupper
(in the any call) is prefixed with the lambda variable, resulting in an invalid call -- instead searchProp should be prefixed.
Result : $filter=contains(toupper(name),'C') or items/any(a:contains(a/toupper(searchProp),'C')
Which should actually be: $filter=contains(toupper(name),'C') or items/any(a:contains(toupper(a/searchProp),'C')
edit: I'm rather new to odata-queries, please do let me know if there is a prefered way to do this nested collection type of query.
Hello, any help would be appreciated!
When filtering inside an $expand, the lamda that gets applied is missing the necessary prefixes.
Input:
{ expand: { Item: { filter: { any: { ItemsProp: true } } } } }
output:
$expand=Item($filter=any/ItemsProp eq true)
Expected output:
$expand=Item($filter=Item/any(x:x/ItemsProp eq true))
Regular top level filter applies the proper lamda. Is this a problem in how I'm constructing the object?
Thanks
It would be nice to have typed filter in order to avoid typo in the property names.
As an example:
interface Square {
width: number;
height: number;
}
const filter: Filter<Square> = { width: { lt: 5 } };
It would be the foundation to introduce even typed nested properties handling.
Currently we explode an in
operator into multiple eq
/or
. For example:
const filter = { SomeProp: { in: [1, 2, 3] } };
const actual = buildQuery({ filter });
=> '?$filter=(SomeProp eq 1 or SomeProp eq 2 or SomeProp eq 3)'
When attempting to do this with a lot of values, I received a 414 (URI Too Long)
error. Upon more researching, I found this proposal to support an actual in
operator, and it appears to be accepted.: https://issues.oasis-open.org/browse/ODATA-556
I then tested against my WebApi/OData backend and it appears to work when using $filter=Id in (1,2,3,4,5)
or $filter=Code in ('abc','def','ghi')
While not in the 4.0
spec, I just found mention of it in the 4.01
spec part1 and part 2.
Considering supporting this and releasing as a breaking change (6.0.0
)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.