From 8bfbe051465176c94cdcb6fbd46784bf0e9acdb2 Mon Sep 17 00:00:00 2001 From: Joannis Orlandos Date: Sun, 19 Nov 2017 12:22:22 +0100 Subject: [PATCH 1/5] Fix many dead links --- 3.0/docs/async/package.md | 6 ++++-- 3.0/docs/async/worker.md | 12 ++++++++++- 3.0/docs/concepts/async.md | 20 +++++++++---------- 3.0/docs/concepts/http.md | 2 +- 3.0/docs/concepts/vapor.md | 2 +- 3.0/docs/crypto/{index.md => package.md} | 2 +- 3.0/docs/jwt/{index.md => package.md} | 0 3.0/docs/redis/{index.md => package.md} | 0 3.0/docs/security/index.md | 7 ++++--- 3.0/docs/sockets/{index.md => package.md} | 0 .../mysql/index.md => tls/package.md} | 0 3.0/mkdocs.yml | 14 ++++++++----- 12 files changed, 41 insertions(+), 24 deletions(-) rename 3.0/docs/crypto/{index.md => package.md} (96%) rename 3.0/docs/jwt/{index.md => package.md} (100%) rename 3.0/docs/redis/{index.md => package.md} (100%) rename 3.0/docs/sockets/{index.md => package.md} (100%) rename 3.0/docs/{databases/mysql/index.md => tls/package.md} (100%) diff --git a/3.0/docs/async/package.md b/3.0/docs/async/package.md index 8f91a2bd..c97b1162 100644 --- a/3.0/docs/async/package.md +++ b/3.0/docs/async/package.md @@ -2,8 +2,10 @@ Async is a library revolving around two main concepts: -- [Streams](stream.md) -- [Promises](promise-future-introduction.md) +- [Streams Introduction](streams-introduction.md) +- [Streams Basics](streams-basics.md) +- [Promises and Futures](promise-future-introduction.md) +- [Workers](worker.md) Together they form the foundation of Vapor 3's data flow. diff --git a/3.0/docs/async/worker.md b/3.0/docs/async/worker.md index 1333ed77..635de4b0 100644 --- a/3.0/docs/async/worker.md +++ b/3.0/docs/async/worker.md @@ -1 +1,11 @@ -TODO +# Worker + +Workers are any type that keep track of the current [EventLoop](../concepts/async.md#multi-reactor). + +Worker is a simple protocol and can be conformed to if the context can return (by means of a computed property) or contain an EventLoop. + +There are three primary Workers that you can use to access the eventloop and it's queue or context. + +- [Request](../http/request.md) can be used, usually from within a [Route](../routing/basics.md) +- [EventLoop](../concepts/eventloop.md#multi-reactor) is itself a worker to ensure the extensions and consumers of Worker can be used, too. +- `DispatchQueue` is a worker that will return a *new and clean* EventLoop based on the current DispatchQueue diff --git a/3.0/docs/concepts/async.md b/3.0/docs/concepts/async.md index 77e59642..856effd1 100644 --- a/3.0/docs/concepts/async.md +++ b/3.0/docs/concepts/async.md @@ -2,19 +2,19 @@ Async APIs are one of the most important aspects of Vapor 3. You have likely noticed that they are the biggest change to the API from previous versions. To learn more about how to use the new async -APIs check out the [Async → Getting Started](../async/getting-started.md) section. This document +APIs check out the [Async → Getting Started](../async/package.md) section. This document aims to explain _why_ Vapor 3 moved to async APIs and _how_ it has led to huge performance gains. ## Differences -Let's first make sure we understand the different between a "sync" and "async" API. +Let's first make sure we understand the different between a "sync" and "async" API. ```swift // sync let users = try api.getUsers() ``` -Sync APIs return values immediately. +Sync APIs return values immediately. ```swift // async @@ -29,7 +29,7 @@ async APIs (such as [futures](../getting-started/futures.md)), but we will use c ## Concurrency It's fairly obvious from the above snippet that sync APIs are more concise. So why would anyone use an async API? -To understand why, you must first understand how concurrency works with these two API types. +To understand why, you must first understand how concurrency works with these two API types. ### Sync (Blocking) @@ -40,7 +40,7 @@ let hello = "Hello".uppercased() ``` There is no problem with this API being synchronous because at no point in converting "Hello" to uppercase is -the computer ever _waiting_. Waiting, also called "blocking", is an important concept. Most simple functions, +the computer ever _waiting_. Waiting, also called "blocking", is an important concept. Most simple functions, like `.uppercased()` never wait—they are busy doing the requested work (i.e., capitalizing letters) the entire time. However, some functions do wait. The classic example of this is a function that makes a network request. @@ -50,8 +50,8 @@ let res = try HTTPClient.getSync("http://google.com") ``` In the above example, the `.getSync` function must block. After it sends the request to `google.com`, it must wait for -their server to generate a response. While this function is waiting, it can't do anything. In other words, the CPU -is idle. If you're trying to optimize your application, you'd quickly notice that letting your (expensive) CPU sit +their server to generate a response. While this function is waiting, it can't do anything. In other words, the CPU +is idle. If you're trying to optimize your application, you'd quickly notice that letting your (expensive) CPU sit idle is not a great idea. ### Async (Non-blocking) @@ -65,7 +65,7 @@ HTTPClient.getAsync("http://google.com", onCompletion: { res, error in }) ``` -Now, after the `.getAsync` function is done sending the request to `google.com`, it can simply continue executing your +Now, after the `.getAsync` function is done sending the request to `google.com`, it can simply continue executing your program. When Google's servers return a response, the CPU will be notified, and it will give the response to your callback. ## Advanced @@ -77,14 +77,14 @@ Let's now take a deeper look at why Vapor prefers async (non-blocking) APIs over You may be thinking, what's the problem with `.getSync`? Why not just call that function on a background thread. That is indeed a valid solution, and it would work. This style of threading is widely used and taught. However, the problem is that threads are not "free" to create. When you're programming for iOS, this doesn't (usually) matter. You can -often spin up as many threads as you like. You have a whole CPU to yourself! Things are different on the server though. +often spin up as many threads as you like. You have a whole CPU to yourself! Things are different on the server though. Web applications must respond to thousands (even millions) of requests _at the same time_. Imagine running a million instances of your iOS app on one device. Even relatively small costs, like creating a thread, become extremely important as a web server. ### Multi-Reactor To achieve optimal performance, Vapor only creates one thread per CPU core on your machine. This means there will be no -wasted time allocating new threads and very little wasted time switching between them. +wasted time allocating new threads and very little wasted time switching between them. Each one of these threads is called an event loop, similar to Node.js. The difference is that Vapor has multiple event loops (one for each core) where as Node.js just has one. This means you don't need to spin up multiple instances of your app diff --git a/3.0/docs/concepts/http.md b/3.0/docs/concepts/http.md index 497835c0..2e846a9e 100644 --- a/3.0/docs/concepts/http.md +++ b/3.0/docs/concepts/http.md @@ -12,7 +12,7 @@ HTTP/2 is a protocol with security and performance in mind. Designed with experi ## How it works -At the heart of HTTP lie the [Request](../http/request.md) and [Response](../http/response,d). Both of them are "HTTP Messages". Both HTTP messages consists of [Headers](../http/headers.md) and [a body](../http/body.md). +At the heart of HTTP lie the [Request](../http/request.md) and [Response](../http/response.md). Both of them are "HTTP Messages". Both HTTP messages consists of [Headers](../http/headers.md) and [a body](../http/body.md). HTTP clients connect to an HTTP server. The clients can send a request to which the server will send a response. diff --git a/3.0/docs/concepts/vapor.md b/3.0/docs/concepts/vapor.md index f5e3a837..3dcfc12c 100644 --- a/3.0/docs/concepts/vapor.md +++ b/3.0/docs/concepts/vapor.md @@ -1,6 +1,6 @@ # What is Vapor? -Vapor is a [high performance](../supplementary/performance.md), type-safe and [easy to use](setup.md) web framework written in and for Swift. +Vapor is a [high performance](../supplementary/performance.md), type-safe and [easy to use](../getting-started/hello-world.md) web framework written in and for Swift. Vapor is designed for both big and small services, providing a low entry barrier to get started in addition to high performance, well tested and well documented APIs. diff --git a/3.0/docs/crypto/index.md b/3.0/docs/crypto/package.md similarity index 96% rename from 3.0/docs/crypto/index.md rename to 3.0/docs/crypto/package.md index bc917d19..a086bcad 100644 --- a/3.0/docs/crypto/index.md +++ b/3.0/docs/crypto/package.md @@ -2,7 +2,7 @@ Crypto is a library containing all common APIs related to cryptography and security. -This project does **not** support TLS. For that, please see [the TLS package](../tls/index.md). +This project does **not** support TLS. For that, please see [the TLS package](../tls/package.md). ### Index diff --git a/3.0/docs/jwt/index.md b/3.0/docs/jwt/package.md similarity index 100% rename from 3.0/docs/jwt/index.md rename to 3.0/docs/jwt/package.md diff --git a/3.0/docs/redis/index.md b/3.0/docs/redis/package.md similarity index 100% rename from 3.0/docs/redis/index.md rename to 3.0/docs/redis/package.md diff --git a/3.0/docs/security/index.md b/3.0/docs/security/index.md index 33dcf3fb..991d72a6 100644 --- a/3.0/docs/security/index.md +++ b/3.0/docs/security/index.md @@ -1,12 +1,13 @@ # Security -Security is a critical part of any software and comes in many forms. Denial of Service attacks and code injection are only the tip of the iceberg. -It is always important to understand some basics of security. Vapor 3 is designed to be secure by default, but anything you build on top of Vapor 3 might not be. +Security is a critical part of any software and comes in many forms. + +It is always important to understand some basics of security. Vapor 3 is designed to be secure by default and has many tools to support building secure applications. ### Recommended information - [Password management](../crypto/passwords.md) -- [JWT (Session) Tokens](../jwt/index.md) +- [JWT (Session) Tokens](../jwt/package.md) ### Additional information diff --git a/3.0/docs/sockets/index.md b/3.0/docs/sockets/package.md similarity index 100% rename from 3.0/docs/sockets/index.md rename to 3.0/docs/sockets/package.md diff --git a/3.0/docs/databases/mysql/index.md b/3.0/docs/tls/package.md similarity index 100% rename from 3.0/docs/databases/mysql/index.md rename to 3.0/docs/tls/package.md diff --git a/3.0/mkdocs.yml b/3.0/mkdocs.yml index b172c34e..8ecc1925 100644 --- a/3.0/mkdocs.yml +++ b/3.0/mkdocs.yml @@ -65,11 +65,10 @@ pages: - 'Overview': 'databases/sqlite/overview.md' - 'MySQL': - 'Package': 'databases/mysql/package.md' - - 'Index': 'databases/mysql/index.md' - 'Setup': 'databases/mysql/setup.md' - 'Basics': 'databases/mysql/basics.md' - 'Redis': - - 'Index': 'redis/index.md' + - 'Package': 'redis/package.md' - 'Basics': 'redis/basics.md' - 'Custom commands': 'redis/custom-commands.md' - 'Publish and Subscribe': 'redis/pub-sub.md' @@ -89,17 +88,22 @@ pages: - 'Services': - 'Getting Started': 'services/getting-started.md' - 'JWT': + - 'Package': 'jwt/package.md' - 'Signed Tokens': 'jwt/jws.md' - 'Crypto': - - 'Index': 'crypto/index.md' + - 'Package': 'crypto/package.md' - 'Base64': 'crypto/base64.md' - 'Hashes': 'crypto/hash.md' - 'Message authentication': 'crypto/mac.md' - 'Password hashing': 'crypto/passwords.md' - 'Random': 'crypto/random.md' +- 'TLS': + - 'Package': 'tls/package.md' - 'Sockets': - - 'Index': 'sockets/index.md' + - 'Package': 'sockets/package.md' - 'TCP Client': 'sockets/tcp-client.md' + - 'TCP Server': 'sockets/tcp-server.md' + - 'TCP Socket': 'sockets/tcp-socket.md' - 'Testing': - 'Getting Started': 'testing/getting-started.md' - 'Security': @@ -122,7 +126,7 @@ markdown_extensions: - meta - toc(permalink=true) -theme: +theme: name: 'material' palette: primary: 'blue' From e1d1dbf5dffcf9869eaff6e2cfae4404c2dad54e Mon Sep 17 00:00:00 2001 From: Joannis Orlandos Date: Sun, 19 Nov 2017 13:05:06 +0100 Subject: [PATCH 2/5] Fixed remaining dead links --- .../async/{futures-basics.md => futures.md} | 159 +++++++----------- 3.0/docs/async/package.md | 5 +- 3.0/docs/async/promise-future-introduction.md | 159 ------------------ 3.0/docs/async/streams-basics.md | 45 ----- .../{streams-introduction.md => streams.md} | 14 +- 3.0/docs/async/worker.md | 6 +- 3.0/docs/concepts/http.md | 14 +- 3.0/docs/databases/mysql/basics.md | 4 +- 3.0/docs/databases/mysql/setup.md | 2 +- .../postgres/basics.md} | 0 3.0/docs/databases/postgres/package.md | 0 3.0/docs/databases/postgres/setup.md | 0 3.0/docs/databases/sqlite/overview.md | 16 +- 3.0/docs/databases/sqlite/package.md | 4 +- 3.0/docs/fluent/getting-started/package.md | 12 +- 3.0/docs/fluent/getting-started/provider.md | 18 +- 3.0/docs/getting-started/futures.md | 9 +- 3.0/docs/getting-started/json.md | 0 3.0/docs/http/body-stream.md | 0 3.0/docs/http/request.md | 4 +- 3.0/docs/http/responder.md | 2 +- 3.0/docs/http/response.md | 4 +- 3.0/docs/redis/basics.md | 2 +- 3.0/docs/redis/custom-commands.md | 2 +- 3.0/docs/redis/pub-sub.md | 2 +- 3.0/docs/routing/basics.md | 2 +- 3.0/docs/routing/router.md | 6 +- 3.0/docs/security/dos.md | 2 +- 3.0/docs/services/getting-started.md | 3 +- 3.0/docs/sockets/tcp-client.md | 2 +- 3.0/docs/tls/client.md | 0 3.0/docs/tls/package.md | 14 +- 3.0/mkdocs.yml | 12 +- 33 files changed, 148 insertions(+), 376 deletions(-) rename 3.0/docs/async/{futures-basics.md => futures.md} (85%) delete mode 100644 3.0/docs/async/promise-future-introduction.md delete mode 100644 3.0/docs/async/streams-basics.md rename 3.0/docs/async/{streams-introduction.md => streams.md} (91%) rename 3.0/docs/{http/streaming-response.md => databases/postgres/basics.md} (100%) create mode 100644 3.0/docs/databases/postgres/package.md create mode 100644 3.0/docs/databases/postgres/setup.md create mode 100644 3.0/docs/getting-started/json.md create mode 100644 3.0/docs/http/body-stream.md create mode 100644 3.0/docs/tls/client.md diff --git a/3.0/docs/async/futures-basics.md b/3.0/docs/async/futures.md similarity index 85% rename from 3.0/docs/async/futures-basics.md rename to 3.0/docs/async/futures.md index e45e85d6..fca1ce98 100644 --- a/3.0/docs/async/futures-basics.md +++ b/3.0/docs/async/futures.md @@ -1,29 +1,80 @@ -# Introduction into Promises and Futures +# Future basics -When working with asynchronous APIs, one of the problems you'll face is not knowing when a variable is set. +Futures are used throughout Vapor, so it is useful to know some of the available helpers. We explain the reasoning and use cases [here](../concepts/async.md). -When querying a database synchronously, the thread is blocked until a result has been received. At which point the result will be returned to you and the thread continues from where you left off querying the database. +## Adding awaiters to all results + +If you need to handle the results of an operation regardless of success or failure, you can do so by calling the `.addAwaiter` function on a future. + +The awaiter shall be called on completion with a `Result`. This is an enum with either the `Expectation` or an `Error` contained within. ```swift -let user = try database.fetchUser(named: "Admin") +let future = Future("Hello world") -print(user.username) +future.addAwaiter { result in + switch result { + case .expectation(let string): + print(string) + case .error(let error): + print("Error: \(error)") + } +} ``` -In the asynchronous world, you won't receive a result immediately. Instead, you'll receive a result in a callback. +## Flat-Mapping results + +Nested async callbacks can be a pain to unwind. An example of a painfully complex "callback hell" scenario is demonstrated below: ```swift -// Callback `found` will receive the user. If an error occurred, the `onError` callback will be called instead. -try database.fetchUser(named: "Admin", found: { user in - print(user.username) -}, onError: { error in - print(error) -}) +app.get("friends") { request in + let session = try request.getSessionCookie() as UserSession + + let promise = Promise() + + // Fetch the user + try session.user.resolve().then { user in + // Returns all the user's friends + try user.friends.resolve().then { friends in + return try view.make("friends", context: friends, for: request).then { renderedView in + promise.complete(renderedView) + }.catch(promise.fail) + }.catch(promise.fail) + }.catch(promise.fail) + + return promise.future +} ``` -You can imagine code becoming complex. Difficult to read and comprehend. +Vapor 3 offers a `flatMap` solution here that will help keep the code readable and maintainable. -Promises and futures are two types that this library introduces to solve this. +```swift +app.get("friends") { request in + let session = try request.getSessionCookie() as UserSession + + // Fetch the user + return try session.user.resolve().flatten { user in + // Returns all the user's friends + return try user.friends.resolve() + }.map { friends in + // Flatten replaced this future with + return try view.make("friends", context: friends, for: request) + } +} +``` + +## Combining multiple futures + +If you're expecting the same type of result from multiple sources you can group them using the `flatten` function. + +```swift +var futures = [Future]() +futures.append(Future("Hello")) +futures.append(Future("World")) +futures.append(Future("Foo")) +futures.append(Future("Bar")) + +let futureResults = futures.flatten() // Future<[String]> +``` ## Creating a promise @@ -157,83 +208,3 @@ If you expect a result with a specified duration, say, 30 seconds: // This will also throw an error if the deadline wasn't met let user = try future.blocked(timeout: .seconds(30)) ``` - - - -# Future basics - -Futures are used throughout Vapor, so it is useful to know some of the available helpers. - -## Adding awaiters to all results - -If you need to handle the results of an operation regardless of success or failure, you can do so by calling the `.addAwaiter` function on a future. - -The awaiter shall be called on completion with a `Result`. This is an enum with either the `Expectation` or an `Error` contained within. - -```swift -let future = Future("Hello world") - -future.addAwaiter { result in - switch result { - case .expectation(let string): - print(string) - case .error(let error): - print("Error: \(error)") - } -} -``` - -## Flat-Mapping results - -Nested async callbacks can be a pain to unwind. An example of a painfully complex "callback hell" scenario is demonstrated below: - -```swift -app.get("friends") { request in - let session = try request.getSessionCookie() as UserSession - - let promise = Promise() - - // Fetch the user - try session.user.resolve().then { user in - // Returns all the user's friends - try user.friends.resolve().then { friends in - return try view.make("friends", context: friends, for: request).then { renderedView in - promise.complete(renderedView) - }.catch(promise.fail) - }.catch(promise.fail) - }.catch(promise.fail) - - return promise.future -} -``` - -Vapor 3 offers a `flatMap` solution here that will help keep the code readable and maintainable. - -```swift -app.get("friends") { request in - let session = try request.getSessionCookie() as UserSession - - // Fetch the user - return try session.user.resolve().flatten { user in - // Returns all the user's friends - return try user.friends.resolve() - }.map { friends in - // Flatten replaced this future with - return try view.make("friends", context: friends, for: request) - } -} -``` - -## Combining multiple futures - -If you're expecting the same type of result from multiple sources you can group them using the `flatten` function. - -```swift -var futures = [Future]() -futures.append(Future("Hello")) -futures.append(Future("World")) -futures.append(Future("Foo")) -futures.append(Future("Bar")) - -let futureResults = futures.flatten() // Future<[String]> -``` diff --git a/3.0/docs/async/package.md b/3.0/docs/async/package.md index c97b1162..e50faec3 100644 --- a/3.0/docs/async/package.md +++ b/3.0/docs/async/package.md @@ -2,9 +2,8 @@ Async is a library revolving around two main concepts: -- [Streams Introduction](streams-introduction.md) -- [Streams Basics](streams-basics.md) -- [Promises and Futures](promise-future-introduction.md) +- [Promises and Futures](futures.md) +- [Streams](streams.md) - [Workers](worker.md) Together they form the foundation of Vapor 3's data flow. diff --git a/3.0/docs/async/promise-future-introduction.md b/3.0/docs/async/promise-future-introduction.md deleted file mode 100644 index 942974f3..00000000 --- a/3.0/docs/async/promise-future-introduction.md +++ /dev/null @@ -1,159 +0,0 @@ -# Introduction into Promises and Futures - -When working with asynchronous APIs, one of the problems you'll face is not knowing when a variable is set. - -When querying a database synchronously, the thread is blocked until a result has been received. At which point the result will be returned to you and the thread continues from where you left off querying the database. - -```swift -let user = try database.fetchUser(named: "Admin") - -print(user.username) -``` - -In the asynchronous world, you won't receive a result immediately. Instead, you'll receive a result in a callback. - -```swift -// Callback `found` will receive the user. If an error occurred, the `onError` callback will be called instead. -try database.fetchUser(named: "Admin", found: { user in - print(user.username) -}, onError: { error in - print(error) -}) -``` - -You can imagine code becoming complex. Difficult to read and comprehend. - -Promises and futures are two types that this library introduces to solve this. - -## Creating a promise - -Promises are important if you're implementing a function that returns a result in the future, such as the database shown above. - -Promises need to be created without a result. They can then be completed with the expectation or an error at any point. - -You can extract a `future` from the `promise` that you can hand to the API consumer. - -```swift -// the example `fetchUser` implementation -func fetchUser(named name: String) -> Future { - // Creates a promise that can be fulfilled in the future - let promise = Promise() - - do { - // TODO: Run a query asynchronously, looking for the user - - // Initialize the user using the datbase result - // This can throw an error if the result is empty or invalid - let user = try User(decodingFrom: databaseResult) - - // If initialization is successful, complete the promise. - // - // Completing the promise will notify the promise's associated future with this user - promise.complete(user) - } catch { - // If initialization is successful, fail the promise. - // - // Failing the promise will notify the promise's associated future with an error - promise.fail(error) - } - - // After spawning the asynchronous operation, return the promise's associated future - // - // The future can then be used by the API consumer - return promise.future -} -``` - -## On future completion - -When a promise completes, you can chain the result/error into a closure: - -```swift -// The future provided by the above function will be used -let future: Future = fetchUser(named: "Admin") - -// `.then`'s closure will be executed on success -future.then { user in - print(user.username) -// `.catch` will catch any error on failure -}.catch { error in - print(error) -} -``` - -## Catching specific errors - -Sometimes you only care for specific errors, for example, for logging. - -```swift -// The future provided by the above function will be used -let future: Future = fetchUser(named: "Admin") - -// `.then`'s closure will be executed on success -future.then { user in - print(user.username) -// This `.catch` will only catch `DatabaseError`s -}.catch(DatabaseError.self) { databaseError in - print(databaseError) -// `.catch` will catch any error on failure, including `DatabaseError` types -}.catch { error in - print(error) -} -``` - -## Mapping results - -Futures can be mapped to different results asynchronously. - -```swift -// The future provided by the above function will be used -let future: Future = fetchUser(named: "Admin") - -// Maps the user to it's username -let futureUsername: Future = future.map { user in - return user.username -} - -// Mapped futures can be mapped and chained, too -futureUsername.then { username in - print(username) -} -``` - -## Futures without promise - -In some scenarios you're required to return a `Future` where a `Promise` isn't necessary as you already have the result. - -In these scenarios you can initialize a future with the already completed result. - -```swift -// Already completed on initialization -let future = Future("Hello world!") - -future.then { string in - print(string) -} -``` - -## Synchronous APIs - -Sometimes, an API needs to be used synchronously in a synchronous envinronment. - -Rather than using a synchronous API with all edge cases involved, we recommend using the `try future.blockingAwait()` function. - -```swift -// The future provided by the above function will be used -let future: Future = fetchUser(named: "Admin") - -// This will either receive the user if the promise was completed or throw an error if the promise was failed. -let user: User = try future.blockingAwait() -``` - -This will wait for a result indefinitely, blocking the thread. - -If you expect a result with a specified duration, say, 30 seconds: - -```swift -// This will also throw an error if the deadline wasn't met -let user = try future.blocked(timeout: .seconds(30)) -``` diff --git a/3.0/docs/async/streams-basics.md b/3.0/docs/async/streams-basics.md deleted file mode 100644 index 5ef8a627..00000000 --- a/3.0/docs/async/streams-basics.md +++ /dev/null @@ -1,45 +0,0 @@ -# Stream Basics - -Streams are a set of events that occur asynchronously in time. Some events may dispatch immediately, and others may have a delay in nanoseconds, milliseconds, minutes or any other duration. Streams can be linked together to create simple, performant and maintainable software. - -## Chaining streams - -If an `OutputStream`'s Output is the same as an `InputStream`'s input, you can "chain" these streams together to create really performant and readable solutions. - -This doesn't work for all situation, but let's look at an example that *does* accept [base64](../crypto/base64.md). - -```swift -client.stream(to: base64encoder).stream(to: client) -``` - -The result is an "echo" server that base64-encodes incoming data, and replies it back in base64-encoded format. - -Another good example is how Vapor processes requests internally. - -```swift -let server = try TCPServer(port: 8080, worker: Worker(queue: myDispatchQueue)) - -// Servers are a stream of accepted web client connections -// Clients are an input and output stream of bytes -server.drain { client in - let parser = RequestParser() - let router = try yourApplication.make(Router.self) - let serialiser = ResponseSerializer() - - // Parses client-sent bytes into the RequestParser - let requestStream = client.stream(to: parser) - - // Parses requests to the Vapor router, creating a response - let responseStream = requestStream.stream(to: router) - - // Serializes the responses, creating a byte stream - let serializedResponseStream = responseStream.stream(to: serializer) - - // Drains the serialized responses back into the client socket - serializedResponseStream.drain(into: client) -} -``` - -In the above example, the output of one stream is inputted into another. This ends up taking care of the entire HTTP [Request](../http/request.md)/[Response](../http/response.md) process. - -`Socket -> Request-Data -> Request -> Processing -> Response -> Response-Data -> Socket` diff --git a/3.0/docs/async/streams-introduction.md b/3.0/docs/async/streams.md similarity index 91% rename from 3.0/docs/async/streams-introduction.md rename to 3.0/docs/async/streams.md index 25fe162c..9b62bf83 100644 --- a/3.0/docs/async/streams-introduction.md +++ b/3.0/docs/async/streams.md @@ -1,5 +1,3 @@ -# Introduction into Streams - Streams is a mechanism that you can implement on objects that process any information efficiently and asynchronously without bloat. There are three primary stream protocols: @@ -125,3 +123,15 @@ let stringStream = tcpStream.flatMap(utf8String) // `optionalStringStream` is a stream outputting `String?` let optionalStringStream = tcpStream.map(utf8String) ``` + +## Chaining streams + +If an `OutputStream`'s Output is the same as an `InputStream`'s input, you can "chain" these streams together to create really performant and readable solutions. + +This doesn't work for all situation, but let's look at an example that *does* accept [base64](../crypto/base64.md). + +```swift +client.stream(to: base64encoder).stream(to: client) +``` + +The result is an "echo" server that base64-encodes incoming data, and replies it back in base64-encoded format. diff --git a/3.0/docs/async/worker.md b/3.0/docs/async/worker.md index 635de4b0..b8fe9aad 100644 --- a/3.0/docs/async/worker.md +++ b/3.0/docs/async/worker.md @@ -1,11 +1,11 @@ # Worker -Workers are any type that keep track of the current [EventLoop](../concepts/async.md#multi-reactor). +Workers are any type that keep track of the current [EventLoop](../concepts/async.md). Worker is a simple protocol and can be conformed to if the context can return (by means of a computed property) or contain an EventLoop. -There are three primary Workers that you can use to access the eventloop and it's queue or context. +There are three primary Workers that you can use to access the `EventLoop` and it's queue or context. - [Request](../http/request.md) can be used, usually from within a [Route](../routing/basics.md) -- [EventLoop](../concepts/eventloop.md#multi-reactor) is itself a worker to ensure the extensions and consumers of Worker can be used, too. +- [EventLoop](../concepts/async.md) is itself a worker to ensure the extensions and consumers of Worker can be used, too. - `DispatchQueue` is a worker that will return a *new and clean* EventLoop based on the current DispatchQueue diff --git a/3.0/docs/concepts/http.md b/3.0/docs/concepts/http.md index 2e846a9e..b58d4e9d 100644 --- a/3.0/docs/concepts/http.md +++ b/3.0/docs/concepts/http.md @@ -16,11 +16,11 @@ At the heart of HTTP lie the [Request](../http/request.md) and [Response](../htt HTTP clients connect to an HTTP server. The clients can send a request to which the server will send a response. -Bodies contain the concrete information being transferred. Think of the web-page, images, videos, [JSON](../vapor/json.md) and [forms](../http/multipart.md). +Bodies contain the concrete information being transferred. Think of the web-page, images, videos, [JSON](../getting-started/json.md) and [forms](../http/multipart.md). Headers contain metadata. -[Cookies](../http/cookies.md) are metadataabout the client that, for example, can be used for identifying users after they've (successfully) logged in. One of these methods are [session tokens](../jwt/index.md). +[Cookies](../http/cookies.md) are metadataabout the client that, for example, can be used for identifying users after they've (successfully) logged in. One of these methods are [session tokens](../jwt/package.md). Another type of metadata can be related to the content. For example, defining the type of content transferred in the body. @@ -38,7 +38,7 @@ Responses have one additional property in addition to the message's properties. ## Handling requests -Requests in Vapor will be handled by a [router](../vapor/routing.md). This allows registering a path to a method. For example, registering `.get("users")` will register the path `/users/` to the method `GET`. The responder/closure associated with this route can then handle requests sent to `/users/` with the `GET` method. +Requests in Vapor will be handled by a [router](../getting-started/routing.md). This allows registering a path to a method. For example, registering `.get("users")` will register the path `/users/` to the method `GET`. The responder/closure associated with this route can then handle requests sent to `/users/` with the `GET` method. ## Types of endpoints @@ -50,12 +50,12 @@ iOS and Android apps usually communicate with an API, where a web browser such a Websites come in two major flavours. Server and client rendered pages. "Rendering" in this context doesn't mean the graphical rendering on your monitor, but instead the way information is injected into the HTML DOM to display the information to the users. -Server rendered pages make use of a templating system such as [leaf](../leaf/index.md) whereas client rendered pages communicate with an API. +Server rendered pages make use of a templating system such as [leaf](../leaf/package.md) whereas client rendered pages communicate with an API. ### API -APIs are endpoints that sometimes receive but always reply with raw data. The raw data can be in any format. Most commonly, APIs communicate with [JSON](../vapor/json.md). Sometimes, they communicate with XML or other data types. Vapor can flexibly switch between supported formats, both by official or by community made libraries. +APIs are endpoints that sometimes receive but always reply with raw data. The raw data can be in any format. Most commonly, APIs communicate with [JSON](../getting-started/json.md). Sometimes, they communicate with XML or other data types. Vapor can flexibly switch between supported formats, both by official or by community made libraries. -APIs in Vapor are (almost) always creating using a "MVC" or "Model View Controller" model [which we explain here.](controllers.md) +APIs in Vapor are (almost) always creating using a "MVC" or "Model View Controller" model [which we explain here.](../getting-started/controllers.md) -Designing an API in Vapor is really simple. [We dive into this more here.](application-design.md) +Designing an API in Vapor is really simple. [We dive into this from here.](../getting-started/application.md) diff --git a/3.0/docs/databases/mysql/basics.md b/3.0/docs/databases/mysql/basics.md index c2490bff..6e1387fb 100644 --- a/3.0/docs/databases/mysql/basics.md +++ b/3.0/docs/databases/mysql/basics.md @@ -29,7 +29,7 @@ struct User { ### Streams -Streams, [as described on this page](../async/streams-introduction.md), are a source of information that calls a single reader's callback. Streams are best used in larger datasets to prevent the query from consuming a large amount of memory. The downside of a stream is that you cannot return all results in a single future. You'll need to stream the results to the other endpoint, too. For HTTP [this is described here.](../http/streaming-response.md) +Streams, [as described on this page](../../async/streams.md), are a source of information that calls a single reader's callback. Streams are best used in larger datasets to prevent the query from consuming a large amount of memory. The downside of a stream is that you cannot return all results in a single future. You'll need to stream the results to the other endpoint, too. For HTTP [this is described here.](../../http/body-stream.md) Querying a database for a stream of results is achieved through the `stream` function and requires specifying the `Decodable` type that the results need to be deserialized into. @@ -42,7 +42,7 @@ This stream will return all results in the ModelStream's output callback which y ### Futures -Futures are often easier to use but significantly heavier on your memory and thus performance. [They are thoroughly described here](../async/promise-future-introduction.md) +Futures are often easier to use but significantly heavier on your memory and thus performance. [They are thoroughly described here](../../async/futures.md) Querying a database for a future is achieved through the `all` function and requires specifying the `Decodable` type that the results need to be deserialized into. diff --git a/3.0/docs/databases/mysql/setup.md b/3.0/docs/databases/mysql/setup.md index d12353a7..253b9509 100644 --- a/3.0/docs/databases/mysql/setup.md +++ b/3.0/docs/databases/mysql/setup.md @@ -28,7 +28,7 @@ The following code creates a new connectionpool to `localhost` and the default M The database is the database that is selected and authenticated to. Any future queries will be sent to this database. -The `worker` is defined in [the async documentation](../async/worker.md). +The `worker` is defined in [the async documentation](../../async/worker.md). ```swift let connectionPool = ConnectionPool(hostname: "localhost", user: "root", password: nil, database: "test-db", worker: worker) diff --git a/3.0/docs/http/streaming-response.md b/3.0/docs/databases/postgres/basics.md similarity index 100% rename from 3.0/docs/http/streaming-response.md rename to 3.0/docs/databases/postgres/basics.md diff --git a/3.0/docs/databases/postgres/package.md b/3.0/docs/databases/postgres/package.md new file mode 100644 index 00000000..e69de29b diff --git a/3.0/docs/databases/postgres/setup.md b/3.0/docs/databases/postgres/setup.md new file mode 100644 index 00000000..e69de29b diff --git a/3.0/docs/databases/sqlite/overview.md b/3.0/docs/databases/sqlite/overview.md index 75d040bb..dc582531 100644 --- a/3.0/docs/databases/sqlite/overview.md +++ b/3.0/docs/databases/sqlite/overview.md @@ -1,12 +1,12 @@ # SQLite Overview -Let's dive into the [vapor/sqlite](https://github.com/vapor/sqlite) package and +Let's dive into the [vapor/sqlite](https://github.com/vapor/sqlite) package and see how to connect to and query a database. !!! warning - This documentation provides an overview for the SQLite API. + This documentation provides an overview for the SQLite API. If you are using SQLite with Fluent, you will likely never need to use - this API. Use [Fluent's APIs](../fluent/overview.md) instead. + this API. Use [Fluent's APIs](../../fluent/getting-started/package.md) instead. Follow the instructions in the [package](package.md) section to add the SQLite package to your project. Once its added, you should be able to use `import SQLite.` @@ -46,11 +46,11 @@ let conn = try db.makeConnection(on: .global()) ``` !!! note - Pay special attention to which `DispatchQueue` you pass to `makeConnection(on:)`. + Pay special attention to which `DispatchQueue` you pass to `makeConnection(on:)`. This will be the queue SQLite calls you back on. !!! tip - If you are using SQLite with Vapor, make sure to pass the [worker](async/worker.md)'s queue here. + If you are using SQLite with Vapor, make sure to pass the [worker](../../async/worker.md)'s queue here. ## Query @@ -62,7 +62,7 @@ let query = conn.query("SELECT * FROM users") ### Binding Values -If you are executing a query that has input values, you should bind these using parameters. +If you are executing a query that has input values, you should bind these using parameters. ```swift let query = conn.query("INSERT INTO users (name, age) VALUES (?, ?)") @@ -130,7 +130,7 @@ let name = row["name"]?.text // String ### Run -Once your query is ready to execute, you simply call `.execute()`. This returns a `Future` +Once your query is ready to execute, you simply call `.execute()`. This returns a `Future` that will be completed when the query is done executing. ```swift @@ -162,7 +162,7 @@ let rows = try conn.query("SELECT * FROM users").sync() ### Example -Now for the complete example: +Now for the complete example: ```swift import SQLite diff --git a/3.0/docs/databases/sqlite/package.md b/3.0/docs/databases/sqlite/package.md index 7172f314..e148e101 100644 --- a/3.0/docs/databases/sqlite/package.md +++ b/3.0/docs/databases/sqlite/package.md @@ -6,7 +6,7 @@ On top of [vapor/sqlite](https://github.com/vapor/sqlite), we have built [vapor/ ## With Fluent -SQLite works great with Fluent, you just need to make sure to add the [vapor/fluent-sqlite](https://github.com/vapor/fluent-sqlite) +SQLite works great with Fluent, you just need to make sure to add the [vapor/fluent-sqlite](https://github.com/vapor/fluent-sqlite) package to your project. To do this, add the Fluent SQLite package to your Package manifest. @@ -27,7 +27,7 @@ let package = Package( ) ``` -If this is your first time adding a dependency, you should read our introduction to [Package.swift](../spm/manfiest.md). +If this is your first time adding a dependency, you should read our introduction to [Package.swift](../../getting-started/spm.md). Use `import FluentSQLite` to access SQLite's Fluent compatible APIs. diff --git a/3.0/docs/fluent/getting-started/package.md b/3.0/docs/fluent/getting-started/package.md index ee5ae778..529d7790 100644 --- a/3.0/docs/fluent/getting-started/package.md +++ b/3.0/docs/fluent/getting-started/package.md @@ -1,12 +1,12 @@ # Adding Fluent to your Project -Fluent ([vapor/fluent](https://github.com/vapor/fluent)) is a type-safe, fast, and easy-to-use ORM built for Swift. +Fluent ([vapor/fluent](https://github.com/vapor/fluent)) is a type-safe, fast, and easy-to-use ORM built for Swift. It takes advantage of Swift's strong type system to provide an elegant API for your database. ## Database -In addition to adding Fluent to your project, you must also add a Fluent compatible database. -Fluent does not include any databases by default. All official databases have a getting started guide similar to this one. +In addition to adding Fluent to your project, you must also add a Fluent compatible database. +Fluent does not include any databases by default. All official databases have a getting started guide similar to this one. | database | library | driver | guide | |------------|-------------------------|--------------------------|------------------------------------------------------------------| @@ -16,7 +16,7 @@ Fluent does not include any databases by default. All official databases have a | MongoDB | mongokitten/mongokitten | vapor/fluent-mongokitten | [README.md](http://github.com/vapor/fluent-mongokitten/readme.md)| !!! tip - Any database can be made to work with Fluent by conforming to its [Database](database-protocol.md) protocol. + Any database can be made to work with Fluent by conforming to its [Database](../database.md) protocol. For a list of all compatible database types, search GitHub for the [fluent-driver](https://github.com/topics/fluent-driver) topic. ## Fluent @@ -39,9 +39,9 @@ let package = Package( ) ``` -If this is your first time adding a dependency, you should read our introduction to [Package.swift](../spm/manfiest.md). +If this is your first time adding a dependency, you should read our introduction to [Package.swift](../../getting-started/spm.md). -!!! note +!!! note Use `import Fluent` to access Fluent's APIs. Once you have Fluent added to your project, you are ready to [configure your database(s)](provider.md). diff --git a/3.0/docs/fluent/getting-started/provider.md b/3.0/docs/fluent/getting-started/provider.md index 7ea83383..414445e2 100644 --- a/3.0/docs/fluent/getting-started/provider.md +++ b/3.0/docs/fluent/getting-started/provider.md @@ -1,6 +1,6 @@ # Configuring Fluent -Fluent integrates seamlessly into your Vapor project using [services](../getting-started/application.md#services). +Fluent integrates seamlessly into your Vapor project using [services](../getting-started/application.md#services). In this section we will add the Fluent service provider to your application and configure your databases. !!! warning @@ -18,21 +18,21 @@ import Fluent try services.register(FluentProvider()) ``` -Register the `FluentProvider` in the [configure section](../getting-started/structure.md#configure) of your application. +Register the `FluentProvider` in the [configure section](../../getting-started/structure.md#configure) of your application. !!! question - Learn more about how service providers work in [Getting Started: Application](../getting-started/application.md#providers) - and [Concepts: Services](../concepts/services.md#providers). + Learn more about how service providers work in [Getting Started: Application](../../getting-started/application.md#providers) + and [Concepts: Services](../../concepts/services.md#providers). ## Config -Once the service provider has been added, we can configure one or more databases +Once the service provider has been added, we can configure one or more databases to be used with Fluent. ### Identifier -Each database you use with Fluent must have a unique identifier. The easiest way to +Each database you use with Fluent must have a unique identifier. The easiest way to keep track of this identifier is to add it as static extension to `DatabaseIdentifier`. ```swift @@ -57,13 +57,13 @@ Now we can use the identifier anywhere in our project: req.database(.foo) { ... } ``` -The [configure section](../getting-started/structure.md#configure) of your project is a good place to put this extension. +The [configure section](../../getting-started/structure.md#configure) of your project is a good place to put this extension. ### Databases Now that we have created a unique identifier for our database, we can register it to our application using `DatabaseConfig`. A good place to do this is in the -[configure section](../getting-started/structure.md#configure) of your project. +[configure section](../../getting-started/structure.md#configure) of your project. You can add databases to the `DatabaseConfig` using either a type (`.self`) or an instance. @@ -87,7 +87,7 @@ services.register(databaseConfig) #### Instance -You can also register a pre-initialized database. This is especially useful if you'd +You can also register a pre-initialized database. This is especially useful if you'd like to configure two instances of the same database type. ```swift diff --git a/3.0/docs/getting-started/futures.md b/3.0/docs/getting-started/futures.md index 579d7512..88215e6e 100644 --- a/3.0/docs/getting-started/futures.md +++ b/3.0/docs/getting-started/futures.md @@ -1,13 +1,13 @@ # Futures -You may have noticed some APIs in Vapor expect or return a `Future` type. +You may have noticed some APIs in Vapor expect or return a `Future` type. If this is your first time hearing about futures, they might seem a little confusing at first. But don't worry, Vapor makes them easy to use. ## Callbacks Futures are a way of representing an object that you don't have yet. You have probably seen -APIs that use callbacks before (especially if you have developed iOS apps). Futures are just a +APIs that use callbacks before (especially if you have developed iOS apps). Futures are just a way to make APIs with callbacks more user-friendly. This is a what a common callback API looks like. @@ -27,7 +27,7 @@ print(users) // Future<[User]> ``` !!! info - Futures use fewer system resources than synchronous APIs. This is part of + Futures use fewer system resources than synchronous APIs. This is part of what makes Vapor so fast! ## Chaining @@ -77,5 +77,4 @@ four.do { four in ``` Learn more about Vapor's async architecture in [Concepts → Async](../concepts/async.md). -Or take a deeper look at Futures and Promises in [Async → Getting Started](../async/getting-started.md). - +Or take a deeper look at Futures and Promises in [Async → Getting Started](../async/futures.md). diff --git a/3.0/docs/getting-started/json.md b/3.0/docs/getting-started/json.md new file mode 100644 index 00000000..e69de29b diff --git a/3.0/docs/http/body-stream.md b/3.0/docs/http/body-stream.md new file mode 100644 index 00000000..e69de29b diff --git a/3.0/docs/http/request.md b/3.0/docs/http/request.md index 15f0d1d5..f573fb8e 100644 --- a/3.0/docs/http/request.md +++ b/3.0/docs/http/request.md @@ -1,8 +1,8 @@ # Request -When a client connects with an HTTP Server it sends a `Request`. This HTTP request will be processed [as discussed here](../getting-started/http.md) and resolved into a [`Response`](response.md). This is the response in the http [Request/Response model](../getting-started/http.md). +When a client connects with an HTTP Server it sends a `Request`. This HTTP request will be processed [as discussed here](../concepts/http.md) and resolved into a [`Response`](response.md). This is the response in the http [Request/Response model](../concepts/http.md). -Requests consist of a [Method](method.md), [URI](uri.md) and [Headers](../web/headers.md). +Requests consist of a [Method](method.md), [URI](uri.md) and [Headers](headers.md). Requests can optionally also contain a [Body](body.md). diff --git a/3.0/docs/http/responder.md b/3.0/docs/http/responder.md index ef31f295..fb8c2dc5 100644 --- a/3.0/docs/http/responder.md +++ b/3.0/docs/http/responder.md @@ -2,7 +2,7 @@ Responders are a type capable of [responding](response.md) to a [Request](request.md). -Responders are always [async](../async/promise-future-introduction.md) by returning a `Future` by either transforming/mapping an existing future or creating it's own promise. +Responders are always [async](../async/futures.md) by returning a `Future` by either transforming/mapping an existing future or creating it's own promise. ## Implementing a static Responder diff --git a/3.0/docs/http/response.md b/3.0/docs/http/response.md index e5383a85..7ef37e0c 100644 --- a/3.0/docs/http/response.md +++ b/3.0/docs/http/response.md @@ -1,8 +1,8 @@ # HTTP Response -When a client connects with an HTTP Server it sends a [`Request`](request.md). This HTTP request will be processed [as discussed here](../getting-started/http.md) and resolved into a `Response`. This is the response in the http Request/Response model. +When a client connects with an HTTP Server it sends a [`Request`](request.md). This HTTP request will be processed [as discussed here](../concepts/http.md) and resolved into a `Response`. This is the response in the http Request/Response model. -HTTP's Response object contains a [Status](status.md), [Headers](../web/headers.md) and a [Body](body.md). Before further reading this page, you must have read and understood the previous pages for Status, Headers and Body. +HTTP's Response object contains a [Status](status.md), [Headers](headers.md) and a [Body](body.md). Before further reading this page, you must have read and understood the previous pages for Status, Headers and Body. Responses are Extensible using the `extend` property. This allow storing additional data for use by integrating libraries. diff --git a/3.0/docs/redis/basics.md b/3.0/docs/redis/basics.md index 4f58b624..1172cf02 100644 --- a/3.0/docs/redis/basics.md +++ b/3.0/docs/redis/basics.md @@ -10,7 +10,7 @@ This requires a hostname, port and [worker](../async/worker.md). The worker's Di let client = try RedisClient.connect(worker: worker) // Future> ``` -The `connect` method will return a [Future](../async/promise-future-introduction.md) containing the TCP based Redis Client. +The `connect` method will return a [Future](../async/futures.md) containing the TCP based Redis Client. ## Redis Data Types diff --git a/3.0/docs/redis/custom-commands.md b/3.0/docs/redis/custom-commands.md index a6186ffa..b2b5b3a0 100644 --- a/3.0/docs/redis/custom-commands.md +++ b/3.0/docs/redis/custom-commands.md @@ -16,4 +16,4 @@ let future = client.run(command: "GET", arguments: ["my-key"]) // Future`](../vapor/response.md) conforming type. +The trailing closure receives a [Request](../http/request.md). The route can throw errors and needs to return a [`Future`](../http/response.md) conforming type. ## Registering a route using Vapor diff --git a/3.0/docs/routing/router.md b/3.0/docs/routing/router.md index 9a005e38..7fa1b554 100644 --- a/3.0/docs/routing/router.md +++ b/3.0/docs/routing/router.md @@ -6,17 +6,17 @@ Router is a protocol that you can conform your own routers to. First, create a [Route](route.md) using a [Method](../http/method.md), path and a responder. -The following example shows an [async route](async.md) with a constant path. +The following example shows a route with a constant path. ```swift let responder = BasicAsyncResponder { request in - return Future("Hello world") + return "Hello world" } let route = Route(method: .get, path: [.constant("hello"), .constant("world")], responder: responder) ``` -The following example shows a [synchronous route](sync.md) with a [Parameter](parameters.md): +The following example shows a with a [Parameter](parameters.md): ```swift let responder = BasicSyncResponder { request in diff --git a/3.0/docs/security/dos.md b/3.0/docs/security/dos.md index af44f4d7..a153b4b5 100644 --- a/3.0/docs/security/dos.md +++ b/3.0/docs/security/dos.md @@ -36,7 +36,7 @@ Both are (partially) prevented by default using the `PeerValidator` which is par ### How PeerValidator works -As part of Vapor 3's design goals, all notification-like I/O is implemented using [Streams](../async/streams-basics.md). This also includes the [TCP Server](../sockets/tcp-server.md). The TCP server is seen as a stream of clients/peers that are accepted and then sent to the client. It has a hook called `willAccept`. This closure's input is a `TCPClient`, and the output is a `Bool`. If the returned boolean is `true`, the peer will be accepted where `false` will deny the peer and will close the connection. +As part of Vapor 3's design goals, all notification-like I/O is implemented using [Streams](../async/streams.md). This also includes the [TCP Server](../sockets/tcp-server.md). The TCP server is seen as a stream of clients/peers that are accepted and then sent to the client. It has a hook called `willAccept`. This closure's input is a `TCPClient`, and the output is a `Bool`. If the returned boolean is `true`, the peer will be accepted where `false` will deny the peer and will close the connection. `PeerValidator` hooks into this capability by looking the peer's address up in it's cache and keeps track of the amount of connections this peer has currently opened to this server. If the counter exceeds a threshold as specified in the `PeerValidator` initializer, the connection will be rejected. diff --git a/3.0/docs/services/getting-started.md b/3.0/docs/services/getting-started.md index 7895848a..e384fe52 100644 --- a/3.0/docs/services/getting-started.md +++ b/3.0/docs/services/getting-started.md @@ -1,6 +1,6 @@ ## Configure -You configure the application in the [`configure.swift`](structure.md#configureswift) file. Here you can +You configure the application in the [`configure.swift`](structure.md) file. Here you can register your own services, or override the ones provided by default. ```swift @@ -21,4 +21,3 @@ let foo = try app.make(FooService.self) ``` ### Providers - diff --git a/3.0/docs/sockets/tcp-client.md b/3.0/docs/sockets/tcp-client.md index a7c1f531..a4a94127 100644 --- a/3.0/docs/sockets/tcp-client.md +++ b/3.0/docs/sockets/tcp-client.md @@ -34,7 +34,7 @@ let client = TCPClient(socket: socket, worker: worker) Now that your socket is connected you can start communicating. First, you'll need to start by setting up the handlers for incoming data. -Since `TCPClient` is a stream, you can use [the introduction](../async/streams-introduction.md) and [the basics](../async/streams-basics.md) of streams for reading the socket's output (incoming data). +Since `TCPClient` is a stream, you can use [the introduction](../async/streams.md) of streams for reading the socket's output (incoming data). Sending data is done through the `inputStream` function. diff --git a/3.0/docs/tls/client.md b/3.0/docs/tls/client.md new file mode 100644 index 00000000..e69de29b diff --git a/3.0/docs/tls/package.md b/3.0/docs/tls/package.md index 8b44a30d..aaef90b4 100644 --- a/3.0/docs/tls/package.md +++ b/3.0/docs/tls/package.md @@ -1,18 +1,10 @@ -# MySQL +# TLS -This package is a driver for the [MySQL Database](https://en.wikipedia.org/wiki/MySQL), an [RDBMS](https://en.wikipedia.org/wiki/Relational_database_management_system) oriented towards stability and robustness. - -This driver supports both MySQL and MariaDB. These two databases are almost identical towards the user. - -!!! warning - This documentation provides an overview for the MySQL API. - If you are using MySQL with Fluent, you will likely never need to use - this API. Use [Fluent's APIs](../fluent/overview.md) instead. +The TLS packages allow writing TLS/SSL clients and servers. ### Index -- [Setup](setup.md) -- [Basics](basics.md) +- [Client](client.md) ## Package.swift diff --git a/3.0/mkdocs.yml b/3.0/mkdocs.yml index 8ecc1925..67ce1b97 100644 --- a/3.0/mkdocs.yml +++ b/3.0/mkdocs.yml @@ -14,6 +14,7 @@ pages: - 'Folder Structure': 'getting-started/structure.md' - 'Application': 'getting-started/application.md' - 'Controllers': 'getting-started/controllers.md' + - 'JSON': 'getting-started/json.md' - 'Routing': 'getting-started/routing.md' - 'Content': 'getting-started/content.md' - 'Futures': 'getting-started/futures.md' @@ -26,9 +27,8 @@ pages: - 'HTTP': 'concepts/http.md' - 'Async': - 'Package': 'async/package.md' - - 'Future Basics': 'async/futures-basics.md' - - 'Streams Introduction': 'async/streams-introduction.md' - - 'Stream Basics': 'async/streams-basics.md' + - 'Futures': 'async/futures.md' + - 'Streams': 'async/streams.md' - 'Worker': 'async/worker.md' - 'HTTP': - 'Index': 'http/index.md' @@ -43,6 +43,7 @@ pages: - 'Response': 'http/response.md' - 'Responder': 'http/responder.md' - 'Status codes': 'http/status.md' + - 'Streaming Body': 'http/body-stream.md' - 'URI': 'http/uri.md' - 'Fluent': - 'Getting Started': @@ -67,6 +68,10 @@ pages: - 'Package': 'databases/mysql/package.md' - 'Setup': 'databases/mysql/setup.md' - 'Basics': 'databases/mysql/basics.md' + - 'PostgreSQL': + - 'Package': 'databases/postgres/package.md' + - 'Setup': 'databases/postgres/setup.md' + - 'Basics': 'databases/postgres/basics.md' - 'Redis': - 'Package': 'redis/package.md' - 'Basics': 'redis/basics.md' @@ -99,6 +104,7 @@ pages: - 'Random': 'crypto/random.md' - 'TLS': - 'Package': 'tls/package.md' + - 'Client': 'tls/client.md' - 'Sockets': - 'Package': 'sockets/package.md' - 'TCP Client': 'sockets/tcp-client.md' From 55105cb090160f21e4fa8fbc348c9c40cef16d1a Mon Sep 17 00:00:00 2001 From: Joannis Orlandos Date: Sun, 19 Nov 2017 14:10:18 +0100 Subject: [PATCH 3/5] more websocket docs --- 3.0/docs/http/client.md | 47 ++++++++++++++++++++++++++++++++- 3.0/docs/redis/basics.md | 4 +-- 3.0/docs/websocket/client.md | 5 +++- 3.0/docs/websocket/server.md | 1 - 3.0/docs/websocket/upgrade.md | 46 ++++++++++++++++++++++++++++++++ 3.0/docs/websocket/websocket.md | 4 ++- 3.0/mkdocs.yml | 1 - 7 files changed, 101 insertions(+), 7 deletions(-) delete mode 100644 3.0/docs/websocket/server.md diff --git a/3.0/docs/http/client.md b/3.0/docs/http/client.md index 1333ed77..6f78a56b 100644 --- a/3.0/docs/http/client.md +++ b/3.0/docs/http/client.md @@ -1 +1,46 @@ -TODO +# HTTP Client + +HTTP Clients are often used to communicate with external APIs such as PayPal, Stripe or Mailgun. + +## Connecting + +Connecting only requires a hostname and a boolean indicating if you want to use SSL. For almost every use case it is recommended to use SSL. If you're processing any sensitive data such as payments, emails and other personal data you will need to use SSL by setting it to `true`. + +HTTP clients require a [Worker](../async/worker.md), too, so it can run on the current [EventLoop](../concepts/async.md) + +```swift +// Future + let client = try HTTPClient.connect( + to: "example.com", + ssl: true, + worker: worker + ) +``` + +You can override the port by specifying a custom port using the following parameters: + +```swift +// Future + let client = try HTTPClient.connect( + to: "localhost", + port: 8080, + ssl: false, + worker: worker + ) +``` + +## Sending Requests + +From here, you can send [Requests](../http/request.md). You can only send one request at a time. Sending a request before a [Response](../http/response.md) has been received has unpredictable consequences. + +```swift +// Future +let response = client.flatMap { connectedClient in + let request = Request( + method: .get, + uri: "https://example.com/" + ) + + return connectedClient.send(request: request) +} +``` diff --git a/3.0/docs/redis/basics.md b/3.0/docs/redis/basics.md index 1172cf02..f4669284 100644 --- a/3.0/docs/redis/basics.md +++ b/3.0/docs/redis/basics.md @@ -7,7 +7,7 @@ To connect to Redis you can use a variety of methods. The Redis library primaril This requires a hostname, port and [worker](../async/worker.md). The worker's DispatchQueue will be used for Redis' Socket. The hostname and port have a default. The hostname is defaulted to `localhost`, and the port to Redis' default port `6379`. ```swift -let client = try RedisClient.connect(worker: worker) // Future> +let client = try RedisClient.connect(worker: worker) // Future ``` The `connect` method will return a [Future](../async/futures.md) containing the TCP based Redis Client. @@ -64,7 +64,7 @@ let array = RedisData.array([ ## CRUD using Redis -From here on it is assumed that your client has been successfully created and is available in the variable `client` as a `RedisClient`. +From here on it is assumed that your client has been successfully created and is available in the variable `client` as a `RedisClient`. ### Creating a record diff --git a/3.0/docs/websocket/client.md b/3.0/docs/websocket/client.md index d86cd136..026aaa5a 100644 --- a/3.0/docs/websocket/client.md +++ b/3.0/docs/websocket/client.md @@ -6,10 +6,13 @@ WebSocket clients work the same on the client side as the [server side](server.m WebSockets require an [URI](../http/uri.md) to connect to and a [Worker](../async/worker.md) to run on. +!!! warning + Vapor does not retain the WebSocket. It is the responsibility of the user to keep the WebSocket active by means of strong references and pings. + ```swift let worker: Worker = ... -let futureWebSocket: Future = try WebSocket.connect(to: "ws://localhost/path", queue: queue) +let futureWebSocket: Future = try WebSocket.connect(to: "ws://localhost/path", worker: worker) ``` ## Using websockets diff --git a/3.0/docs/websocket/server.md b/3.0/docs/websocket/server.md deleted file mode 100644 index 46409041..00000000 --- a/3.0/docs/websocket/server.md +++ /dev/null @@ -1 +0,0 @@ -# TODO diff --git a/3.0/docs/websocket/upgrade.md b/3.0/docs/websocket/upgrade.md index a8601713..a0216495 100644 --- a/3.0/docs/websocket/upgrade.md +++ b/3.0/docs/websocket/upgrade.md @@ -1,5 +1,51 @@ # WebSocket Upgrading +Servers can upgrade HTTP requests to a WebSocket if the client indicated an upgrade. + ## Determining an upgrade +You will need to `import WebSocket` for the upgrade functionality. + +Create a basic `GET` route. WebSockets always connect with a GET [method](../http/method.md). + +```swift +import WebSocket + +drop.get("api/v1/websocket") { req in + let shouldUpgrade = WebSocket.shouldUpgrade(for: req) +} +``` + ## Upgrading the connection + +The WebSocket library can generate an appropriate [Response](../http/response.md) for you. You can return this in your route. + +You will be able to set a handler inside `onUpgrade` in which a websocket will be returned after completion of the upgrade. + +!!! warning + Vapor does not retain the WebSocket. It is the responsibility of the user to keep the WebSocket active by means of strong references and pings. + +```swift +if shouldUpgrade { + let response = try WebSocket.upgradeResponse(for: req) + + response.onUpgrade = { client in + let websocket = WebSocket(client: client) + websocket.onText { text in + let rev = String(text.reversed()) + websocket.send(rev) + } + websocket.onBinary { buffer in + websocket.send(buffer) + } + } + + return response +} +``` + +## Using websockets + +WebSockets are interacted with using [binary streams](binary-stream.md) or [text streams](text-stream.md). + +All other information about websockets [is defined here.](websocket.md) diff --git a/3.0/docs/websocket/websocket.md b/3.0/docs/websocket/websocket.md index b55b17dd..50044d54 100644 --- a/3.0/docs/websocket/websocket.md +++ b/3.0/docs/websocket/websocket.md @@ -1,6 +1,8 @@ # WebSocket -WebSockets are a type of connection that can be instantiated by upgrading an existing HTTP/1 connection. They're used to dispatch notifications and communicate real-time binary and textual Data. +WebSockets are a type of connection that can be instantiated by upgrading an existing HTTP/1.1 connection. They're used to dispatch notifications and communicate real-time binary and textual Data. + +Vapor 3 supports both WebSocket [Client](client.md) and [Server](upgrade.md). ## Using websockets diff --git a/3.0/mkdocs.yml b/3.0/mkdocs.yml index 67ce1b97..1fd7d044 100644 --- a/3.0/mkdocs.yml +++ b/3.0/mkdocs.yml @@ -83,7 +83,6 @@ pages: - 'Binary': 'websocket/binary-stream.md' - 'Text': 'websocket/text-stream.md' - 'Client': 'websocket/client.md' - - 'Server': 'websocket/server.md' - 'Server Upgrades': 'websocket/upgrade.md' - 'Routing': - 'Basics': 'routing/basics.md' From 93e80654fbbb1fa5c22ad4e53900fd7ca7eace23 Mon Sep 17 00:00:00 2001 From: Joannis Orlandos Date: Mon, 20 Nov 2017 01:46:50 +0100 Subject: [PATCH 4/5] mysql binding docs --- 3.0/docs/databases/mysql/basics.md | 82 ++++++++++++------- 3.0/docs/databases/mysql/binding.md | 1 - .../databases/mysql/prepared-statements.md | 37 +++++++++ 3.0/docs/databases/mysql/setup.md | 6 ++ 3.0/mkdocs.yml | 1 + 5 files changed, 96 insertions(+), 31 deletions(-) delete mode 100644 3.0/docs/databases/mysql/binding.md create mode 100644 3.0/docs/databases/mysql/prepared-statements.md diff --git a/3.0/docs/databases/mysql/basics.md b/3.0/docs/databases/mysql/basics.md index 6e1387fb..b2a49fd8 100644 --- a/3.0/docs/databases/mysql/basics.md +++ b/3.0/docs/databases/mysql/basics.md @@ -6,16 +6,38 @@ This guide assumes you've set up MySQL and are connected to MySQL using a connec The MySQL driver is written to embrace type-safety and Codable. We currently *only* expose Codable based results until we've found a good design for the non-codable API. +## Retaining a connection + +Before you can send queries, you first need to retain a connection from the pool. + +You are *required* to return a [Future](../../async/futures.md) from within the closure. This closure's completion will be used to determine when the connection can be released back into the pool for the next request. + +```swift +// The result from within the closure + +// Future +let result = pool.retain { connection in + // query the connection + + return Future(Response(status: .ok)) +} +``` + +The future returned from the retain call will be equal to the future returned within the retain call. + +This way you can return the connection back into the pool after one or more successive operations and return the (final) result. + ## Queries Queries are any type conforming to the protocol `Query`, which requires being convertible to a `String`. `String` is a Query by default. -You can receive results from Queries in 3 kinds of formats. +You can receive results from Queries in 4 kinds of formats. -- Stream -- Future -- forEach +- Stream +- Future +- forEach +- Future All examples assume the following model: @@ -27,6 +49,26 @@ struct User { } ``` +### Futures + +Futures are often easier to use but significantly heavier on your memory and thus performance. [They are thoroughly described here](../../async/futures.md) + +Querying a database for a future is achieved through the `all` function and requires specifying the `Decodable` type that the results need to be deserialized into. + +```swift + // Future<[User]> +let users = connection.all(User.self, in: "SELECT * FROM users") +``` + +For partial results (`SELECT username, age FROM`) it is recommended to create a second decodable struct specifically for this query to ensure correctness and type-safety. + +```swift +struct UserLoginDetails: Decodable { + var username: String + var age: Int +} +``` + ### Streams Streams, [as described on this page](../../async/streams.md), are a source of information that calls a single reader's callback. Streams are best used in larger datasets to prevent the query from consuming a large amount of memory. The downside of a stream is that you cannot return all results in a single future. You'll need to stream the results to the other endpoint, too. For HTTP [this is described here.](../../http/body-stream.md) @@ -35,37 +77,17 @@ Querying a database for a stream of results is achieved through the `stream` fun ```swift // `ModelStream` -let usersStream = connectionPool.stream(User.self, in: "SELECT * FROM users") +let usersStream = connection.stream(User.self, in: "SELECT * FROM users") ``` This stream will return all results in the ModelStream's output callback which you can drain. You can register a callback on `usersStream.onClose` that will trigger when the end of the `ModelStream` has been reached. -### Futures - -Futures are often easier to use but significantly heavier on your memory and thus performance. [They are thoroughly described here](../../async/futures.md) - -Querying a database for a future is achieved through the `all` function and requires specifying the `Decodable` type that the results need to be deserialized into. - -```swift - // Future<[User]> -let users = connectionPool.all(User.self, in: "SELECT * FROM users") -``` - -For partial results (`SELECT username, age FROM`) it is recommended to create a second decodable struct specifically for this query to ensure correctness and type-safety. - -```swift -struct UserLoginDetails: Decodable { - var username: String - var age: Int -} -``` - ### ForEach If you don't need to stream complex results to a third party such as using an HTTP Response you can use `forEach`. This is particularly useful for asynchronous actions such as sending a lot of email to the results of a query without depending on the completion/success of one email for the next email. ```swift -connectionPool.forEach(User.self, in: "SELECT * FROM users") { user in +connection.forEach(User.self, in: "SELECT * FROM users") { user in print(user.username) } ``` @@ -73,7 +95,7 @@ connectionPool.forEach(User.self, in: "SELECT * FROM users") { user in `forEach` returns a future that you can optionally capture. It will be completed when all users have been processed. ```swift -let completed = connectionPool.forEach(User.self, in: "SELECT * FROM users") { user in +let completed = connection.forEach(User.self, in: "SELECT * FROM users") { user in print(user.username) } @@ -86,16 +108,16 @@ completed.then { Some queries (mostly administrative queries) do not require/return a response. Instead, they only indicate success or error. -You can execute these queries using the `query` command. +You can execute these queries using the `administrativeQuery` command. ```swift -connectionPool.query("DROP TABLE users") +connection.administrativeQuery("DROP TABLE users") ``` You can handle success or response using the returned future. ```swift -connectionPool.query("DROP TABLE users").then { +connection.administrativeQuery("DROP TABLE users").then { print("success") }.catch { print("failure") diff --git a/3.0/docs/databases/mysql/binding.md b/3.0/docs/databases/mysql/binding.md deleted file mode 100644 index 1333ed77..00000000 --- a/3.0/docs/databases/mysql/binding.md +++ /dev/null @@ -1 +0,0 @@ -TODO diff --git a/3.0/docs/databases/mysql/prepared-statements.md b/3.0/docs/databases/mysql/prepared-statements.md new file mode 100644 index 00000000..1f4e9a04 --- /dev/null +++ b/3.0/docs/databases/mysql/prepared-statements.md @@ -0,0 +1,37 @@ +# Prepared statements + +Preparing statements is important in many SQL operations to prevent SQL injection. + +You first have to set up your query to make use of statement binding. + +To design your query for preparation you must replace all user inputted values with a `?` such as the following statement: + +```sql +SELECT * FROM users WHERE username = ? +``` + +## Preparing a statement + +To prepare a statement from a query you call the `withPreparation` function on `Connection`. + +```swift +try connection.withPreparation(statement: "SELECT * FROM users WHERE username = ?") { statement in + // Bind +} +``` + +## Binding to a statement + +The statement can be bound by calling the `bind` function on `statement`. This will provide you with a temporary binding context. + +```swift +try statement.bind { binding in + try binding.bind("ExampleUser") +} +``` + +Bindings will throw an error if the inputted value did not meet the query's required type. + +## Reading the query's results + +You can then use the [Future or Streaming query functions as described in the basics](basics.md) to receive the queried results from the prepared and bound `statement` object. diff --git a/3.0/docs/databases/mysql/setup.md b/3.0/docs/databases/mysql/setup.md index 253b9509..b21178d8 100644 --- a/3.0/docs/databases/mysql/setup.md +++ b/3.0/docs/databases/mysql/setup.md @@ -34,6 +34,12 @@ The `worker` is defined in [the async documentation](../../async/worker.md). let connectionPool = ConnectionPool(hostname: "localhost", user: "root", password: nil, database: "test-db", worker: worker) ``` +You can set the maximum amount of connections after creating the `ConnectionPool`. + +```swift +connectionPool.maxConnections = 50 +``` + Creating a connection pool successfully does not imply that the configuration is correct. The (first) query's success or failure will indicate the successful or unsuccessful connection. This way the API stays much simpler than it would otherwise be. [Learn how you can execute queries here](basics.md) diff --git a/3.0/mkdocs.yml b/3.0/mkdocs.yml index 1fd7d044..80976fbc 100644 --- a/3.0/mkdocs.yml +++ b/3.0/mkdocs.yml @@ -68,6 +68,7 @@ pages: - 'Package': 'databases/mysql/package.md' - 'Setup': 'databases/mysql/setup.md' - 'Basics': 'databases/mysql/basics.md' + - 'Prepared Statements': 'databases/mysql/prepared-statements.md' - 'PostgreSQL': - 'Package': 'databases/postgres/package.md' - 'Setup': 'databases/postgres/setup.md' From d5820551f2b4844b538a335419839fb15ed650b3 Mon Sep 17 00:00:00 2001 From: Joannis Orlandos Date: Mon, 20 Nov 2017 02:00:02 +0100 Subject: [PATCH 5/5] add SPM overview references --- 3.0/docs/databases/mysql/package.md | 57 ++++++++++++++++++++++- 3.0/docs/databases/sqlite/package.md | 4 +- 3.0/docs/http/{index.md => package.md} | 2 + 3.0/docs/jwt/package.md | 2 + 3.0/docs/redis/package.md | 2 + 3.0/docs/routing/{index.md => package.md} | 5 +- 3.0/docs/sockets/package.md | 2 + 3.0/docs/tls/package.md | 2 + 3.0/mkdocs.yml | 3 +- 9 files changed, 73 insertions(+), 6 deletions(-) rename 3.0/docs/http/{index.md => package.md} (87%) rename 3.0/docs/routing/{index.md => package.md} (83%) diff --git a/3.0/docs/databases/mysql/package.md b/3.0/docs/databases/mysql/package.md index b5c6dbeb..1f581fd6 100644 --- a/3.0/docs/databases/mysql/package.md +++ b/3.0/docs/databases/mysql/package.md @@ -1,3 +1,56 @@ -# MySQL Package +# Using MySQL -Coming soon. \ No newline at end of file +The [vapor/mysql](https://github.com/vapor/mysql) package is a lightweight, async pure swift MySQL/MariaDB driver. It provides an intuitive Swift interface for working with MySQL that can be used with any Swift project. + + + + + +## Just MySQL + +This package was built to be a powerful interface for MySQL. To include this MySQL package in your project, simply add it to your Package manifest. + +```swift +// swift-tools-version:4.0 +import PackageDescription + +let package = Package( + name: "Project", + dependencies: [ + ... + .package(url: "https://github.com/vapor/mysql.git", .upToNextMajor(from: "3.0.0")), + ], + targets: [ + .target(name: "Project", dependencies: ["MySQL", ... ]) + ] +) +``` + +If this is your first time adding a dependency, you should read our introduction to [Package.swift](../../getting-started/spm.md). + +Use `import SQLite` to access the Swift SQLite APIs. diff --git a/3.0/docs/databases/sqlite/package.md b/3.0/docs/databases/sqlite/package.md index e148e101..5e083f6f 100644 --- a/3.0/docs/databases/sqlite/package.md +++ b/3.0/docs/databases/sqlite/package.md @@ -46,9 +46,11 @@ let package = Package( .package(url: "https://github.com/vapor/sqlite.git", .upToNextMajor(from: "3.0.0")), ], targets: [ - .target(name: "Project", dependencies: ["Async", ... ]) + .target(name: "Project", dependencies: ["SQLite", ... ]) ] ) ``` +If this is your first time adding a dependency, you should read our introduction to [Package.swift](../../getting-started/spm.md). + Use `import SQLite` to access the Swift SQLite APIs. diff --git a/3.0/docs/http/index.md b/3.0/docs/http/package.md similarity index 87% rename from 3.0/docs/http/index.md rename to 3.0/docs/http/package.md index 47c45076..b4f4a603 100644 --- a/3.0/docs/http/index.md +++ b/3.0/docs/http/package.md @@ -43,4 +43,6 @@ let package = Package( ) ``` +If this is your first time adding a dependency, you should read our introduction to [Package.swift](../getting-started/spm.md). + Use `import HTTP` to access HTTP's APIs. diff --git a/3.0/docs/jwt/package.md b/3.0/docs/jwt/package.md index e59bdc66..d5fdd52f 100644 --- a/3.0/docs/jwt/package.md +++ b/3.0/docs/jwt/package.md @@ -44,4 +44,6 @@ let package = Package( ) ``` +If this is your first time adding a dependency, you should read our introduction to [Package.swift](../getting-started/spm.md). + Use `import JWT` to access JSON Web Token's APIs. diff --git a/3.0/docs/redis/package.md b/3.0/docs/redis/package.md index 71f22352..95d527a4 100644 --- a/3.0/docs/redis/package.md +++ b/3.0/docs/redis/package.md @@ -35,4 +35,6 @@ let package = Package( ) ``` +If this is your first time adding a dependency, you should read our introduction to [Package.swift](../getting-started/spm.md). + Use `import Redis` to access Redis' APIs. diff --git a/3.0/docs/routing/index.md b/3.0/docs/routing/package.md similarity index 83% rename from 3.0/docs/routing/index.md rename to 3.0/docs/routing/package.md index ced5470d..6dd8256b 100644 --- a/3.0/docs/routing/index.md +++ b/3.0/docs/routing/package.md @@ -4,8 +4,7 @@ Routing is a library containing all Routing related APIs. ### Index -- [Aync routing](async.md) -- [Sync routing](sync.md) +- [Basics](basics.md) - [Route Parameters](parameters.md) - [Route](route.md) - [TrieRouter](router.md) @@ -38,4 +37,6 @@ let package = Package( ) ``` +If this is your first time adding a dependency, you should read our introduction to [Package.swift](../getting-started/spm.md). + Use `import Routing` to access Routing's APIs. diff --git a/3.0/docs/sockets/package.md b/3.0/docs/sockets/package.md index e65297d5..6c472943 100644 --- a/3.0/docs/sockets/package.md +++ b/3.0/docs/sockets/package.md @@ -36,4 +36,6 @@ let package = Package( ) ``` +If this is your first time adding a dependency, you should read our introduction to [Package.swift](../getting-started/spm.md). + Use `import Sockets` to access Sockets's APIs. diff --git a/3.0/docs/tls/package.md b/3.0/docs/tls/package.md index aaef90b4..817ee204 100644 --- a/3.0/docs/tls/package.md +++ b/3.0/docs/tls/package.md @@ -26,4 +26,6 @@ let package = Package( ) ``` +If this is your first time adding a dependency, you should read our introduction to [Package.swift](../getting-started/spm.md). + Use `import MySQL` to access MySQL' APIs. diff --git a/3.0/mkdocs.yml b/3.0/mkdocs.yml index 80976fbc..a2e7c6ba 100644 --- a/3.0/mkdocs.yml +++ b/3.0/mkdocs.yml @@ -31,7 +31,7 @@ pages: - 'Streams': 'async/streams.md' - 'Worker': 'async/worker.md' - 'HTTP': - - 'Index': 'http/index.md' + - 'Package': 'http/package.md' - 'Body': 'http/body.md' - 'Client': 'http/client.md' - 'Cookies': 'http/cookies.md' @@ -86,6 +86,7 @@ pages: - 'Client': 'websocket/client.md' - 'Server Upgrades': 'websocket/upgrade.md' - 'Routing': + - 'Package': 'routing/package.md' - 'Basics': 'routing/basics.md' - 'Parameters': 'routing/parameters.md' - 'Route': 'routing/route.md'