Hi, I'm Alex (short for Alexander).
- Blog: kean.blog
- Built Nuke, Pulse, and other popular frameworks and tools
- Developed the initial CreateAPI version which is now maintained by @cookpad
Contacts
- Mastodon: @[email protected]
Web API client built using async/await
License: MIT License
Hi, I'm Alex (short for Alexander).
Contacts
Hi. I want to make multiple requests whose parameters are identical except for their body.
Since requests can be created only by the factory methods, I have to write code like this to duplicate requests.
Is there a better way to handle this case?
func duplicate<Response: Decodable, Body: Encodable>(request: Request<Response>, body: Body) -> Request<Response> {
switch request.method {
case "post":
return .post(request.path, query: request.query, body: body, headers: request.headers)
case "put":
return .put(request.path, query: request.query, body: body, headers: request.headers)
case "delete":
return .delete(request.path, query: request.query, body: body, headers: request.headers)
default:
return .get(request.path)
}
}
let baseURL = URL(string: "https://gitlab.com/api/v4")
URL(string: "/test", relativeTo: baseURL)?.absoluteString
expect
https://gitlab.com/api/v4/test
result
https://gitlab.com/test
I'm remaking our new API with CreateAPI and you've left generous comment in our thread jellyfin/jellyfin-sdk-swift#5. Instead of directly using APIClient
, I'm making a wrapper JellyfinClient
actor that handles the APIClientDelegate
creation to inject required headers for the Jellyfin server, among other things for convenience.
Working with this wrapper would just manually require re-declaring the main APIClient
interface methods however can cause issues in the future over the evolution of the package. Would creating a protocol for these methods be appropriate for easier implementation of wrappers?
Hello 👋🏻
After switching the compiler option Strict Concurrency Checking
to Complete
, I received the warning below:
⚠️ Non-sendable type '(inout APIClient.Configuration) -> Void' passed in call to nonisolated initializer 'init(baseURL:_:)' cannot cross actor boundary.
Just informing, I don't know how important it is.
Xcode 14.0
beta 5 | Get 1.0.1
Please make a small example of an application using SwiftUI
& Get
.
Or can someone share a gist?
I'm considering redesigning the APIClient
public API for Get 1.0. It was initially designed to accommodate only the most basic scenarios, but I'd like it to be more flexible. Here is a draft of a new API.
public protocol APIClient2 {
// No changes in these APIs, except for removal of magic `Response<Data>` and `Response<String>` support (see p3)
func send<T: Decodable>(_ request: Request<T>) async throws -> Response<T>
func send<T: Decodable>(_ request: Request<T?>) async throws -> Response<T?>
@discardableResult func send(_ request: Request<Void>) async throws -> Response<Void>
// Getting decoded response for `URL`/`URLRequest`
func value<T: Decodable>(_ type: T.Type, for url: URL) async throws -> Response<T>
func value<T: Decodable>(_ type: T.Type, for urlRequest: URLRequest) async throws -> Response<T>
// Getting raw response `Data` (no decoding)
func data(for url: URL) async throws -> Response<Data>
func data(for urlRequest: URLRequest) async throws -> Response<Data>
func data<T>(for request: Request<T>) async throws -> Response<Data>
// Getting response `String` (no decoding except for .utf8 decoding)
func string(for url: URL) async throws -> Response<String>
func string(for urlRequest: URLRequest) async throws -> Response<String>
func string<T>(for request: Request<T>) async throws -> Response<String>
// (?) Add `URLSessionDataDelegate` to all APIs too for pogress reporting, etc.
func response<T: Decodable>(for request: Request<T>, delegate: URLSessionDataDelegate) async throws -> Response<T>
}
Main Changes
URL
and URLRequest
in addition to Request
URLSessionDataDelegate
for progress reporting, per-task auth, etc.Response<Data>
and Response<String>
support from send(_:)
. If you use send(_:)
, they'll just use the default Decodable
implementation for these types. You'll need to call data(for:)
or string(for:)
instead – faster, more discoverable, self-documentingHi!
I'm trying to get Get to work in my current project, and have created a small struct implementing a simple call to our backend. I need to set a token in the header for each call, so I have implemented a APIClientDelegate like this: (more or less a copy of the sample in the readme)
final class AuthorizingDelegate: APIClientDelegate {
func client(_ client: APIClient, willSendRequest request: inout URLRequest) async throws {
print("-- client:willSendRequest")
request.allHTTPHeaderFields = ["Authorization": "Bearer \(await getToken())"]
}
func shouldClientRetry(_ client: APIClient, withError error: Error) async throws -> Bool {
print("-- client:withError")
if case .unacceptableStatusCode(let statusCode) = (error as? APIError), statusCode == 401 {
return await !getToken().isEmpty
}
return false
}
func client(_ client: APIClient, didReceiveInvalidResponse response: HTTPURLResponse, data: Data) -> Error {
print("-- client:didReceiveInvalidResponse:data")
return APIError.unacceptableStatusCode(response.statusCode)
}
func getToken() async -> String {
await withUnsafeContinuation { continuation in
Auth.auth().currentUser?.getIDToken(completion: { token, error in
continuation.resume(returning: token ?? "")
})
}
}
}
The request I'm testing looks like this:
struct ReqTest {
let delegate = AuthorizingDelegate()
func testMe() async -> String {
let client = APIClient(host: "xxxxxx.xxxx") {
$0.sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData
$0.delegate = delegate
}
do {
let req = Request<MyResponse>.get("/api/user")
let resp = try await client.send(req).value
return resp.nickname
} catch {
print(error)
return error.localizedDescription
}
}
}
When calling testMe(), this us what is logged:
-- client:didReceiveInvalidResponse:data
unacceptableStatusCode(401)
Response status code was unacceptable: 401.
I would expect to see a call to the other delegate functions to get the neccessary token, but this is not the case. Any clue?
Working url from Postman:
http://localhost:9009/yourname/v1/endpointName
Paths are generated automatically from OpenApi via CreateAPI and looks like that: /v1/endpointName
Client host: APIClient(host: "localhost:9009/yourname")
or: APIClient(host: "localhost/yourname")
let client: APIClient = APIClient(host: "localhost:9009/yourname") {
//$0.port = 9009
$0.isInsecure = true
$0.delegate = YourDelegate()
}
Generated via Get package method makeURL(path: String, query: [(String, String?)]?) throws -> URL:
http://localhost%3A9009%2Fyourname/v1/endpointName
Output error:
NSLocalizedDescription=A server with the specified hostname could not be found., NSErrorFailingURLStringKey=http://localhost%3a9009%2fyourname/v1/endpointName?, NSErrorFailingURLKey=http://localhost%3a9009%2fyourname/v1/endpointName?, _kCFStreamErrorDomainKey=12}
I'm not sure why public func cURLDescription() -> String made it into the original release. It's not documented and there is no reason for it to be shipped as part of the framework.
It is handy we can set our own custom URLSessionDelegate in APIClient init.
self.api = APIClient(baseURL: baseURL, {
$0.sessionDelegate = ...
})
But custom delegate methods are just not called for simple GET/POST queries. Don't think this how it should be. It looks like DataLoader.swift just lacks them.
Particularly in my case (I need to do some SSl pinning) if I add the following code to DataLoader.swift
everything begins to work as expected:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
userSessionDelegate?.urlSession?(session, didReceive: challenge, completionHandler: completionHandler) ??
completionHandler(.performDefaultHandling, nil)
}
It looks like delegation is forwarded only for some subtypes of URLSessionDelegate
(URLSessionTaskDelegate
, URLSessionDataDelegate
, URLSessionDownloadDelegate
) but not for the type itself. Just not sure if it will be correct to implement such forwarding for both URLSessionDelegate
and it subtypes. That's why made it an issue, not a pull request.
If I try to lookup a different api.github.com/users/briviere the test fails with the following within the APIClient.Serializer.decode actor:
test case:
final class APIClientIntegrationTests: XCTestCase {
var sut: APIClient!
override func setUp() {
super.setUp()
sut = APIClient(host: "api.github.com")
}
func testGitHubUsersApi() async throws {
let user = try await sut.send(Resources.users("briviere").get)
print(user)
XCTAssertEqual(user.login, "briviere")
}
}
Exception:
Printing description of data:
▿ 1282 bytes
Our api server gives a 422 response when validating request params:
{
"errors": [{ "detail": "Missing field: anon_id" }]
}
However, Get
only supplies the error code: Response status code was unacceptable: 422
via APIError
.unacceptableStatusCode(let statusCode)
How can we hook into the actual error response for logging and (sometimes) showing to the user?
As title, is there a throttle feature to avoid duplicate calls?
Hi @kean hope you're doing great, first thanks for this awesome library, I'm using it on a lot of my projects.
And one thing I find myself always implementing is this MultiAPIClientDelegate
.
An APIClientDelegate
implementation that forwards calls to multiple underlying delegates, below is the implementation I use.
if you find this useful for adding to the library, I can PR it.
/// An ``APIClientDelegate`` that forward calls to multiple delegates in order.
public struct MultiAPIClientDelegate: APIClientDelegate {
let delegates: [APIClientDelegate]
public init(_ delegates: [APIClientDelegate]) {
self.delegates = delegates
}
public func client(_ client: APIClient, willSendRequest request: inout URLRequest) async throws {
for delegate in delegates {
try await delegate.client(client, willSendRequest: &request)
}
}
public func client(_ client: APIClient, shouldRetry task: URLSessionTask, error: Error, attempts: Int) async throws -> Bool {
for delegate in delegates {
if try await delegate.client(client, shouldRetry: task, error: error, attempts: attempts) {
return true
}
}
return false
}
public func client(_ client: APIClient, validateResponse response: HTTPURLResponse, data: Data, task: URLSessionTask) throws {
for delegate in delegates {
try delegate.client(client, validateResponse: response, data: data, task: task)
}
}
public func client<T>(_ client: APIClient, makeURLForRequest request: Request<T>) throws -> URL? {
for delegate in delegates {
if let url = try delegate.client(client, makeURLForRequest: request) {
return url
}
}
return nil
}
public func client<T>(_ client: APIClient, encoderForRequest request: Request<T>) -> JSONEncoder? {
for delegate in delegates {
if let encoder = delegate.client(client, encoderForRequest: request) {
return encoder
}
}
return nil
}
public func client<T>(_ client: APIClient, decoderForRequest request: Request<T>) -> JSONDecoder? {
for delegate in delegates {
if let decoder = delegate.client(client, decoderForRequest: request) {
return decoder
}
}
return nil
}
}
Line #43 of APIClient has this:
public func send(_ request: URLRequest) async throws -> (Data, URLResponse) {
do {
return try await actuallySend(request)
} catch {
guard await delegate.shouldClientRetry(self, withError: error) else { throw error }
return try await actuallySend(request)
}
}
Perhaps I'm mistaken, but a 401/403 doesn't actually throw an error here, it'll just continue a normal right? The shouldClientRetry
would only be called if the network had some actually throwing error here, right?
I usually approach OAuth in a completely different way, and if you agree I'd love to submit a PR with a different approach where auth'ed endpoints are wrapped at the call site, rather than handled inside of the API framework.
Could you please add method to APIClientDelegate
for post processing raw response?
/deleted
The current situation is I have to assign headers
to Request
for each API request while my expectation is to set that globally.
For example, I have a base header required to send to the backend for any request. Ideally, there's a method I can use like
APIClient.setCommonHeader(headers)
import Foundation
import Get
struct LeisureAPIClient {
static func getUser() async throws -> [User] {
let config = LeisureAPI.users
return try await APIClient(baseURL:config.baseURL) {
$0.delegate = LeisureAPIClientDelegate.shared
}.send(Request(path: config.path, headers: ["example"])).value
}
}
public enum LeisureAPI {
case users
case topContributors
}
extension LeisureAPI: TargetType {
var baseURL: URL {
return URL(string: "https://api.github.com")!
}
var path: String {
switch self {
case .users:
return "/users"
case .topContributors:
return "/repos/scala/scala/contributors"
}
}
var method: HttpMethod {
return .get
}
var headers: [String : String]? {
return nil
}
}
enum HttpMethod: String {
case post = "post"
case get = "get"
}
protocol TargetType {
/// The target's base `URL`.
var baseURL: URL { get }
/// The path to be appended to `baseURL` to form the full `URL`.
var path: String { get }
/// The HTTP method used in the request.
var method: HttpMethod { get }
/// Provides stub data for use in testing. Default is `Data()`.
var sampleData: Data? { get }
/// The headers to be used in the request.
var headers: [String: String]? { get }
}
extension TargetType {
var sampleData: Data? {
return nil
}
var headers: [String: String]? {
baseHeader
}
var baseHeader: [String: String] {
[
"Authorization": "allow",
"userType": "xxx",
"type": "iOS",
"x-api-key": "xxxx",
"device_id": "xxxx",
"token": "xxxx",
"uid": "xxxx"
]
}
}
final class LeisureAPIClientDelegate: APIClientDelegate {
static var shared = LeisureAPIClientDelegate()
func client(_ client: APIClient, validateResponse response: HTTPURLResponse, data: Data, task: URLSessionTask) throws {
if response.statusCode == 304 {
print("304 returned")
NotificationCenter.default.post(name: Notification.Name("EXPIRED"), object: nil)
} else {
print("validateResponse")
}
}
}
I didn't see an example of how to do this, i'm using MultipartFormDataKit & what i'm doing is
return Future {
let filename = UUID().uuidString
let boundary = RandomBoundaryGenerator.generate()
let multipartFormData = try MultipartFormDataKit.MultipartFormData.Builder.build(
with: [(
name: "file",
filename: "\(filename).png",
mimeType: MIMEType.imagePng,
data: data
)],
willSeparateBy: boundary
).body
return try await self.apiStore.send(Paths.me.picture.patch(multipartFormData), configure: {
$0.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
}).value
}
but it feels a little verbose at the callsite. my swagger definition says that the form should contain data at the key file
, for example
is this the expected integration? or is there something a little less verbose?
I want to know why use AnyEncodable in encoder
In the signature of the delegate method
func shouldClientRetry(_ client: APIClient, withError error: Error) async throws -> Bool
might be useful to add URLRequest.
This could be useful for handling situations where multiple requests fail.
Or can you please show an example or hint how you handle this situation?
We're building an iOS framework that primarily supports SPM, but we know there are some adopters that still use CocoaPods. Could this library support CocoaPods as well?
Would it be possible to add urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)
to DataLoader
's list of implemented URLSessionTaskDelegate
callbacks?
I'm not using one of APIClient
's upload(...)
functions where I could just set the delegate directly because those functions don't do multipart form requests (unless I'm missing something?) and thus can't get at the uploaded file on my PHP server side, so I instead implemented them "manually" with send(..)
by modifying the inout URLRequest
directly. Unfortunately, since DataLoader
doesn't have the requested callback implemented, I can't monitor the progress of the data send.
I was just going to add it in an extension, but DataLoader
isn't exposed outside the package.
Hello @kean. Thank you for this library, I really admire the elegant API you achieved with this.
I've been using Get in a side project and stumbled into an issue where an explicitly written +
character was not encoded when used in a query string (e.g. domain.tld?query=value1+value2
). This resulted in an unexpected response from the API i was using.
According to a note in this Apple Doc which also refers to RFC 3986 and W3C, the +
sign is a valid character for representing a space in a query string. Due to this fact it seems, the character is not automatically encoded when reading the url
from a URLComponents
instance. The same note suggests to preemptively encode the +
character as well if needed.
I think it would be reasonable to add this behavior to Get, what are your thoughts?
I made a sample implementation in a fork here. Heavily inspired by Alamofire.
Hi 👋🏻
When declaring a method with an optional response body (see example), the call only results in a decoding error because the method tries to decode an empty response.
Optional response body method:
func getWatching(slug: String, extended info: ExtendedInfo?) async throws -> Response<Users.GetWatching.Response?>
Error:
dataCorrupted(Swift.DecodingError.Context(
codingPath: [],
debugDescription: "The given data was not valid JSON.",
underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Unable to parse empty data." UserInfo={NSDebugDescription=Unable to parse empty data.})
)
)
The same code worked in previous version though (it simply returned nil
).
Hi Alex 🤝
Is it possible to conform a struct Response to Codable
?
My data model conforms to Codable
and has a Response<String>
property.
I implemented a func encode(to encoder: Encoder) throws {
and init(from decoder: Decoder) throws {
but I'm getting an error:
let response: URLResponse
does not conform to Codable.
I understand what this error means.
brew install mint
mint install unsignedapps/swift-create-xcframework
swift create-xcframework --platform ios
ft:359:11: error: 'value' is only available in iOS 15.0 or newer
}.value
^
/Users/johndpope/Documents/gitWorkspace/Supabase/Sources/Get/Sources/Get/DataLoader.swift:359:11: note: add 'if #available' version check
}.value
^
/Users/johndpope/Documents/gitWorkspace/Supabase/Sources/Get/Sources/Get/DataLoader.swift:348:6: note: add @available attribute to enclosing global function
func decode<T: Decodable>(_ data: Data, using decoder: JSONDecoder) async throws -> T {
^
** ARCHIVE FAILED **
The following build commands failed:
CompileSwift normal arm64 (in target 'Get' from project 'Get')
CompileSwiftSources normal arm64 com.apple.xcode.tools.swift.compiler (in target 'Get' from project 'Get')
(2 failures)
Error: xcodebuild exited with a non-zero code: 65
swift-driver version: 1.26.9 Apple Swift version 5.5.1 (swiftlang-1300.0.31.4 clang-1300.0.29.6)
Target: x86_64-apple-macosx12.0
will try updating xcode to latest.
UPDATE
actually even just running
swift build - I get errors - must be my machine.
ate.swift:11:17: note: add @available attribute to enclosing protocol
public protocol APIClientDelegate {
^
/Users/johndpope/Documents/gitWorkspace/Supabase/Sources/Get/Sources/Get/APIClientDelegate.swift:63:81: error: concurrency is only available in macOS 12.0.0 or newer
func client(_ client: APIClient, willSendRequest request: inout URLRequest) async throws {
^
/Users/johndpope/Documents/gitWorkspace/Supabase/Sources/Get/Sources/Get/APIClientDelegate.swift:63:10: note: add @available attribute to enclosing instance method
func client(_ client: APIClient, willSendRequest request: inout URLRequest) async throws {
^
/Users/johndpope/Documents/gitWorkspace/Supabase/Sources/Get/Sources/Get/APIClientDelegate.swift:62:8: note: add @available attribute to enclosing extension
public extension APIClientDelegate {
^
/Users/johndpope/Documents/gitWorkspace/Supabase/Sources/Get/Sources/Get/APIClientDelegate.swift:67:101: error: concurrency is only available in macOS 12.0.0 or newer
func client(_ client: APIClient, shouldRetry task: URLSessionTask, error: Error, attempts: Int) async throws -> Bool {
^
/Users/johndpope/Documents/gitWorkspace/Supabase/Sources/Get/Sources/Get/APIClientDelegate.swift:67:10: note: add @available attribute to enclosing instance method
func client(_ client: APIClient, shouldRetry task: URLSessionTask, error: Error, attempts: Int) async throws -> Bool {
^
/Users/johndpope/Documents/gitWorkspace/Supabase/Sources/Get/Sources/Get/APIClientDelegate.swift:62:8: note: add @available attribute to enclosing extension
public extension APIClientDelegate {
^
I would like to use Pulse in my App.
I can't use PulseCore.URLSessionProxyDelegate()
This is my code
import Get
import Pulse
let client = APIClient(baseURL: URL(string: "http://localhost")) {
$0.sessionDelegate = PulseCore.URLSessionProxyDelegate() // Error: Cannot find 'PulseCore' in scope
}
Can you help me?
Can I use HTTP instead of HTTPS? If I manually specify http:// it doesn't work and if I don't specify it it uses https.
Code:
@IBAction func backendDetails(_ sender: Any) { let client = APIClient(host: "http://\(hostname):\(port)") Task{ let result = try? await client.send(.get("/STOInfo/STOVersion")).value } }
Result:
2022-04-09 16:11:17.499954+0200 ServiceTOOLS Control[1854:101545] Connection 0: encountered error(12:1) 2022-04-09 16:11:17.500943+0200 ServiceTOOLS Control[1854:101620] Task <758B7F50-2C4C-42E1-8D91-EDF251350051>.<1> HTTP load failed, 0/0 bytes (error code: -1000 [1:22]) 2022-04-09 16:11:17.508481+0200 ServiceTOOLS Control[1854:101620] Task <758B7F50-2C4C-42E1-8D91-EDF251350051>.<1> finished with error [-1000] Error Domain=NSURLErrorDomain Code=-1000 "bad URL" UserInfo={_kCFStreamErrorCodeKey=22, NSUnderlyingError=0x2804f12c0 {Error Domain=kCFErrorDomainCFNetwork Code=-1000 "(null)" UserInfo={_kCFStreamErrorCodeKey=22, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <758B7F50-2C4C-42E1-8D91-EDF251350051>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=( "LocalDataTask <758B7F50-2C4C-42E1-8D91-EDF251350051>.<1>" ), NSLocalizedDescription=bad URL, NSErrorFailingURLStringKey=https://http%3a%2f%2f192.168.178.20%3a5704/STOInfo/STOVersion, NSErrorFailingURLKey=https://http%3a%2f%2f192.168.178.20%3a5704/STOInfo/STOVersion, _kCFStreamErrorDomainKey=1}
Result without http:// :
2022-04-09 16:19:26.078668+0200 ServiceTOOLS Control[1912:103890] Connection 3: received failure notification 2022-04-09 16:19:26.078771+0200 ServiceTOOLS Control[1912:103890] Connection 3: failed to connect 12:8, reason -1 2022-04-09 16:19:26.078822+0200 ServiceTOOLS Control[1912:103890] Connection 3: encountered error(12:8) 2022-04-09 16:19:26.080123+0200 ServiceTOOLS Control[1912:103890] Task <E28CB0EE-1963-436A-9334-38F862483B88>.<1> HTTP load failed, 0/0 bytes (error code: -1003 [12:8]) 2022-04-09 16:19:26.084242+0200 ServiceTOOLS Control[1912:103890] Task <E28CB0EE-1963-436A-9334-38F862483B88>.<1> finished with error [-1003] Error Domain=NSURLErrorDomain Code=-1003 "A server with the specified hostname could not be found." UserInfo={_kCFStreamErrorCodeKey=8, NSUnderlyingError=0x2836f70c0 {Error Domain=kCFErrorDomainCFNetwork Code=-1003 "(null)" UserInfo={_NSURLErrorNWPathKey=satisfied (Path is satisfied), interface: en0, ipv4, dns, _kCFStreamErrorCodeKey=8, _kCFStreamErrorDomainKey=12}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <E28CB0EE-1963-436A-9334-38F862483B88>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=( "LocalDataTask <E28CB0EE-1963-436A-9334-38F862483B88>.<1>" ), NSLocalizedDescription=A server with the specified hostname could not be found., NSErrorFailingURLStringKey=https://192.168.178.20%3a5704/STOInfo/STOVersion, NSErrorFailingURLKey=https://192.168.178.20%3a5704/STOInfo/STOVersion, _kCFStreamErrorDomainKey=12}
👋 I noticed that Request
defines the following property:
Line 17 in f93c8ef
But it's not currently used. I'm wondering what the intended purpose of this property was?
For context, for crash reporting/debugging purposes, I want a way to identify which path (endpoint/controller) is being represented by a Request
when using CreateAPI to generate those request definitions. Using the actual URL path doesn't cut it, because /repos/1
and /repos/2
are not unique when they are both representing the same endpoint (/repos/{id}
).
For that reason, I had considered that maybe CreateAPI could pass the operationId
to this property, but I didn't want to open a PR that could make that possible if I misunderstood the purpose of it.
WDYT? Thanks!
Tired out calling the GitHubAPI usage with updating GitHubAPIClientDelegete with my personal Github token but getting a 401 unauthorized.
try await usage()
I don't think the GitHubAPIClientDelegete is adding the required requests headers.
Brian
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.