bearsunday / bear.resource Goto Github PK
View Code? Open in Web Editor NEWA hypermedia framework for an object as a service
Home Page: https://packagist.org/packages/bear/resource
License: MIT License
A hypermedia framework for an object as a service
Home Page: https://packagist.org/packages/bear/resource
License: MIT License
Currently, JsonSchema validation is only performed on the "body" of the ResourceObject, but there is an option to perform validation on the "view" as well.
現在、JsonSchemaのバリデーションをResourceObjectの"body"に対してだけ行っていますが、"view"に対しても行うオプションを用意します。
annotation
/**
* @JsonSchema(schema="user.json", target="view")
*/
attribute
#[JsonSchema(schema: 'user.json', target: 'view')]
(string) to turn the ResourceObject into a JSON string, and then validate it against the decoded JSON.
(string)でResourceObjectをJSON文字列にして、それをdecodeしたJSONに対してのバリデーションを行います。
例えば、BEAR.SundayのHalレンダラーでレンダリングされるlink
に対してもスキーマを記述することができるようになります。
injector に Injector
の代わりに ScriptInjector
クラスを利用し、
存在しないリソースをfactoryに渡すと ResourceNotFoundException
でなく NotCompiled
が throw されるのですがこちらは仕様になりますでしょうか?
(古いですが 1.1.6 時点だと ResourceNotFoundException
がthrowされるようです)
version: 1.2.1
失敗するテストを作成すると以下になります
<?php
namespace BEAR\Resource;
use BEAR\Resource\Exception\ResourceNotFoundException;
use Ray\Compiler\ScriptInjector;
use Ray\Di\Injector;
class FactoryTest extends \PHPUnit_Framework_TestCase
{
/**
* @var Factory
*/
protected $factory;
protected function setUp()
{
parent::setUp();
// $injector = new Injector;
$injector = new ScriptInjector($_ENV['TMP_DIR']);
$scheme = (new SchemeCollection)
->scheme('app')->host('self')->toAdapter(new AppAdapter($injector, 'FakeVendor\Sandbox', 'Resource\App'))
->scheme('page')->host('self')->toAdapter(new AppAdapter($injector, 'FakeVendor\Sandbox', 'Resource\Page'))
->scheme('prov')->host('self')->toAdapter(new FakeProv)
->scheme('nop')->host('self')->toAdapter(new FakeNop);
$this->factory = new Factory($scheme);
}
public function testResourceNotFound()
{
$this->setExpectedException(ResourceNotFoundException::class);
$this->factory->newInstance('page://self/not_found_XXXX');
}
}
ひとまずご報告です。
http://bearsunday.github.io/manuals/1.0/ja/tutorial.html
を参考に、「アプリケーションのインポート」を試していますが、呼び出し元のアプリケーション(A) からインポートしたアプリケーションの Page リソース (B) を利用するとき、その Page リソースが App リソース(C)を利用していると、400 Bad Request が返ってきます。
<?php
namespace Kilica\Core\Resource\App;
use BEAR\Resource\ResourceObject;
use BEAR\Sunday\Inject\ResourceInject;
class Import extends ResourceObject
{
use ResourceInject;
public function onGet()
{
// OK
$this['item'] = $this->resource->get->uri('app://content/item')->eager->request()->body['item'];
// NG : "400 Bad Request"
$this['content'] = $this->resource->get->uri('page://content/index')->eager->request()->body['content'];
return $this;
}
}
App リソースを $this->resource->get->uri()
しようとしている部分を、 $this['content'] = 'New Content'
などに変更すると正常に動作します。
<?php
namespace Kilica\Content\Resource\Page;
use BEAR\Resource\ResourceObject;
use BEAR\Sunday\Inject\ResourceInject;
class Index extends ResourceObject
{
use ResourceInject;
public function onGet()
{
$this['content'] = $this->resource->get->uri('app://content/item')->eager->request();
return $this;
}
}
<?php
namespace Kilica\Content\Resource\App;
use BEAR\Resource\ResourceObject;
class Item extends ResourceObject
{
public function onGet()
{
$this['item'] = 'New Content';
return $this;
}
}
> php bootstrap/api.php get '/import'
400 Bad Request
content-type: application/vnd.error+json
{"message":"Bad Request"}
For example, following code send a HTTP request GET /index HTTP/1.1
to example.com.
$resource
->get
->uri('http://example.com/')
->eager
->request();
It seems that this caused by: https://github.com/koriym/BEAR.Resource/blob/develop/src/BEAR/Resource/Resource.php#L135
UriMapper
convert http external URL and internal BEAR.Resource URI.
<?php
/**
* This file is part of the BEAR.Package package
*
* @package BEAR.Package
* @license http://opensource.org/licenses/bsd-license.php BSD
*/
namespace BEAR\Resource;
interface UriMapperInterface
{
/**
* @param $requestUri "/blog/posts"
*
* @return string
*/
public function map($requestUri);
/**
* @param string $internalUri
*
* @return string
*/
public function reverseMap($internalUri);
}
The depending guzzle version is 3.3.* now.
On the other hand, the latest guzzle is 3.8.0.
And major libraries such as "aws/sdk" depends on newer version.
So, I suggest that
Conflict occurred in reflection-docblock
when installing felixfbecker/language-server
$ composer require felixfbecker/language-server
Using version ^5.4 for felixfbecker/language-server
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Conclusion: don't install felixfbecker/language-server v5.4.2
- Conclusion: don't install felixfbecker/language-server v5.4.1
- Conclusion: remove phpdocumentor/reflection-docblock 3.3.2
- Installation request for felixfbecker/language-server ^5.4 -> satisfiable by felixfbecker/language-server[v5.4.0, v5.4.1, v5.4.2].
- Conclusion: don't install phpdocumentor/reflection-docblock 3.3.2
- felixfbecker/language-server v5.4.0 requires phpdocumentor/reflection-docblock ^4.0.0 -> satisfiable by phpdocumentor/reflection-docblock[4.0.0, 4.0.1, 4.1.0, 4.1.1, 4.2.0, 4.3.0].
- Can only install one of: phpdocumentor/reflection-docblock[4.0.0, 3.3.2].
- Can only install one of: phpdocumentor/reflection-docblock[4.0.1, 3.3.2].
- Can only install one of: phpdocumentor/reflection-docblock[4.1.0, 3.3.2].
- Can only install one of: phpdocumentor/reflection-docblock[4.1.1, 3.3.2].
- Can only install one of: phpdocumentor/reflection-docblock[4.2.0, 3.3.2].
- Can only install one of: phpdocumentor/reflection-docblock[4.3.0, 3.3.2].
- Installation request for phpdocumentor/reflection-docblock (locked at 3.3.2) -> satisfiable by phpdocumentor/reflection-docblock[3.3.2].
Installation failed, reverting ./composer.json to its original content.
Confirm installation version and dependency
$ composer show -i | grep docblock
You are using the deprecated option "installed". Only installed packages are shown by default now. The --all option can be used to show all packages.
phpdocumentor/reflection-docblock 3.3.2 With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is ...
$ composer depends phpdocumentor/reflection-docblock
bear/resource 1.11.3 requires phpdocumentor/reflection-docblock (^3.1)
phpspec/prophecy 1.8.0 requires phpdocumentor/reflection-docblock (^2.0|^3.0.2|^4.0)
If the reflection-docblock
version of bear/resource
is 4.0 or later, it is likely to be resolved.
@JsonSchema
をアノテートして実行したところ、以下AnnotationExceptionが発生しました。
Doctrine\\Common\\Annotations\\AnnotationException([Type Error] Attribute \"key\" of @JsonSchema declared on method Chanshige\\BearApp\\Resource\\App\\Sample::onGet() expects a(n) ?string, but got string.)
以下、keyプロパティのアノテーション型指定(?string)が影響しているようです。
https://github.com/bearsunday/BEAR.Resource/blob/1.x/src/JsonSchema/Annotation/JsonSchema.php#L16
final class JsonSchema
{
/**
* Json schema body key name
*
* @var ?string
*/
public $key;
エラーを再現した簡単なリポジトリを用意しましたので、ご確認よろしくお願いします🙏
https://github.com/chanshige/bear_sample
JsonSchemaExceptionHandlerInterface
handles JsonException
s on ill-formed responses but not on ill-formed requests.
Is this behavior intentional?
For example, in this test case
BEAR.Resource/tests/Module/JsonSchemaFakeModuleTest.php
Lines 42 to 53 in 6211582
we get JsonException
when we provide $gender = 'invalid gender'
even if we bind a handler to JsonSchemaExceptionHandlerInterface
.
I guess this is intentional, because ill-formed requests normally cannot continue and end up with abort. We might need handlers for this if we want to return some error-reason-responses or specify errors in response headers.
If this is NOT intentional, we may want to handle JsonException
s thrown in the request validation here:
BEAR.Resource/src/JsonSchema/Interceptor/JsonSchemaInterceptor.php
Lines 60 to 84 in d28d3ba
Although I've got no good idea. Like this? (maybe BC-break):
diff --git a/src/JsonSchema/Interceptor/JsonSchemaInterceptor.php b/src/JsonSchema/Interceptor/JsonSchemaInterceptor.php
index 024a9e4..d3d2113 100644
--- a/src/JsonSchema/Interceptor/JsonSchemaInterceptor.php
+++ b/src/JsonSchema/Interceptor/JsonSchemaInterceptor.php
@@ -63,11 +63,11 @@ final class JsonSchemaInterceptor implements MethodInterceptor
$method = $invocation->getMethod();
/** @var JsonSchema $jsonSchema */
$jsonSchema = $method->getAnnotation(JsonSchema::class);
- if ($jsonSchema->params) {
- $arguments = $this->getNamedArguments($invocation);
- $this->validateRequest($jsonSchema, $arguments);
- }
/** @var ResourceObject $ro */
+ $ro = $this->validateRequest($jsonSchema, $invocation);
+ if ($ro->code === 400) {
+ return $ro;
+ }
$ro = $invocation->proceed();
if ($ro->code === 200 || $ro->code == 201) {
$this->validateResponse($ro, $jsonSchema);
@@ -76,11 +76,23 @@ final class JsonSchemaInterceptor implements MethodInterceptor
return $ro;
}
- private function validateRequest(JsonSchema $jsonSchema, array $arguments)
+ private function validateRequest(JsonSchema $jsonSchema, MethodInvocation $invocation)
{
+ $ro = $invocation->getThis();
+ if (! $jsonSchema->params) {
+ return $ro;
+ }
$schemaFile = $this->validateDir . '/' . $jsonSchema->params;
$this->validateFileExists($schemaFile);
- $this->validate($arguments, $schemaFile);
+ $arguments = $this->getNamedArguments($invocation);
+ try {
+ $this->validate($arguments, $schemaFile);
+ } catch (JsonSchemaException $e) {
+ $ro->code = 400;
+ $this->handler->handle($ro, $e, $schemaFile);
+ }
+
+ return $ro;
}
private function validateResponse(ResourceObject $ro, JsonSchema $jsonSchema)
Resource client can have exception handler on each.
code sample:
namespace ExceptionTest {
use BEAR\Resource\ExceptionHandlerInterface;
use BEAR\Resource\Request;
class MyHandler implements ExceptionHandlerInterface
{
public function handle(\Exception $e, Request $request)
{
return 'handled:' . $request->toUri();
}
}
}
namespace ExceptionTest\Resource\Page {
use BEAR\Resource\ResourceInterface;
use BEAR\Resource\ResourceObject;
use ExceptionTest\MyHandler;
use Ray\Di\Di\Inject;
class Index extends ResourceObject
{
private $resource;
/**
* @Inject
*/
public function __construct(ResourceInterface $resource)
{
$this->resource = $resource;
$this->resource->setExceptionHandler(new MyHandler());
}
public function onGet()
{
throw new \RuntimeException;
}
}
}
$resource = Injector::create([new ResourceModule('ExceptionTest')])->getInstance('BEAR\Resource\ResourceInterface');
$result = $this->resource->get->uri('page://self/index')->eager->request();
// handled:page://self/index
実行時のログをとる開発用Invoker
ヘッダーに付加する情報は以下の通り。
['x-interceptors'] インターセプター
['x-links'] リソースリンク
['x-execution-time'] 実行時間
['x-memory-usage] 消費メモリ
InvalidArgumentException: The tag "@Link(rel="friend", href="/fiend{?id}")" does not seem to be wellformed, please check it for errors
Same issue reported at phpDocumentor/ReflectionDocBlock#108
デモを実行しようとしたところ、5番目のデモでエラーが発生しました。
php demo/5.embed.php
Fatal error: Cannot redeclare MyVendor\Demo\Resource\App\Weather_IggGcyM::onGet() in /private/var/folders/9q/1jhss6_j32vbq4qjn9krk69c0000gn/T/MyVendor_Demo_Resource_App_News_IggGcyM.php on line 35
生成されていたファイルでなぜか onGet が重複定義されているようです。
<?php
declare (strict_types=1);
namespace MyVendor\Demo\Resource\App;
use Ray\Aop\WeavedInterface;
use Ray\Aop\ReflectiveMethodInvocation as Invocation;
use BEAR\Resource\Annotation\Embed;
use BEAR\Resource\Module\HalModule;
use BEAR\Resource\Module\ResourceModule;
use BEAR\Resource\ResourceInterface;
use BEAR\Resource\ResourceObject;
use Ray\Di\Injector;
class Weather_IggGcyM extends \MyVendor\Demo\Resource\App\News implements WeavedInterface
{
public $bind;
public $bindings = [];
public $methodAnnotations = 'a:2:{s:5:"onGet";a:1:{i:0;O:30:"BEAR\\Resource\\Annotation\\Embed":2:{s:3:"rel";s:7:"weather";s:3:"src";s:15:"/weather{?date}";}}s:11:"setRenderer";a:1:{i:0;O:16:"Ray\\Di\\Di\\Inject":1:{s:8:"optional";b:1;}}}';
public $classAnnotations = 'a:0:{}';
private $isAspect = true;
/**
* @Embed(rel="weather", src="/weather{?date}")
*/
public function onGet(string $date) : ResourceObject
{
if (!$this->isAspect) {
$this->isAspect = true;
return call_user_func_array([$this, 'parent::' . __FUNCTION__], func_get_args());
}
$this->isAspect = false;
$result = (new Invocation($this, __FUNCTION__, func_get_args(), $this->bindings[__FUNCTION__]))->proceed();
$this->isAspect = true;
return $result;
}
public function onGet(string $date) : ResourceObject
{
if (!$this->isAspect) {
$this->isAspect = true;
return call_user_func_array([$this, 'parent::' . __FUNCTION__], func_get_args());
}
$this->isAspect = false;
$result = (new Invocation($this, __FUNCTION__, func_get_args(), $this->bindings[__FUNCTION__]))->proceed();
$this->isAspect = true;
return $result;
}
}
原因がよくわからなかったのですが、再現しないようでしたら却下で大丈夫です。
In resource request,
Exception\Parameter
should thrown in client sideException\ParameterInService
should thrown in server sideTypeError : BEAR\Resource\Interceptor\JsonSchemaInterceptor::deepArray(): Argument #1 ($values) must be of type object, null given, called in /Users/akihito/git/BEAR.Resource/src/JsonSchema/Interceptor/JsonSchemaInterceptor.php on line 136
When the view of ResourceObject is not json. (HTML)
JsonRenderTest fails because json_encode
function returns false.
I can get error message "Recursion detected" when I call json_last_error_msg
function after json_encode
failed.
How to get resource client with JSON render
$modules = [new ResourceModule('Sandbox'), new JsonModule]:
$resource = Injector::create(modules)
->getInstance('BEAR\Resource\ResourceInterface');
How to get resource client with Hal render
$modules = [new ResourceModule('Sandbox'), new HalModule]:
$resource = Injector::create(modules)
->getInstance('BEAR\Resource\ResourceInterface');
Request resource with resource client
$user = $resource
->get
->uri('app://self/link/user')
->withQuery(['id' => 1])
->eager
->request();
Get its representation
echo $user;
// "name": "Aramis",
// "age": 16,
// "blog_id": 12,
// "_links": {
// "self": {
// "href": "app://self/link/user?id=1"
// }
// }
Get the the content
echo $user[`name`];
// Aramis
Get the the code
echo $user->code
// 200
The resource is rendered in HAL format.
public function onPost(User $user)
現在このInputクラスUser
はプロパティに直接代入されるだけですが
Currently this Input class User
is just assigned directly to a property, but
class Person
{
public $givenName;
public $familyName;
}
コンストラクタが存在すればコンストラクタをコールするようにします。
Make the constructor call the constructor if one exists.
PHP8なら名前付き引数です。PHP7なら順序の引数になってしまいます。
In PHP8, they are named arguments; in PHP7, they are ordinal arguments.
class PersonConstructor
{
public $givenName;
public $familyName;
public function __construct(string $givenName, string $familyName)
{
$this->givenName = $givenName;
$this->familyName = $familyName;
}
}
webの入力とphpの入力をブリッジするこのタイミングでの入力値の追加/変更も行えます。
You can also add/change input values at this time to bridge web input and php input.
class PersonConstructor
{
public $givenName;
public $familyName;
public $fullName;
public function __construct(string $givenName, string $familyName)
{
$this->givenName = $givenName;
$this->familyName = $familyName;
$this->fullName = $givenName . ' ' . $familyName;
}
}
before:
namespace {Namespace}\Resource\Page;
class News extends ResourceObject
{
after:
namespace {Vendor}\{Namespace}\Resource\Page;
class News extends ResourceObject
{
Because Adapter\App
is used for page scheme in Module/SchemeCollectionProvider
.
I also searched Adapter\Page
in BEAR.Sunday and BEAR.Resource, however, couldn't find that.
So, it seems Adapter\Page
is never used, at least, in BEAR Sunday Project.
Attach ProvidesParam
signal parameter to resource client.
$injector = Injector::create([new ResourceModule('Sandbox')]);
$resource =
$injector->getInstance('BEAR\Resource\ResourceInterface');
$resource->attachParamProvider('*', new OnProvidesParam);
A parameter is passed by Provides
method if consumer does't pass and method has no default value in method signature.
class Post
{
public function onPost($date)
{
// $date was passed by onProvidesDate method.
}
public function onProvidesDate()
{
return date(DATE_RFC822);
}
}
Parameter is injected by Provides{$varName}
method in run time. Consumer and method has no responsibility to prepare parameter. It's easy for testing.
Annotations are missed in generated aspect class.
"Is it possible to use Guzzle 3 and 4 in the same project"
http://docs.guzzlephp.org/en/latest/faq.html#is-it-possible-to-use-guzzle-3-and-4-in-the-same-project
php bin/app.php get '/weekday?year=2001&month=1&day='
のようなアクセスがBOTアクセスにより発生している環境がありまして、対処方法を検討しています。可能であれば400エラーになってくれるとありがたいですが、どのように対応するべきでしょうか?$(cat << 'EOF' > src/Resource/App/Weekday.php
<?php
declare(strict_types=1);
namespace MyVendor\Weekday\Resource\App;
use BEAR\Resource\ResourceObject;
use DateTimeImmutable;
class Weekday extends ResourceObject
{
public function onGet(int $year, int $month, int $day): static
{
$dateTime = DateTimeImmutable::createFromFormat('Y-m-d', "$year-$month-$day");
$weekday = $dateTime->format('D');
$this->body = ['weekday' => $weekday];
return $this;
}
}
EOF
)
# 200 OK: 想定通りです。問題ありません。
php bin/app.php get '/weekday?year=2001&month=1&day=1'
# 400 Bad Request: 想定通りです。問題ありません。
php bin/app.php get '/weekday?year=2001'
# 500 Internal Server Errorでなく、400 Bad Requestにしたいです
php bin/app.php get '/weekday?year=2001&month=1&day='
500 Internal Server Error
content-type: application/vnd.error+json
{
"message": "Internal Server Error",
"logref": "87cb1b89",
"request": "get app://self/weekday?year=2001&month=1&day=",
"exceptions": "ErrorException(MyVendor\\Weekday\\Resource\\App\\Weekday::onGet(): Argument #3 ($day) must be of type int, string given, called in /Users/adachi/tmp/MyVendor.Weekday/vendor/bear/resource/src/Invoker.php on line 40)",
"file": "/Users/adachi/tmp/MyVendor.Weekday/src/Resource/App/Weekday.php(12)"
}
onHead method, primary used In HEAD request if resource object have 'onHead' method.
or 'onGet' method with no content returns if resource object has onGet method.
headリクエストをした時にonHeadメソッドがあればそれが優先され使われ、無い場合は次にonGetメソッドがあればそのメソッドを実行してbodyを消去し返します。
ReflectionException is thrown when method is not exists.
Should be MethodNotAllowed exception.
We are wondering what would be a good name for this software.
In the beginning, we called it "Service layer framework".
And now "Hypermedia library", but still wondering if maybe we can have another and more proper name.
このソフトは何と呼ぶのが適切でしょうか?
Question one: Which is better: "framework" or "library" ?
Question two: What would be a good and fitting name for this software ?
However, fixing this breaks BC :(
interface ProviderInterface
{
/**
* Return new resource object
*
* @param string $uri
*
* @return ResourceObject
*/
public function get($uri);
}
@Embed
アノテーションを使って外部のリソースを自身のbodyに埋め込みます。
@Embed
annotation makes embedding resource easier.
use BEAR\Resource\ResourceObject;
use BEAR\Resource\Annotation\Embed;
class Birds extends ResourceObject
{
/**
* @Embed(rel="bird1", src="app://self/bird/canary")
* @Embed(rel="bird2", src="app://self/bird/sparrow{?id}")
*/
public function onGet($id)
{
return $this;
}
}
is equivalent of
use BEAR\Resource\ResourceObject;
use BEAR\Resource\ResourceInterface;
class Birds extends ResourceObject
{
public function __construct(ResourceInterface $resource)
{
$this->resource = $resource;
}
public function onGet($id)
{
$this['bird1'] = $this->resource
->get
->uri('app://self/bird/canary')
->request();
$this['bird2'] = $this->resource
->get
->uri('app://self/bird/sparrow')
->withQuery(['id' => $id])
->request();
return $this;
}
}
Please pay attention what is embed is a Request, not a result of request
You can modify query parameter after embedded.
use BEAR\Resource\ResourceObject;
use BEAR\Resource\Annotation\Embed;
class Birds extends ResourceObject
{
/**
* @Embed(rel="bird1", src="app://self/bird/canary")
* @Embed(rel="bird2", src="app://self/bird/sparrow{?id}")
*/
public function onGet($id, $userId)
{
// ....
$this['bird2']->withQuery(['id' => $userId];
// or add query parameter
$this['bird2']->addQuery(['option' => 'yes'];
}
}
@Embed
makes not only code more readable and intention clear but also helps for optimization in compile and enable to draw the resource dependency graph without run.
You see the analogy between this and some tag in HTML like <iframe src=$url>
or <img src=$url>
. The resource embed external resource to its own.
"web" scheme resource enable to access WEB API more BEAR.Resource native and easier.
client
$this->resource
->post
->uri('web://{service}/{resource}')
->withQuery('title' => $title)
->eager
->request();
config
{$appDir}/Resource/Web/{service}.php
<?php
return [
'baseUrl' => 'http://httpbin.org/',
'operations' => [
'POST:{resource}' => [
'httpMethod' => 'POST',
'uri' => '/post',
'responseModel' => 'getResponse',
'parameters' => [
'title' => [
'type' => 'string',
'location' => 'postField'
]
],
'_link' => [
'new_post' => 'app://self/new_post/?{title}'
]
]
],
'models' => [
'getResponse' => [
'type' => 'object',
'additionalProperties' => [
'location' => 'json'
]
]
]
];
See more for config https://github.com/guzzle/guzzle-services
Invoker
Request::toUriWithMethod()をメソッドを付加したURI表現
ex) get app://self/posts/
Request::toUri()はリクエストメソッドを除いたURI表現
ex) app://self/posts/
returns wrong value
Different application share same cache key.
This iterator reveales resource availability and its option.
/** @var $resource \BEAR\Resource\Resource */
foreach ($resource as $meta) {
var_dump($meta);
}
// class BEAR\Resource\Meta#271 (4) {
// public $uri =>
// string(26) "app://self/restbucks/order"
// public $options =>
// class BEAR\Resource\Options#209 (2) {
// public $allow =>
// array(2) {
// [0] =>
// string(3) "get"
// [1] =>
// string(4) "post"
// }
// public $params =>
// array(1) {
// [0] =>
// class BEAR\Resource\Params#210 (3) {
// public $method =>
// string(4) "post"
// public $required =>
// array(1) {
// [0] =>
// string(5) "drink"
// }
// public $optional =>
// array(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.