diff --git a/build/3.0/databases/mongodb/basics/index.html b/build/3.0/databases/mongodb/basics/index.html index c0f4d26f..802fa0ef 100644 --- a/build/3.0/databases/mongodb/basics/index.html +++ b/build/3.0/databases/mongodb/basics/index.html @@ -994,46 +994,6 @@ -
  • - - CRUD - - - - -
  • - -
  • - - Troubleshooting - - - - -
  • - @@ -1501,46 +1461,6 @@ -
  • - - CRUD - - - - -
  • - -
  • - - Troubleshooting - - - - -
  • - @@ -1586,24 +1506,137 @@ The database will also need access to your w -

    CRUD

    +

    CRUD

    Before applying a CRUD operation you need to select a Collection first. This is the MongoDB equivalent of a table.

    You can subscript a database with a string to get a collection with that name. You do not need to set up a schema first.

    router.get("users") { request in
         let database = try request.make(Database.self)
    -    let users = database["users"]
    +    let users = database["users"] // Collection<Document>
     
         ...
     }
     
    -

    Creating

    -
    
    +

    Subscripting will give you a Collection<Document>. If you want to read the collection as a different (Codable) type you'll need to map the collection to a different type.

    +
    struct User: Codable {
    +    var _id = ObjectId()
    +    var username: String
    +    var age: Int
    +
    +    init(named name: String, age: Int) {
    +        self.username = named
    +        self.age = age
    +    }
    +}
    +
    +let users = database["users"].map(to: User.self)
     
    -

    Troubleshooting

    +

    CRUD operations

    +

    Insert

    +

    Inserting entities is done by using .insert with either one or a sequence of the entity.

    +
    users.insert(user)
    +users.insertAll([user1, user2, user3])
    +
    + + +

    Insert returns a Future<Reply.Insert> which you can use to determine if one (or more) inserts failed. Is reply.ok != 1 the future will not be completed but failed with the reply, instead.

    +
    let reply = users.insertAll([user1, user2, user3])
    +
    +reply.do { success in
    +    print("\(success.n) users inserted")
    +}.catch { error in
    +    // Insert failed!
    +}
    +
    + + +

    Find

    +

    Using find on a collection returns a Cursor<C> where C is the type nested in the collection as demonstrated above.

    +

    find can take 4 arguments. A filter, range, sort and projection.

    +

    There is also findOne, which is useful when you want to exactly one object. This does not support a range expression.

    +

    Filter

    +

    The filter is a MongoKitten Query object.

    +
    // User?
    +guard let joannis = users.findOne("username" == "Joannis" && "active" == true) else {
    +    return
    +}
    +
    + + +

    Range

    +

    A range is any native swift Range. It is used for skip and limit.

    +
    // Cursor with the first 10 users
    +
    +let users1 = users.find(in: ..<10) // Cursor<User>
    +let users2 = users.find(in: 10..<20) // Cursor<User>
    +let users3 = users.find(in: 30...) // Cursor<User>
    +
    + + +

    Sort

    +

    The Sort is used to sort entities in either ascending or descending order based on one or more fields.

    +
    let maleUsers = users.find("gender" == "male", sortedBy: ["age": .ascending]) // Cursor<User>
    +
    + + +

    Projection

    +

    The projection ensures only the specifies subset of fields are returned. Projections can also be computed fields. Remember that the projection must fit within the Collection's Codable type. If this is not a dictionary(-like) type such as Document it will be required that the Document fetched matches the Codable type.

    +
    let adults = users.find("age" >= 21, projecting: ["_id": .excluded, "username": .included]) // Cursor<User>
    +
    + + +

    Count

    +

    Count is similar to a find operation in that it is a read-only operation. Since it does not return the actual data (only an integer of the total entities found) it does not support projections and sorting. It does support a range expression, although it's not often used.

    +
    let totalUsers = users.count() // Future<Int>
    +let femaleUsers = users.count("gender" == "female") // Future<Int>
    +
    + + +

    Update

    +

    Updating can be done on one or all entities. Updates can either update the entire entity or only a subset of fields.

    +
    let user = User(named: "Joannis", age: 111)
    +
    +users.update("username" == "Joannis", to: user)
    +
    + + +

    Update also indicates a success state in the same way insert does.

    +

    Updating fields

    +

    Updating a subset of fields can be done more efficiently using

    +
    // Rename `Joannis` -> `JoannisO`
    +users.update("username" == "Joannis", fields: [
    +    "username": "JoannisO"
    +])
    +
    +// Migrate all users to require a password update
    +users.update(fields: [
    +    "resetPassword": true
    +])
    +
    + + +

    Upsert

    +

    If you don't know if an entity exists but want it inserted/updated accordingly you should use upsert.

    +
    let user = User(named: "Joannis", age: 111)
    +
    +users.upsert("_id" == user._id, to: user)
    +
    + + +

    Remove

    +

    Remove removes the first or all entities matching a query.

    +
    // Remove me!
    +users.remove("username" == "Joannis")
    +
    +// Remove all Dutch users
    +users.removeAll("country" == "NL")
    +
    + + +

    Troubleshooting

    Ambiguous naming

    In some situations you may find that MongoKitten's Database or ClientSettings are ambiguous with another library. The following lines will help get rid of that.

    typealias MongoDB = MongoKitten.Database
    diff --git a/build/3.0/search/search_index.json b/build/3.0/search/search_index.json
    index 1361e76b..5b2beb0f 100644
    --- a/build/3.0/search/search_index.json
    +++ b/build/3.0/search/search_index.json
    @@ -1627,7 +1627,7 @@
             },
             {
                 "location": "/databases/mongodb/basics/",
    -            "text": "MongoDB Basics\n\u00b6\n\n\nimport MongoKitten\n in your file(s) using MongoKitten. This will automatically also import the \nBSON\n APIs.\n\n\nConnecting to MongoDB\n\u00b6\n\n\nTo connect to MongoDB you need to provide your connection URI and the database you want to access.\nThe database will also need access to your \nworker\n.\n\n\nlet\n \ndatabase\n \n=\n \nDatabase\n.\nconnect\n(\nserver\n:\n \n\"mongodb://localhost:27017\"\n,\n \ndatabase\n:\n \n\"test-db\"\n,\n \nworker\n:\n \nworker\n)\n \n// Future\n\n\n\n\n\n\nYou cannot use the connection globally. If you plan to use this connection globally you'll need to use a lock on it for querying since the connection is not thread safe. This will be added in a later stage of MongoKitten 5's beta.\n\n\nConnection as a Service\n\u00b6\n\n\nIf you want to use the connection within a Vapor 3 route you'll need to register this connection to your \nServices\n.\n\n\nservices\n.\nregister\n \n{\n \ncontainer\n \n->\n \nClientSettings\n \nin\n\n    \nreturn\n \n\"mongodb://localhost:27017\"\n \n// Your MongoDB URI here\n\n\n}\n\n\n\nservices\n.\nregister\n \n{\n \ncontainer\n \n->\n \nDatabase\n \nin\n\n    \nlet\n \nserver\n \n=\n \ntry\n \ncontainer\n.\nmake\n(\nClientSettings\n.\nself\n,\n \nfor\n:\n \nDatabase\n.\nself\n)\n\n\n    \n// Future\n\n    \nlet\n \ndb\n \n=\n \ntry\n \nDatabase\n.\nconnect\n(\nsever\n:\n \nserver\n,\n \ndatabase\n:\n \n\"my-database\"\n,\n \nworker\n:\n \ncontainer\n)\n\n\n    \n// Await here, it doesn't hurt performant much and it only needs to wait once per worker\n\n    \nreturn\n \ntry\n \ndb\n.\nawait\n(\non\n:\n \ncontainer\n)\n\n\n}\n\n\n\n\n\n\nCRUD\n\u00b6\n\n\nBefore applying a CRUD operation you need to select a Collection first. This is the MongoDB equivalent of a table.\n\n\nYou can subscript a database with a string to get a collection with that name. You do not need to set up a schema first.\n\n\nrouter\n.\nget\n(\n\"users\"\n)\n \n{\n \nrequest\n \nin\n\n    \nlet\n \ndatabase\n \n=\n \ntry\n \nrequest\n.\nmake\n(\nDatabase\n.\nself\n)\n\n    \nlet\n \nusers\n \n=\n \ndatabase\n[\n\"users\"\n]\n\n\n    \n...\n\n\n}\n\n\n\n\n\n\nCreating\n\u00b6\n\n\n\n\n\n\n\n\nTroubleshooting\n\u00b6\n\n\nAmbiguous naming\n\u00b6\n\n\nIn some situations you may find that MongoKitten's \nDatabase\n or \nClientSettings\n are ambiguous with another library. The following lines will help get rid of that.\n\n\ntypealias\n \nMongoDB\n \n=\n \nMongoKitten\n.\nDatabase\n\n\ntypealias\n \nMongoDBSettings\n \n=\n \nMongoKitten\n.\nClientSettings\n\n\n\n\n\n\nIn the above case you'll have to use the aliases instead of the normal references to \nDatabase\n and/or \nClientSettings\n. Alternatively you can prefix the occurences of those instances with \nMongoKitten.\n, indicating you want the \nDatabase\n object of the MongoKitten module.",
    +            "text": "MongoDB Basics\n\u00b6\n\n\nimport MongoKitten\n in your file(s) using MongoKitten. This will automatically also import the \nBSON\n APIs.\n\n\nConnecting to MongoDB\n\u00b6\n\n\nTo connect to MongoDB you need to provide your connection URI and the database you want to access.\nThe database will also need access to your \nworker\n.\n\n\nlet\n \ndatabase\n \n=\n \nDatabase\n.\nconnect\n(\nserver\n:\n \n\"mongodb://localhost:27017\"\n,\n \ndatabase\n:\n \n\"test-db\"\n,\n \nworker\n:\n \nworker\n)\n \n// Future\n\n\n\n\n\n\nYou cannot use the connection globally. If you plan to use this connection globally you'll need to use a lock on it for querying since the connection is not thread safe. This will be added in a later stage of MongoKitten 5's beta.\n\n\nConnection as a Service\n\u00b6\n\n\nIf you want to use the connection within a Vapor 3 route you'll need to register this connection to your \nServices\n.\n\n\nservices\n.\nregister\n \n{\n \ncontainer\n \n->\n \nClientSettings\n \nin\n\n    \nreturn\n \n\"mongodb://localhost:27017\"\n \n// Your MongoDB URI here\n\n\n}\n\n\n\nservices\n.\nregister\n \n{\n \ncontainer\n \n->\n \nDatabase\n \nin\n\n    \nlet\n \nserver\n \n=\n \ntry\n \ncontainer\n.\nmake\n(\nClientSettings\n.\nself\n,\n \nfor\n:\n \nDatabase\n.\nself\n)\n\n\n    \n// Future\n\n    \nlet\n \ndb\n \n=\n \ntry\n \nDatabase\n.\nconnect\n(\nsever\n:\n \nserver\n,\n \ndatabase\n:\n \n\"my-database\"\n,\n \nworker\n:\n \ncontainer\n)\n\n\n    \n// Await here, it doesn't hurt performant much and it only needs to wait once per worker\n\n    \nreturn\n \ntry\n \ndb\n.\nawait\n(\non\n:\n \ncontainer\n)\n\n\n}\n\n\n\n\n\n\nCRUD\n\u00b6\n\n\nBefore applying a CRUD operation you need to select a Collection first. This is the MongoDB equivalent of a table.\n\n\nYou can subscript a database with a string to get a collection with that name. You do not need to set up a schema first.\n\n\nrouter\n.\nget\n(\n\"users\"\n)\n \n{\n \nrequest\n \nin\n\n    \nlet\n \ndatabase\n \n=\n \ntry\n \nrequest\n.\nmake\n(\nDatabase\n.\nself\n)\n\n    \nlet\n \nusers\n \n=\n \ndatabase\n[\n\"users\"\n]\n \n// Collection\n\n\n    \n...\n\n\n}\n\n\n\n\n\n\nSubscripting will give you a \nCollection\n. If you want to read the collection as a different (Codable) type you'll need to \nmap\n the collection to a different type.\n\n\nstruct\n \nUser\n:\n \nCodable\n \n{\n\n    \nvar\n \n_id\n \n=\n \nObjectId\n()\n\n    \nvar\n \nusername\n:\n \nString\n\n    \nvar\n \nage\n:\n \nInt\n\n\n    \ninit\n(\nnamed\n \nname\n:\n \nString\n,\n \nage\n:\n \nInt\n)\n \n{\n\n        \nself\n.\nusername\n \n=\n \nnamed\n\n        \nself\n.\nage\n \n=\n \nage\n\n    \n}\n\n\n}\n\n\n\nlet\n \nusers\n \n=\n \ndatabase\n[\n\"users\"\n].\nmap\n(\nto\n:\n \nUser\n.\nself\n)\n\n\n\n\n\n\nCRUD operations\n\n\nInsert\n\u00b6\n\n\nInserting entities is done by using \n.insert\n with either one or a sequence of the entity.\n\n\nusers\n.\ninsert\n(\nuser\n)\n\n\nusers\n.\ninsertAll\n([\nuser1\n,\n \nuser2\n,\n \nuser3\n])\n\n\n\n\n\n\nInsert returns a \nFuture\n which you can use to determine if one (or more) inserts failed. Is \nreply.ok != 1\n the future will not be completed but failed with the reply, instead.\n\n\nlet\n \nreply\n \n=\n \nusers\n.\ninsertAll\n([\nuser1\n,\n \nuser2\n,\n \nuser3\n])\n\n\n\nreply\n.\ndo\n \n{\n \nsuccess\n \nin\n\n    \nprint\n(\n\"\n\\(\nsuccess\n.\nn\n)\n users inserted\"\n)\n\n\n}.\ncatch\n \n{\n \nerror\n \nin\n\n    \n// Insert failed!\n\n\n}\n\n\n\n\n\n\nFind\n\u00b6\n\n\nUsing \nfind\n on a collection returns a \nCursor\n where \nC\n is the type nested in the collection as demonstrated above.\n\n\nfind\n can take 4 arguments. A filter, range, sort and projection.\n\n\nThere is also \nfindOne\n, which is useful when you want to exactly one object. This does not support a range expression.\n\n\nFilter\n\u00b6\n\n\nThe filter is a MongoKitten \nQuery\n object.\n\n\n// User?\n\n\nguard\n \nlet\n \njoannis\n \n=\n \nusers\n.\nfindOne\n(\n\"username\"\n \n==\n \n\"Joannis\"\n \n&&\n \n\"active\"\n \n==\n \ntrue\n)\n \nelse\n \n{\n\n    \nreturn\n\n\n}\n\n\n\n\n\n\nRange\n\u00b6\n\n\nA range is any native swift Range. It is used for \nskip\n and \nlimit\n.\n\n\n// Cursor with the first 10 users\n\n\n\nlet\n \nusers1\n \n=\n \nusers\n.\nfind\n(\nin\n:\n \n..<\n10\n)\n \n// Cursor\n\n\nlet\n \nusers2\n \n=\n \nusers\n.\nfind\n(\nin\n:\n \n10.\n.<\n20\n)\n \n// Cursor\n\n\nlet\n \nusers3\n \n=\n \nusers\n.\nfind\n(\nin\n:\n \n30.\n..)\n \n// Cursor\n\n\n\n\n\n\nSort\n\u00b6\n\n\nThe \nSort\n is used to sort entities in either ascending or descending order based on one or more fields.\n\n\nlet\n \nmaleUsers\n \n=\n \nusers\n.\nfind\n(\n\"gender\"\n \n==\n \n\"male\"\n,\n \nsortedBy\n:\n \n[\n\"age\"\n:\n \n.\nascending\n])\n \n// Cursor\n\n\n\n\n\n\nProjection\n\u00b6\n\n\nThe \nprojection\n ensures only the specifies subset of fields are returned. Projections can also be computed fields. Remember that the projection must fit within the Collection's Codable type. If this is not a dictionary(-like) type such as \nDocument\n it will be required that the Document fetched matches the Codable type.\n\n\nlet\n \nadults\n \n=\n \nusers\n.\nfind\n(\n\"age\"\n \n>=\n \n21\n,\n \nprojecting\n:\n \n[\n\"_id\"\n:\n \n.\nexcluded\n,\n \n\"username\"\n:\n \n.\nincluded\n])\n \n// Cursor\n\n\n\n\n\n\nCount\n\u00b6\n\n\nCount is similar to a find operation in that it is a read-only operation. Since it does not return the actual data (only an integer of the total entities found) it does not support projections and sorting. It does support a range expression, although it's not often used.\n\n\nlet\n \ntotalUsers\n \n=\n \nusers\n.\ncount\n()\n \n// Future\n\n\nlet\n \nfemaleUsers\n \n=\n \nusers\n.\ncount\n(\n\"gender\"\n \n==\n \n\"female\"\n)\n \n// Future\n\n\n\n\n\n\nUpdate\n\u00b6\n\n\nUpdating can be done on one or all entities. Updates can either update the entire entity or only a subset of fields.\n\n\nlet\n \nuser\n \n=\n \nUser\n(\nnamed\n:\n \n\"Joannis\"\n,\n \nage\n:\n \n111\n)\n\n\n\nusers\n.\nupdate\n(\n\"username\"\n \n==\n \n\"Joannis\"\n,\n \nto\n:\n \nuser\n)\n\n\n\n\n\n\nUpdate also indicates a success state in the same way \ninsert\n does.\n\n\nUpdating fields\n\u00b6\n\n\nUpdating a subset of fields can be done more efficiently using\n\n\n// Rename `Joannis` -> `JoannisO`\n\n\nusers\n.\nupdate\n(\n\"username\"\n \n==\n \n\"Joannis\"\n,\n \nfields\n:\n \n[\n\n    \n\"username\"\n:\n \n\"JoannisO\"\n\n\n])\n\n\n\n// Migrate all users to require a password update\n\n\nusers\n.\nupdate\n(\nfields\n:\n \n[\n\n    \n\"resetPassword\"\n:\n \ntrue\n\n\n])\n\n\n\n\n\n\nUpsert\n\u00b6\n\n\nIf you don't know if an entity exists but want it inserted/updated accordingly you should use \nupsert\n.\n\n\nlet\n \nuser\n \n=\n \nUser\n(\nnamed\n:\n \n\"Joannis\"\n,\n \nage\n:\n \n111\n)\n\n\n\nusers\n.\nupsert\n(\n\"_id\"\n \n==\n \nuser\n.\n_id\n,\n \nto\n:\n \nuser\n)\n\n\n\n\n\n\nRemove\n\u00b6\n\n\nRemove removes the first or all entities matching a query.\n\n\n// Remove me!\n\n\nusers\n.\nremove\n(\n\"username\"\n \n==\n \n\"Joannis\"\n)\n\n\n\n// Remove all Dutch users\n\n\nusers\n.\nremoveAll\n(\n\"country\"\n \n==\n \n\"NL\"\n)\n\n\n\n\n\n\nTroubleshooting\n\u00b6\n\n\nAmbiguous naming\n\u00b6\n\n\nIn some situations you may find that MongoKitten's \nDatabase\n or \nClientSettings\n are ambiguous with another library. The following lines will help get rid of that.\n\n\ntypealias\n \nMongoDB\n \n=\n \nMongoKitten\n.\nDatabase\n\n\ntypealias\n \nMongoDBSettings\n \n=\n \nMongoKitten\n.\nClientSettings\n\n\n\n\n\n\nIn the above case you'll have to use the aliases instead of the normal references to \nDatabase\n and/or \nClientSettings\n. Alternatively you can prefix the occurences of those instances with \nMongoKitten.\n, indicating you want the \nDatabase\n object of the MongoKitten module.",
                 "title": "Basics"
             },
             {
    @@ -1647,13 +1647,63 @@
             },
             {
                 "location": "/databases/mongodb/basics/#crud",
    -            "text": "Before applying a CRUD operation you need to select a Collection first. This is the MongoDB equivalent of a table.  You can subscript a database with a string to get a collection with that name. You do not need to set up a schema first.  router . get ( \"users\" )   {   request   in \n     let   database   =   try   request . make ( Database . self ) \n     let   users   =   database [ \"users\" ] \n\n     ...  }",
    +            "text": "Before applying a CRUD operation you need to select a Collection first. This is the MongoDB equivalent of a table.  You can subscript a database with a string to get a collection with that name. You do not need to set up a schema first.  router . get ( \"users\" )   {   request   in \n     let   database   =   try   request . make ( Database . self ) \n     let   users   =   database [ \"users\" ]   // Collection \n\n     ...  }   Subscripting will give you a  Collection . If you want to read the collection as a different (Codable) type you'll need to  map  the collection to a different type.  struct   User :   Codable   { \n     var   _id   =   ObjectId () \n     var   username :   String \n     var   age :   Int \n\n     init ( named   name :   String ,   age :   Int )   { \n         self . username   =   named \n         self . age   =   age \n     }  }  let   users   =   database [ \"users\" ]. map ( to :   User . self )   CRUD operations",
                 "title": "CRUD"
             },
             {
    -            "location": "/databases/mongodb/basics/#creating",
    -            "text": "",
    -            "title": "Creating"
    +            "location": "/databases/mongodb/basics/#insert",
    +            "text": "Inserting entities is done by using  .insert  with either one or a sequence of the entity.  users . insert ( user )  users . insertAll ([ user1 ,   user2 ,   user3 ])   Insert returns a  Future  which you can use to determine if one (or more) inserts failed. Is  reply.ok != 1  the future will not be completed but failed with the reply, instead.  let   reply   =   users . insertAll ([ user1 ,   user2 ,   user3 ])  reply . do   {   success   in \n     print ( \" \\( success . n )  users inserted\" )  }. catch   {   error   in \n     // Insert failed!  }",
    +            "title": "Insert"
    +        },
    +        {
    +            "location": "/databases/mongodb/basics/#find",
    +            "text": "Using  find  on a collection returns a  Cursor  where  C  is the type nested in the collection as demonstrated above.  find  can take 4 arguments. A filter, range, sort and projection.  There is also  findOne , which is useful when you want to exactly one object. This does not support a range expression.",
    +            "title": "Find"
    +        },
    +        {
    +            "location": "/databases/mongodb/basics/#filter",
    +            "text": "The filter is a MongoKitten  Query  object.  // User?  guard   let   joannis   =   users . findOne ( \"username\"   ==   \"Joannis\"   &&   \"active\"   ==   true )   else   { \n     return  }",
    +            "title": "Filter"
    +        },
    +        {
    +            "location": "/databases/mongodb/basics/#range",
    +            "text": "A range is any native swift Range. It is used for  skip  and  limit .  // Cursor with the first 10 users  let   users1   =   users . find ( in :   ..< 10 )   // Cursor  let   users2   =   users . find ( in :   10. .< 20 )   // Cursor  let   users3   =   users . find ( in :   30. ..)   // Cursor",
    +            "title": "Range"
    +        },
    +        {
    +            "location": "/databases/mongodb/basics/#sort",
    +            "text": "The  Sort  is used to sort entities in either ascending or descending order based on one or more fields.  let   maleUsers   =   users . find ( \"gender\"   ==   \"male\" ,   sortedBy :   [ \"age\" :   . ascending ])   // Cursor",
    +            "title": "Sort"
    +        },
    +        {
    +            "location": "/databases/mongodb/basics/#projection",
    +            "text": "The  projection  ensures only the specifies subset of fields are returned. Projections can also be computed fields. Remember that the projection must fit within the Collection's Codable type. If this is not a dictionary(-like) type such as  Document  it will be required that the Document fetched matches the Codable type.  let   adults   =   users . find ( \"age\"   >=   21 ,   projecting :   [ \"_id\" :   . excluded ,   \"username\" :   . included ])   // Cursor",
    +            "title": "Projection"
    +        },
    +        {
    +            "location": "/databases/mongodb/basics/#count",
    +            "text": "Count is similar to a find operation in that it is a read-only operation. Since it does not return the actual data (only an integer of the total entities found) it does not support projections and sorting. It does support a range expression, although it's not often used.  let   totalUsers   =   users . count ()   // Future  let   femaleUsers   =   users . count ( \"gender\"   ==   \"female\" )   // Future",
    +            "title": "Count"
    +        },
    +        {
    +            "location": "/databases/mongodb/basics/#update",
    +            "text": "Updating can be done on one or all entities. Updates can either update the entire entity or only a subset of fields.  let   user   =   User ( named :   \"Joannis\" ,   age :   111 )  users . update ( \"username\"   ==   \"Joannis\" ,   to :   user )   Update also indicates a success state in the same way  insert  does.",
    +            "title": "Update"
    +        },
    +        {
    +            "location": "/databases/mongodb/basics/#updating-fields",
    +            "text": "Updating a subset of fields can be done more efficiently using  // Rename `Joannis` -> `JoannisO`  users . update ( \"username\"   ==   \"Joannis\" ,   fields :   [ \n     \"username\" :   \"JoannisO\"  ])  // Migrate all users to require a password update  users . update ( fields :   [ \n     \"resetPassword\" :   true  ])",
    +            "title": "Updating fields"
    +        },
    +        {
    +            "location": "/databases/mongodb/basics/#upsert",
    +            "text": "If you don't know if an entity exists but want it inserted/updated accordingly you should use  upsert .  let   user   =   User ( named :   \"Joannis\" ,   age :   111 )  users . upsert ( \"_id\"   ==   user . _id ,   to :   user )",
    +            "title": "Upsert"
    +        },
    +        {
    +            "location": "/databases/mongodb/basics/#remove",
    +            "text": "Remove removes the first or all entities matching a query.  // Remove me!  users . remove ( \"username\"   ==   \"Joannis\" )  // Remove all Dutch users  users . removeAll ( \"country\"   ==   \"NL\" )",
    +            "title": "Remove"
             },
             {
                 "location": "/databases/mongodb/basics/#troubleshooting",
    diff --git a/build/3.0/site/404.html b/build/3.0/site/404.html
    deleted file mode 100644
    index b36d6a53..00000000
    --- a/build/3.0/site/404.html
    +++ /dev/null
    @@ -1,1431 +0,0 @@
    -
    -
    -
    -
    -
    -
    -  
    -    
    -      
    -      
    -      
    -      
    -      
    -      
    -      
    -        
    -      
    -        
    -      
    -        
    -      
    -        
    -      
    -        
    -      
    -        
    -      
    -        
    -      
    -      
    -      
    -    
    -    
    -      
    -        Vapor Docs
    -      
    -    
    -    
    -      
    -      
    -        
    -      
    -    
    -    
    -      
    -    
    -    
    -      
    -        
    -        
    -      
    -      
    -    
    -    
    -    
    -  
    -  
    -    
    -    
    -    
    -      
    -    
    -  
    -    
    -      
    -        
    -        
    -          
    -        
    -      
    -    
    -    
    -    
    -    
    -    
    -      
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - - -
    -
    - -

    404 - Not found

    - - - - -
    -
    -
    -
    - - -
    - - -
    - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/assets/images/favicon.ico b/build/3.0/site/assets/images/favicon.ico deleted file mode 100644 index e85006a3..00000000 Binary files a/build/3.0/site/assets/images/favicon.ico and /dev/null differ diff --git a/build/3.0/site/assets/images/icons/bitbucket-670608a71a.svg b/build/3.0/site/assets/images/icons/bitbucket-670608a71a.svg deleted file mode 100644 index 7d95cb22..00000000 --- a/build/3.0/site/assets/images/icons/bitbucket-670608a71a.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/build/3.0/site/assets/images/icons/github-1da075986e.svg b/build/3.0/site/assets/images/icons/github-1da075986e.svg deleted file mode 100644 index 3cacb2e0..00000000 --- a/build/3.0/site/assets/images/icons/github-1da075986e.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/build/3.0/site/assets/images/icons/gitlab-5ad3f9f9e5.svg b/build/3.0/site/assets/images/icons/gitlab-5ad3f9f9e5.svg deleted file mode 100644 index b036a9b5..00000000 --- a/build/3.0/site/assets/images/icons/gitlab-5ad3f9f9e5.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/build/3.0/site/assets/javascripts/application-8e4952e681.js b/build/3.0/site/assets/javascripts/application-8e4952e681.js deleted file mode 100644 index 49fe7d1b..00000000 --- a/build/3.0/site/assets/javascripts/application-8e4952e681.js +++ /dev/null @@ -1 +0,0 @@ -window.app=function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=35)}([function(e,t,n){"use strict";var r=n(23)("wks"),i=n(14),o=n(1).Symbol,a="function"==typeof o;(e.exports=function(e){return r[e]||(r[e]=a&&o[e]||(a?o:i)("Symbol."+e))}).store=r},function(e,t,n){"use strict";var r=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},function(e,t,n){"use strict";var r=n(10),i=n(25);e.exports=n(5)?function(e,t,n){return r.f(e,t,i(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){"use strict";var r=n(11);e.exports=function(e){if(!r(e))throw TypeError(e+" is not an object!");return e}},function(e,t,n){"use strict";var r=n(1),i=n(2),o=n(6),a=n(14)("src"),s=Function.toString,c=(""+s).split("toString");n(7).inspectSource=function(e){return s.call(e)},(e.exports=function(e,t,n,s){var u="function"==typeof n;u&&(o(n,"name")||i(n,"name",t)),e[t]!==n&&(u&&(o(n,a)||i(n,a,e[t]?""+e[t]:c.join(String(t)))),e===r?e[t]=n:s?e[t]?e[t]=n:i(e,t,n):(delete e[t],i(e,t,n)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[a]||s.call(this)})},function(e,t,n){"use strict";e.exports=!n(24)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t,n){"use strict";var r={}.hasOwnProperty;e.exports=function(e,t){return r.call(e,t)}},function(e,t,n){"use strict";var r=e.exports={version:"2.4.0"};"number"==typeof __e&&(__e=r)},function(e,t,n){"use strict";e.exports={}},function(e,t,n){"use strict";var r={}.toString;e.exports=function(e){return r.call(e).slice(8,-1)}},function(e,t,n){"use strict";var r=n(3),i=n(38),o=n(39),a=Object.defineProperty;t.f=n(5)?Object.defineProperty:function(e,t,n){if(r(e),t=o(t,!0),r(n),i)try{return a(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(e[t]=n.value),e}},function(e,t,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};e.exports=function(e){return"object"===(void 0===e?"undefined":r(e))?null!==e:"function"==typeof e}},function(e,t,n){"use strict";var r=n(18);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,i){return e.call(t,n,r,i)}}return function(){return e.apply(t,arguments)}}},function(e,t,n){"use strict";var r=n(9),i=n(0)("toStringTag"),o="Arguments"==r(function(){return arguments}()),a=function(e,t){try{return e[t]}catch(e){}};e.exports=function(e){var t,n,s;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=a(t=Object(e),i))?n:o?r(t):"Object"==(s=r(t))&&"function"==typeof t.callee?"Arguments":s}},function(e,t,n){"use strict";var r=0,i=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++r+i).toString(36))}},function(e,t,n){"use strict";var r=n(11),i=n(1).document,o=r(i)&&r(i.createElement);e.exports=function(e){return o?i.createElement(e):{}}},function(e,t,n){"use strict";var r=Math.ceil,i=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?i:r)(e)}},function(e,t,n){"use strict";e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,t,n){"use strict";e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t,n){"use strict";var r=n(47),i=n(17);e.exports=function(e){return r(i(e))}},function(e,t,n){"use strict";var r=n(23)("keys"),i=n(14);e.exports=function(e){return r[e]||(r[e]=i(e))}},function(e,t,n){"use strict";var r=n(10).f,i=n(6),o=n(0)("toStringTag");e.exports=function(e,t,n){e&&!i(e=n?e:e.prototype,o)&&r(e,o,{configurable:!0,value:t})}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={createElement:function(e,t){var n=document.createElement(e);t&&Array.prototype.forEach.call(Object.keys(t),function(e){n.setAttribute(e,t[e])});for(var r=arguments.length,i=Array(r>2?r-2:0),o=2;o0?i(r(e),9007199254740991):0}},function(e,t,n){"use strict";e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t,n){"use strict";e.exports=n(1).document&&document.documentElement},function(e,t,n){"use strict";var r,i,o,a=n(12),s=n(63),c=n(31),u=n(15),l=n(1),f=l.process,h=l.setImmediate,d=l.clearImmediate,p=l.MessageChannel,m=0,y={},v=function(){var e=+this;if(y.hasOwnProperty(e)){var t=y[e];delete y[e],t()}},g=function(e){v.call(e.data)};h&&d||(h=function(e){for(var t=[],n=1;arguments.length>n;)t.push(arguments[n++]);return y[++m]=function(){s("function"==typeof e?e:Function(e),t)},r(m),m},d=function(e){delete y[e]},"process"==n(9)(f)?r=function(e){f.nextTick(a(v,e,1))}:p?(i=new p,o=i.port2,i.port1.onmessage=g,r=a(o.postMessage,o,1)):l.addEventListener&&"function"==typeof postMessage&&!l.importScripts?(r=function(e){l.postMessage(e+"","*")},l.addEventListener("message",g,!1)):r="onreadystatechange"in u("script")?function(e){c.appendChild(u("script")).onreadystatechange=function(){c.removeChild(this),v.call(e)}}:function(e){setTimeout(a(v,e,1),0)}),e.exports={set:h,clear:d}},function(e,t){(function(t){e.exports=t}).call(t,{})},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n=t.length?{value:void 0,done:!0}:(e=r(t,n),this._i+=e.length,{value:e,done:!1})})},function(e,t,n){"use strict";var r=n(16),i=n(17);e.exports=function(e){return function(t,n){var o,a,s=String(i(t)),c=r(n),u=s.length;return c<0||c>=u?e?"":void 0:(o=s.charCodeAt(c),o<55296||o>56319||c+1===u||(a=s.charCodeAt(c+1))<56320||a>57343?e?s.charAt(c):o:e?s.slice(c,c+2):a-56320+(o-55296<<10)+65536)}}},function(e,t,n){"use strict";var r=n(43),i=n(25),o=n(21),a={};n(2)(a,n(0)("iterator"),function(){return this}),e.exports=function(e,t,n){e.prototype=r(a,{next:i(1,n)}),o(e,t+" Iterator")}},function(e,t,n){"use strict";var r=n(3),i=n(44),o=n(30),a=n(20)("IE_PROTO"),s=function(){},c=function(){var e,t=n(15)("iframe"),r=o.length;for(t.style.display="none",n(31).appendChild(t),t.src="javascript:",e=t.contentWindow.document,e.open(),e.write(" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    EventLoop

    -

    Event loops are at the heart of Vapor's non-blocking concurrency model. There is usually one event loop per logical core in your computer's CPU. The event loop's main purpose is to detect when data is ready to be read from or written to a socket. By detecting socket events before actually attempting to read or write data, Vapor can avoid making function calls that may block. Avoiding blocking calls is critical for performance as it allows Vapor to aggressively re-use threads, making your app very fast and efficient.

    -

    In addition to the above, they're also able to run single tasks inbetween listening for events.

    -

    There are three main forms of eventloops.

    -

    DispatchEventLoop is based on Dispatch, KQueueEventLoop is a macOS-only eventloop that is more performant than Dispatch.

    -

    The third one (currently work in progress) is the EPollEventLoop, a Linux only eventloop that is also more performant than Dispatch.

    -

    Workers

    -

    To carry around context, the Worker protocol exists to indicate the current eventloop context.

    -
    print(worker.eventLoop) // EventLoop
    -
    - - -

    When looking for a worker, the most common ones you'll come across are the Request and Response.

    -

    Future changes during beta

    -

    It is likely that we'll start inferring the current EventLoop using the Thread Local Storage, removing the need for Workers and passing EventLoop as an argument.

    -

    Sources

    -

    To add/remove listeners from EventLoops you can ask for a readable or writable source or EventSource. EventSources are a handle which can be resumed, suspended and cancelled. -When requesting said handle on an EventLoop you must provide a closure which calls back with the notification.

    -

    This notification indicates that data is available for work in the provided descriptor. This includes the descriptor being closed.

    -
    let sourceHandle = eventLoop.onReadable(descriptor: socket.descriptor) { cancelled in
    -  if cancelled {
    -    print("descriptor closed")
    -  } else {
    -    print("Data is readable")
    -  }
    -}
    -
    - - -

    Write sources are the opposite of a Source in that they notify the ability to write data. They should be suspended after the first write so that they do not call back every loop.

    -

    Whilst Sources indicate the availability of data, Drains

    -

    Sockets

    -

    As part of the EventLoops in Vapor 3, we also centralized the asynchronous part of Sockets, simplifying the APIs for I/O and improving it's asynchronous usability. It is recommended for (raw) TCP, (raw) UDP and SSL implementations to conform to the Socket protocol.

    -

    SocketSink and SocketSource

    -

    SocketSink is a helper that assists with writing data to sockets reactively. -SocketSource is a helper that functions as the Sink's counterpart with reading data from sockets reactively.

    -
    let sink = socket.sink(on: eventLoop)
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/async/futures/index.html b/build/3.0/site/async/futures/index.html deleted file mode 100644 index 97a93b0a..00000000 --- a/build/3.0/site/async/futures/index.html +++ /dev/null @@ -1,1509 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Futures - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Futures

    -

    Futures are used throughout Vapor 3.

    -

    They aim to provide a more simplified but high performance interface to process -data actions and transformations.

    -

    We explain the future basics and use cases here.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/async/getting-started/index.html b/build/3.0/site/async/getting-started/index.html deleted file mode 100644 index 09686495..00000000 --- a/build/3.0/site/async/getting-started/index.html +++ /dev/null @@ -1,1593 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Getting Started - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Using Async

    -

    Async is a library revolving around two main concepts:

    - -

    Together they form the foundation of Vapor 3's data flow.

    -

    With Vapor

    -

    This package is included with Vapor by default, just add:

    -
    import Async
    -
    - - -

    Without Vapor

    -

    Async is a powerful library for any Swift project. To include it in your package, add the following to your Package.swift file.

    -
    // swift-tools-version:4.0
    -import PackageDescription
    -
    -let package = Package(
    -    name: "Project",
    -    dependencies: [
    -        ...
    -        .package(url: "https://github.com/vapor/async.git", .revision("beta")),
    -    ],
    -    targets: [
    -      .target(name: "Project", dependencies: ["Async", ... ])
    -    ]
    -)
    -
    - - -

    Use import Async to access Async's APIs.

    - - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/async/reactive/index.html b/build/3.0/site/async/reactive/index.html deleted file mode 100644 index 0ba96bf3..00000000 --- a/build/3.0/site/async/reactive/index.html +++ /dev/null @@ -1,1648 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Reactive Programming - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Reactive Programming

    -

    As part of the Vapor 3 ecosystem we embrace reactiveness.

    -

    Reactiveness means your Vapor applications will be more resilient to the producer-consumer problem that has long plagued web applications. Vapor achieves this by moving from "push" streams (aka, fire hose streams) to "pull" streams. At a high level, this means better performance and less memory usage during peak demand.

    -

    Learn more at reactive-streams.org.

    -

    As part of our API design we strive to minimize the impact on code.

    -

    Reducing code impact

    -

    Most of our code impact is reduced using protocols. -By conforming Futures and Streams to the Codable protocol we can use reactiveness throughout all components of Vapor 3.

    -

    This allows for reactive templating with less code than before reactiveness was introduced.

    -

    Rules

    -

    The following rules are critical to reactive programming with Vapor 3:

    -

    Information flow

    -

    Stream data must be asynchronously available. This means that when input is received, the information stays intact until new data is requested or the sending stream is cancelled/closed.

    -

    Output to another stream must stay intact (in the case of ByteBuffer, must not be deallocated or reused) until a new request for data has been made.

    -

    Upstream

    -

    Requesting data from upstream must only be done if you do not have enough information to complete a request from downstream.

    -

    Downstream

    -

    You must not feed more data to downstream than was requested.

    -

    Blocking

    -

    You must not block within a stream using sleep or blocking sockets.

    - - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/async/streams/index.html b/build/3.0/site/async/streams/index.html deleted file mode 100644 index 69ab655e..00000000 --- a/build/3.0/site/async/streams/index.html +++ /dev/null @@ -1,1643 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Streams - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Streams

    -

    Streams are a mechanism that process any information efficiently, reactively and asynchronously without bloat. They make asynchronous data flows easier to deal with.

    -

    Streams are designed to limit memory usage and copies. They are used in all domains of Vapor 3, be it sockets, be it (larger) database operations.

    -

    Draining streams

    -

    In this example we print the string representation of the TCP connnection's incoming data.

    -

    Since this socket is reactive we need to first request data before we can expect a result. -After requesting data we need to set up the output

    -
    import Async
    -import Foundation
    -
    -...
    -
    -tcpSocket.drain { upstream in
    -    upstream.request()
    -}.output { buffer in
    -    print(String(bytes: buffer, encoding: .utf8))
    -    tcpSocket.request()
    -}.catch { error in
    -    print("Error occurred \(error)")
    -}.finally {
    -    print("TCP socket closed")
    -}
    -
    - - -

    In the above implementation we explicitly request more information from the socket after receiving output.

    -

    Emitting output

    -

    Emitter streams are useful if you don't want to create your own reactive stream implementation.

    -

    They allow emitting output easily which can then be used like any other stream.

    -
    let emitter = EmitterStream<Int>()
    -
    -emitter.drain { upstream in
    -  upstream.request()
    -}.output { number in
    -  print(number)
    -  emitter.request()
    -}
    -
    -emitter.emit(3)
    -emitter.emit(4)
    -emitter.emit(3)
    -emitter.emit(5)
    -
    - - -

    Mapping Streams

    -

    To transform a string to another type you can map it similarly to futures. -The following assumes stream contains a stream of Int as defined in the above emitter.

    -
    let stringStream = emitter.map(to: String.self) { number in
    -  return number.description
    -}
    -
    - - -

    Implementing custom streams

    -

    Coming soon

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/concepts/code-contributions/index.html b/build/3.0/site/concepts/code-contributions/index.html deleted file mode 100644 index a66f975d..00000000 --- a/build/3.0/site/concepts/code-contributions/index.html +++ /dev/null @@ -1,1835 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code Contributions - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    API Design

    -

    For contributing code we have guidelines that we strive for, and requirements. -Accepted code must comply to all requirements and should follow all guidelines.

    -

    Requirements

    -

    The requirements stated here must be followed for all Vapor 3 code starting with the beta. -We designed all of Vapor 3's APIs (including the Async library) to require little to no breaking changes over the coming years.

    -

    Enums

    -

    enums should only be used where adding a new case should result in a breaking change. Specifically since exhaustively switching on an enum will no longer compile when a new case is added. For things like errors, this is obviously undesirable as supporting a new error type results in code no longer compiling. However, for something like a supported data type, enums make sense because the errors help you track down all of places that need support for that new type added. Most use cases for enums are when the enum is internal to the current module.

    -

    Classes

    -

    Always mark classes as final. If you plan on using a public class, look into a protocol or generics oriented approach.

    -

    Low-level APIs

    -

    Low level APIs such as sockets, SSL, cryptography and HTTP should be oriented towards simplicity, maintainability and correctness. -If you feel an API becomes more complex for end-users, you can add high level APIs that rely on the low-level ones.

    -

    Tests

    -

    The unit tests must have a minimum of 80% code coverage.

    -

    Uniformity

    -

    Stick to the familiar API patterns. If connecting a socket has the following signature:

    -
    try socket.connect(to: "example.com", port: 80)
    -
    - - -

    Copy the signature, rather than adding an extra case. -If you need more metadata for connecting, consider setting them in the initializer or as a variable on the type.

    -

    Binary data

    -

    Binary data should be passed around in 3 formats;

    -
      -
    • ByteBuffer
    • -
    • Data
    • -
    • [UInt8]
    • -
    -

    You should use ByteBuffer when working with Streams to limit copies and improve the stream chaining applicability. -Data for larger sets of data (>1000 bytes) and [UInt8] for smaller data sets (<=1000 bytes).

    -

    Dictionaries and Arrays

    -

    Where you'd normally use a dictionary (such as HTTP headers) you should create a separate struct instead. -This improves the freedom for future optimizations and keeps the implementation separate from the end user API. -This results in better future-proofing and helps building better (more specialized) APIs.

    -

    Guidelines

    -

    Performance

    -

    Performance regression is acceptable to some degree but should be limited. -Here are some tips on achieving high performance code or ensuring more performance can be added in the future.

    -

    Access modifiers

    -

    Try to use the public keyword as little as possible. More APIs adds more complexity than freedom. -Specialized use cases are always free to build their own modules, and a public keyword can be added but not undone.

    -

    Comments

    -

    Green is the future, and we believe in that, too! I'm not talking about green energy, but the often green coloured code comments. -Code comments important for the users of an API. Do not add meaningless comments, though.

    -

    Documentation

    -

    Every public function, type and variable should have a link to the documentation describing it's use case(s) with examples.

    -

    Argument labels

    -

    Along with uniformity described above, you should try to keep argument labels short and descriptive.

    -

    Argument count

    -

    We strive for functions with a maximum of 3 parameters, although certain use cases permit for more arguments. -Often called functions should strive for less arguments.

    -

    Initializer complexity

    -

    Initializers are complex enough, it is recommended to not put optional arguments in an initializer since they can be modified on the entity itself. -Try to limit the initializer to what really matters, and put the rest in functions.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/concepts/http/index.html b/build/3.0/site/concepts/http/index.html deleted file mode 100644 index 3453c0b1..00000000 --- a/build/3.0/site/concepts/http/index.html +++ /dev/null @@ -1,1698 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - HTTP - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    HTTP

    -

    At the heart of the web lies HTTP. HTTP (or HyperText Transfer Protocol) is used for communicating multiple types of media between a client and a server.

    -

    HTTP comes in two major versions. HTTP/1 and HTTP/2. Vapor comes with HTTP/1 support by default but has an official package for HTTP/2, too.

    -

    What is the difference?

    -

    HTTP/1 is a protocol designed in the '90s for the then new and rapidly evolving internet. The protocol is designed around simplicity above security and functionality.

    -

    HTTP/2 is a protocol with security and performance in mind. Designed with experience of the past 20 years of internet in addition to modern standards such as a high bandwidth and many resources per page.

    -

    How it works

    -

    At the heart of HTTP lie the Request and Response types. Both of them are "HTTP Messages". Both HTTP messages consists of Headers and a body.

    -

    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 and forms.

    -

    Headers contain metadata, meaning they carry information describing the HTTP message, it's context and it's content.

    -

    Cookies are context metadata about the client that, for example, can be used for identifying users after they've (successfully) logged in. One of these methods are session tokens.

    -

    Another type of metadata that is often used to define the type of content transferred in the body is the Content-Type header

    -

    Request

    -

    Requests have two additional properties in addition to all properties of a Message. The Method and path.

    -

    The path is used to specify the resource being accessed. Although there are conventions, there are no rules/limitations to how you structure your paths except their format. Paths consist of components. The components are separated by a forward slash (/). All components must be encoded with percent encoding, affecting special characters only.

    -

    The method indicated the operation to this resource. GET is used for reading a resource where DELETE will (attempt to) remove the resource. This does not mean you need to blindly comply. If a user doesn't have the permissions for said operation, you can emit a response indicating this.

    -

    Response

    -

    Responses have one additional property in addition to the message's properties. This is the status code. The status code is used to indicate to the client what the status/result is of a Request. If a client was not authenticated, for example, you would return a status 401 or 403 for "Unauthorized" or "Forbidden" respectively. More about status codes here.

    -

    Handling requests

    -

    Requests in Vapor will be handled by a router. 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

    -

    In the web we usually define two types of endpoints. Either a website or an API. Websites are HTML pages, usually with associated styling, code and images. APIs are endpoints that communicate with raw information rather than types and user friendly information. APIs are aimed to developers and their applications.

    -

    iOS and Android apps usually communicate with an API, where a web browser such as Safari, Firefox, Chrome or Edge will usually communicate with a website.

    -

    Websites

    -

    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 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. 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 created using a "MVC" or "Model View Controller" model which we explain here.

    -

    Designing an API in Vapor is really simple. We dive into this from here.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/concepts/services/index.html b/build/3.0/site/concepts/services/index.html deleted file mode 100644 index 1fac6603..00000000 --- a/build/3.0/site/concepts/services/index.html +++ /dev/null @@ -1,1968 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Services - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - - - - -
    -
    - - - - - -

    Services

    -

    Services is a framework for creating things you need in your application in a type-safe fashion with protocol and environment support.

    -

    The Services framework is designed to be thread unsafe. The framework aims to guarantee that a service exists on the same EventLoop it was created from and will be used on.

    -

    Glossary

    -

    Container

    -

    Containers are EventLoops that can create and cache services.

    -

    Request is the most common Container type, which can be accessed in every Route.

    -

    Containers cache instances of a given service (keyed by the requested protocol) on a per-container basis.

    -
      -
    1. Any given container has its own cache. No two containers will ever share a service instance, whether singleton or not.
    2. -
    3. A singleton service is chosen and cached only by which interface(s) it supports and the service tag. -There will only ever be one instance of a singleton service per-container, regardless of what requested it.
    4. -
    5. A normal service is chosen and cached by which interface(s) it supports, the service tag, and the requesting client interface. -There will be as many instances of a normal service per-container as there are unique clients requesting it. -(Remembering that clients are also interface types, not instances - that's the for: parameter to .make())
    6. -
    -

    EphemeralContainer

    -

    EphemeralContainers are containers that are short-lived. -Their cache does not stretch beyond a short lifecycle. -The most common EphemeralContainer is an HTTP Request which lives for the duration of the route handler.

    -

    Service

    -

    Services are a type that can be requested from a Container. They are registered as part of the application setup.

    -

    Services are registered to a matching type or protocol it can represent, including it's own concrete type.

    -

    Services are registered to a blueprint before the Application is initialized. Together they make up the blueprint that Containers use to create an individual Service.

    -

    Environment

    -

    Environments indicate the type of deployment/situation in which an application is ran. Environments can be used to change database credentials or API tokens per environment automatically.

    -

    Registering

    -

    Services are registered as a concrete (singleton) type or factories. Singleton types should be a struct, but can be a class.

    -

    To create an empty list of Services you can call the initializer without parameters

    -
    var services = Services()
    -
    - - -

    The Vapor framework has a default setup with the most common (and officially supported) Services already registered.

    -
    var services = Services.default()
    -
    - - -

    Concrete implementations

    -

    A common use case for registering a struct is for registering configurations. -Vapor 3 configurations are always a concrete struct type. Registering a concrete type is simple:

    -
    struct EmptyService {}
    -
    -services.instance(EmptyService())
    -
    - - -

    Singletons

    -

    Singleton services (which declare themselves, or were registered, as such) are cached on a per-container basis, but the singleton cache ignores which Client is requesting the service (whereas the normal cache does not).

    -

    Singleton classes must be thread-safe to prevent crashes. If you want your class to be a singleton type (across all threads):

    -
    final class SingletonService {
    -  init() {}
    -}
    -
    -services.instance(isSingleton: true, SingletonService())
    -
    - - -

    Assuming the above service, you can now make this service from a container. The global container in Vapor is Application which must not be used within routes.

    -
    let app = try Application(services: services)
    -let emptyService = app.make(EmptyService.self)
    -
    - - -

    Protocol conforming services

    -

    Often times when registering a service is conforms to one or more protocols for which it can be used. This is one of the more widely used use cases for Services.

    -
    enum Level {
    -  case verbose, error
    -}
    -
    -protocol Logger {
    -  func log(_ message: String, level: Level)
    -}
    -
    -struct PrintLogger: Logger {
    -  init() {}
    -
    -  func log(_ message: String, level: Level) {
    -    print(message)
    -  }
    -}
    -
    -services.instance(Logger.self, PrintLogger())
    -
    - - -

    The above can be combined with isSingleton: true

    -

    Registering multiple conformances

    -

    A single type can conform to multiple protocols, and you might want to register a single service for all those conforming situations.

    -
    protocol Console {
    -  func write(_ message: String, color: AnsiColor)
    -}
    -
    -struct PrintConsole: Console, Logger {
    -  func write(_ message: String, color: AnsiColor) {
    -    print(message)
    -  }
    -
    -  func log(_ message: String, level: Level) {
    -    print(message)
    -  }
    -
    -  init() {}
    -}
    -
    -services.instance(
    -  supports: [Logger.self, Console.self],
    -  ErrorLogger()
    -)
    -
    - - -

    Registering for a specific requester

    -

    Sometimes, the implementation should change depending on the user. A database connector might need to run over a VPN tunnel, redis might use an optimized local loopback whilst the default implementation is a normal TCP socket.

    -

    Other times, you simply want to change the log destination depending on the type that's logging (such as logging HTTP errors differently from database errors).

    -

    This comes in useful when changing configurations per situation, too.

    -
    struct VerboseLogger: Logger {
    -  init() {}
    -
    -  func log(_ message: String, level: Level) {
    -    print(message)
    -  }
    -}
    -
    -struct ErrorLogger: Logger {
    -  init() {}
    -
    -  func log(_ message: String, level: Level) {
    -    if level == .error {
    -      print(message)
    -    }
    -  }
    -}
    -
    -// Only log errors
    -services.instance(Logger.self, ErrorLogger())
    -
    -// Except the router, do log not found errors verbosely
    -services.instance(Logger.self, PrintLogger(), for: Router.self)
    -
    - - -

    Factorized services

    -

    Some services have dependencies. An extremly useful use case is TLS, where the implementation is separated from the protocol. This allows users to create a TLS socket to connect to another host with without relying on a specific implementation. Vapor uses this to better integrate with the operating system by changing the default TLS implementation from OpenSSL on Linux to the Transport Security Framework on macOS and iOS.

    -

    Factorized services get access to the event loop to factorize dependencies.

    -
    services.register { container -> GithubClient in
    -  // Create an HTTP client for our GithubClient
    -  let client = try container.make(Client.self, for: GithubClient.self)
    -  try client.connect(hostname: "github.com", ssl: true)
    -
    -  return GithubClient(using: client)
    -}
    -
    - - -

    Please do note that we explicitly stated that the GithubClient requests an (HTTP) Client. We recommend doing this at all times, so that you leave configuration options open.

    -

    Environments

    -

    Vapor 3 supports (custom) environments. By default we recommend (and support) the .production, .development and .testing environments.

    -

    You can create a custom environment type as .custom(<my-environment-name>).

    -
    let environment = Environment.custom("staging")
    -
    - - -

    Containers give access to the current environment, so libraries may change behaviour depending on the environment.

    -

    Changing configurations per environment

    -

    For easy of development, some parameters may and should change for easy of debugging. -Password hashes can be made intentionally weaker in development scenarios to compensate for debug compilation performance, or API tokens may change to the correct one for your environment.

    -
    services.register { container -> BCryptConfig in
    -  let cost: Int
    -
    -  switch container.environment {
    -  case .production:
    -      cost = 12
    -  default:
    -      cost = 4
    -  }
    -
    -  return BCryptConfig(cost: cost)
    -}
    -
    - - -

    Getting a Service

    -

    To get a service you need an existing container matching the current EventLoop. -If you're processing a Request, you should almost always use the Request as a Container type.

    -
    // ErrorLogger
    -let errorLogger = myContainerType.make(Logger.self, for: Request.self)
    -
    -// PrintLogger
    -let printLogger = myContainerType.make(Logger.self, for: Router.self)
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/concepts/vapor/index.html b/build/3.0/site/concepts/vapor/index.html deleted file mode 100644 index d67096bb..00000000 --- a/build/3.0/site/concepts/vapor/index.html +++ /dev/null @@ -1,1611 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Vapor - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    What is Vapor?

    -

    Vapor 3 is an asynchronous, codable and protocol oriented framework. This document will outline the major lines how vapor is designed and why.

    -

    Async

    -

    Vapor 3 async is a framework consisting of two basic principles. Futures and Reactive Streams. Both have their ideal use cases and strengths.

    -

    Performance

    -

    Vapor's high performance is achieved by a combination of an asynchronous architecture, Copy on Write mechanics and highly optimized lazy parsers. These three techniques combined with Swift's compiler ensure that our performance is comparable to Go.

    -

    Type safety

    -

    Vapor is designed around type-safety and compile-time checks, ensuring that code doesn't behave in unexpected ways and shows you most problems at compile time. Vapor achieves this by leveraging Swift and its Codable protocol.

    -

    We believe type-safety is critical to both security and developer productivity.

    -

    Easy to use

    -

    Creating a new Vapor project takes only a few minutes.

    -

    We've got you covered with our thorough documentation and have a helpful community to back it up!

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/crypto/base64/index.html b/build/3.0/site/crypto/base64/index.html deleted file mode 100644 index 7f7a14d1..00000000 --- a/build/3.0/site/crypto/base64/index.html +++ /dev/null @@ -1,1506 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Base64 - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Base64

    -

    Coming soon

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/crypto/getting-started/index.html b/build/3.0/site/crypto/getting-started/index.html deleted file mode 100644 index 82216626..00000000 --- a/build/3.0/site/crypto/getting-started/index.html +++ /dev/null @@ -1,1587 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Getting Started - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Using Crypto

    -

    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.

    -

    With Vapor

    -

    This package is included with Vapor by default, just add:

    -
    import Crypto
    -
    - - -

    Without Vapor

    -

    To include it in your package, add the following to your Package.swift file.

    -
    // swift-tools-version:4.0
    -import PackageDescription
    -
    -let package = Package(
    -    name: "Project",
    -    dependencies: [
    -        ...
    -        .package(url: "https://github.com/vapor/crypto.git", .upToNextMajor(from: "x.0.0")),
    -    ],
    -    targets: [
    -      .target(name: "Project", dependencies: ["Crypto", ... ])
    -    ]
    -)
    -
    - - -

    Use import Crypto to access Crypto's APIs.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/crypto/hash/index.html b/build/3.0/site/crypto/hash/index.html deleted file mode 100644 index 2debd036..00000000 --- a/build/3.0/site/crypto/hash/index.html +++ /dev/null @@ -1,1690 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Hashes - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Hash

    -

    Hashes are a one-directional encryption that is commonly used for validating files or one-way securing data such as passwords.

    -

    Available hashes

    -

    Crypto currently supports a few hashes.

    -
      -
    • MD5
    • -
    • SHA1
    • -
    • SHA2 (all variants)
    • -
    -

    MD5 and SHA1 are generally used for file validation or legacy (weak) passwords. They're performant and lightweight.

    -

    Every Hash type has a set of helpers that you can use.

    -

    Hashing blobs of data

    -

    Every Hash has a static method called hash that can be used for hashing the entire contents of Foundation.Data, ByteBuffer or String.

    -

    The result is Data containing the resulting hash. The hash's length is according to spec and defined in the static variable digestSize.

    -
    // MD5 with `Data`
    -let fileData = Data()
    -let fileMD5 = MD5.hash(fileData)
    -
    -// SHA1 with `ByteBuffer`
    -let fileBuffer: ByteBuffer = ...
    -let fileSHA1 = SHA1.hash(fileBuffer)
    -
    -// SHA2 variants with String
    -let staticUnsafeToken: String = "rsadd14ndmasidfm12i4j"
    -
    -let tokenHashSHA224 = SHA224.hash(staticUnsafeToken)
    -let tokenHashSHA256 = SHA256.hash(staticUnsafeToken)
    -let tokenHashSHA384 = SHA384.hash(staticUnsafeToken)
    -let tokenHashSHA512 = SHA512.hash(staticUnsafeToken)
    -
    - - -

    Incremental hashes (manual)

    -

    To incrementally process hashes you can create an instance of the Hash. This will set up a context.

    -

    All hash context initializers are empty:

    -
    // Create an MD5 context
    -let md5Context = MD5()
    -
    - - -

    To process a single chunk of data, you can call the update function on a context using any Sequence of UInt8. That means Array, Data and ByteBuffer work alongside any other sequence of bytes.

    -
    md5Context.update(data)
    -
    - - -

    The data data need not be a specific length. Any length works.

    -

    When you need the result, you can call md5Context.finalize(). This will finish calculating the hash by appending the standard 1 bit, padding and message bitlength.

    -

    You can optionally provide a last set of data to finalize().

    -

    After calling finalize(), do not update the hash if you want correct results.

    -

    Fetching the results

    -

    The context can then be accessed to extract the resulting Hash.

    -
    let hash: Data = md5Context.hash
    -
    - - -

    Streaming hashes (Async)

    -

    Sometimes you need to hash the contents of a Stream, for example, when processing a file transfer. In this case you can use ByteStreamHasher.

    -

    First, create a new generic ByteStreamHasher<Hash> where Hash is the hash you want to use. In this case, SHA512.

    -
    let streamHasher = ByteStreamHasher<SHA512>()
    -
    - - -

    This stream works like any inputStream by consuming the incoming data and passing the buffers to the hash context.

    -

    For example, draining a TCP socket.

    -
    let socket: TCP.Socket = ...
    -
    -socket.drain(into: streamHasher)
    -
    - - -

    This will incrementally update the hash using the provided TCP socket's data.

    -

    When the hash has been completely accumulated, you can complete the hash.

    -
    let hash = streamHasher.complete() // Foundation `Data`
    -
    - - -

    This will reset the hash's context to the default configuration, ready to start over.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/crypto/mac/index.html b/build/3.0/site/crypto/mac/index.html deleted file mode 100644 index 7fc21213..00000000 --- a/build/3.0/site/crypto/mac/index.html +++ /dev/null @@ -1,1553 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Message authentication - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Message authentication

    -

    Message authentication is used for verifying message authenticity and validity.

    -

    Common use cases are JSON Web Tokens.

    -

    For message authentication, Vapor only supports HMAC.

    -

    Using HMAC

    -

    To use HMAC you first need to select the used hashing algorithm for authentication. This works using generics.

    -
    let hash = HMAC<SHA224>.authenticate(message, withKey: authenticationKey)
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/crypto/passwords/index.html b/build/3.0/site/crypto/passwords/index.html deleted file mode 100644 index a5820325..00000000 --- a/build/3.0/site/crypto/passwords/index.html +++ /dev/null @@ -1,1693 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Password hashing - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Password hashing

    -

    Password management is critical for good user security and doesn't need to cost a lot of effort. No software is perfect. Even if your software is perfect, other software on the same server likely isn't. Good password encryption security prevents users' passwords from leaking out in case of a hypothetical future data breach.

    -

    For password hashing Vapor supports PBKDF2 and BCrypt.

    -

    We recommend using BCrypt over PBKDF2 for almost all scenarios. Whilst PBKDF2 is a proven standard, it's much more easily brute-forced than BCrypt and is less future-proof.

    -

    BCrypt

    -

    BCrypt is an algorithm specifically designed for password hashing. It's easy to store and verify.

    -

    Deriving a key

    -

    Unlike PBKDF2 you don't need to generate and store a salt, that's part of the BCrypt hashing and verification process.

    -

    The output is a combination of the BCrypt "cost" factor, salt and resulting hash. Meaning that the derived output contains all information necessary for verification, simplifying the database access.

    -
    let result: Data = try BCrypt.make(message: "MyPassword")
    -
    -guard try BCrypt.verify(message: "MyPassword", matches: result) else {
    -    fatalError("This never triggers, since the verification process will always be successful for the same password and conditions")
    -}
    -
    - - -

    The default cost factor is 12, based on the official recommendations.

    -

    Storing the derived key as a String

    -

    BCrypt always outputs valid ASCII/UTF-8 for the resulting hash.

    -

    This means you can convert the output Data to a String as such:

    -
    guard let string = String(bytes: result, encoding: .utf8) else {
    -    // This must never trigger
    -}
    -
    - - -

    PBKDF2

    -

    PBKDF2 is an algorithm that is almost always (and in Vapor, exclusively) used with HMAC for message authentication.

    -

    PBKDF2 can be paired up with any hashing algorithm and is simple to implement. PBKDF2 is used all over the world through the WPA2 standard, securing WiFi connections. But we still recommend PBKDF2 above any normal hashing function.

    -

    For PBKDF2 you also select the Hash using generics.

    -

    Deriving a key

    -

    In the following example:

    -
      -
    • password is either a String or Data
    • -
    • The salt is Data
    • -
    • Iterations is defaulted to 10_000 iterations
    • -
    • The keySize is equivalent to 1 hash's length.
    • -
    -
    // Generate a random salt
    -let salt: Data = OSRandom().data(count: 32)
    -
    -let hash = try PBKDF2<SHA256>.derive(fromPassword: password, salt: salt)
    -
    - - -

    You can optionally configure PBKDF2 to use a different iteration count and output keysize.

    -
    // Iterates 20'000 times and outputs 100 bytes
    -let hash = try PBKDF2<SHA256>.derive(fromPassword: password, salt: salt, iterating: 20_000, derivedKeyLength: 100)
    -
    - - -

    Storing the results

    -

    When you're storing the PBKDF2 results, be sure to also store the Salt. Without the original salt, iteration count and other parameters you cannot reproduce the same hash for validation or authentication.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/crypto/random/index.html b/build/3.0/site/crypto/random/index.html deleted file mode 100644 index 82e6992e..00000000 --- a/build/3.0/site/crypto/random/index.html +++ /dev/null @@ -1,1621 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Random - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Random

    -

    Crypto has two primary random number generators.

    -

    OSRandom generates random numbers by calling the operating system's random number generator.

    -

    URandom generates random numbers by reading from /dev/urandom.

    -

    Accessing random numbers

    -

    First, create an instance of the preferred random number generator:

    -
    let random = OSRandom()
    -
    - - -

    or

    -
    let random = try URandom()
    -
    - - -

    Reading integers

    -

    For every Swift integer a random number function exists.

    -
    let int8: Int8 = try random.makeInt8()
    -let uint8: UInt8 = try random.makeUInt8()
    -let int16: Int16 = try random.makeInt16()
    -let uint16: UInt16 = try random.makeUInt16()
    -let int32: Int32 = try random.makeInt32()
    -let uint32: UInt32 = try random.makeUInt32()
    -let int64: Int64 = try random.makeInt64()
    -let uint64: UInt64 = try random.makeUInt64()
    -let int: Int = try random.makeInt()
    -let uint: UInt = try random.makeUInt()
    -
    - - -

    Reading random data

    -

    Random buffers of data are useful when, for example, generating tokens or other unique strings/blobs.

    -

    To generate a buffer of random data:

    -
    // generates 20 random bytes
    -let data: Data = random.data(count: 20)
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/databases/mongodb/basics/index.html b/build/3.0/site/databases/mongodb/basics/index.html deleted file mode 100644 index 802fa0ef..00000000 --- a/build/3.0/site/databases/mongodb/basics/index.html +++ /dev/null @@ -1,1744 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Basics - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    MongoDB Basics

    -

    import MongoKitten in your file(s) using MongoKitten. This will automatically also import the BSON APIs.

    -

    Connecting to MongoDB

    -

    To connect to MongoDB you need to provide your connection URI and the database you want to access. -The database will also need access to your worker.

    -
    let database = Database.connect(server: "mongodb://localhost:27017", database: "test-db", worker: worker) // Future<Database>
    -
    - - -

    You cannot use the connection globally. If you plan to use this connection globally you'll need to use a lock on it for querying since the connection is not thread safe. This will be added in a later stage of MongoKitten 5's beta.

    -

    Connection as a Service

    -

    If you want to use the connection within a Vapor 3 route you'll need to register this connection to your Services.

    -
    services.register { container -> ClientSettings in
    -    return "mongodb://localhost:27017" // Your MongoDB URI here
    -}
    -
    -services.register { container -> Database in
    -    let server = try container.make(ClientSettings.self, for: Database.self)
    -
    -    // Future<Database>
    -    let db = try Database.connect(sever: server, database: "my-database", worker: container)
    -
    -    // Await here, it doesn't hurt performant much and it only needs to wait once per worker
    -    return try db.await(on: container)
    -}
    -
    - - -

    CRUD

    -

    Before applying a CRUD operation you need to select a Collection first. This is the MongoDB equivalent of a table.

    -

    You can subscript a database with a string to get a collection with that name. You do not need to set up a schema first.

    -
    router.get("users") { request in
    -    let database = try request.make(Database.self)
    -    let users = database["users"] // Collection<Document>
    -
    -    ...
    -}
    -
    - - -

    Subscripting will give you a Collection<Document>. If you want to read the collection as a different (Codable) type you'll need to map the collection to a different type.

    -
    struct User: Codable {
    -    var _id = ObjectId()
    -    var username: String
    -    var age: Int
    -
    -    init(named name: String, age: Int) {
    -        self.username = named
    -        self.age = age
    -    }
    -}
    -
    -let users = database["users"].map(to: User.self)
    -
    - - -

    CRUD operations

    -

    Insert

    -

    Inserting entities is done by using .insert with either one or a sequence of the entity.

    -
    users.insert(user)
    -users.insertAll([user1, user2, user3])
    -
    - - -

    Insert returns a Future<Reply.Insert> which you can use to determine if one (or more) inserts failed. Is reply.ok != 1 the future will not be completed but failed with the reply, instead.

    -
    let reply = users.insertAll([user1, user2, user3])
    -
    -reply.do { success in
    -    print("\(success.n) users inserted")
    -}.catch { error in
    -    // Insert failed!
    -}
    -
    - - -

    Find

    -

    Using find on a collection returns a Cursor<C> where C is the type nested in the collection as demonstrated above.

    -

    find can take 4 arguments. A filter, range, sort and projection.

    -

    There is also findOne, which is useful when you want to exactly one object. This does not support a range expression.

    -

    Filter

    -

    The filter is a MongoKitten Query object.

    -
    // User?
    -guard let joannis = users.findOne("username" == "Joannis" && "active" == true) else {
    -    return
    -}
    -
    - - -

    Range

    -

    A range is any native swift Range. It is used for skip and limit.

    -
    // Cursor with the first 10 users
    -
    -let users1 = users.find(in: ..<10) // Cursor<User>
    -let users2 = users.find(in: 10..<20) // Cursor<User>
    -let users3 = users.find(in: 30...) // Cursor<User>
    -
    - - -

    Sort

    -

    The Sort is used to sort entities in either ascending or descending order based on one or more fields.

    -
    let maleUsers = users.find("gender" == "male", sortedBy: ["age": .ascending]) // Cursor<User>
    -
    - - -

    Projection

    -

    The projection ensures only the specifies subset of fields are returned. Projections can also be computed fields. Remember that the projection must fit within the Collection's Codable type. If this is not a dictionary(-like) type such as Document it will be required that the Document fetched matches the Codable type.

    -
    let adults = users.find("age" >= 21, projecting: ["_id": .excluded, "username": .included]) // Cursor<User>
    -
    - - -

    Count

    -

    Count is similar to a find operation in that it is a read-only operation. Since it does not return the actual data (only an integer of the total entities found) it does not support projections and sorting. It does support a range expression, although it's not often used.

    -
    let totalUsers = users.count() // Future<Int>
    -let femaleUsers = users.count("gender" == "female") // Future<Int>
    -
    - - -

    Update

    -

    Updating can be done on one or all entities. Updates can either update the entire entity or only a subset of fields.

    -
    let user = User(named: "Joannis", age: 111)
    -
    -users.update("username" == "Joannis", to: user)
    -
    - - -

    Update also indicates a success state in the same way insert does.

    -

    Updating fields

    -

    Updating a subset of fields can be done more efficiently using

    -
    // Rename `Joannis` -> `JoannisO`
    -users.update("username" == "Joannis", fields: [
    -    "username": "JoannisO"
    -])
    -
    -// Migrate all users to require a password update
    -users.update(fields: [
    -    "resetPassword": true
    -])
    -
    - - -

    Upsert

    -

    If you don't know if an entity exists but want it inserted/updated accordingly you should use upsert.

    -
    let user = User(named: "Joannis", age: 111)
    -
    -users.upsert("_id" == user._id, to: user)
    -
    - - -

    Remove

    -

    Remove removes the first or all entities matching a query.

    -
    // Remove me!
    -users.remove("username" == "Joannis")
    -
    -// Remove all Dutch users
    -users.removeAll("country" == "NL")
    -
    - - -

    Troubleshooting

    -

    Ambiguous naming

    -

    In some situations you may find that MongoKitten's Database or ClientSettings are ambiguous with another library. The following lines will help get rid of that.

    -
    typealias MongoDB = MongoKitten.Database
    -typealias MongoDBSettings = MongoKitten.ClientSettings
    -
    - - -

    In the above case you'll have to use the aliases instead of the normal references to Database and/or ClientSettings. Alternatively you can prefix the occurences of those instances with MongoKitten., indicating you want the Database object of the MongoKitten module.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/databases/mongodb/bson/index.html b/build/3.0/site/databases/mongodb/bson/index.html deleted file mode 100644 index bba0d7f3..00000000 --- a/build/3.0/site/databases/mongodb/bson/index.html +++ /dev/null @@ -1,1704 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - BSON - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    BSON

    -

    BSON is a performant (not compact) format for storing data in MongoDB. -The BSON module used here is extremely performant and support Codable.

    -

    Primitives

    -

    MongoDB has a set of supported primitives. At the root of any BSON data lies a Document.

    -
      -
    • Double
    • -
    • String
    • -
    • Document (Array and Dictionary)
    • -
    • ObjectId
    • -
    • Bool
    • -
    • Int32
    • -
    • Int (Int64)
    • -
    • Binary
    • -
    • Decimal128 (not supported)
    • -
    • JavascriptCode
    • -
    • Null (not nil)
    • -
    • Date (from Foundation)
    • -
    • MinKey
    • -
    • MaxKey
    • -
    • RegularExpression (BSON Type)
    • -
    -

    Document

    -

    Document is a type that comes in two representations. Array and Dictionary-like. -You should see Document as [(String, Primitive)].

    -

    Array-like Documents ignore the key (String) whilst Dictionary-like Documents require using it. -For this reason, both Document variants are the same struct type and behave the same way.

    -

    You can subscript a dictionary-like document with an integer, and an array-like document by it's key.

    -

    Usage

    -

    The root type of any BSON structure is a Document, please note that MongoDB entities must be a dictionary-like.

    -

    To create a dictionary-like BSON Document:

    -
    // Dictionary document by default
    -var document = Document()
    -
    - - -

    You can also use a dictionary or array literal. This creates the respective BSON document type.

    -
    var arrayDocument: Document = []
    -var dictionaryDocument: Document = [:]
    -
    - - -

    Accessing Dictionary Documents

    -

    To access a dictionary document you must subscript with a key:

    -
    let username = dictionaryDocument["username"] // Primitive?
    -
    - - -

    The return type is a Primitive type, which is a protocol.

    -

    Accessing Array Documents

    -

    To a

    -

    Primitives

    -

    To access the concrete type of the primitive you must either cast the primitive to a concrete type or loosely unwrap the type.

    -

    For the purpose of demonstration we're assuming the following Document:

    -
    var doc: Document = [
    -    "_id": ObjectId(),
    -    "username": "Joannis",
    -    "admin": true,
    -    "year": 2018
    -]
    -
    - - -

    Casting

    -

    Casting is used when you want exactly that type. A good example is a String.

    -
    let username = doc["username"] as? String // String?
    -print(username) // Optional("Joannis")
    -
    - - -

    The following will be nil because they're not a String.

    -
    let _id: = doc["_id"] as? String // String?
    -print(_id) // nil
    -
    -let admin = doc["admin"] as? String // String?
    -print(admin) // nil
    -
    -let year = doc["year"] as? String // String?
    -print(year) // nil
    -
    - - -

    Loosely Converting

    -

    Converting is useful when you don't care about the specifics. -For example, when exposing data over JSON.

    -
    let username = String(lossy: doc["username"]) // String?
    -print(username) // Optional("Joannis")
    -
    - - -

    This converts types to a String when it's sensible:

    -
    let _id: = doc["_id"] as? String // String?
    -print(_id) // Optional("afafafafafafafafafafafaf")
    -
    -let admin = doc["admin"] as? String // String?
    -print(admin) // Optional("true")
    -
    -let year = doc["year"] as? String // String?
    -print(year) // Optional("2018")
    -
    - - -

    Codable

    -

    BSON has highly optimized support for Codable through BSONEncoder and BSONDecoder.

    -
    struct User: Codable {
    -    var _id = ObjectId()
    -    var username: String
    -    var admin: Bool = false
    -    var year: Int
    -
    -    init(named name: String, year: Int) {
    -        self.username = name
    -        self.year = year
    -    }
    -}
    -
    -let user = User(named: "Joannis", year: 2018)
    -
    -let userDocument = try BSONEncoder().encode(user)
    -
    -let username = userDocument["username"] as? String
    -print(username) // Optional("Joannis")
    -
    -let sameUser = try BSONDecoder().decode(User.self, from: userDocument)
    -
    -print(sameUser.username) // "Joannis"
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/databases/mongodb/getting-started/index.html b/build/3.0/site/databases/mongodb/getting-started/index.html deleted file mode 100644 index 011f6083..00000000 --- a/build/3.0/site/databases/mongodb/getting-started/index.html +++ /dev/null @@ -1,1646 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Getting Started - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Using MongoDB

    -

    The vapor/mongodb package is an extremely performant, reactive and pure swift MonogDB driver. It provides a simple interface to MongoDB for powerful features.

    -

    On top of vapor/mysql, we have built vapor/fluent-sqlite which allows SQLite databases to be used with Fluent.

    -

    Just MongoDB

    -

    This package works really well with and without Fluent. To include this package in your project, simply add it to your Package manifest.

    -
    // swift-tools-version:4.0
    -import PackageDescription
    -
    -let package = Package(
    -    name: "Project",
    -    dependencies: [
    -        ...
    -        .package(url: "https://github.com/OpenKitten/MongoKitten.git", .revision("master/5.0")),
    -    ],
    -    targets: [
    -      .target(name: "Project", dependencies: ["MongoKitten", ... ])
    -    ]
    -)
    -
    - - -

    If this is your first time adding a dependency, you should read our introduction to Package.swift.

    -

    If this is your first time using MongoDB, have a look at the setup guides.

    -

    The official documentation assumed either the MongoDB shell or one of their more popular (official) drivers. -Please refer to the MongoKitten interpretation guide before reading the official documentation.

    -

    Use import MongoKitten to access the APIs.

    -

    With Fluent

    -

    To use MongoDB with Fluent, you just need to make sure to add the vapor/fluent-mongodb package to your project.

    -

    To do this, add the Fluent MySQL package to your Package manifest.

    -
    // swift-tools-version:4.0
    -import PackageDescription
    -
    -let package = Package(
    -    name: "Project",
    -    dependencies: [
    -        ...
    -        .package(url: "https://github.com/vapor/fluent-mongodb.git", .revision("beta")),
    -    ],
    -    targets: [
    -      .target(name: "Project", dependencies: ["FluentMongoDB", ... ])
    -    ]
    -)
    -
    - - -

    If this is your first time adding a dependency, you should read our introduction to Package.swift.

    -

    Use import FluentMongoDB to access MongoDB's Fluent compatible APIs. Learn more about the Fluent APIs here

    - - - - -

    More info on working with Fluent can be found here.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/databases/mongodb/interpreting/index.html b/build/3.0/site/databases/mongodb/interpreting/index.html deleted file mode 100644 index a1205cf4..00000000 --- a/build/3.0/site/databases/mongodb/interpreting/index.html +++ /dev/null @@ -1,1706 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Interpreting tutorials - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Interpreting official MongoDB tutorials

    -

    https://docs.mongodb.com/manual/reference/ has a lot of tutorials on every detail of MongoDB. -Using them in MongoKitten can be a bit tricky. This guide will explain how they are best applied.

    -

    Documents/BSON Data

    -

    MongoDB writes Documents in a JSON-like syntax. The following Documents are taken from the documentation on the MongoDB website.

    -
    var document0 = {
    -  "_id" : ObjectId("512bc95fe835e68f199c8686"),
    -  "author" : "dave",
    -  "score" : 80,
    -  "views" : NumberLong(100),
    -  "awesome": true,
    -  "users": [
    -    { _id: 2, user: "ijk123", status: "A" },
    -    { _id: 3, user: "xyz123", status: "P" },
    -    { _id: 4, user: "mop123", status: "P" }
    -  ]
    -}
    -
    - - -

    These Documents read to this equivalent in BSON.

    -
    let document0: Doucment = [
    -  "_id": try ObjectId("512bc95fe835e68f199c8686")
    -  "author": "dave",
    -  "score": Int32(80),
    -  "views": 100,
    -  "aweosme": true,
    -  "users": [
    -    ["_id": 2, "user": "ijk123", "status": "A"],
    -    ["_id": 3, "user": "xyz123", "status": "P"],
    -    ["_id": 4, "user": "mop123", "status": "P"]
    -  ]
    -]
    -
    - - -

    As you see, ObjectId works similarly but initializing an creating ObjectId from a String can throw an error. -More information about BSON, the MongoDB data type on this page.

    -

    Integers in MongoDB are Int32 by default, MongoKitten uses Int64 by default. -MongoDB and MongoKitten can often use the 2 interchangeably with the exception of large numbers that do not fit in an Int32 (and are requested as Int32).

    -

    In Swift your dictionary literals don’t start with { and don’t end with } but instead also use [ and ] for dictionaries.

    -

    Dictionaries in Swift require a String to have quotes (") around them. In JavaScript syntax this is optional.

    -

    Selecting databases/collections

    -

    In the shell use mydatabase selects the database named "mydatabase". From here you can use db.mycollection.find() to fetch all data in the collection.

    -

    In MongoKitten, you can subscript the server and database. -Subscripting the server selects a database, and subscripting the database selects a collection. -Usually you'll be working with a single database, so the following code connects to a single database named "example-db".

    -
    // Connect to the localhost database `example-db` without credentials
    -let database = try Database.connect(server: "mongodb://localhost:27017", database: "mongokitten-example-db", worker: eventLoop).await(on: eventLoop)
    -
    -// Select a collection
    -let mycollection = mydatabase["mycollection"]
    -
    -// Query all entities
    -try mycollection.find()
    -
    - - -

    Common operations in collections

    -

    Most operations are available on a collection object. -By typing mycollection. , Swift will autocomplete a list of available methods.

    -

    For inserting you can use insert which have various parameters which you can use. -Most parameters have a default and can thus be left out of not needed.

    -
    myCollection.insert([
    -    "_id": ObjectId(),
    -    "some": "string",
    -    "date": Date()
    -])
    -
    - - -

    Aggregates

    -

    If you followed this article you should know the basics of MongoKitten aggregates. -If you, at any point, need a specific stage, you can look for the stage by the MongoDB name in the documentation or make use of autocomplete.

    -

    All supported aggregates can be quickly accessed using by simply typing a dot (.) inside the pipeline array literal like you would for enum cases. -This will autocomplete to all available static functions that can help you create a stage.

    -

    If you miss a stage or want to create a stage manually you can do so by providing a BSON Document like this:

    -
    let stage = Stage([
    -  "$match": [
    -    "username": [
    -      "$eq": "Joannis"
    -    ]
    -  ]
    -])
    -
    - - -

    The stage must be equal to the Document representation of the stage operators described here. -You can then add this stage as one of the values in the array.

    -

    Document Queries

    -

    MongoDB queries can work if converted to a Document. -If you do this, you must either use a literal query like below or write it using the query builder described here.

    -
    let query = Query(document)
    -let literalQuery: Query = [
    -  "age": [
    -    "$gte": 15
    -  ]
    -]
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/databases/mysql/basics/index.html b/build/3.0/site/databases/mysql/basics/index.html deleted file mode 100644 index c8701ff4..00000000 --- a/build/3.0/site/databases/mysql/basics/index.html +++ /dev/null @@ -1,1735 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Basics - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    MySQL Basics

    -

    This guide assumes you've set up MySQL and are connected to MySQL using a connection pool as described in the getting started guide.

    -

    Type safety

    -

    The MySQL driver is written to embrace type-safety and Codable. We currently only expose Codable based results.

    -

    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 4 kinds of formats.

    -
      -
    • Stream
    • -
    • Future
    • -
    • forEach
    • -
    • Future
    • -
    -

    All examples assume the following model:

    -
    struct User: Codable {
    -  var username: String
    -  var passwordHash: String
    -  var age: Int
    -}
    -
    - - -

    Futures

    -

    Futures are often easier to use but significantly heavier on your memory and thus performance. They are thoroughly described here

    -

    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.

    -
     // 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.

    -
    struct UserLoginDetails: Codable {
    -  var username: String
    -  var age: Int
    -}
    -
    - - -

    Streams

    -

    Streams, as described on this page, 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.

    -

    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.

    -
    try connection.stream(User.self, in: "SELECT * FROM users", to: inputStream)
    -
    - - -

    This will put all Users into the inputStream. This requires inputStream to be an InputStream accepting User as Input.

    -

    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.

    -
    connection.forEach(User.self, in: "SELECT * FROM users") { user in
    -  print(user.username)
    -}
    -
    - - -

    forEach returns a future that you can optionally capture. It will be completed when all users have been processed.

    -
    let completed = connection.forEach(User.self, in: "SELECT * FROM users") { user in
    -  print(user.username)
    -}
    -
    -completed.do {
    -    print("All users printed")
    -}.catch { error in
    -    print("An error occurred while printing the users: \(error)")
    -}
    -
    - - -

    Single column rows

    -

    When the query returns only one column, you can decode resulting rows as a single value.

    -
    let usernames = connection.all(String.self, in: "SELECT username FROM users") // Future<[String]>
    -
    - - -

    Resultless queries

    -

    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 administrativeQuery command.

    -
    connection.administrativeQuery("DROP TABLE users")
    -
    - - -

    You can handle success or response using the returned future.

    -
    connection.administrativeQuery("DROP TABLE users").then {
    -  print("success")
    -}.catch {
    -  print("failure")
    -}
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/databases/mysql/getting-started/index.html b/build/3.0/site/databases/mysql/getting-started/index.html deleted file mode 100644 index 33167574..00000000 --- a/build/3.0/site/databases/mysql/getting-started/index.html +++ /dev/null @@ -1,1676 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Getting Started - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Using MySQL

    -

    The vapor/mysql package is a lightweight, reactive, async and pure swift MySQL/MariaDB driver. It provides an intuitive interface for working with MySQL that can be used with any Swift project.

    -

    On top of vapor/mysql, we have built vapor/fluent-sqlite which allows SQLite databases to be used with Fluent.

    -

    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-tools-version:4.0
    -import PackageDescription
    -
    -let package = Package(
    -    name: "Project",
    -    dependencies: [
    -        ...
    -        .package(url: "https://github.com/vapor/mysql.git", .revision("beta")),
    -    ],
    -    targets: [
    -      .target(name: "Project", dependencies: ["MySQL", ... ])
    -    ]
    -)
    -
    - - -

    If this is your first time adding a dependency, you should read our introduction to Package.swift.

    -

    Use import MySQL to access the Swift MySQL APIs.

    -

    With Fluent

    -

    MySQL works well Fluent, you just need to make sure to add the vapor/fluent-mysql package to your project.

    -

    To do this, add the Fluent MySQL package to your Package manifest.

    -
    // swift-tools-version:4.0
    -import PackageDescription
    -
    -let package = Package(
    -    name: "Project",
    -    dependencies: [
    -        ...
    -        .package(url: "https://github.com/vapor/fluent-mysql.git", .revision("beta")),
    -    ],
    -    targets: [
    -      .target(name: "Project", dependencies: ["FluentMySQL", ... ])
    -    ]
    -)
    -
    - - -

    If this is your first time adding a dependency, you should read our introduction to Package.swift.

    -

    Use import FluentMySQL to access MySQL's Fluent compatible APIs. Learn more about the Fluent APIs here

    -

    Setting up services

    -

    In order to set up MySQL with Fluent you first need to register the Fluent provider:

    -
    try services.provider(FluentProvider())
    -
    - - -

    After this, you need to register the MySQL config and database.

    -
    services.instance(FluentMySQLConfig())
    -
    -var databaseConfig = DatabaseConfig()
    -
    -let username = "<mysql-user-here>"
    -let password = "<mysql-pass-here>"
    -let database = "<mysql-database>"
    -
    -let db = MySQLDatabase(hostname: "localhost", user: username, password: password, database: database)
    -databaseConfig.add(database: db, as: .mysql)
    -services.instance(databaseConfig)
    -
    - - -

    Notice the variable mysqlDB. This is an identifier for the MySQL database. -You need to create an identifier for each database you attach to Fluent.

    -

    The following identifer can be global, or as a static variable in an extension on DatabaseIdentifier.

    -
    extension DatabaseIdentifier {
    -    static var mysql: DatabaseIdentifier<MySQLDatabase> {
    -        return .init("foo")
    -    }
    -}
    -
    - - -

    Last, you can specify migrations for Fluent to execute. This can be used for creating and altering schemas and migrating data within the table.

    -

    Fluent Models that conform to the protocol Migration automatically inherit a migration for schema creation. Nothing needs to be programmed for this.

    -
    var migrationConfig = MigrationConfig()
    -migrationConfig.add(model: MyModel.self, database: .mysql)
    -services.instance(migrationConfig)
    -
    - - -

    More info on working with Fluent can be found here.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/databases/mysql/prepared-statements/index.html b/build/3.0/site/databases/mysql/prepared-statements/index.html deleted file mode 100644 index b724a059..00000000 --- a/build/3.0/site/databases/mysql/prepared-statements/index.html +++ /dev/null @@ -1,1630 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Prepared Statements - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    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:

    -
    SELECT * FROM users WHERE username = ?
    -
    - - -

    Preparing a statement

    -

    To prepare a statement from a query you call the withPreparation function on Connection.

    -
    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.

    -

    You must bind the same or a similar type to the one related to the field being bound to. Bindings must be added in the same order they appear in the query.

    -
    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.

    -

    Binding encodable data

    -

    When inserting entities, often you need to encode all of the values to the query, preferably with type safety.

    -
    try binding.withEncoder { encoder in
    -    try model.encode(to: encoder)
    -}
    -
    - - -

    You can use this withEncoder closure to bind multiple structures, too.

    -

    Reading the query's results

    -

    You can then use the Future or Streaming query functions as described in the basics to receive the queried results from the prepared and bound statement object.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/fluent/database/index.html b/build/3.0/site/fluent/database/index.html deleted file mode 100644 index 0cce4f4a..00000000 --- a/build/3.0/site/fluent/database/index.html +++ /dev/null @@ -1,1565 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Database - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Fluent Database

    -

    Coming soon.

    -

    Connection

    -

    Coming soon.

    -

    Logger

    -

    Coming soon.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/fluent/getting-started/index.html b/build/3.0/site/fluent/getting-started/index.html deleted file mode 100644 index 1cf4e25f..00000000 --- a/build/3.0/site/fluent/getting-started/index.html +++ /dev/null @@ -1,1887 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Getting Started - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Adding Fluent to your Project

    -

    Fluent (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.

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    databaselibrarydriverguide
    PostgreSQLvapor/postgresvapor/fluent-postgresPostgreSQL → Package
    MySQLvapor/mysqlvapor/fluent-mysqlMySQL → Package
    SQLitevapor/sqlitevapor/fluent-sqliteSQLite → Package
    MongoDBmongokitten/mongokittenvapor/fluent-mongokittenREADME.md
    -
    -

    Tip

    -

    Any database can be made to work with Fluent by conforming to its Database protocol. -For a list of all compatible database types, search GitHub for the fluent-driver topic.

    -
    -

    Fluent

    -

    After you have added your database driver, simply add the Fluent package to your Package manifest.

    -
    // swift-tools-version:4.0
    -import PackageDescription
    -
    -let package = Package(
    -    name: "Project",
    -    dependencies: [
    -        ...
    -        .package(url: "https://github.com/vapor/fluent.git", .upToNextMajor(from: "3.0.0")),
    -    ],
    -    targets: [
    -      .target(name: "Project", dependencies: ["Fluent", ... ])
    -    ]
    -)
    -
    - - -

    If this is your first time adding a dependency, you should read our introduction to Package.swift.

    -
    -

    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).

    -

    Configuring Fluent

    -

    Fluent integrates seamlessly into your Vapor project using services. -In this section we will add the Fluent service provider to your application and configure your databases.

    -
    -

    Warning

    -

    This section assumes you have added both Fluent and a Fluent database to your package.

    -
    -

    Service Provider

    -

    The first step to using Fluent, is registering it with your Vapor application.

    -
    import Fluent
    -
    -...
    -
    -try services.instance(FluentProvider())
    -
    - - -

    Register the FluentProvider in the configure section of your application.

    -
    -

    Question

    -

    Learn more about how service providers work in Getting Started: Application -and Concepts: Services.

    -
    -

    Config

    -

    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 -keep track of this identifier is to add it as static extension to DatabaseIdentifier.

    -
    import Fluent
    -import FluentMySQL
    -import FluentSQLite
    -
    -extension DatabaseIdentifier {
    -    static var foo: DatabaseIdentifier<SQLiteDatabase> {
    -        return .init("foo")
    -    }
    -
    -    static var bar: DatabaseIdentifier<MySQLDatabase> {
    -        return .init("bar")
    -    }
    -}
    -
    - - -

    Now we can use the identifier anywhere in our project:

    -
    req.database(.foo) { ... }
    -
    - - -

    The configure section 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 of your project.

    -

    You can add databases to the DatabaseConfig using either a type (.self) or an instance.

    -

    Type

    -

    If you register a database type (like SQLiteDatabase.self), Fluent will ask the application -to create an instance of your database at boot.

    -
    import Fluent
    -import FluentSQLite
    -
    -...
    -
    -var databaseConfig = DatabaseConfig()
    -
    -databaseConfig.add(database: SQLiteDatabase.self, as: .foo)
    -
    -services.instance(databaseConfig)
    -
    - - -

    Instance

    -

    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.

    -
    import Fluent
    -import FluentMySQL
    -
    -...
    -
    -var databaseConfig = DatabaseConfig()
    -
    -let mysql = MySQLDatabase(...)
    -databaseConfig.add(database: mysql, as: .bar)
    -
    -services.instance(databaseConfig)
    -
    - - -

    Migrations

    -

    If your database uses schemas (most SQL databases do, whereas NoSQL databases don't), you will also want to configure -your migrations using MigrationConfig.

    -
    import Fluent
    -
    -...
    -
    -var migrationConfig = MigrationConfig()
    -
    -migrationConfig.add(migration: User.self, database: .foo)
    -
    -services.instance(migrationConfig)
    -
    - - -

    You can read more about migrations in Fluent: Migrations.

    -

    Done

    -

    You should now be able to compile and run your application. The next step is to create your models.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/fluent/migration/index.html b/build/3.0/site/fluent/migration/index.html deleted file mode 100644 index 197322a7..00000000 --- a/build/3.0/site/fluent/migration/index.html +++ /dev/null @@ -1,1565 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Migration - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Fluent Migration

    -

    Coming soon.

    -

    Protocol

    -

    Coming soon.

    -

    Config

    -

    Coming soon.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/fluent/migrations/index.html b/build/3.0/site/fluent/migrations/index.html deleted file mode 100644 index 8bc6a21e..00000000 --- a/build/3.0/site/fluent/migrations/index.html +++ /dev/null @@ -1,1698 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Migrations - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Getting Started with Migrations

    -

    Migrations are a way of making organized, testable, and reliable changes to your database's structure-- -even while it's in production!

    -

    Migrations are often used for preparing a database schema for your models. However, they can also be used to -make normal queries to your database.

    -

    In this guide we will cover creating both types of migrations.

    -

    Model Schema

    -

    Let's take a look at how we can prepare a schema supporting database to accept the -User model from the previous section.

    -

    Just like we did with the Model protocol, we will conform our User to Migration.

    -
    import Fluent
    -
    -extension User: Migration {
    -
    -}
    -
    - - -

    Swift will inform us that User does not yet conform. Let's add the required methods!

    -

    Prepare

    -

    The first method to implement is prepare. This method is where you make any of your -desired changes to the database.

    -

    For our User model, we simply want to create a table that can store one or more users. To do this, -we will use the .create(...) function on the supplied database connection.

    -
    extension User: Migration {
    -    /// See Migration.prepare
    -    static func prepare(on connection: MySQLConnection) -> Future<Void> {
    -        return connection.create(self) { builder in
    -            try builder.field(for: \.id)
    -            try builder.field(for: \.name)
    -            try builder.field(for: \.age)
    -        }
    -    }
    -}
    -
    - - -

    We pass self (shorthand for User.self since this is a static method) as the first argument to the .create method. This indicates -to Fluent that we would like to create a schema for the User model.

    -

    Next, we pass a closure that accepts a SchemaBuilder for our User model. -We can then call .fieldon this builder to describe what fields we'd like our table to have.

    -

    Since we are passing key paths to our User model (indicated by \.), Fluent can see what type those properties are. -For most common types (String, Int, Double, etc) Fluent will automatically be able to determine the best -database field type to use.

    -

    You can also choose to manually select which database field type to use for a given field.

    -
    try builder.field(type: .text, for: \.name)
    -
    - - -

    Learn more about creating, updating, and deleting schemas in Fluent → Schema Builder.

    -

    Revert

    -

    Revert is the opposite of prepare. It's job is to undo anything that was done in prepare. It is used when you boot your -app with the --revert option. See Fluent → Migration for more information.

    -

    To implement revert for our model, we simply use .delete to indicate that we would like to delete the schema created for User.

    -
    extension User: Migration {
    -    /// See Migration.revert
    -    static func revert(on connection: MySQLConnection) -> Future<Void> {
    -        return connection.delete(self)
    -    }
    -}
    -
    - - -

    Example

    -

    We now have a fully functioning model with migration!

    -
    extension TestUser: Migration {
    -    /// See Migration.prepare
    -    static func prepare(on connection: SQLiteConnection) -> Future<Void> {
    -        return connection.create(self) { builder in
    -            try builder.field(for: \.id)
    -            try builder.field(for: \.name)
    -            try builder.field(for: \.age)
    -        }
    -    }
    -
    -    /// See Migration.revert
    -    static func revert(on connection: SQLiteConnection) -> Future<Void> {
    -        return connection.delete(self)
    -    }
    -}
    -
    - - -

    Done

    -

    Now that you have a working Fluent model and migration, you can move onto querying your model.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/fluent/models/index.html b/build/3.0/site/fluent/models/index.html deleted file mode 100644 index e88f1045..00000000 --- a/build/3.0/site/fluent/models/index.html +++ /dev/null @@ -1,1735 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Models - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Getting Started with Models

    -

    Models are the heart of Fluent. Unlike ORMs in other languages, Fluent doesn't return untyped -arrays or dictionaries for queries. Instead, you query the database using models. This allows the -Swift compiler to catch many errors that have burdened ORM users for ages.

    -

    In this guide, we will cover the creation of a basic User model.

    -

    Class

    -

    Every Fluent model starts with a Codable class. You can make any Codable class a Fluent model, -even ones that come from a different module. All you have to do is conform to Model.

    -
    import Foundation
    -import Vapor
    -
    -final class User: Content {
    -    var id: UUID?
    -    var name: String
    -    var age: Int
    -}
    -
    - - -

    Although it's not necessary, adding final to your Swift classes can make them more performant -and also make adding init methods in extensions easier.

    -

    Conforming to Model

    -

    Now that we have our User class, let's conform it to Model.

    -
    import FluentMySQL
    -
    -extension User: Model {
    -
    -}
    -
    - - -

    Once you add this conformance requirement, Swift will tell you that it does not yet conform. -Let's add the necessary items to make User conform to model.

    -
    -

    Tip

    -

    We recommend adding Model conformance in an extension to help keep your code clean.

    -
    -

    Database

    -

    The first step to conforming to Model is to let Fluent know which type of database you plan -on using this model with. This allows Fluent to enable database-specific features wherever you -use this model.

    -
    import FluentMySQL
    -
    -extension User: Model {
    -    ...
    -
    -    /// See Model.Database
    -    typealias Database = MySQLDatabase
    -}
    -
    - - -

    ID

    -

    Now we can tell Fluent what type of ID this model uses. In this example, our User model -has an ID property of type UUID named id.

    -
    import FluentMySQL
    -import Foundation
    -
    -extension User: Model {
    -    ...
    -
    -    /// See Model.ID
    -    typealias ID = UUID
    -
    -    /// See Model.idKey
    -    static var idKey: IDKey {
    -        return \.id
    -    }
    -}
    -
    - - -

    You can use any type that conforms to IDType as a Fluent ID. See Fluent → Model → ID for more information. -You can also use any property name you'd like for the id.

    -
    -

    Warning

    -

    Some databases require certain ID keys. For example, MongoDB requires _id.

    -
    -

    Example

    -

    We now have a fully-conformed Fluent model!

    -
    import FluentMySQL
    -import Foundation
    -import Vapor
    -
    -final class User: Codable {
    -    var id: UUID?
    -    var name: String
    -    var age: Int
    -}
    -
    -extension User: Model {
    -    /// See Model.Database
    -    typealias Database = MySQLDatabase
    -
    -    /// See Model.ID
    -    typealias ID = UUID
    -
    -    /// See Model.idKey
    -    static var idKey: IDKey {
    -        return \.id
    -    }
    -}
    -
    - - -

    Done

    -

    Now that you have a working Fluent model, you can move onto querying your model. -However, if your database uses schemas, you may need to create a migration for your model first.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/fluent/pivot/index.html b/build/3.0/site/fluent/pivot/index.html deleted file mode 100644 index db7d5c15..00000000 --- a/build/3.0/site/fluent/pivot/index.html +++ /dev/null @@ -1,1506 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Pivot - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Fluent Pivot

    -

    Coming soon.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/fluent/query-builder/index.html b/build/3.0/site/fluent/query-builder/index.html deleted file mode 100644 index 7f7980a7..00000000 --- a/build/3.0/site/fluent/query-builder/index.html +++ /dev/null @@ -1,1749 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Query Builder - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Fluent Query Builder

    -

    Coming soon.

    -

    Filter

    -

    Coming soon.

    -

    Compare

    -

    Coming soon.

    -

    Group

    -

    Coming soon.

    -

    Subset

    -

    Coming soon.

    -

    Join

    -

    Coming soon.

    -

    Range

    -

    Coming soon.

    -

    Sort

    -

    Coming soon.

    -

    Execute

    -

    Coming soon.

    -

    Aggregate

    -

    Coming soon.

    -

    All

    -

    Coming soon.

    -

    First

    -

    Coming soon.

    -

    Query

    -

    Coming soon.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/fluent/querying/index.html b/build/3.0/site/fluent/querying/index.html deleted file mode 100644 index 7e9f159d..00000000 --- a/build/3.0/site/fluent/querying/index.html +++ /dev/null @@ -1,1897 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Querying - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Querying Models

    -

    Once you have a model (and optionally a migration) you can start -querying your database to create, read, update, and delete data.

    -

    Connection

    -

    The first thing you need to query your database, is a connection to it. Luckily, they are easy to get.

    -

    You can use either the application or an incoming request to create a database connection. You just need -access to the DatabaseIdentifier.

    -

    Request

    -

    The preferred method for getting access to a database connection is via an incoming request.

    -
    router.get(...) { req in
    -    return req.withConnection(to: .foo) { db in
    -        // use the db here
    -    }
    -}
    -
    - - -

    The first parameter is the database's identifier. The second parameter is a closure -that accepts a connection to that database.

    -
    -

    Tip

    -

    Although the closure to .withConnection(to: ...) accepts a database connection, we often use just db for short.

    -
    -

    The closure is expected to return a Future<Void>. When this future is completed, the connection will be released -back into Fluent's connection pool. This is usually acheived by simply returning the query as we will soon see.

    -

    Application

    -

    You can also create a database connection using the application. This is useful for cases where you must access -the database from outside a request/response event.

    -
    let res = app.withConnection(to: .foo) { db in
    -    // use the db here
    -}
    -print(res) // Future<T>
    -
    - - -

    This is usually done in the boot section of your application.

    -
    -

    Warning

    -

    Do not use database connections created by the application in a route closure (when responding to a request). -Always use the incoming request to create a connection to avoid threading issues.

    -
    -

    Create

    -

    To create (save) a model to the database, first initialize an instance of your model, then call .save(on: ).

    -
    router.post(...) { req in
    -    return req.withConnection(to: .foo) { db -> Future<User> in
    -        let user = User(name: "Vapor", age: 3)
    -        return user.save(on: db).transform(to: user) // Future<User>
    -    }
    -}
    -
    - - -

    Response

    -

    .save(on: ) returns a Future<Void> that completes when the user has finished saving. In this example, we then -map that Future<Void> to a Future<User> by calling .map and passing in the recently-saved user.

    -

    You can also use .map to return a simple success response.

    -
    router.post(...) { req in
    -    return req.withConnection(to: .foo) { db -> Future<HTTPResponse> in
    -        let user = User(name: "Vapor", age: 3)
    -        return user.save(on: db).map(to: HTTPResponse.self) {
    -            return HTTPResponse(status: .created)
    -        }
    -    }
    -}
    -
    - - -

    Multiple

    -

    If you have multiple instances to save, do so using an array. Arrays containing only futures behave like futures.

    -
    router.post(...) { req in
    -    return req.withConnection(to: .foo) { db -> Future<HTTPResponse> in
    -        let marie = User(name: "Marie Curie", age: 66)
    -        let charles = User(name: "Charles Darwin", age: 73)
    -        return [
    -            marie.save(on: db),
    -            charles.save(on: db)
    -        ].map(to: HTTPResponse.self) {
    -            return HTTPResponse(status: .created)
    -        }
    -    }
    -}
    -
    - - -

    Read

    -

    To read models from the database, use .query() on the database connection to create a QueryBuilder.

    -

    All

    -

    Fetch all instances of a model from the database using .all().

    -
    router.get(...) { req in
    -    return req.withConnection(to: .foo) { db -> Future<[User]> in
    -        return db.query(User.self).all()
    -    }
    -}
    -
    - - -

    Filter

    -

    Use .filter(...) to apply filters to your query.

    -
    router.get(...) { req in
    -    return req.withConnection(to: .foo) { db -> Future<[User]> in
    -        return try db.query(User.self).filter(\User.age > 50).all()
    -    }
    -}
    -
    - - -

    First

    -

    You can also use .first() to just get the first result.

    -
    router.get(...) { req in
    -    return req.withConnection(to: .foo) { db -> Future<User> in
    -        return try db.query(User.self).filter(\User.name == "Vapor").first().map(to: User.self) { user in
    -            guard let user = user else {
    -                throw Abort(.notFound, reason: "Could not find user.")
    -            }
    -
    -            return user
    -        }
    -    }
    -}
    -
    - - -

    Notice we use .map(to:) here to convert the optional user returned by .first() to a non-optional -user, or we throw an error.

    -

    Update

    -
    router.put(...) { req in
    -    return req.withConnection(to: .foo) { db -> Future<User> in
    -        return db.query(User.self).first().map(to: User.self) { user in
    -            guard let user = $0 else {
    -                throw Abort(.notFound, reason: "Could not find user.")
    -            }
    -
    -            return user
    -        }.flatMap(to: User.self) { user in
    -            user.age += 1
    -            return user.update(on: db).map(to: User.self) { user }
    -        }
    -    }
    -}
    -
    - - -

    Notice we use .map(to:) here to convert the optional user returned by .first() to a non-optional -user, or we throw an error.

    -

    Delete

    -
    router.delete(...) { req in
    -    return req.withConnection(to: .foo) { db -> Future<User> in
    -        return db.query(User.self).first().map(to: User.self) { user in
    -            guard let user = $0 else {
    -                throw Abort(.notFound, reason: "Could not find user.")
    -            }
    -            return user
    -        }.flatMap(to: User.self) { user in
    -            return user.delete(on: db).transfom(to: user)
    -        }
    -    }
    -}
    -
    - - -

    Notice we use .map(to:) here to convert the optional user returned by .first() to a non-optional -user, or we throw an error.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/fluent/relations/index.html b/build/3.0/site/fluent/relations/index.html deleted file mode 100644 index 4b952b40..00000000 --- a/build/3.0/site/fluent/relations/index.html +++ /dev/null @@ -1,1565 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Relations - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Fluent Relations

    -

    Coming soon.

    -

    Parent / Child

    -

    Coming soon.

    -

    Siblings

    -

    Coming soon.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/fluent/schema-builder/index.html b/build/3.0/site/fluent/schema-builder/index.html deleted file mode 100644 index 0fe1958f..00000000 --- a/build/3.0/site/fluent/schema-builder/index.html +++ /dev/null @@ -1,1597 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Schema Builder - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Fluent Schema Builder

    -

    Coming soon.

    -

    Create

    -

    Coming soon.

    -

    Update

    -

    Coming soon.

    -

    Delete

    -

    Coming soon.

    -

    References

    -

    Coming soon.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/fluent/transaction/index.html b/build/3.0/site/fluent/transaction/index.html deleted file mode 100644 index 8021466b..00000000 --- a/build/3.0/site/fluent/transaction/index.html +++ /dev/null @@ -1,1506 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Transaction - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Fluent Transactions

    -

    Coming soon.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/getting-started/application/index.html b/build/3.0/site/getting-started/application/index.html deleted file mode 100644 index 6cdb851b..00000000 --- a/build/3.0/site/getting-started/application/index.html +++ /dev/null @@ -1,1590 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Application - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Application

    -

    Every Vapor project has an Application. You use the application to create any services -you might need while developing.

    -

    The best place to access the application is in your project's boot.swift file.

    -
    import Vapor
    -
    -public func boot(_ app: Application) throws {
    -    // your code here
    -}
    -
    - - -

    You can also access the application from your routes.swift file. It's stored -as a property there.

    -
    import Vapor
    -
    -final class Routes: RouteCollection {
    -    ...
    -}
    -
    - - -

    Unlike some other web frameworks, Vapor doesn't support statically accessing the application. -If you need to access it from another class or struct, you should pass through a method or initializer.

    -
    -

    Info

    -

    Avoiding static access to variables helps make Vapor performant by preventing -the need for thread-safe locks or semaphores.

    -
    -

    Services

    -

    The application's main function is to make services. For example, you might need a BCryptHasher to hash -some passwords before storing them in a database. You can use the application to create one.

    -
    import BCrypt
    -
    -let bcryptHasher = try app.make(BCryptHasher.self)
    -
    - - -

    Or you might use the application to create an HTTP client.

    -
    let client = try app.make(Client.self)
    -let res = client.get("http://vapor.codes")
    -
    - - -

    Learn more about services in Services → Getting Started.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/getting-started/cloud/index.html b/build/3.0/site/getting-started/cloud/index.html deleted file mode 100644 index e5070872..00000000 --- a/build/3.0/site/getting-started/cloud/index.html +++ /dev/null @@ -1,1577 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Deployment - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Deployment

    -

    Deploying code is the process of making your Vapor project publically available. -It can be one of the most difficult aspects of web development. Fortunately, there -are services to help.

    -

    Vapor Cloud

    -

    The best way to deploy your application is through Vapor Cloud. It's a cloud platform built -specifically for the Vapor web framework. This means it's incredibly easy to deploy your -project quickly and be confident that it will be fast and stable.

    -

    Deploying your project to Vapor Cloud is simple, it's built right into the Vapor Toolbox. -Just run this command from within the root directory of your project.

    -
    vapor cloud deploy
    -
    - - -

    For a detailed guide, visit Vapor Cloud → Quick Start.

    -

    Other Options

    -

    Vapor can be deployed anywhere that supports Ubuntu (basically everywhere). To learn more about -deploying your code, checkout Deploy → Getting Started

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/getting-started/content/index.html b/build/3.0/site/getting-started/content/index.html deleted file mode 100644 index b1ab176f..00000000 --- a/build/3.0/site/getting-started/content/index.html +++ /dev/null @@ -1,1768 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Content - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Content

    -

    In Vapor 3, all content types (JSON, protobuf, FormURLEncoded, Multipart, etc) are treated the same. -All you need to parse and serialize content is a Codable class or struct.

    -

    For this introduction, we will use JSON as an example. But keep in mind the API is the same for any supported content type.

    -

    Request

    -

    Let's take a look at how you would parse the following HTTP request.

    -
    POST /login HTTP/1.1
    -Content-Type: application/json
    -
    -{
    -    "email": "user@vapor.codes",
    -    "password": "don't look!"
    -}
    -
    - - -

    Decode Request

    -

    First, create a struct or class that represents the data you expect.

    -
    import Foundation
    -import Vapor
    -
    -struct LoginRequest: Content {
    -    var email: String
    -    var password: String
    -}
    -
    - - -

    Then simply conform this struct or class to Content. -Now we are ready to decode that HTTP request.

    -
    router.post("login") { req -> Response in
    -    let loginRequest = try req.content.decode(LoginRequest.self)
    -
    -    print(loginRequest.email) // user@vapor.codes
    -    print(loginRequest.password) // don't look!
    -
    -    return Response(status: .ok)
    -}
    -
    - - -

    It's that simple!

    -

    Other Request Types

    -

    Since the request in the previous example declared JSON as it's content type, -Vapor knows to use a JSON decoder automatically. -This same method would work just as well for the following request.

    -
    POST /login HTTP/1.1
    -Content-Type: application/x-www-form-urlencoded
    -
    -email=user@vapor.codes&don't+look!
    -
    - - -
    -

    Tip

    -

    You can configure which encoders/decoders Vapor uses. Read on to learn more.

    -
    -

    Response

    -

    Let's take a look at how you would create the following HTTP response.

    -
    HTTP/1.1 200 OK
    -Content-Type: application/json
    -
    -{
    -    "name": "Vapor User",
    -    "email": "user@vapor.codes"
    -}
    -
    - - -

    Encode Response

    -

    Just like decoding, first create a struct or class that represents the data your expect.

    -
    import Foundation
    -import Vapor
    -
    -struct User: Content {
    -    var name: String
    -    var email: String
    -}
    -
    - - -

    Then just conform this struct or class to Content. -Now we are ready to encode that HTTP response.

    -
    router.get("user") { req -> Response in
    -    let user = User(
    -        name: "Vapor User",
    -        email: "user@vapor.codes"
    -    )
    -
    -    let res = Response(status: .ok)
    -    try res.content.encode(user, as: .json)
    -    return res
    -}
    -
    - - -

    Other Response Types

    -

    Content will automatically encode as JSON by default. You can always override which content type is used -using the as: parameter.

    -
    try res.content.encode(user, as: .formURLEncoded)
    -
    - - -

    You can also change the default media type for any class or struct.

    -
    struct User: Content {
    -    /// See Content.defaultMediaType
    -    static let defaultMediaType: MediaType = .formURLEncoded
    -
    -    ...
    -}
    -
    - - -

    Configuring Content

    -

    Coming soon.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/getting-started/controllers/index.html b/build/3.0/site/getting-started/controllers/index.html deleted file mode 100644 index 48353cc4..00000000 --- a/build/3.0/site/getting-started/controllers/index.html +++ /dev/null @@ -1,1604 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Controllers - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Controllers

    -

    Controllers are a great way to organize your code. They are collections of methods that accept -a request and return a response.

    -

    A good place to put your controllers is in the Controllers folder.

    -

    Methods

    -

    Let's take a look at an example controller.

    -
    import Vapor
    -
    -final class HelloController {
    -    func greet(_ req: Request) throws -> String {
    -        return "Hello!"
    -    }
    -}
    -
    - - -

    Controller methods should always accept a Request and return something ResponseRepresentable. -This also includes futures whose expectations are ResponseRepresentable (i.e, Future<String>).

    -

    To use this controller, we can simply initialize it, then pass the method to a router.

    -
    let helloController = HelloController()
    -router.get("greet", use: helloController.greet)
    -
    - - -

    Use Services

    -

    You will probably want to access your application's services from within your controllers. -Luckily this is easy to do. First, declare what services your controller needs in its init method. Then store them -as properties on the controller.

    -
    final class HelloController {
    -    let hasher: BCryptHasher
    -
    -    init(hasher: BCryptHasher) {
    -        self.hasher = hasher
    -    }
    -
    -    ...
    -}
    -
    - - -

    Next, use the application to create these services when you initialize your controller.

    -
    let helloController = try HelloController(
    -    hasher: app.make()
    -)
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/getting-started/futures/index.html b/build/3.0/site/getting-started/futures/index.html deleted file mode 100644 index dc0203a6..00000000 --- a/build/3.0/site/getting-started/futures/index.html +++ /dev/null @@ -1,1763 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Futures - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Futures

    -

    You may have noticed some APIs in Vapor expect or return a Future<T> 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.

    -

    Promises and Futures are two strongly related types. Every promise has a future. -A promise is a write-only entity that has the ability to complete (or fail) it's Future counterpart.

    -

    Futures are a read-only entity that can have a successful or error case. Successful cases are called the "Expectation".

    -

    Futures can be used to register callbacks to, which will always executed in the order of registration. Promises can only be completed once. If a promise is completed more than once the input will be ignored.

    -

    Basics

    -

    Creating a promise is when the result is returned in the future at an unknown time. -For the sake of demonstration, however, the promise will be completed at a predefined point in time and execution.

    -

    Within the .do block you may not throw an error or return a result.

    -
    let promise = Promise<String>()
    -let future = promise.future // Future<String>
    -
    -future.do { string in
    -    print(string)
    -}
    -
    -promise.complete("Hello")
    -
    - - -

    The above code prints "Hello" in the console.

    -

    Errors

    -

    When running the above code, you may have noticed a warning pop up. This is because the .do block only handles successful completions. If we were to replace the completion with the following code the .do block would never get run:

    -
    struct MyError: Error {}
    -
    -promise.fail(MyError())
    -
    - - -

    Instead, a .catch block will be triggered.

    -
    future.do { string in
    -    print(string)
    -}.catch { error in
    -    print("Error '\(error)' occurred")
    -}
    -
    - - -

    In this scenario the test "Error 'MyError' occurred" will appear.

    -

    Within the .catch block you may not throw an error or return a result.

    -

    Basic Transformations

    -

    Transformations are one of the more critical parts of Vapor 3's future system. They assist in reducing the complexity of futures and keep code isolated and readable. You can use the .map function to transform the future expectation to another future of the same or a different type. You need to explicitly state which type will be returned in the mapping closure.

    -

    The mapping closure(s) will only be executed if an expectation has been received in the previous step. If at any point a transformation function throws an error, execution stops there and the .catch block will be executed.

    -

    If the promise that was mapped failed to begin with, the .catch block will also be executed without triggering any mapping closures.

    -
    let promise = Promise<Int>()
    -
    -promise.future.do { int in
    -    print(int)
    -}.map(to: Int.self) { int in
    -    return int + 4
    -}.map(to: String.self) { int in
    -    return int.description
    -}.do { string in
    -    print(string)
    -}.catch { error in
    -    print("Error '\(error)' occurred")
    -}
    -
    -promise.complete(3)
    -
    - - -

    The above code will print the inputted integer. Then map the input to (integer + 4) == 7. -Then the textual representation of the integer is returned as a String which will be printed.

    -

    This results in the following console output:

    -
    3
    -7
    -
    - - -

    Recursive futures

    -

    In the above map function we returned a new result synchronously. In some situations, however, you'll need to dispatch another asynchronous call based on the result of a previous call.

    -

    First, let's see how this would work out using map by exaggerating synchronous code as if it were an asynchronous call.

    -
    -

    Warning

    -

    Do not use this implementation, use the next one instead. This is an unnecessarily complicated way of nesting futures.

    -
    -
    let promise = Promise<Int>()
    -
    -promise.map(to: Future<Int>.self) { int in
    -    return Future(int + 4)
    -}.map(to: Future<Future<String>>.self) { futureInt in
    -    return futureInt.map(to: Future<String.self>) { int in
    -        return Future(int.description)
    -    }
    -}.do { doubleFutureString in // Future<Future<String>>
    -    doubleFutureString.do { futureString in // Future<String>
    -      futureString.do { string in
    -          print(string)
    -      }.catch { error in
    -          print("Error '\(error)' occurred")
    -      }
    -    }.catch { error in
    -        print("Error '\(error)' occurred")
    -    }
    -}.catch { error in
    -    print("Error '\(error)' occurred")
    -}
    -
    -promise.complete(3)
    -
    - - -

    To flatten this asynchronous recursion, instead, we recommend using flatMap. -The type supplied in the to: argument is implied to be wrapped in a Future<>.

    -
    let promise = Promise<Int>()
    -
    -promise.flatMap(to: Int.self) { int in
    -    return Future<Int>(int + 4)
    -}.flatMap(to: String.self) { int in
    -    return Future(int.description)
    -}.do { string in
    -    print(string)
    -}.catch { error in
    -    print("Error '\(error)' occurred")
    -}
    -
    -promise.complete(3)
    -
    - - -

    Always

    -

    Sometimes you want to always execute a function as part of the cleanup phase. -You can use the .always block to execute a block of code after the future has been successfully executed (and mapped if applicable) or when an error occurs. Please do consider that always also will be executed in the order in which it has been registered, like all other closures.

    -
    var i = 0
    -
    -let promise = Promise<Int>()
    -let future = promise.future // Future<Int>
    -
    -future.do { int in
    -    i += int * 3
    -}.do { int in
    -    i += (int - 1)
    -}.always {
    -    print(i)
    -    i = 0
    -}.catch { _ in
    -    i = -1
    -}
    -
    - - -

    At the end of the above function, i will always be 0. If the promise is completed with the successful result i, the number "11" will be printed. On error, "-1" will be printed.

    -

    Signals

    -

    Signals, or Future<Void> is a Future that can contain either an Error or Void (the Expectation). Future<Void> is often used to indicate the successful or unsuccessful completion of a task.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/getting-started/hello-world/index.html b/build/3.0/site/getting-started/hello-world/index.html deleted file mode 100644 index 88e66136..00000000 --- a/build/3.0/site/getting-started/hello-world/index.html +++ /dev/null @@ -1,1631 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Hello, world - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Hello, world

    -

    Now that you've installed Vapor, let's create your first Vapor app! -This guide will take you step by step through creating a new project, building, and running it.

    -

    New Project

    -

    The first step is to create a new Vapor project on your computer. -For this guide, we will call the project Hello.

    -

    Open up your terminal, and use Vapor Toolbox's new command.

    -
    vapor new Hello
    -
    - - -
    -

    Warning

    -

    Make sure to add --branch=beta while using Vapor 3 pre-release.

    -
    -

    Once that finishes, change into the newly created directory.

    -
    cd Hello
    -
    - - -

    Generate Xcode Project

    -

    Let's now use the Vapor Toolbox's xcode command to generate an Xcode project. -This will allow us to build and run our app from inside of Xcode, just like an iOS app.

    -
    vapor xcode
    -
    - - -

    The toolbox will ask you if you'd like to open Xcode automatically, select yes.

    -

    Build & Run

    -

    You should now have Xcode open and running. Select the run scheme from the scheme menu, -then click the play button.

    -

    You should see the terminal pop up at the bottom of the screen.

    -
    Server starting on localhost:8080
    -
    - - -

    Visit Localhost

    -

    Open your web browser, and visit localhost:8080/hello →

    -

    You should see the following page.

    -
    Hello, world!
    -
    - - -

    Congratulations on creating, building, and running your first Vapor app! 🎉

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/getting-started/routing/index.html b/build/3.0/site/getting-started/routing/index.html deleted file mode 100644 index 2e3dd9b1..00000000 --- a/build/3.0/site/getting-started/routing/index.html +++ /dev/null @@ -1,1662 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Routing - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Routing

    -

    Routing is the process of finding the appropriate response to an incoming request.

    -

    Making a Router

    -

    In Vapor the default Router is the EngineRouter. You can implement custom routers by implementing one conformant to the Router protocol.

    -
    let router = try EngineRouter.default()
    -
    - - -

    There are two APIs available, one is supplied by the Routing library and a set of helpers is available in Vapor itself.

    -

    We recommend using the helpers and will continue to describe those here.

    -

    Registering a route

    -

    Imagine you want to return a list of users when someone visits GET /users. -Leaving authorization on the side, that would look something like this.

    -
    router.get("users") { req in
    -    return // fetch the users
    -}
    -
    - - -

    In Vapor, routing is usually done using the .get, .put, .post, .patch and .delete shorthands. -You can supply the path as / or comma-separated strings. We recommend comma separated, as it's more readable.

    -
    router.get("path", "to", "something") { ... }
    -
    - - -

    Routes

    -

    The best place to add routes is in the routes.swift file. -You will find a router there that is ready to use.

    -
    import Vapor
    -
    -final class Routes: RouteCollection {
    -    ...
    -
    -    func boot(router: Router) throws {
    -        router.get("hello") { req in
    -            return "Hello, world!"
    -        }
    -    }
    -}
    -
    - - -

    You must return a Future containing a ResponseEncodable here. -The most common ResponseEncodable types are Content, Response amd View.

    -

    Parameters

    -

    Sometimes you may want one of the components of your route path to be dynamic. This is often used when -you want to get an item with a supplied identifier, i.e., GET /users/:id

    -
    router.get("users", Int.parameter) { req -> Future<String> in
    -    let id = try req.parameter(Int.self)
    -    return // fetch the user with id
    -}
    -
    - - -

    Instead of passing a string, pass the type of parameter you expect. In this case, our User has an Int ID.

    -
    -

    Tip

    -

    You can define your own custom parameter types as well.

    -
    -

    After registering your routes

    -

    After registering your routes you must register the Router as a Service

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/getting-started/spm/index.html b/build/3.0/site/getting-started/spm/index.html deleted file mode 100644 index 8f29609b..00000000 --- a/build/3.0/site/getting-started/spm/index.html +++ /dev/null @@ -1,1696 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SPM - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Managing your project

    -

    The Swift Package Manager (SPM for short) is used for building your project's source code and dependencies. -It's a similar idea to Cocoapods, Ruby gems, and NPM. Most of the time the Vapor Toolbox will -interact with SPM on your behalf. However, it's important to understand the basics.

    -
    -

    Tip

    -

    Learn more about SPM on Swift.org →

    -
    -

    Package Manifest

    -

    The first place SPM looks in your project is the package manfiest. This should always be located in the root -directory of your project and named Package.swift.

    -

    Dependencies

    -

    Dependencies are other SPM packages that your package relies on. All Vapor applications rely on the Vapor package, -but you can add as many other dependencies as you want.

    -
    // swift-tools-version:4.0
    -import PackageDescription
    -
    -let package = Package(
    -    name: "VaporApp",
    -    dependencies: [
    -        // 💧 A server-side Swift web framework. 
    -        .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),
    -    ],
    -    targets: [ ... ]
    -)
    -
    - - -

    In the above example, you can see vapor/vapor → version 3.0 -or later is a dependency of this package. -When you add a dependency to your package, you must next signal which targets depend on -the newly available modules.

    -
    -

    Warning

    -

    Anytime you modify the package manifest, call vapor update to effect the changes.

    -
    -

    Targets

    -

    Targets are all of the modules, executables, and tests that your package contains.

    -
    // swift-tools-version:4.0
    -import PackageDescription
    -
    -let package = Package(
    -    name: "VaporApp",
    -    dependencies: [ ... ],
    -    targets: [
    -        .target(name: "App", dependencies: ["Vapor"]),
    -        .target(name: "Run", dependencies: ["App"]),
    -        .testTarget(name: "AppTests", dependencies: ["App"]),
    -    ]
    -)
    -
    - - -

    Most Vapor apps will have three targets, although you can add as many as you like to organize your code. -Each target declares which modules it depends on. You must add module names here in order to import them in your code. -A target can depend on other targets in your project or any modules exposed by packages you've added to -the main dependencies array.

    -
    -

    Tip

    -

    Executable targets (targets that contain a main.swift file) cannot be imported by other modules. -This is why Vapor has both an App and a Run target. -Any code you include in App can be tested in the AppTests.

    -
    -

    Folder Structure

    -

    Below is the typical folder structure for an SPM package.

    -
    .
    -├── Sources
    -│   ├── App
    -│   │   └── (Source code)
    -│   └── Run
    -│       └── main.swift
    -├── Tests
    -│   └── AppTests
    -└── Package.swift
    -
    - - -

    Each .target corresponds to a folder in the Sources folder. -Each .testTarget corresponds to a folder in the Tests folder.

    -

    Troubleshooting

    -

    If you are experiencing problems with SPM, sometimes cleaning your project can help.

    -
    vapor clean
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/getting-started/structure/index.html b/build/3.0/site/getting-started/structure/index.html deleted file mode 100644 index 9f5de92f..00000000 --- a/build/3.0/site/getting-started/structure/index.html +++ /dev/null @@ -1,1806 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Folder Structure - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Structure

    -

    This section explains the structure of a typical Vapor application to help get -you familiar with where things go.

    -

    Folder Structure

    -

    Vapor's folder structure builds on top of SPM's folder structure.

    -
    .
    -├── Public
    -├── Sources
    -│   ├── App
    -│   │   ├── Controllers
    -│   │   ├── Models
    -│   │   ├── boot.swift
    -│   │   ├── configure.swift
    -│   │   └── routes.swift
    -│   └── Run
    -│       └── main.swift
    -├── Tests
    -│   └── AppTests
    -└── Package.swift
    -
    - - -

    Let's take a look at what each of these folders and files does.

    -

    Public

    -

    This folder contains any public files that will be served by your app. -This is usually images, style sheets, and browser scripts.

    -

    Whenever Vapor responds to a request, it will first check if the requested -item is in this folder. If it is, it skips your application logic and returns -the file immediately.

    -

    For example, a request to localhost:8080/favicon.ico will check to see -if Public/favicon.ico exists. If it does, Vapor will return it.

    -

    Sources

    -

    This folder contains all of the Swift source files for your project. -The top level folders (App and Run) reflect your package's modules, -as declared in the package manifest.

    -

    App

    -

    This is the most important folder in your application, it's where all of -the application logic goes!

    -

    Controllers

    -

    Controllers are great way of grouping together application logic. Most controllers -have many functions that accept a request and return some sort of response.

    -
    -

    Tip

    -

    Vapor supports, but does not enforce the MVC pattern

    -
    -

    Models

    -

    The Models folder is a great place to store your Content structs or -Fluent Models.

    -

    boot.swift

    -

    This file contains a function that will be called after your application has booted, -but before it has started running. This is a great place do things that should happen -every time your application starts.

    -

    You have access to the Application here which you can use to create -any services you might need.

    -

    configure.swift

    -

    This file contains a function that receives the config, environment, and services for your -application as input. This is a great place to make changes to your config or register -services to your application.

    -

    routes.swift

    -

    This file contains the main route collection for your app. This is where you should assign routes -for your controller methods.

    -

    You'll notice there's one example route in there that returns the "hello, world" response we saw earlier.

    -

    You can create as many route collections as you want to further organize your code. Just make sure -to register them in this main route collection.

    -

    Tests

    -

    Each non-executable module in your Sources folder should have a corresponding ...Tests folder.

    -

    AppTests

    -

    This folder contains the unit tests for code in your App module. -Learn more about testing in Testing → Getting Started.

    -

    Package.swift

    -

    Finally is SPM's package manifest.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/getting-started/toolbox/index.html b/build/3.0/site/getting-started/toolbox/index.html deleted file mode 100644 index 5f714ce0..00000000 --- a/build/3.0/site/getting-started/toolbox/index.html +++ /dev/null @@ -1,1717 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Toolbox - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Install Toolbox

    -

    Vapor's command line interface provides shortcuts and assistance for common tasks.

    -

    Vapor Toolbox

    -

    Help prints useful information about available commands and flags.

    -
    vapor --help
    -
    - - -

    You can also run the --help option on any Toolbox command.

    -
    vapor new --help
    -
    - - -

    The --help flag should be your goto for learning about the toolbox as it is the most up-to-date.

    -

    New

    -

    The Toolbox's most important feature is helping you create a new project.

    -
    vapor new <name>
    -
    - - -

    Just pass the name of your project as the first argument to the new command.

    -
    -

    Note

    -

    Project names should be PascalCase →, like HelloWorld or MyProject.

    -
    -

    Templates

    -

    By default, Vapor will create your new project from the API template. You can choose -a different template by passing the --template flag.

    - - - - - - - - - - - - - - - - - - - - -
    NameFlagDescription
    API--template=apiJSON API with Fluent database.
    Web--template=webHTML website with Leaf templates.
    -
    -

    Info

    -

    There are lots of unofficial Vapor templates on GitHub under the vapor + template topcs →. -You can use these by passing the full GitHub URL to the --template option.

    -
    -

    Build & Run

    -

    You can use the toolbox to build and run your Vapor app.

    -
    vapor build
    -vapor run
    -
    - - -
    -

    Tip

    -

    We recommend building and running through Xcode if you have a Mac. -It's a bit faster and you can set breakpoints! -Just use vapor xcode to generate an Xcode project.

    -
    -

    Updating

    -

    The toolbox should be updated by the package manager it was installed with.

    -

    Homebrew

    -
    brew upgrade vapor
    -
    - - -

    APT

    -
    sudo apt-get update
    -sudo apt-get install vapor
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/getting-started/xcode/index.html b/build/3.0/site/getting-started/xcode/index.html deleted file mode 100644 index f502ee82..00000000 --- a/build/3.0/site/getting-started/xcode/index.html +++ /dev/null @@ -1,1600 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Xcode - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Xcode

    -

    If you're on a Mac, you can develop your Vapor project using Xcode. -You can build, run, and stop your server from within Xcode, as well as use breakpoints and instruments to debug your code.

    -

    Xcode 9 running Vapor

    -

    Xcode is a great way to develop Vapor apps, but you can use any text editor you like.

    -

    Generate Project

    -

    To use Xcode, you just need to generate an Xcode project using Vapor Toolbox.

    -
    vapor xcode
    -
    - - -
    -

    Tip

    -

    Don't worry about comitting the generated Xcode Project to git, just generate a new -one whenever you need it.

    -
    -

    Run

    -

    To build and run your Vapor app, first make sure you have the Run scheme selected from the schemes menu. -Also make sure to select "My Mac" as the device.

    -

    Run Scheme

    -

    Once that's selected, just click the play button or press Command + R on your keyboard.

    -

    Test

    -

    To run your unit tests, select the scheme ending in -Package and press Command + U.

    -
    -

    Warning

    -

    There may be a few extraneous schemes in the dropdown menu. Ignore them!

    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/http/body/index.html b/build/3.0/site/http/body/index.html deleted file mode 100644 index be232044..00000000 --- a/build/3.0/site/http/body/index.html +++ /dev/null @@ -1,1581 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Body - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    HTTPBody

    -

    HTTPBody is a type that contains the raw representation of a Request or Response. It's contents are related to the Content-Type header.

    -

    Body can contain, but is not limited to Data, String, DispatchData or binary streams. -Binary streams will be chunk-encoded in HTTP/1.

    -

    Creating a Body

    -

    Empty bodies can be created using the empty initializer Body(). -Alternatively you can provide Data or DispatchData as the content of the body.

    -

    HTTPBodyRepresentable

    -

    If you want to serialize your data to a body using a predefined format such as JSON or XML, look into Content first.

    -

    When adding a new struct/class that can be serialized to a raw Body as part of a Request or Response you can consider implementing the HTTPBodyRepresentable protocol.

    -

    Below is how String is implemented.

    -
    /// String can be represented as an HTTP body.
    -extension String: HTTPBodyRepresentable {
    -    /// See BodyRepresentable.makeBody()
    -    public func makeBody() throws -> HTTPBody {
    -
    -    }
    -}
    -
    - - -

    Although often unnecessary it is possible to throw an error here if the creation of the body failed.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/http/client/index.html b/build/3.0/site/http/client/index.html deleted file mode 100644 index ba90a63d..00000000 --- a/build/3.0/site/http/client/index.html +++ /dev/null @@ -1,1596 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Client - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    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 an eventloop to run on. The EventLoop is described in the async concepts introduction.

    -
    // Future<HTTPClient>
    - let client = try HTTPClient.connect(
    -    to: "example.com",
    -    ssl: true,
    -    on: eventLoop
    - )
    -
    - - -

    You can override the port by specifying a custom port using the following parameters:

    -
    // Future<HTTPClient>
    - let client = try HTTPClient.connect(
    -    to: "localhost",
    -    port: 8080,
    -    ssl: false,
    -    on: eventLoop
    - )
    -
    - - -

    Sending Requests

    -

    From here, you can send Requests. You can only send one request at a time. Sending a request before a Response has been received has unpredictable consequences.

    -
    // Future<Response>
    -let response = client.flatMap(to: Response.self) { connectedClient in
    -  let request = Request(
    -    method: .get,
    -    uri: "https://example.com/"
    -  )
    -
    -  return connectedClient.send(request: request)
    -}
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/http/cookies/index.html b/build/3.0/site/http/cookies/index.html deleted file mode 100644 index 374d1e1a..00000000 --- a/build/3.0/site/http/cookies/index.html +++ /dev/null @@ -1,1633 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cookies - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Cookies

    -

    Cookies are used to store data on the client-side between multiple requests. They're often used for keeping track of a user for various reasons. One of the more important purposes is to store a session cookie containing identification of an account.

    -

    Creating cookies

    -

    Vapor has three related objects for Cookies.

    -

    The Cookies object is an array of multiple Cookie objects.

    -

    The Cookie object is a single key-value pair. Where the key is the Cookie name.

    -

    The Value object contains a String representing the Cookie's Value and optional attributes with metadata such as the expiration date of the Cookie.

    -

    Values

    -

    Values can be initialized with a String Literal.

    -
    var value: Cookie.Value = "String Literal"
    -
    - - -

    They can be manipulated to add other properties.

    -
    // Expires in one day
    -value.expires = Date().addingTimeInterval(24 * 3600)
    -
    - - - -

    Creating a Cookie requires a name and a Value.

    -
    let cookie = Cookie(named: "session", value: value)
    -
    - - -

    Multiple cookies

    -

    Cookies can be initialized with a dictionary literal.

    -
    let cookies: Cookies = [
    -  "session": "String Literal"
    -]
    -
    - - -

    The above will create a single cookie named "session" with a value of "String Literal".

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/http/getting-started/index.html b/build/3.0/site/http/getting-started/index.html deleted file mode 100644 index 7cef53aa..00000000 --- a/build/3.0/site/http/getting-started/index.html +++ /dev/null @@ -1,1587 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Getting Started - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Using HTTP

    -

    HTTP is a module as part of the Engine library containing all HTTP related APIs.

    -

    With Vapor

    -

    This package is included with Vapor by default, just add:

    -
    import HTTP
    -
    - - -

    Without Vapor

    -

    To include it in your package, add the following to your Package.swift file.

    -
    // swift-tools-version:4.0
    -import PackageDescription
    -
    -let package = Package(
    -    name: "Project",
    -    dependencies: [
    -        ...
    -        .package(url: "https://github.com/vapor/engine.git", .upToNextMajor(from: "3.0.0")),
    -    ],
    -    targets: [
    -      .target(name: "Project", dependencies: ["HTTP", ... ])
    -    ]
    -)
    -
    - - -

    If this is your first time adding a dependency, you should read our introduction to Package.swift.

    -

    Use import HTTP to access HTTP's APIs.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/http/headers/index.html b/build/3.0/site/http/headers/index.html deleted file mode 100644 index 6a6ca078..00000000 --- a/build/3.0/site/http/headers/index.html +++ /dev/null @@ -1,1638 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Headers - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Headers

    -

    HTTP Headers are the metadata of a request/response. They can provide a wide variety of information.

    -

    Headers are an array of key-value pairs. As such it's possible, but not common for multiple pairs to have the same key.

    -

    Creating a Headers object

    -

    The most common syntax for creating Headers is a dictionary literal.

    -
    let headers: Headers = [
    -  .contentType: "text/html"
    -]
    -
    - - -

    The left hand side (key) is a Header.Name.

    -

    A name can also be initialized with a String literal.

    -
    let headers: Headers = [
    -  "Content-Type": "text/html"
    -]
    -
    - - -

    Accessing headers

    -

    There are two ways to access Headers. Either by accessing a single (the first) value, or all values.

    -

    A single value example:

    -
    let headers: Headers = [
    -  .contentType: "text/html"
    -]
    -
    -print(headers[.contentType]) // prints "text/html"
    -
    - - -

    Accessing all values example:

    -
    let headers: Headers = [
    -  .setCookie: "session=afasfwrw3qr241j4qwmdsijfo13k43",
    -  .setCookie: "awesome=true"
    -]
    -
    -// prints ["session=afasfwrw3qr241j4qwmdsijfo13k43", "awesome=true"]
    -print(headers[valuesFor: .contentType])
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/http/method/index.html b/build/3.0/site/http/method/index.html deleted file mode 100644 index e0e348f7..00000000 --- a/build/3.0/site/http/method/index.html +++ /dev/null @@ -1,1537 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Methods - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Method

    -

    Methods are used to indicate the type of operation requested for a route. They're part exclusively in HTTP Requests and are required.

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    MethodPurpose
    .getUsed for retrieving content
    .headUsed for retrieving content metadata
    .putUsed for replacing content
    .postUsed for creating content
    .deleteUsed for deleting content
    -

    A path is used for specifying a specific resource/content. The method influences the type of interaction with this resource/content.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/http/middleware/index.html b/build/3.0/site/http/middleware/index.html deleted file mode 100644 index 773f1349..00000000 --- a/build/3.0/site/http/middleware/index.html +++ /dev/null @@ -1,1613 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Middleware - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Middleware

    -

    Middleware are a step in Vapor's responder chain. They're capable of modifying Requests/Responses, preventing the chain from continuing and transforming the data flow.

    -

    They can be employed for authorization checks, logging and a wide range of other functionalities.

    -

    Implementing a Middleware

    -

    The following example is a middleware that will prevent all requests from going to their respective responder unless the origin has a special header set. In the case of a missing header, status code 404 (not found) will be returned.

    -

    Don't secure your APIs using this example code, it's very unsafe and exclusively to be used as a test.

    -
    public final class SpecialHeaderCheckMiddleware: Middleware {
    -  public func respond(to request: Request, chainingTo next: Responder) throws -> Future<Response> {
    -    guard request.headers["Secret-Header"] == "MagicK3y" else {
    -      return Response(status: .notFound)
    -    }
    -
    -    return try next.respond(to: request)
    -  }
    -}
    -
    - - -

    Intercepting/transforming Responses

    -

    The following example demonstrates a middleware that creates a session token for new users.

    -
    // For the random number
    -import Crypto
    -
    -struct InvalidString : Swift.Error {}
    -
    -public final class SessionTokenMiddleware: Middleware {
    -  func generateSessionToken() throws -> String {
    -    // Generate token here ...
    -    let base64 = Base64Encoder.encode(OSRandom().data(count: 32))
    -
    -    // Convert to a String
    -    guard let string = String(bytes: base64, encoding: .utf8) else {
    -      // This can never happen, but throw an error anyways
    -      throw InvalidString()
    -    }
    -
    -    return string
    -  }
    -
    -  public func respond(to request: Request, chainingTo next: Responder) throws -> Future<Response> {
    -    let response = try next.respond(to: request)
    -
    -    // If the session cookie is not set
    -    guard request.cookies["session"] != nil else {
    -      // Set a new session token
    -      response.cookies["session"] = try generateSessionToken()
    -
    -      return response
    -    }
    -
    -    return response
    -  }
    -}
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/http/multipart/index.html b/build/3.0/site/http/multipart/index.html deleted file mode 100644 index 6bce44de..00000000 --- a/build/3.0/site/http/multipart/index.html +++ /dev/null @@ -1,1598 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Multipart - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Multipart Forms

    -

    Multipart is a module that is primarily used with Forms. Multipart is used for complex forms containing one or more files, input fields and other HTML form data.

    -

    Extracting from a request

    -

    MultipartForm is a type of Content and can be extracted like any type of Content.

    -
    let form = try MultipartForm.decode(from: request) // Future<MultipartForm>
    -
    - - -

    A future is returned because of streaming/reactive body parsing.

    -

    Parsing a multipart form

    -

    Multipart forms can be parsed using MultipartParser provided a body and boundary to read.

    -
    let form = try MultipartParser(body: httpBody, boundary: boundaryBytes).parse()
    -
    - - -

    Reading forms

    -

    The parsed form is an array of Part instances. -Each of them contains data and headers.

    -

    You can read a part using either manually or using the MultipartForm's helpers.

    -
    let pictureData = try form.getFile(named: "profile-picture")
    -
    - - -
    let newPassword = try form.getString(named: "password")
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/http/status/index.html b/build/3.0/site/http/status/index.html deleted file mode 100644 index d9845f5e..00000000 --- a/build/3.0/site/http/status/index.html +++ /dev/null @@ -1,1817 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Status codes - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Status codes

    -

    Status codes are exclusively part of the HTTP Response and are required.

    -

    Status codes are a 3 digit number.

    -

    The first of the 3 numbers indicated the type of response.

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    _xxMeaning
    1xxInformational response
    2xxSuccess
    3xxRedirection
    4xxClient error
    5xxServer error
    -

    The other 2 numbers in a status code are used to define a specific code.

    -

    Selecting a status code

    -

    The enum Status has all supported status codes. It can be accessed using a . or created using an integer literal.

    -
    let ok = Status.ok
    -let notFound = Status.notFound
    -
    - - -
    let ok: Status = 200
    -let notFound: Status = 404
    -
    - - -

    Informational responses

    -

    Informational responses indicate a Request was received and understood.

    -

    101 - switching protocols

    -

    Switching Protocols is a status code used to upgrade the connection to a different protocol. Commonly used by WebSocket or HTTP/2.

    -

    Success responses

    -

    Success responses indicate that the request was received, understood, accepted and processed.

    -

    200 - OK

    -

    200, or "OK" is the most common status code. It's used to indicate successful processing of the Request.

    -

    Redirection responses

    -

    Redirection responses indicate the client must take additional action to complete the request. Many of these status codes are used in URL redirection.

    -

    Client error responses

    -

    Client errors indicate an error was caused by the client.

    -

    400 - Bad Request

    -

    The error was caused by the client sending an invalid request.

    -

    For example an invalid message, malformed request syntax or too large request size.

    -

    403 - Forbidden

    -

    The client does not have the permissions to execute this operation on the specified resource.

    -

    404 - Not found

    -

    The requested resource does not exist.

    -

    Server error responses

    -

    Server errors occur when the an error occurred on the server side.

    -

    500 - Internal Server Error

    -

    Internal server errors are almost exclusively used when an error occurred on the server.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/http/uri/index.html b/build/3.0/site/http/uri/index.html deleted file mode 100644 index 00fae12f..00000000 --- a/build/3.0/site/http/uri/index.html +++ /dev/null @@ -1,1565 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - URI - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    URI

    -

    URIs or "Uniform Resource Identifiers" are used for defining a resource.

    -

    They consist of the following components:

    -
      -
    • scheme
    • -
    • authority
    • -
    • path
    • -
    • query
    • -
    • fragment
    • -
    -

    Creating an URI

    -

    URIs can be created from it's initializer or from a String literal.

    -
    let stringLiteralURI: URI = "http://localhost:8080/path"
    -let manualURI: URI = URI(
    -    scheme: "http",
    -    hostname: "localhost",
    -    port: 8080,
    -    path: "/path"
    -)
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/images/droplet-color.svg b/build/3.0/site/images/droplet-color.svg deleted file mode 100644 index 11c540fb..00000000 --- a/build/3.0/site/images/droplet-color.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - Vapor - The Vapor droplet logo in pink and blue. - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/images/droplet-white.svg b/build/3.0/site/images/droplet-white.svg deleted file mode 100644 index 9f645196..00000000 --- a/build/3.0/site/images/droplet-white.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - Vapor - The Vapor droplet logo in white. - - - - \ No newline at end of file diff --git a/build/3.0/site/index.html b/build/3.0/site/index.html deleted file mode 100644 index 1c44aa53..00000000 --- a/build/3.0/site/index.html +++ /dev/null @@ -1,1633 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Vapor Documentation

    -

    This is the documentation for Vapor, a Web Framework for Swift that works on iOS, macOS, and Ubuntu; and all of the packages that Vapor offers.

    -

    Vapor is the most used web framework for Swift. It provides a beautifully expressive and easy to use foundation for your next website or API.

    -

    Getting Started

    -

    If this is your first time using Vapor, head to the Install → macOS section to install Swift and Vapor.

    -

    Once you have Vapor installed, check out Getting Started → Hello, world to create your first Vapor app!

    -

    Like Vapor?

    -

    Our small team works hard to make Vapor awesome (and free). Support the framework by starring Vapor on GitHub -or donating $1 monthly—it helps us a lot. Thanks!

    -

    Other Sources

    -

    Here are some other great places to find information about Vapor.

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    namedescriptionlink
    Vapor SlackChat with ~5,000 Vapor developers.visit →
    API docsAuto-generated documentation from code comments.visit →
    Stack OverflowAsk and answer questions with the vapor tag.visit →
    Source CodeLearn how Vapor works under the hood.visit →
    GitHub IssuesReport bugs or request features on GitHub.visit →
    -

    Providers

    -

    Vapor providers are a convenient way to add functionality to your Vapor projects. -For a full list of providers, check out the vapor-provider tag on GitHub.

    -

    Authors

    -

    Tanner Nelson, Logan Wright, Joannis Orlandos, and the hundreds of members of Vapor.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/install/macos/index.html b/build/3.0/site/install/macos/index.html deleted file mode 100644 index 77cd62f2..00000000 --- a/build/3.0/site/install/macos/index.html +++ /dev/null @@ -1,1672 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - macOS - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Install on macOS

    -

    To use Vapor on macOS, you just need to have Xcode 9 or greater installed.

    -
    -

    Tip

    -

    You need to install Xcode to install Swift, but after that you can use any text editor -you like to develop Vapor apps.

    -
    -

    Install Xcode

    -

    Install Xcode 9 or greater from the Mac App Store.

    -

    Xcode 9.1

    -
    -

    Warning

    -

    After Xcode has been downloaded, you must open it to finish the installation. This may take a while.

    -
    -

    Verify Installation

    -

    Double check the installation was successful by opening Terminal and running:

    -
    swift --version
    -
    - - -

    You should see output similar to:

    -
    Apple Swift version 4.0.2 (swiftlang-900.0.69.2 clang-900.0.38)
    -Target: x86_64-apple-macosx10.9
    -
    - - -

    Vapor requires Swift 4.

    -

    Install Vapor

    -

    Now that you have Swift 4, let's install the Vapor Toolbox.

    -

    The toolbox includes all of Vapor's dependencies as well as a handy CLI tool for creating new projects.

    -
    brew install vapor/tap/vapor
    -
    - - -
    -

    Tip

    -

    If you don't already have Homebrew installed, install it at brew.sh →

    -
    -

    Verify Installation

    -

    Double check the installation was successful by opening Terminal and running:

    -
    vapor --help
    -
    - - -

    You should see a long list of available commands.

    -

    Done

    -

    Now that you have installed Vapor, create your first app in Getting Started → Hello, world.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/install/ubuntu/index.html b/build/3.0/site/install/ubuntu/index.html deleted file mode 100644 index 3ddc221e..00000000 --- a/build/3.0/site/install/ubuntu/index.html +++ /dev/null @@ -1,1803 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Ubuntu - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Install on Ubuntu

    -

    Installing Vapor on Ubuntu only takes a couple of minutes.

    -

    Supported

    -

    Vapor supports the same versions of Ubuntu that Swift supports.

    - - - - - - - - - - - - - - - - - - - - - -
    VersionCodename
    16.10Yakkety Yak
    16.04Xenial Xerus
    14.04Trusty Tahr
    -

    APT Repo

    -

    Add Vapor's APT repo to get access to all of Vapor's Ubuntu packages.

    -

    Quick Script

    -

    Easily add Vapor's APT repo with this handy script.

    -
    eval "$(curl -sL https://apt.vapor.sh)"
    -
    - - -
    -

    Tip

    -

    This command requires curl which can be installed using sudo apt-get install curl

    -
    -

    Dockerfile

    -

    When configuring Ubuntu from a Dockerfile, adding the APT repo can be done via this command:

    -
    RUN /bin/bash -c "$(wget -qO- https://apt.vapor.sh)"
    -
    - - -

    Manual

    -

    Or add the repo manually.

    -
    wget -q https://repo.vapor.codes/apt/keyring.gpg -O- | sudo apt-key add -
    -echo "deb https://repo.vapor.codes/apt $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/vapor.list
    -sudo apt-get update
    -
    - - -

    Install Vapor

    -

    Now that you have added Vapor's APT repo, you can install the required dependencies.

    -
    sudo apt-get install swift vapor
    -
    - - -

    Verify Installation

    -

    Double check everything worked with the following commands.

    -

    Swift

    -
    swift --version
    -
    - - -

    You should see output similar to:

    -
    Apple Swift version 4.0.2 (swiftlang-900.0.69.2 clang-900.0.38)
    -Target: x86_64-apple-macosx10.9
    -
    - - -

    Vapor requires Swift 4.

    -

    Vapor Toolbox

    -
    vapor --help
    -
    - - -

    You should see a long list of available commands.

    -

    Done

    -

    Now that you have installed Vapor, create your first app in Getting Started → Hello, world.

    -

    Swift.org

    -

    Check out Swift.org's guide to using downloads if you need more detailed instructions for installing Swift 4.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/leaf/basics/index.html b/build/3.0/site/leaf/basics/index.html deleted file mode 100644 index 59b7be96..00000000 --- a/build/3.0/site/leaf/basics/index.html +++ /dev/null @@ -1,1957 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Basics - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Basics

    -

    Welcome to Leaf. Leaf's goal is to be a simple templating language that can make generating views easier. There are plenty of great templating languages, so use what's best for you – maybe that's Leaf! The goals of Leaf are:

    -
      -
    • Small set of strictly enforced rules
    • -
    • Consistency
    • -
    • Parser first mentality
    • -
    • Extensibility
    • -
    • Asynchronous and reactive
    • -
    -

    Rendering a template

    -

    Once you have Leaf installed, you should create a directory called “Resources” inside your project folder, and inside that create another directory called “Views”. This Resources/Views directory is the default location for Leaf templates, although you can change it if you want.

    -

    To render a basic Leaf template from a route, add this code:

    -
    router.get { req -> Future<View> in
    -    let leaf = try req.make(LeafRenderer.self)
    -    let context = [String: String]()
    -    return try leaf.make("home", context)
    -}
    -
    - - -

    That will load home.leaf in the Resources/Views directory and render it. The context dictionary is there to let you provide custom data to render inside the template, but you might find it easier to use codable structs instead because they provide extra type safety. For example:

    -
    struct HomePage: Codable {
    -    var title: String
    -    var content: String
    -}
    -
    - - -

    Async

    -

    Leaf's engine is completely reactive, supporting both streams and futures. One of the only ones of it's kind.

    -

    When working with Future results, simply pass the Future in your template context. -Streams that carry an encodable type need to be encoded before they're usable within Leaf.

    -
    struct Profile: Codable {
    -    var friends: EncodableStream
    -    var currentUser: Future<User>
    -}
    -
    - - -

    In the above context, the currentUser variable in Leaf will behave as being a User type. Leaf will not read the user Future if it's not used during rendering.

    -

    EncodableStream will behave as an array of LeafData, only with lower memory impact and better performance. It is recommended to use EncodableStream for (large) database queries.

    -
    Your name is #(currentUser.name).
    -
    -#for(friend in friends) {
    -    #(friend.name) is a friend of you.
    -}
    -
    - - -

    Template syntax

    -

    Structure

    -

    Leaf tags are made up of four elements:

    -
      -
    • Token: # is the token
    • -
    • Name: A string that identifies the tag
    • -
    • Parameter List: () May accept 0 or more arguments
    • -
    • Body (optional): {} Must be separated from the parameter list by a space
    • -
    -

    There can be many different usages of these four elements depending on the tag's implementation. Let's look at a few examples of how Leaf's built-in tags might be used:

    -
      -
    • #()
    • -
    • #(variable)
    • -
    • #embed("template")
    • -
    • #set("title") { Welcome to Vapor }
    • -
    • #count(friends)
    • -
    • #for(friend in friends) { <li>#(friend.name)</li> }
    • -
    -

    Working with context

    -

    In our Swift example from earlier, we used an empty [String: String] dictionary for context, which passes no custom data to Leaf. To try rendering content, use this code instead:

    -
    let context = ["title": "Welcome", "message": "Vapor and Leaf work hand in hand"]
    -return try leaf.make("home", context)
    -
    - - -

    That will expose title and message to our Leaf template, which can then be used inside tags. For example:

    -
    <h1>#(title)</h1>
    -<p>#(message)</p>
    -
    - - -

    Checking conditions

    -

    Leaf is able to evaluate a range of conditions using its #if tag. For example, if you provide a variable it will check that variable exists in its context:

    -
    #if(title) {
    -    The title is #(title)
    -} else {
    -    No title was provided.
    -}
    -
    - - -

    You can also write comparisons, for example:

    -
    #if(title == "Welcome") {
    -    This is a friendly web page.
    -} else {
    -    No strangers allowed!
    -}
    -
    - - -

    If you want to use another tag as part of your condition, you should omit the # for the inner tag. For example:

    -
    #if(lowercase(title) == "welcome") {
    -    This is a friendly web page.
    -} else {
    -    No strangers allowed!
    -}
    -
    - - -

    Loops

    -

    If you provide an array of items, Leaf can loop over them and let you manipulate each item individually using its #for tag. For example, we could update our Swift code to provide a list of names in a team:

    -
    let context = ["team": ["Malcolm", "Kaylee", "Jayne"]]
    -
    - - -

    We could then loop over them in Leaf like this:

    -
    #for(name in team) {
    -    <p>#(name) is in the team.</p>
    -}
    -
    - - -

    Leaf provides some extra variables inside a #for loop to give you more information about the loop's progress:

    -
      -
    • The loop.isFirst variable is true when the current iteration is the first one.
    • -
    • The loop.isLast variable is true when it's the last iteration.
    • -
    • The loop.index variable will be set to the number of the current iteration, counting from 0.
    • -
    -

    Embedding templates

    -

    Leaf’s #embed tag allows you to copy the contents of one template into another. When use this, you should always omit the template file's .leaf extension.

    -

    Embedding is useful for copying in a standard piece of content, for example a page footer or advert code:

    -
    #embed("footer")
    -
    - - -

    This tag is also useful for building one template on top of another. For example, you might have a master.leaf file that includes all the code required to lay out your website – HTML structure, CSS and JavaScript – with some gaps in place that represent where page content varies.

    -

    Using this approach, you would construct a child template that fills in its unique content, then embeds the parent template that places the content appropriately.

    -

    For example, you might create a child.leaf template like this:

    -
    #set("body") {
    -<p>Welcome to Vapor!</p>
    -}
    -
    -#embed("master")
    -
    - - -

    That configures one item of context, body, but doesn’t display it directly. Instead, it embeds master.leaf, which can render body along with any other context variables passed in from Swift. For example, master.leaf might look like this:

    -
    <html>
    -<head><title>#(title)</title></head>
    -<body>#(body)</body>
    -</html>
    -
    - - -

    When given the context ["title": "Hi there!"], child.leaf will render as follows:

    -
    <html>
    -<head><title>Hi there!</title></head>
    -<body><p>Welcome to Vapor!</p></body>
    -</html>
    -
    - - -

    Other tags

    -

    #capitalize

    -

    The #capitalize tag uppercases the first letter of any string. For example, “taylor” will become “Taylor”.

    -
    #capitalize(name)
    -
    - - -

    #contains

    -

    The #contains tag accepts an array and a value as its two parameters, and returns true if the array in parameter one contains the value in parameter two. For example, given the array team:

    -
    #if(contains(team, "Jayne")) {
    -    You're all set!
    -} else {
    -    You need someone to do PR.
    -}
    -
    - - -

    #count

    -

    The #count tag returns the number of items in an array. For example:

    -
    Your search matched #count(matches) pages.
    -
    - - -

    #lowercase

    -

    The #lowercase tag lowercases all letters in a string. For example, “Taylor” will become “taylor”.

    -
    #lowercase(name)
    -
    - - -

    #uppercase

    -

    The #uppercase tag uppercases all letters in a string. For example, “Taylor” will become “TAYLOR”.

    -
    #uppercase(name)
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/leaf/custom-tags/index.html b/build/3.0/site/leaf/custom-tags/index.html deleted file mode 100644 index 84e1b622..00000000 --- a/build/3.0/site/leaf/custom-tags/index.html +++ /dev/null @@ -1,1555 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Custom tags - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Custom Tags

    -

    You can extend Leaf to provide your own tags that add custom functionality. To demonstrate this, let's look at a basic example by recreating #uppercase together. This tag will take one argument, which is the string to uppercase.

    -

    When working with custom tags, there are four important things to know:

    -
      -
    1. You should call requireParameterCount() with the number of parameters you expect to receive. This will throw an error if your tag is used incorrectly.
    2. -
    3. If you do or do not require a body, you should use either requireBody() or requireNoBody(). Again, this will throw an error if your tag is used incorrectly.
    4. -
    5. You can read individual parameters using the parameters array. Each parameter will be of type LeafData, which you can convert to concrete data types using properties such as .string, .dictionary, and so on.
    6. -
    7. You must return a Future<LeafData?> containing what should be rendered. In the example below we wrap the resulting uppercase string in a LeafData string, then send that back wrapped in a future.
    8. -
    -

    Here’s example code for a CustomUppercase Leaf tag:

    -
    import Async
    -import Leaf
    -
    -public final class CustomUppercase: Leaf.LeafTag {
    -    public init() {}
    -    public func render(parsed: ParsedTag, context: LeafContext, renderer: LeafRenderer) throws -> Future<LeafData?> {
    -        // ensure we receive precisely one parameter
    -        try parsed.requireParameterCount(1)
    -
    -        // pull out our lone parameter as a string then uppercase it, or use an empty string
    -        let string = parsed.parameters[0].string?.uppercased() ?? ""
    -
    -        // send it back wrapped in a LeafData
    -        return Future(.string(string))
    -    }
    -}
    -
    - - -

    We can now register this Tag in our configure.swift file with:

    -
    services.register { container -> LeafConfig in
    -    // take a copy of Leaf's default tags
    -    var tags = defaultTags
    -
    -    // add our custom tag
    -    tags["customuppercase"] = CustomUppercase()
    -
    -    // find the location of our Resources/Views directory
    -    let directoryConfig = try container.make(DirectoryConfig.self, for: LeafRenderer.self)
    -    let viewsDirectory = directoryConfig.workDir + "Resources/Views"
    -
    -    // put all that into a new Leaf configuration and return it
    -    return LeafConfig(tags: tags, viewsDir: viewsDirectory)
    -}
    -
    - - -

    Once that is complete, you can use #customuppercase(some_variable) to run your custom code.

    -
    -

    Note: Use of non-alphanumeric characters in tag names is strongly discouraged and may be disallowed in future versions of Leaf.

    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/leaf/getting-started/index.html b/build/3.0/site/leaf/getting-started/index.html deleted file mode 100644 index be17fdea..00000000 --- a/build/3.0/site/leaf/getting-started/index.html +++ /dev/null @@ -1,1709 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Getting Started - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Leaf

    -

    Leaf is a templating language that integrates with Futures, Reactive Streams and Codable. This section outlines how to import the Leaf package into a Vapor project.

    -

    Example Folder Structure

    -
    Hello
    -├── Package.resolved
    -├── Package.swift
    -├── Public
    -├── Resources
    -│   ├── Views
    -│   │   └── hello.leaf
    -├── Public
    -│   ├── images (images resources)
    -│   ├── styles (css resources)
    -├── Sources
    -│   ├── App
    -│   │   ├── boot.swift
    -│   │   ├── configure.swift
    -│   │   └── routes.swift
    -│   └── Run
    -│       └── main.swift
    -├── Tests
    -│   ├── AppTests
    -│   │   └── AppTests.swift
    -│   └── LinuxMain.swift
    -└── LICENSE
    -
    - - -

    Adding Leaf to your project

    -

    The easiest way to use Leaf with Vapor is to include the Leaf repository as a dependency in Package.swift:

    -
    // swift-tools-version:4.0
    -import PackageDescription
    -
    -let package = Package(
    -    name: "project1",
    -    dependencies: [
    -        // 💧 A server-side Swift web framework.
    -        .package(url: "https://github.com/vapor/vapor.git", .branch("beta")),
    -        .package(url: "https://github.com/vapor/leaf.git", .branch("beta")),
    -    ],
    -    targets: [
    -        .target(
    -            name: "App",
    -            dependencies: ["Vapor", "Leaf"]
    -        ),
    -        .target(name: "Run", dependencies: ["App"]),
    -        .testTarget(name: "AppTests", dependencies: ["App"]),
    -    ]
    -)
    -
    - - -

    The Leaf package adds Leaf to your project, but to configure it for use you must modify configure.swift:

    -
      -
    1. Add import Leaf to the top of the file so that Leaf is available to use. You will also need to add this to any file that will render templates.
    2. -
    3. Add try services.provider(LeafProvider()) to the configure() function so that routes may render Leaf templates as needed.
    4. -
    -

    Syntax Highlighting

    -

    You may also wish to install one these third-party packages that provide support for syntax highlighting in Leaf templates.

    -

    Atom

    -

    language-leaf by ButkiewiczP

    -

    Xcode

    -

    It is not currently possible to implement Leaf Syntax Highlighting in Xcode, however, using Xcode's HTML Syntax Coloring can help a bit. Select one or more Leaf files and then choose Editor > Syntax Coloring > HTML. Your selected Leaf files will now use Xcode's HTML Syntax Coloring. Unfortunately the usefulness of this is limited because this association will be removed when vapor xcode is run.

    -

    There appears to be a way to make Xcode file associations persist but that requires a bit more kung-fu.

    -

    VS Code

    -

    html-leaf by FranciscoAmado

    -

    CLion & AppCode

    -

    Some preliminary work has been done to implement a Leaf Plugin for CLion & AppCode but lack of skill and interest in Java has slowed progress! If you have IntelliJ SDK experience and want to help with this, message Tom Holland on Vapor Slack

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/redis/basics/index.html b/build/3.0/site/redis/basics/index.html deleted file mode 100644 index 244d0d16..00000000 --- a/build/3.0/site/redis/basics/index.html +++ /dev/null @@ -1,1694 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Basics - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Redis basic usage

    -

    To interact with Redis, you first need to construct a Redis client. -The Redis library primarily supports TCP sockets.

    -

    This requires a hostname, port and Worker. The eventloop 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.

    -
    let client = try RedisClient.connect(on: worker) // Future<RedisClient>
    -
    - - -

    The connect method will return a Future containing the TCP based Redis Client.

    -

    Redis Data Types

    -

    Redis has 6 data types:

    -
      -
    • null
    • -
    • Int
    • -
    • Error
    • -
    • Array
    • -
    • Basic String (used for command names and basic replies only)
    • -
    • Bulk String (used for Strings and binary data blobs)
    • -
    -

    You can instantiate one from the static functions and variables on RedisData.

    -
    let null = RedisData.null
    -
    -let helloWorld = RedisData.bulkString("Hello World")
    -
    -let three = RedisData.integer(3)
    -
    -let oneThroughTen = RedisData.array([
    -  .integer(1),
    -  .integer(2),
    -  .integer(3),
    -  .integer(4),
    -  .integer(5),
    -  .integer(6),
    -  .integer(7),
    -  .integer(8),
    -  .integer(9),
    -  .integer(10)
    -])
    -
    - - -

    The above is the explicit way of defining Redis Types. You can also use literals in most scenarios:

    -
    let array = RedisData.array([
    -  [
    -    1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    -  ],
    -  "Hello World",
    -  "One",
    -  "Two",
    -  .null,
    -  .null,
    -  "test"
    -])
    -
    - - -

    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.

    -

    Creating a record

    -

    Creating a record is done using a RedisData for a value and a key.

    -
    client.set("world", forKey: "hello")
    -
    - - -

    This returns a future that'll indicate successful or unsuccessful insertion.

    -

    Reading a record

    -

    Reading a record is similar, only you'll get a warning if you don't use the returned future.

    -

    The Future<RedisData> for the key "hello" will be "world" if you created the record as shown above.

    -
    let futureRecord = client.getData(forKey: "hello") // Future<RedisData>
    -
    - - -

    Deleting a record

    -

    Deleting a record is similar but allows querying the keys, too.

    -
    client.delete(keys: ["hello"])
    -
    - - -

    Where the above command will remove the key "hello", the next command will delete all keys from the Redis database.

    -
    client.delete(keys: ["*"])
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/redis/custom-commands/index.html b/build/3.0/site/redis/custom-commands/index.html deleted file mode 100644 index 8b51f241..00000000 --- a/build/3.0/site/redis/custom-commands/index.html +++ /dev/null @@ -1,1557 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Custom commands - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Custom commands

    -

    Many commands are not (yet) implemented by the driver using a convenience function. This does not mean the feature/command is not usable.

    -

    (Almost) all functions listed here work out of the box using custom commands.

    -

    Usage

    -

    The Redis client has a run function that allows you to run these commands.

    -

    The following code demonstrates a "custom" implementation for GET.

    -
    let future = client.run(command: "GET", arguments: ["my-key"]) // Future<RedisData>
    -
    - - -

    This future will contain the result as specified in the article on the redis command page or an error.

    -

    The future can be used as described in the Async API.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/redis/getting-started/index.html b/build/3.0/site/redis/getting-started/index.html deleted file mode 100644 index a147c603..00000000 --- a/build/3.0/site/redis/getting-started/index.html +++ /dev/null @@ -1,1584 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Getting Started - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Redis

    -

    Redis is a Redis client library that can communicate with a Redis database.

    -

    What is Redis?

    -

    Redis is an in-memory data store used as a database, cache and message broker. It supports most common data structures. Redis is most commonly used for caching data such as sessions and notifications (between multiple servers).

    -

    Redis works as a key-value store, but allows querying the keys, unlike most databases.

    -

    With and without Vapor

    -

    To include it in your package, add the following to your Package.swift file.

    -
    // swift-tools-version:4.0
    -import PackageDescription
    -
    -let package = Package(
    -    name: "Project",
    -    dependencies: [
    -        ...
    -        .package(url: "https://github.com/vapor/redis.git", .upToNextMajor(from: "3.0.0")),
    -    ],
    -    targets: [
    -      .target(name: "Project", dependencies: ["Redis", ... ])
    -    ]
    -)
    -
    - - -

    If this is your first time adding a dependency, you should read our introduction to Package.swift.

    -

    Use import Redis to access Redis' APIs.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/redis/pipeline/index.html b/build/3.0/site/redis/pipeline/index.html deleted file mode 100644 index de36fb2a..00000000 --- a/build/3.0/site/redis/pipeline/index.html +++ /dev/null @@ -1,1573 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Pipeline - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Pipelining

    -

    Pipelining is used for sending multiple commands at once. The performance advantages become apparent when sending a large number of queries. Redis' pipelining cuts down latency by reducing the RTT (Round Trip Time) between the client and server. Pipelining also reduces the amount of IO operations Redis has to perform, this increases the amount of queries per second Redis can handle.

    -

    Use cases

    -

    Sometimes multiple commands need to be executed at once. Instead of sending those commands individually in a loop, pipelining allows the commands to be batched and sent in one request. A common scenario might be needing to set a key and increment a count, pipelining those commands would be ideal.

    -

    Enqueuing Commands

    -
     let pipeline = connection.makePipeline()
    - let result = try pipeline
    -         .enqueue(command: "SET", arguments: ["KEY", "VALUE"])
    -         .enqueue(command: "INCR", arguments: ["COUNT"])
    -         .execute() // Future<[RedisData]>
    -
    - - -

    Note: Commands will not be executed until execute is called.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/redis/pub-sub/index.html b/build/3.0/site/redis/pub-sub/index.html deleted file mode 100644 index d10d8da9..00000000 --- a/build/3.0/site/redis/pub-sub/index.html +++ /dev/null @@ -1,1615 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Publish and Subscribe - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Publish & Subscribe

    -

    Redis' Publish and Subscribe model is really useful for notifications.

    -

    Use cases

    -

    Pub/sub is used for notifying subscribers of an event. -A simple and common event for example would be a chat message.

    -

    A channel consists of a name and group of listeners. Think of it as being [String: [Listener]]. -When you send a notification to a channel you need to provide a payload. -Each listener will get a notification consisting of this payload.

    -

    Channels must be a string. For chat groups, for example, you could use the database identifier.

    -

    Publishing

    -

    You cannot get a list of listeners, but sending a payload will emit the amount of listeners that received the notification. -Sending (publishing) an event is done like so:

    -
    // Any redis data
    -let notification: RedisData = "My-Notification"
    -
    -client.publish(notification, to: "my-channel")
    -
    - - -

    If you want access to the listener count:

    -
    let notifiedCount = client.publish(notification, to: "my-channel") // Future<Int>
    -
    - - -

    Subscribing

    -

    To subscribe for notifications you're rendering an entire Redis Client useless in exchange for listening to events.

    -

    A single client can listen to one or more channels, which is provided using a set of unique channel names. The result of subscribing is a SubscriptionStream.

    -
    let notifications = client.subscribe(to: ["some-notification-channel", "other-notification-channel"])
    -
    - - -

    If you try to use the client after subscribing, all operations will fail. These errors are usually emitted through the Future.

    -

    This stream will receive messages asynchronously from the point of draining. This works like any other async stream

    -

    Notifications consist of the channel and payload.

    -
    notifications.drain { notification in
    -  print(notification.channel)
    -
    -  let payload = notification.payload
    -
    -  // TODO: Process the payload
    -}
    -
    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/search/search_index.json b/build/3.0/site/search/search_index.json deleted file mode 100644 index 5b2beb0f..00000000 --- a/build/3.0/site/search/search_index.json +++ /dev/null @@ -1,2379 +0,0 @@ -{ - "docs": [ - { - "location": "/", - "text": "Vapor Documentation\n\u00b6\n\n\nThis is the documentation for Vapor, a Web Framework for Swift that works on iOS, macOS, and Ubuntu; and all of the packages that Vapor offers.\n\n\nVapor is the most used web framework for Swift. It provides a beautifully expressive and easy to use foundation for your next website or API.\n\n\nGetting Started\n\u00b6\n\n\nIf this is your first time using Vapor, head to the \nInstall \u2192 macOS\n section to install Swift and Vapor.\n\n\nOnce you have Vapor installed, check out \nGetting Started \u2192 Hello, world\n to create your first Vapor app!\n\n\nLike Vapor?\n\u00b6\n\n\nOur small team works hard to make Vapor awesome (and free). Support the framework by \nstarring Vapor on GitHub\n\nor \ndonating $1 monthly\n\u2014it helps us a lot. Thanks!\n\n\nOther Sources\n\u00b6\n\n\nHere are some other great places to find information about Vapor.\n\n\n\n\n\n\n\n\nname\n\n\ndescription\n\n\nlink\n\n\n\n\n\n\n\n\n\n\nVapor Slack\n\n\nChat with ~5,000 Vapor developers.\n\n\nvisit \u2192\n\n\n\n\n\n\nAPI docs\n\n\nAuto-generated documentation from code comments.\n\n\nvisit \u2192\n\n\n\n\n\n\nStack Overflow\n\n\nAsk and answer questions with the \nvapor\n tag.\n\n\nvisit \u2192\n\n\n\n\n\n\nSource Code\n\n\nLearn how Vapor works under the hood.\n\n\nvisit \u2192\n\n\n\n\n\n\nGitHub Issues\n\n\nReport bugs or request features on GitHub.\n\n\nvisit \u2192\n\n\n\n\n\n\n\n\nProviders\n\u00b6\n\n\nVapor providers are a convenient way to add functionality to your Vapor projects.\nFor a full list of providers, check out the \nvapor-provider\n tag on GitHub.\n\n\nAuthors\n\u00b6\n\n\nTanner Nelson\n, \nLogan Wright\n, \nJoannis Orlandos\n, and the hundreds of members of Vapor.", - "title": "Overview" - }, - { - "location": "/#vapor-documentation", - "text": "This is the documentation for Vapor, a Web Framework for Swift that works on iOS, macOS, and Ubuntu; and all of the packages that Vapor offers. Vapor is the most used web framework for Swift. It provides a beautifully expressive and easy to use foundation for your next website or API.", - "title": "Vapor Documentation" - }, - { - "location": "/#getting-started", - "text": "If this is your first time using Vapor, head to the Install \u2192 macOS section to install Swift and Vapor. Once you have Vapor installed, check out Getting Started \u2192 Hello, world to create your first Vapor app!", - "title": "Getting Started" - }, - { - "location": "/#like-vapor", - "text": "Our small team works hard to make Vapor awesome (and free). Support the framework by starring Vapor on GitHub \nor donating $1 monthly \u2014it helps us a lot. Thanks!", - "title": "Like Vapor?" - }, - { - "location": "/#other-sources", - "text": "Here are some other great places to find information about Vapor. name description link Vapor Slack Chat with ~5,000 Vapor developers. visit \u2192 API docs Auto-generated documentation from code comments. visit \u2192 Stack Overflow Ask and answer questions with the vapor tag. visit \u2192 Source Code Learn how Vapor works under the hood. visit \u2192 GitHub Issues Report bugs or request features on GitHub. visit \u2192", - "title": "Other Sources" - }, - { - "location": "/#providers", - "text": "Vapor providers are a convenient way to add functionality to your Vapor projects.\nFor a full list of providers, check out the vapor-provider tag on GitHub.", - "title": "Providers" - }, - { - "location": "/#authors", - "text": "Tanner Nelson , Logan Wright , Joannis Orlandos , and the hundreds of members of Vapor.", - "title": "Authors" - }, - { - "location": "/install/macos/", - "text": "Install on macOS\n\u00b6\n\n\nTo use Vapor on macOS, you just need to have Xcode 9 or greater installed.\n\n\n\n\nTip\n\n\nYou need to install Xcode to install Swift, but after that you can use any text editor \nyou like to develop Vapor apps.\n\n\n\n\nInstall Xcode\n\u00b6\n\n\nInstall \nXcode 9 or greater\n from the Mac App Store.\n\n\n\n\n\n\nWarning\n\n\nAfter Xcode has been downloaded, you must open it to finish the installation. This may take a while.\n\n\n\n\nVerify Installation\n\u00b6\n\n\nDouble check the installation was successful by opening Terminal and running:\n\n\nswift --version\n\n\n\n\n\nYou should see output similar to:\n\n\nApple Swift version \n4\n.0.2 \n(\nswiftlang-900.0.69.2 clang-900.0.38\n)\n\nTarget: x86_64-apple-macosx10.9\n\n\n\n\n\nVapor requires Swift 4.\n\n\nInstall Vapor\n\u00b6\n\n\nNow that you have Swift 4, let's install the \nVapor Toolbox\n.\n\n\nThe toolbox includes all of Vapor's dependencies as well as a handy CLI tool for creating new projects.\n\n\nbrew install vapor/tap/vapor\n\n\n\n\n\n\n\nTip\n\n\nIf you don't already have Homebrew installed, install it at \nbrew.sh \u2192\n\n\n\n\nVerify Installation\n\u00b6\n\n\nDouble check the installation was successful by opening Terminal and running:\n\n\nvapor --help\n\n\n\n\n\nYou should see a long list of available commands.\n\n\nDone\n\u00b6\n\n\nNow that you have installed Vapor, create your first app in \nGetting Started \u2192 Hello, world\n.", - "title": "macOS" - }, - { - "location": "/install/macos/#install-on-macos", - "text": "To use Vapor on macOS, you just need to have Xcode 9 or greater installed. Tip You need to install Xcode to install Swift, but after that you can use any text editor \nyou like to develop Vapor apps.", - "title": "Install on macOS" - }, - { - "location": "/install/macos/#install-xcode", - "text": "Install Xcode 9 or greater from the Mac App Store. Warning After Xcode has been downloaded, you must open it to finish the installation. This may take a while.", - "title": "Install Xcode" - }, - { - "location": "/install/macos/#verify-installation", - "text": "Double check the installation was successful by opening Terminal and running: swift --version You should see output similar to: Apple Swift version 4 .0.2 ( swiftlang-900.0.69.2 clang-900.0.38 ) \nTarget: x86_64-apple-macosx10.9 Vapor requires Swift 4.", - "title": "Verify Installation" - }, - { - "location": "/install/macos/#install-vapor", - "text": "Now that you have Swift 4, let's install the Vapor Toolbox . The toolbox includes all of Vapor's dependencies as well as a handy CLI tool for creating new projects. brew install vapor/tap/vapor Tip If you don't already have Homebrew installed, install it at brew.sh \u2192", - "title": "Install Vapor" - }, - { - "location": "/install/macos/#verify-installation_1", - "text": "Double check the installation was successful by opening Terminal and running: vapor --help You should see a long list of available commands.", - "title": "Verify Installation" - }, - { - "location": "/install/macos/#done", - "text": "Now that you have installed Vapor, create your first app in Getting Started \u2192 Hello, world .", - "title": "Done" - }, - { - "location": "/install/ubuntu/", - "text": "Install on Ubuntu\n\u00b6\n\n\nInstalling Vapor on Ubuntu only takes a couple of minutes.\n\n\nSupported\n\u00b6\n\n\nVapor supports the same versions of Ubuntu that Swift supports.\n\n\n\n\n\n\n\n\nVersion\n\n\nCodename\n\n\n\n\n\n\n\n\n\n\n16.10\n\n\nYakkety Yak\n\n\n\n\n\n\n16.04\n\n\nXenial Xerus\n\n\n\n\n\n\n14.04\n\n\nTrusty Tahr\n\n\n\n\n\n\n\n\nAPT Repo\n\u00b6\n\n\nAdd Vapor's APT repo to get access to all of Vapor's Ubuntu packages.\n\n\nQuick Script\n\u00b6\n\n\nEasily add Vapor's APT repo with this handy script.\n\n\neval\n \n\"\n$(\ncurl -sL https://apt.vapor.sh\n)\n\"\n\n\n\n\n\n\n\n\nTip\n\n\nThis command requires \ncurl\n which can be installed using \nsudo apt-get install curl\n\n\n\n\nDockerfile\n\u00b6\n\n\nWhen configuring Ubuntu from a Dockerfile, adding the APT repo can be done via this command:\n\n\nRUN /bin/bash -c \n\"\n$(\nwget -qO- https://apt.vapor.sh\n)\n\"\n\n\n\n\n\n\nManual\n\u00b6\n\n\nOr add the repo manually.\n\n\nwget -q https://repo.vapor.codes/apt/keyring.gpg -O- \n|\n sudo apt-key add -\n\necho\n \n\"deb https://repo.vapor.codes/apt \n$(\nlsb_release -sc\n)\n main\"\n \n|\n sudo tee /etc/apt/sources.list.d/vapor.list\nsudo apt-get update\n\n\n\n\n\nInstall Vapor\n\u00b6\n\n\nNow that you have added Vapor's APT repo, you can install the required dependencies.\n\n\nsudo apt-get install swift vapor\n\n\n\n\n\nVerify Installation\n\u00b6\n\n\nDouble check everything worked with the following commands.\n\n\nSwift\n\u00b6\n\n\nswift --version\n\n\n\n\n\nYou should see output similar to:\n\n\nApple Swift version \n4\n.0.2 \n(\nswiftlang-900.0.69.2 clang-900.0.38\n)\n\nTarget: x86_64-apple-macosx10.9\n\n\n\n\n\nVapor requires Swift 4.\n\n\nVapor Toolbox\n\u00b6\n\n\nvapor --help\n\n\n\n\n\nYou should see a long list of available commands.\n\n\nDone\n\u00b6\n\n\nNow that you have installed Vapor, create your first app in \nGetting Started \u2192 Hello, world\n.\n\n\nSwift.org\n\u00b6\n\n\nCheck out \nSwift.org\n's guide to \nusing downloads\n if you need more detailed instructions for installing Swift 4.", - "title": "Ubuntu" - }, - { - "location": "/install/ubuntu/#install-on-ubuntu", - "text": "Installing Vapor on Ubuntu only takes a couple of minutes.", - "title": "Install on Ubuntu" - }, - { - "location": "/install/ubuntu/#supported", - "text": "Vapor supports the same versions of Ubuntu that Swift supports. Version Codename 16.10 Yakkety Yak 16.04 Xenial Xerus 14.04 Trusty Tahr", - "title": "Supported" - }, - { - "location": "/install/ubuntu/#apt-repo", - "text": "Add Vapor's APT repo to get access to all of Vapor's Ubuntu packages.", - "title": "APT Repo" - }, - { - "location": "/install/ubuntu/#quick-script", - "text": "Easily add Vapor's APT repo with this handy script. eval \" $( curl -sL https://apt.vapor.sh ) \" Tip This command requires curl which can be installed using sudo apt-get install curl", - "title": "Quick Script" - }, - { - "location": "/install/ubuntu/#dockerfile", - "text": "When configuring Ubuntu from a Dockerfile, adding the APT repo can be done via this command: RUN /bin/bash -c \" $( wget -qO- https://apt.vapor.sh ) \"", - "title": "Dockerfile" - }, - { - "location": "/install/ubuntu/#manual", - "text": "Or add the repo manually. wget -q https://repo.vapor.codes/apt/keyring.gpg -O- | sudo apt-key add - echo \"deb https://repo.vapor.codes/apt $( lsb_release -sc ) main\" | sudo tee /etc/apt/sources.list.d/vapor.list\nsudo apt-get update", - "title": "Manual" - }, - { - "location": "/install/ubuntu/#install-vapor", - "text": "Now that you have added Vapor's APT repo, you can install the required dependencies. sudo apt-get install swift vapor", - "title": "Install Vapor" - }, - { - "location": "/install/ubuntu/#verify-installation", - "text": "Double check everything worked with the following commands.", - "title": "Verify Installation" - }, - { - "location": "/install/ubuntu/#swift", - "text": "swift --version You should see output similar to: Apple Swift version 4 .0.2 ( swiftlang-900.0.69.2 clang-900.0.38 ) \nTarget: x86_64-apple-macosx10.9 Vapor requires Swift 4.", - "title": "Swift" - }, - { - "location": "/install/ubuntu/#vapor-toolbox", - "text": "vapor --help You should see a long list of available commands.", - "title": "Vapor Toolbox" - }, - { - "location": "/install/ubuntu/#done", - "text": "Now that you have installed Vapor, create your first app in Getting Started \u2192 Hello, world .", - "title": "Done" - }, - { - "location": "/install/ubuntu/#swiftorg", - "text": "Check out Swift.org 's guide to using downloads if you need more detailed instructions for installing Swift 4.", - "title": "Swift.org" - }, - { - "location": "/getting-started/hello-world/", - "text": "Hello, world\n\u00b6\n\n\nNow that you've installed Vapor, let's create your first Vapor app!\nThis guide will take you step by step through creating a new project, building, and running it.\n\n\nNew Project\n\u00b6\n\n\nThe first step is to create a new Vapor project on your computer.\nFor this guide, we will call the project \nHello\n.\n\n\nOpen up your terminal, and use \nVapor Toolbox's \nnew\n command.\n\n\nvapor new Hello\n\n\n\n\n\n\n\nWarning\n\n\nMake sure to add \n--branch=beta\n while using Vapor 3 pre-release.\n\n\n\n\nOnce that finishes, change into the newly created directory.\n\n\ncd\n Hello\n\n\n\n\n\nGenerate Xcode Project\n\u00b6\n\n\nLet's now use the \nVapor Toolbox's \nxcode\n command to generate an Xcode project.\nThis will allow us to build and run our app from inside of Xcode, just like an iOS app.\n\n\nvapor xcode\n\n\n\n\n\nThe toolbox will ask you if you'd like to open Xcode automatically, select \nyes\n.\n\n\nBuild & Run\n\u00b6\n\n\nYou should now have Xcode open and running. Select the \nrun scheme\n from the scheme menu,\nthen click the play button.\n\n\nYou should see the terminal pop up at the bottom of the screen.\n\n\nServer starting on localhost:8080\n\n\n\n\n\nVisit Localhost\n\u00b6\n\n\nOpen your web browser, and visit \nlocalhost:8080/hello \u2192\n\n\nYou should see the following page.\n\n\nHello, world!\n\n\n\n\n\nCongratulations on creating, building, and running your first Vapor app! \ud83c\udf89", - "title": "Hello, world" - }, - { - "location": "/getting-started/hello-world/#hello-world", - "text": "Now that you've installed Vapor, let's create your first Vapor app!\nThis guide will take you step by step through creating a new project, building, and running it.", - "title": "Hello, world" - }, - { - "location": "/getting-started/hello-world/#new-project", - "text": "The first step is to create a new Vapor project on your computer.\nFor this guide, we will call the project Hello . Open up your terminal, and use Vapor Toolbox's new command. vapor new Hello Warning Make sure to add --branch=beta while using Vapor 3 pre-release. Once that finishes, change into the newly created directory. cd Hello", - "title": "New Project" - }, - { - "location": "/getting-started/hello-world/#generate-xcode-project", - "text": "Let's now use the Vapor Toolbox's xcode command to generate an Xcode project.\nThis will allow us to build and run our app from inside of Xcode, just like an iOS app. vapor xcode The toolbox will ask you if you'd like to open Xcode automatically, select yes .", - "title": "Generate Xcode Project" - }, - { - "location": "/getting-started/hello-world/#build-run", - "text": "You should now have Xcode open and running. Select the run scheme from the scheme menu,\nthen click the play button. You should see the terminal pop up at the bottom of the screen. Server starting on localhost:8080", - "title": "Build & Run" - }, - { - "location": "/getting-started/hello-world/#visit-localhost", - "text": "Open your web browser, and visit localhost:8080/hello \u2192 You should see the following page. Hello, world! Congratulations on creating, building, and running your first Vapor app! \ud83c\udf89", - "title": "Visit Localhost" - }, - { - "location": "/getting-started/toolbox/", - "text": "Install Toolbox\n\u00b6\n\n\nVapor's command line interface provides shortcuts and assistance for common tasks.\n\n\n\n\nHelp prints useful information about available commands and flags.\n\n\nvapor --help\n\n\n\n\n\nYou can also run the \n--help\n option on any Toolbox command.\n\n\nvapor new --help\n\n\n\n\n\nThe \n--help\n flag should be your goto for learning about the toolbox as it is the most up-to-date.\n\n\nNew\n\u00b6\n\n\nThe Toolbox's most important feature is helping you create a new project.\n\n\nvapor new \n\n\n\n\n\nJust pass the name of your project as the first argument to the \nnew\n command.\n\n\n\n\nNote\n\n\nProject names should be \nPascalCase \u2192\n, like \nHelloWorld\n or \nMyProject\n.\n\n\n\n\nTemplates\n\u00b6\n\n\nBy default, Vapor will create your new project from the API template. You can choose\na different template by passing the \n--template\n flag.\n\n\n\n\n\n\n\n\nName\n\n\nFlag\n\n\nDescription\n\n\n\n\n\n\n\n\n\n\nAPI\n\n\n--template=api\n\n\nJSON API with Fluent database.\n\n\n\n\n\n\nWeb\n\n\n--template=web\n\n\nHTML website with Leaf templates.\n\n\n\n\n\n\n\n\n\n\nInfo\n\n\nThere are lots of unofficial Vapor templates on GitHub under the \nvapor\n + \ntemplate\n topcs \u2192\n.\nYou can use these by passing the full GitHub URL to the \n--template\n option.\n\n\n\n\nBuild & Run\n\u00b6\n\n\nYou can use the toolbox to build and run your Vapor app.\n\n\nvapor build\nvapor run\n\n\n\n\n\n\n\nTip\n\n\nWe recommend building and running through \nXcode\n if you have a Mac. \nIt's a bit faster and you can set breakpoints! \nJust use \nvapor xcode\n to generate an Xcode project.\n\n\n\n\nUpdating\n\u00b6\n\n\nThe toolbox should be updated by the package manager it was installed with.\n\n\nHomebrew\n\u00b6\n\n\nbrew upgrade vapor\n\n\n\n\n\nAPT\n\u00b6\n\n\nsudo apt-get update\nsudo apt-get install vapor", - "title": "Toolbox" - }, - { - "location": "/getting-started/toolbox/#install-toolbox", - "text": "Vapor's command line interface provides shortcuts and assistance for common tasks. Help prints useful information about available commands and flags. vapor --help You can also run the --help option on any Toolbox command. vapor new --help The --help flag should be your goto for learning about the toolbox as it is the most up-to-date.", - "title": "Install Toolbox" - }, - { - "location": "/getting-started/toolbox/#new", - "text": "The Toolbox's most important feature is helping you create a new project. vapor new Just pass the name of your project as the first argument to the new command. Note Project names should be PascalCase \u2192 , like HelloWorld or MyProject .", - "title": "New" - }, - { - "location": "/getting-started/toolbox/#templates", - "text": "By default, Vapor will create your new project from the API template. You can choose\na different template by passing the --template flag. Name Flag Description API --template=api JSON API with Fluent database. Web --template=web HTML website with Leaf templates. Info There are lots of unofficial Vapor templates on GitHub under the vapor + template topcs \u2192 .\nYou can use these by passing the full GitHub URL to the --template option.", - "title": "Templates" - }, - { - "location": "/getting-started/toolbox/#build-run", - "text": "You can use the toolbox to build and run your Vapor app. vapor build\nvapor run Tip We recommend building and running through Xcode if you have a Mac. \nIt's a bit faster and you can set breakpoints! \nJust use vapor xcode to generate an Xcode project.", - "title": "Build & Run" - }, - { - "location": "/getting-started/toolbox/#updating", - "text": "The toolbox should be updated by the package manager it was installed with.", - "title": "Updating" - }, - { - "location": "/getting-started/toolbox/#homebrew", - "text": "brew upgrade vapor", - "title": "Homebrew" - }, - { - "location": "/getting-started/toolbox/#apt", - "text": "sudo apt-get update\nsudo apt-get install vapor", - "title": "APT" - }, - { - "location": "/getting-started/spm/", - "text": "Managing your project\n\u00b6\n\n\nThe Swift Package Manager (SPM for short) is used for building your project's source code and dependencies. \nIt's a similar idea to Cocoapods, Ruby gems, and NPM. Most of the time the \nVapor Toolbox\n will \ninteract with SPM on your behalf. However, it's important to understand the basics.\n\n\n\n\nTip\n\n\nLearn more about SPM on \nSwift.org \u2192\n \n\n\n\n\nPackage Manifest\n\u00b6\n\n\nThe first place SPM looks in your project is the package manfiest. This should always be located in the root\ndirectory of your project and named \nPackage.swift\n.\n\n\nDependencies\n\u00b6\n\n\nDependencies are other SPM packages that your package relies on. All Vapor applications rely on the Vapor package,\nbut you can add as many other dependencies as you want.\n\n\n// swift-tools-version:4.0\n\n\nimport\n \nPackageDescription\n\n\n\nlet\n \npackage\n \n=\n \nPackage\n(\n\n \nname\n:\n \n\"VaporApp\"\n,\n\n \ndependencies\n:\n \n[\n\n \n// \ud83d\udca7 A server-side Swift web framework. \n\n \n.\npackage\n(\nurl\n:\n \n\"https://github.com/vapor/vapor.git\"\n,\n \nfrom\n:\n \n\"3.0.0\"\n),\n\n \n],\n\n \ntargets\n:\n \n[\n \n...\n \n]\n\n\n)\n\n\n\n\n\n\nIn the above example, you can see \nvapor/vapor \u2192\n version 3.0\nor later is a dependency of this package.\nWhen you add a dependency to your package, you must next signal which \ntargets\n depend on\nthe newly available modules.\n\n\n\n\nWarning\n\n\nAnytime you modify the package manifest, call \nvapor update\n to effect the changes.\n\n\n\n\nTargets\n\u00b6\n\n\nTargets are all of the modules, executables, and tests that your package contains. \n\n\n// swift-tools-version:4.0\n\n\nimport\n \nPackageDescription\n\n\n\nlet\n \npackage\n \n=\n \nPackage\n(\n\n \nname\n:\n \n\"VaporApp\"\n,\n\n \ndependencies\n:\n \n[\n \n...\n \n],\n\n \ntargets\n:\n \n[\n\n \n.\ntarget\n(\nname\n:\n \n\"App\"\n,\n \ndependencies\n:\n \n[\n\"Vapor\"\n]),\n\n \n.\ntarget\n(\nname\n:\n \n\"Run\"\n,\n \ndependencies\n:\n \n[\n\"App\"\n]),\n\n \n.\ntestTarget\n(\nname\n:\n \n\"AppTests\"\n,\n \ndependencies\n:\n \n[\n\"App\"\n]),\n\n \n]\n\n\n)\n\n\n\n\n\n\nMost Vapor apps will have three targets, although you can add as many as you like to organize your code.\nEach target declares which modules it depends on. You must add module names here in order to \nimport\n them in your code.\nA target can depend on other targets in your project or any modules exposed by packages you've added to\nthe \nmain dependencies\n array.\n\n\n\n\nTip\n\n\nExecutable targets (targets that contain a \nmain.swift\n file) cannot be imported by other modules.\nThis is why Vapor has both an \nApp\n and a \nRun\n target.\nAny code you include in \nApp\n can be tested in the \nAppTests\n.\n\n\n\n\nFolder Structure\n\u00b6\n\n\nBelow is the typical folder structure for an SPM package.\n\n\n.\n\u251c\u2500\u2500 Sources\n\u2502 \u251c\u2500\u2500 App\n\u2502 \u2502 \u2514\u2500\u2500 (Source code)\n\u2502 \u2514\u2500\u2500 Run\n\u2502 \u2514\u2500\u2500 main.swift\n\u251c\u2500\u2500 Tests\n\u2502 \u2514\u2500\u2500 AppTests\n\u2514\u2500\u2500 Package.swift\n\n\n\n\n\nEach \n.target\n corresponds to a folder in the \nSources\n folder. \nEach \n.testTarget\n corresponds to a folder in the \nTests\n folder.\n\n\nTroubleshooting\n\u00b6\n\n\nIf you are experiencing problems with SPM, sometimes cleaning your project can help.\n\n\nvapor clean", - "title": "SPM" - }, - { - "location": "/getting-started/spm/#managing-your-project", - "text": "The Swift Package Manager (SPM for short) is used for building your project's source code and dependencies. \nIt's a similar idea to Cocoapods, Ruby gems, and NPM. Most of the time the Vapor Toolbox will \ninteract with SPM on your behalf. However, it's important to understand the basics. Tip Learn more about SPM on Swift.org \u2192", - "title": "Managing your project" - }, - { - "location": "/getting-started/spm/#package-manifest", - "text": "The first place SPM looks in your project is the package manfiest. This should always be located in the root\ndirectory of your project and named Package.swift .", - "title": "Package Manifest" - }, - { - "location": "/getting-started/spm/#dependencies", - "text": "Dependencies are other SPM packages that your package relies on. All Vapor applications rely on the Vapor package,\nbut you can add as many other dependencies as you want. // swift-tools-version:4.0 import PackageDescription let package = Package ( \n name : \"VaporApp\" , \n dependencies : [ \n // \ud83d\udca7 A server-side Swift web framework. \n . package ( url : \"https://github.com/vapor/vapor.git\" , from : \"3.0.0\" ), \n ], \n targets : [ ... ] ) In the above example, you can see vapor/vapor \u2192 version 3.0\nor later is a dependency of this package.\nWhen you add a dependency to your package, you must next signal which targets depend on\nthe newly available modules. Warning Anytime you modify the package manifest, call vapor update to effect the changes.", - "title": "Dependencies" - }, - { - "location": "/getting-started/spm/#targets", - "text": "Targets are all of the modules, executables, and tests that your package contains. // swift-tools-version:4.0 import PackageDescription let package = Package ( \n name : \"VaporApp\" , \n dependencies : [ ... ], \n targets : [ \n . target ( name : \"App\" , dependencies : [ \"Vapor\" ]), \n . target ( name : \"Run\" , dependencies : [ \"App\" ]), \n . testTarget ( name : \"AppTests\" , dependencies : [ \"App\" ]), \n ] ) Most Vapor apps will have three targets, although you can add as many as you like to organize your code.\nEach target declares which modules it depends on. You must add module names here in order to import them in your code.\nA target can depend on other targets in your project or any modules exposed by packages you've added to\nthe main dependencies array. Tip Executable targets (targets that contain a main.swift file) cannot be imported by other modules.\nThis is why Vapor has both an App and a Run target.\nAny code you include in App can be tested in the AppTests .", - "title": "Targets" - }, - { - "location": "/getting-started/spm/#folder-structure", - "text": "Below is the typical folder structure for an SPM package. .\n\u251c\u2500\u2500 Sources\n\u2502 \u251c\u2500\u2500 App\n\u2502 \u2502 \u2514\u2500\u2500 (Source code)\n\u2502 \u2514\u2500\u2500 Run\n\u2502 \u2514\u2500\u2500 main.swift\n\u251c\u2500\u2500 Tests\n\u2502 \u2514\u2500\u2500 AppTests\n\u2514\u2500\u2500 Package.swift Each .target corresponds to a folder in the Sources folder. \nEach .testTarget corresponds to a folder in the Tests folder.", - "title": "Folder Structure" - }, - { - "location": "/getting-started/spm/#troubleshooting", - "text": "If you are experiencing problems with SPM, sometimes cleaning your project can help. vapor clean", - "title": "Troubleshooting" - }, - { - "location": "/getting-started/xcode/", - "text": "Xcode\n\u00b6\n\n\nIf you're on a Mac, you can develop your Vapor project using Xcode. \nYou can build, run, and stop your server from within Xcode, as well as use breakpoints and instruments to debug your code.\n\n\n\n\nXcode is a great way to develop Vapor apps, but you can use any text editor you like.\n\n\nGenerate Project\n\u00b6\n\n\nTo use Xcode, you just need to generate an Xcode project using \nVapor Toolbox\n.\n\n\nvapor xcode\n\n\n\n\n\n\n\nTip\n\n\nDon't worry about comitting the generated Xcode Project to git, just generate a new\none whenever you need it.\n\n\n\n\nRun\n\u00b6\n\n\nTo build and run your Vapor app, first make sure you have the \nRun\n scheme selected from the schemes menu.\nAlso make sure to select \"My Mac\" as the device.\n\n\n\n\nOnce that's selected, just click the play button or press \nCommand + R\n on your keyboard.\n\n\nTest\n\u00b6\n\n\nTo run your unit tests, select the scheme ending in \n-Package\n and press \nCommand + U\n.\n\n\n\n\nWarning\n\n\nThere may be a few extraneous schemes in the dropdown menu. Ignore them!", - "title": "Xcode" - }, - { - "location": "/getting-started/xcode/#xcode", - "text": "If you're on a Mac, you can develop your Vapor project using Xcode. \nYou can build, run, and stop your server from within Xcode, as well as use breakpoints and instruments to debug your code. Xcode is a great way to develop Vapor apps, but you can use any text editor you like.", - "title": "Xcode" - }, - { - "location": "/getting-started/xcode/#generate-project", - "text": "To use Xcode, you just need to generate an Xcode project using Vapor Toolbox . vapor xcode Tip Don't worry about comitting the generated Xcode Project to git, just generate a new\none whenever you need it.", - "title": "Generate Project" - }, - { - "location": "/getting-started/xcode/#run", - "text": "To build and run your Vapor app, first make sure you have the Run scheme selected from the schemes menu.\nAlso make sure to select \"My Mac\" as the device. Once that's selected, just click the play button or press Command + R on your keyboard.", - "title": "Run" - }, - { - "location": "/getting-started/xcode/#test", - "text": "To run your unit tests, select the scheme ending in -Package and press Command + U . Warning There may be a few extraneous schemes in the dropdown menu. Ignore them!", - "title": "Test" - }, - { - "location": "/getting-started/structure/", - "text": "Structure\n\u00b6\n\n\nThis section explains the structure of a typical Vapor application to help get\nyou familiar with where things go.\n\n\nFolder Structure\n\u00b6\n\n\nVapor's folder structure builds on top of \nSPM's folder structure\n.\n\n\n.\n\u251c\u2500\u2500 Public\n\u251c\u2500\u2500 Sources\n\u2502 \u251c\u2500\u2500 App\n\u2502 \u2502 \u251c\u2500\u2500 Controllers\n\u2502 \u2502 \u251c\u2500\u2500 Models\n\u2502 \u2502 \u251c\u2500\u2500 boot.swift\n\u2502 \u2502 \u251c\u2500\u2500 configure.swift\n\u2502 \u2502 \u2514\u2500\u2500 routes.swift\n\u2502 \u2514\u2500\u2500 Run\n\u2502 \u2514\u2500\u2500 main.swift\n\u251c\u2500\u2500 Tests\n\u2502 \u2514\u2500\u2500 AppTests\n\u2514\u2500\u2500 Package.swift\n\n\n\n\n\nLet's take a look at what each of these folders and files does.\n\n\nPublic\n\u00b6\n\n\nThis folder contains any public files that will be served by your app.\nThis is usually images, style sheets, and browser scripts.\n\n\nWhenever Vapor responds to a request, it will first check if the requested\nitem is in this folder. If it is, it skips your application logic and returns\nthe file immediately.\n\n\nFor example, a request to \nlocalhost:8080/favicon.ico\n will check to see\nif \nPublic/favicon.ico\n exists. If it does, Vapor will return it.\n\n\nSources\n\u00b6\n\n\nThis folder contains all of the Swift source files for your project. \nThe top level folders (\nApp\n and \nRun\n) reflect your package's modules, \nas declared in the \npackage manifest\n.\n\n\nApp\n\u00b6\n\n\nThis is the most important folder in your application, it's where all of\nthe application logic goes!\n\n\nControllers\n\u00b6\n\n\nControllers are great way of grouping together application logic. Most controllers\nhave many functions that accept a request and return some sort of response.\n\n\n\n\nTip\n\n\nVapor supports, but does not enforce the MVC pattern\n\n\n\n\nModels\n\u00b6\n\n\nThe \nModels\n folder is a great place to store your \nContent\n structs or\nFluent \nModel\ns.\n\n\nboot.swift\n\u00b6\n\n\nThis file contains a function that will be called \nafter\n your application has booted,\nbut \nbefore\n it has started running. This is a great place do things that should happen \nevery time your application starts.\n\n\nYou have access to the \nApplication\n here which you can use to create\nany \nservices\n you might need.\n\n\nconfigure.swift\n\u00b6\n\n\nThis file contains a function that receives the config, environment, and services for your\napplication as input. This is a great place to make changes to your config or register \n\nservices\n to your application.\n\n\nroutes.swift\n\u00b6\n\n\nThis file contains the main route collection for your app. This is where you should assign routes\nfor your controller methods.\n\n\nYou'll notice there's one example route in there that returns the \"hello, world\" response we saw earlier.\n\n\nYou can create as many route collections as you want to further organize your code. Just make sure\nto register them in this main route collection. \n\n\nTests\n\u00b6\n\n\nEach non-executable module in your \nSources\n folder should have a corresponding \n...Tests\n folder.\n\n\nAppTests\n\u00b6\n\n\nThis folder contains the unit tests for code in your \nApp\n module. \nLearn more about testing in \nTesting \u2192 Getting Started\n.\n\n\nPackage.swift\n\u00b6\n\n\nFinally is SPM's \npackage manifest\n.", - "title": "Folder Structure" - }, - { - "location": "/getting-started/structure/#structure", - "text": "This section explains the structure of a typical Vapor application to help get\nyou familiar with where things go.", - "title": "Structure" - }, - { - "location": "/getting-started/structure/#folder-structure", - "text": "Vapor's folder structure builds on top of SPM's folder structure . .\n\u251c\u2500\u2500 Public\n\u251c\u2500\u2500 Sources\n\u2502 \u251c\u2500\u2500 App\n\u2502 \u2502 \u251c\u2500\u2500 Controllers\n\u2502 \u2502 \u251c\u2500\u2500 Models\n\u2502 \u2502 \u251c\u2500\u2500 boot.swift\n\u2502 \u2502 \u251c\u2500\u2500 configure.swift\n\u2502 \u2502 \u2514\u2500\u2500 routes.swift\n\u2502 \u2514\u2500\u2500 Run\n\u2502 \u2514\u2500\u2500 main.swift\n\u251c\u2500\u2500 Tests\n\u2502 \u2514\u2500\u2500 AppTests\n\u2514\u2500\u2500 Package.swift Let's take a look at what each of these folders and files does.", - "title": "Folder Structure" - }, - { - "location": "/getting-started/structure/#public", - "text": "This folder contains any public files that will be served by your app.\nThis is usually images, style sheets, and browser scripts. Whenever Vapor responds to a request, it will first check if the requested\nitem is in this folder. If it is, it skips your application logic and returns\nthe file immediately. For example, a request to localhost:8080/favicon.ico will check to see\nif Public/favicon.ico exists. If it does, Vapor will return it.", - "title": "Public" - }, - { - "location": "/getting-started/structure/#sources", - "text": "This folder contains all of the Swift source files for your project. \nThe top level folders ( App and Run ) reflect your package's modules, \nas declared in the package manifest .", - "title": "Sources" - }, - { - "location": "/getting-started/structure/#app", - "text": "This is the most important folder in your application, it's where all of\nthe application logic goes!", - "title": "App" - }, - { - "location": "/getting-started/structure/#controllers", - "text": "Controllers are great way of grouping together application logic. Most controllers\nhave many functions that accept a request and return some sort of response. Tip Vapor supports, but does not enforce the MVC pattern", - "title": "Controllers" - }, - { - "location": "/getting-started/structure/#models", - "text": "The Models folder is a great place to store your Content structs or\nFluent Model s.", - "title": "Models" - }, - { - "location": "/getting-started/structure/#bootswift", - "text": "This file contains a function that will be called after your application has booted,\nbut before it has started running. This is a great place do things that should happen \nevery time your application starts. You have access to the Application here which you can use to create\nany services you might need.", - "title": "boot.swift" - }, - { - "location": "/getting-started/structure/#configureswift", - "text": "This file contains a function that receives the config, environment, and services for your\napplication as input. This is a great place to make changes to your config or register services to your application.", - "title": "configure.swift" - }, - { - "location": "/getting-started/structure/#routesswift", - "text": "This file contains the main route collection for your app. This is where you should assign routes\nfor your controller methods. You'll notice there's one example route in there that returns the \"hello, world\" response we saw earlier. You can create as many route collections as you want to further organize your code. Just make sure\nto register them in this main route collection.", - "title": "routes.swift" - }, - { - "location": "/getting-started/structure/#tests", - "text": "Each non-executable module in your Sources folder should have a corresponding ...Tests folder.", - "title": "Tests" - }, - { - "location": "/getting-started/structure/#apptests", - "text": "This folder contains the unit tests for code in your App module. \nLearn more about testing in Testing \u2192 Getting Started .", - "title": "AppTests" - }, - { - "location": "/getting-started/structure/#packageswift", - "text": "Finally is SPM's package manifest .", - "title": "Package.swift" - }, - { - "location": "/getting-started/application/", - "text": "Application\n\u00b6\n\n\nEvery Vapor project has an \nApplication\n. You use the application to create any services\nyou might need while developing.\n\n\nThe best place to access the application is in your project's \nboot.swift\n file.\n\n\nimport\n \nVapor\n\n\n\npublic\n \nfunc\n \nboot\n(\n_\n \napp\n:\n \nApplication\n)\n \nthrows\n \n{\n\n \n// your code here\n\n\n}\n\n\n\n\n\n\nYou can also access the application from your \nroutes.swift\n file. It's stored\nas a property there.\n\n\nimport\n \nVapor\n\n\n\nfinal\n \nclass\n \nRoutes\n:\n \nRouteCollection\n \n{\n\n \n...\n\n\n}\n\n\n\n\n\n\nUnlike some other web frameworks, Vapor doesn't support statically accessing the application.\nIf you need to access it from another class or struct, you should pass through a method or initializer.\n\n\n\n\nInfo\n\n\nAvoiding static access to variables helps make Vapor performant by preventing\nthe need for thread-safe locks or semaphores.\n\n\n\n\nServices\n\u00b6\n\n\nThe application's main function is to make services. For example, you might need a \nBCryptHasher\n to hash\nsome passwords before storing them in a database. You can use the application to create one.\n\n\nimport\n \nBCrypt\n\n\n\nlet\n \nbcryptHasher\n \n=\n \ntry\n \napp\n.\nmake\n(\nBCryptHasher\n.\nself\n)\n\n\n\n\n\n\nOr you might use the application to create an HTTP client.\n\n\nlet\n \nclient\n \n=\n \ntry\n \napp\n.\nmake\n(\nClient\n.\nself\n)\n\n\nlet\n \nres\n \n=\n \nclient\n.\nget\n(\n\"http://vapor.codes\"\n)\n\n\n\n\n\n\nLearn more about services in \nServices \u2192 Getting Started\n.", - "title": "Application" - }, - { - "location": "/getting-started/application/#application", - "text": "Every Vapor project has an Application . You use the application to create any services\nyou might need while developing. The best place to access the application is in your project's boot.swift file. import Vapor public func boot ( _ app : Application ) throws { \n // your code here } You can also access the application from your routes.swift file. It's stored\nas a property there. import Vapor final class Routes : RouteCollection { \n ... } Unlike some other web frameworks, Vapor doesn't support statically accessing the application.\nIf you need to access it from another class or struct, you should pass through a method or initializer. Info Avoiding static access to variables helps make Vapor performant by preventing\nthe need for thread-safe locks or semaphores.", - "title": "Application" - }, - { - "location": "/getting-started/application/#services", - "text": "The application's main function is to make services. For example, you might need a BCryptHasher to hash\nsome passwords before storing them in a database. You can use the application to create one. import BCrypt let bcryptHasher = try app . make ( BCryptHasher . self ) Or you might use the application to create an HTTP client. let client = try app . make ( Client . self ) let res = client . get ( \"http://vapor.codes\" ) Learn more about services in Services \u2192 Getting Started .", - "title": "Services" - }, - { - "location": "/getting-started/controllers/", - "text": "Controllers\n\u00b6\n\n\nControllers are a great way to organize your code. They are collections of methods that accept\na request and return a response.\n\n\nA good place to put your controllers is in the \nControllers\n folder.\n\n\nMethods\n\u00b6\n\n\nLet's take a look at an example controller.\n\n\nimport\n \nVapor\n\n\n\nfinal\n \nclass\n \nHelloController\n \n{\n\n \nfunc\n \ngreet\n(\n_\n \nreq\n:\n \nRequest\n)\n \nthrows\n \n->\n \nString\n \n{\n\n \nreturn\n \n\"Hello!\"\n\n \n}\n\n\n}\n\n\n\n\n\n\nController methods should always accept a \nRequest\n and return something \nResponseRepresentable\n.\nThis also includes \nfutures\n whose expectations are \nResponseRepresentable\n (i.e, \nFuture\n).\n\n\nTo use this controller, we can simply initialize it, then pass the method to a router.\n\n\nlet\n \nhelloController\n \n=\n \nHelloController\n()\n\n\nrouter\n.\nget\n(\n\"greet\"\n,\n \nuse\n:\n \nhelloController\n.\ngreet\n)\n\n\n\n\n\n\nUse Services\n\u00b6\n\n\nYou will probably want to access your \napplication's services\n from within your controllers.\nLuckily this is easy to do. First, declare what services your controller needs in its init method. Then store them\nas properties on the controller.\n\n\nfinal\n \nclass\n \nHelloController\n \n{\n\n \nlet\n \nhasher\n:\n \nBCryptHasher\n\n\n \ninit\n(\nhasher\n:\n \nBCryptHasher\n)\n \n{\n\n \nself\n.\nhasher\n \n=\n \nhasher\n\n \n}\n\n\n \n...\n\n\n}\n\n\n\n\n\n\nNext, use the \napplication\n to create these services when you initialize your controller.\n\n\nlet\n \nhelloController\n \n=\n \ntry\n \nHelloController\n(\n\n \nhasher\n:\n \napp\n.\nmake\n()\n\n\n)", - "title": "Controllers" - }, - { - "location": "/getting-started/controllers/#controllers", - "text": "Controllers are a great way to organize your code. They are collections of methods that accept\na request and return a response. A good place to put your controllers is in the Controllers folder.", - "title": "Controllers" - }, - { - "location": "/getting-started/controllers/#methods", - "text": "Let's take a look at an example controller. import Vapor final class HelloController { \n func greet ( _ req : Request ) throws -> String { \n return \"Hello!\" \n } } Controller methods should always accept a Request and return something ResponseRepresentable .\nThis also includes futures whose expectations are ResponseRepresentable (i.e, Future ). To use this controller, we can simply initialize it, then pass the method to a router. let helloController = HelloController () router . get ( \"greet\" , use : helloController . greet )", - "title": "Methods" - }, - { - "location": "/getting-started/controllers/#use-services", - "text": "You will probably want to access your application's services from within your controllers.\nLuckily this is easy to do. First, declare what services your controller needs in its init method. Then store them\nas properties on the controller. final class HelloController { \n let hasher : BCryptHasher \n\n init ( hasher : BCryptHasher ) { \n self . hasher = hasher \n } \n\n ... } Next, use the application to create these services when you initialize your controller. let helloController = try HelloController ( \n hasher : app . make () )", - "title": "Use Services" - }, - { - "location": "/getting-started/routing/", - "text": "Routing\n\u00b6\n\n\nRouting is the process of finding the appropriate response to an incoming request.\n\n\nMaking a Router\n\u00b6\n\n\nIn Vapor the default Router is the \nEngineRouter\n. You can implement custom routers by implementing one conformant to the \nRouter\n protocol.\n\n\nlet\n \nrouter\n \n=\n \ntry\n \nEngineRouter\n.\ndefault\n()\n\n\n\n\n\n\nThere are two APIs available, one is supplied by the \nRouting\n library and a set of helpers is available in Vapor itself.\n\n\nWe recommend using the helpers and will continue to describe those here.\n\n\nRegistering a route\n\u00b6\n\n\nImagine you want to return a list of users when someone visits \nGET /users\n.\nLeaving authorization on the side, that would look something like this.\n\n\nrouter\n.\nget\n(\n\"users\"\n)\n \n{\n \nreq\n \nin\n\n \nreturn\n \n// fetch the users\n\n\n}\n\n\n\n\n\n\nIn Vapor, routing is usually done using the \n.get\n, \n.put\n, \n.post\n, \n.patch\n and \n.delete\n shorthands.\nYou can supply the path as \n/\n or comma-separated strings. We recommend comma separated, as it's more readable.\n\n\nrouter\n.\nget\n(\n\"path\"\n,\n \n\"to\"\n,\n \n\"something\"\n)\n \n{\n \n...\n \n}\n\n\n\n\n\n\nRoutes\n\u00b6\n\n\nThe best place to add routes is in the \nroutes.swift\n file.\nYou will find a router there that is ready to use.\n\n\nimport\n \nVapor\n\n\n\nfinal\n \nclass\n \nRoutes\n:\n \nRouteCollection\n \n{\n\n \n...\n\n\n \nfunc\n \nboot\n(\nrouter\n:\n \nRouter\n)\n \nthrows\n \n{\n\n \nrouter\n.\nget\n(\n\"hello\"\n)\n \n{\n \nreq\n \nin\n\n \nreturn\n \n\"Hello, world!\"\n\n \n}\n\n \n}\n\n\n}\n\n\n\n\n\n\nYou \nmust\n return a Future containing a \nResponseEncodable\n here.\nThe most common \nResponseEncodable\n types are \nContent\n, \nResponse\n amd \nView\n.\n\n\nParameters\n\u00b6\n\n\nSometimes you may want one of the components of your route path to be dynamic. This is often used when\nyou want to get an item with a supplied identifier, i.e., \nGET /users/:id\n\n\nrouter\n.\nget\n(\n\"users\"\n,\n \nInt\n.\nparameter\n)\n \n{\n \nreq\n \n->\n \nFuture\n<\nString\n>\n \nin\n\n \nlet\n \nid\n \n=\n \ntry\n \nreq\n.\nparameter\n(\nInt\n.\nself\n)\n\n \nreturn\n \n// fetch the user with id\n\n\n}\n\n\n\n\n\n\nInstead of passing a string, pass the \ntype\n of parameter you expect. In this case, our \nUser\n has an \nInt\n ID.\n\n\n\n\nTip\n\n\nYou can define your own \ncustom parameter types\n as well.\n\n\n\n\nAfter registering your routes\n\u00b6\n\n\nAfter registering your routes you must register the Router as a \nService", - "title": "Routing" - }, - { - "location": "/getting-started/routing/#routing", - "text": "Routing is the process of finding the appropriate response to an incoming request.", - "title": "Routing" - }, - { - "location": "/getting-started/routing/#making-a-router", - "text": "In Vapor the default Router is the EngineRouter . You can implement custom routers by implementing one conformant to the Router protocol. let router = try EngineRouter . default () There are two APIs available, one is supplied by the Routing library and a set of helpers is available in Vapor itself. We recommend using the helpers and will continue to describe those here.", - "title": "Making a Router" - }, - { - "location": "/getting-started/routing/#registering-a-route", - "text": "Imagine you want to return a list of users when someone visits GET /users .\nLeaving authorization on the side, that would look something like this. router . get ( \"users\" ) { req in \n return // fetch the users } In Vapor, routing is usually done using the .get , .put , .post , .patch and .delete shorthands.\nYou can supply the path as / or comma-separated strings. We recommend comma separated, as it's more readable. router . get ( \"path\" , \"to\" , \"something\" ) { ... }", - "title": "Registering a route" - }, - { - "location": "/getting-started/routing/#routes", - "text": "The best place to add routes is in the routes.swift file.\nYou will find a router there that is ready to use. import Vapor final class Routes : RouteCollection { \n ... \n\n func boot ( router : Router ) throws { \n router . get ( \"hello\" ) { req in \n return \"Hello, world!\" \n } \n } } You must return a Future containing a ResponseEncodable here.\nThe most common ResponseEncodable types are Content , Response amd View .", - "title": "Routes" - }, - { - "location": "/getting-started/routing/#parameters", - "text": "Sometimes you may want one of the components of your route path to be dynamic. This is often used when\nyou want to get an item with a supplied identifier, i.e., GET /users/:id router . get ( \"users\" , Int . parameter ) { req -> Future < String > in \n let id = try req . parameter ( Int . self ) \n return // fetch the user with id } Instead of passing a string, pass the type of parameter you expect. In this case, our User has an Int ID. Tip You can define your own custom parameter types as well.", - "title": "Parameters" - }, - { - "location": "/getting-started/routing/#after-registering-your-routes", - "text": "After registering your routes you must register the Router as a Service", - "title": "After registering your routes" - }, - { - "location": "/getting-started/content/", - "text": "Content\n\u00b6\n\n\nIn Vapor 3, all content types (JSON, protobuf, FormURLEncoded, Multipart, etc) are treated the same.\nAll you need to parse and serialize content is a \nCodable\n class or struct.\n\n\nFor this introduction, we will use JSON as an example. But keep in mind the API is the same for any supported content type.\n\n\nRequest\n\u00b6\n\n\nLet's take a look at how you would parse the following HTTP request.\n\n\nPOST\n \n/login\n \nHTTP\n/\n1.1\n\n\nContent-Type\n:\n \napplication/json\n\n\n\n{\n\n \n\"email\"\n:\n \n\"user@vapor.codes\"\n,\n\n \n\"password\"\n:\n \n\"don't look!\"\n\n\n}\n\n\n\n\n\n\nDecode Request\n\u00b6\n\n\nFirst, create a struct or class that represents the data you expect.\n\n\nimport\n \nFoundation\n\n\nimport\n \nVapor\n\n\n\nstruct\n \nLoginRequest\n:\n \nContent\n \n{\n\n \nvar\n \nemail\n:\n \nString\n\n \nvar\n \npassword\n:\n \nString\n\n\n}\n\n\n\n\n\n\nThen simply conform this struct or class to \nContent\n.\nNow we are ready to decode that HTTP request.\n\n\nrouter\n.\npost\n(\n\"login\"\n)\n \n{\n \nreq\n \n->\n \nResponse\n \nin\n\n \nlet\n \nloginRequest\n \n=\n \ntry\n \nreq\n.\ncontent\n.\ndecode\n(\nLoginRequest\n.\nself\n)\n\n\n \nprint\n(\nloginRequest\n.\nemail\n)\n \n// user@vapor.codes\n\n \nprint\n(\nloginRequest\n.\npassword\n)\n \n// don't look!\n\n\n \nreturn\n \nResponse\n(\nstatus\n:\n \n.\nok\n)\n\n\n}\n\n\n\n\n\n\nIt's that simple!\n\n\nOther Request Types\n\u00b6\n\n\nSince the request in the previous example declared JSON as it's content type,\nVapor knows to use a JSON decoder automatically.\nThis same method would work just as well for the following request.\n\n\nPOST\n \n/login\n \nHTTP\n/\n1.1\n\n\nContent-Type\n:\n \napplication/x-www-form-urlencoded\n\n\nemail=user@vapor.codes&don't+look!\n\n\n\n\n\n\n\nTip\n\n\nYou can configure which encoders/decoders Vapor uses. Read on to learn more.\n\n\n\n\nResponse\n\u00b6\n\n\nLet's take a look at how you would create the following HTTP response.\n\n\nHTTP\n/\n1.1\n \n200\n \nOK\n\n\nContent-Type\n:\n \napplication/json\n\n\n\n{\n\n \n\"name\"\n:\n \n\"Vapor User\"\n,\n\n \n\"email\"\n:\n \n\"user@vapor.codes\"\n\n\n}\n\n\n\n\n\n\nEncode Response\n\u00b6\n\n\nJust like decoding, first create a struct or class that represents the data your expect.\n\n\nimport\n \nFoundation\n\n\nimport\n \nVapor\n\n\n\nstruct\n \nUser\n:\n \nContent\n \n{\n\n \nvar\n \nname\n:\n \nString\n\n \nvar\n \nemail\n:\n \nString\n\n\n}\n\n\n\n\n\n\nThen just conform this struct or class to \nContent\n.\nNow we are ready to encode that HTTP response.\n\n\nrouter\n.\nget\n(\n\"user\"\n)\n \n{\n \nreq\n \n->\n \nResponse\n \nin\n\n \nlet\n \nuser\n \n=\n \nUser\n(\n\n \nname\n:\n \n\"Vapor User\"\n,\n\n \nemail\n:\n \n\"user@vapor.codes\"\n\n \n)\n\n\n \nlet\n \nres\n \n=\n \nResponse\n(\nstatus\n:\n \n.\nok\n)\n\n \ntry\n \nres\n.\ncontent\n.\nencode\n(\nuser\n,\n \nas\n:\n \n.\njson\n)\n\n \nreturn\n \nres\n\n\n}\n\n\n\n\n\n\nOther Response Types\n\u00b6\n\n\nContent will automatically encode as JSON by default. You can always override which content type is used\nusing the \nas:\n parameter.\n\n\ntry\n \nres\n.\ncontent\n.\nencode\n(\nuser\n,\n \nas\n:\n \n.\nformURLEncoded\n)\n\n\n\n\n\n\nYou can also change the default media type for any class or struct.\n\n\nstruct\n \nUser\n:\n \nContent\n \n{\n\n \n/// See Content.defaultMediaType\n\n \nstatic\n \nlet\n \ndefaultMediaType\n:\n \nMediaType\n \n=\n \n.\nformURLEncoded\n\n\n \n...\n\n\n}\n\n\n\n\n\n\nConfiguring Content\n\u00b6\n\n\nComing soon.", - "title": "Content" - }, - { - "location": "/getting-started/content/#content", - "text": "In Vapor 3, all content types (JSON, protobuf, FormURLEncoded, Multipart, etc) are treated the same.\nAll you need to parse and serialize content is a Codable class or struct. For this introduction, we will use JSON as an example. But keep in mind the API is the same for any supported content type.", - "title": "Content" - }, - { - "location": "/getting-started/content/#request", - "text": "Let's take a look at how you would parse the following HTTP request. POST /login HTTP / 1.1 Content-Type : application/json { \n \"email\" : \"user@vapor.codes\" , \n \"password\" : \"don't look!\" }", - "title": "Request" - }, - { - "location": "/getting-started/content/#decode-request", - "text": "First, create a struct or class that represents the data you expect. import Foundation import Vapor struct LoginRequest : Content { \n var email : String \n var password : String } Then simply conform this struct or class to Content .\nNow we are ready to decode that HTTP request. router . post ( \"login\" ) { req -> Response in \n let loginRequest = try req . content . decode ( LoginRequest . self ) \n\n print ( loginRequest . email ) // user@vapor.codes \n print ( loginRequest . password ) // don't look! \n\n return Response ( status : . ok ) } It's that simple!", - "title": "Decode Request" - }, - { - "location": "/getting-started/content/#other-request-types", - "text": "Since the request in the previous example declared JSON as it's content type,\nVapor knows to use a JSON decoder automatically.\nThis same method would work just as well for the following request. POST /login HTTP / 1.1 Content-Type : application/x-www-form-urlencoded \n\nemail=user@vapor.codes&don't+look! Tip You can configure which encoders/decoders Vapor uses. Read on to learn more.", - "title": "Other Request Types" - }, - { - "location": "/getting-started/content/#response", - "text": "Let's take a look at how you would create the following HTTP response. HTTP / 1.1 200 OK Content-Type : application/json { \n \"name\" : \"Vapor User\" , \n \"email\" : \"user@vapor.codes\" }", - "title": "Response" - }, - { - "location": "/getting-started/content/#encode-response", - "text": "Just like decoding, first create a struct or class that represents the data your expect. import Foundation import Vapor struct User : Content { \n var name : String \n var email : String } Then just conform this struct or class to Content .\nNow we are ready to encode that HTTP response. router . get ( \"user\" ) { req -> Response in \n let user = User ( \n name : \"Vapor User\" , \n email : \"user@vapor.codes\" \n ) \n\n let res = Response ( status : . ok ) \n try res . content . encode ( user , as : . json ) \n return res }", - "title": "Encode Response" - }, - { - "location": "/getting-started/content/#other-response-types", - "text": "Content will automatically encode as JSON by default. You can always override which content type is used\nusing the as: parameter. try res . content . encode ( user , as : . formURLEncoded ) You can also change the default media type for any class or struct. struct User : Content { \n /// See Content.defaultMediaType \n static let defaultMediaType : MediaType = . formURLEncoded \n\n ... }", - "title": "Other Response Types" - }, - { - "location": "/getting-started/content/#configuring-content", - "text": "Coming soon.", - "title": "Configuring Content" - }, - { - "location": "/getting-started/futures/", - "text": "Futures\n\u00b6\n\n\nYou may have noticed some APIs in Vapor expect or return a \nFuture\n type.\nIf this is your first time hearing about futures, they might seem a little confusing at first.\nBut don't worry, Vapor makes them easy to use.\n\n\nPromises and Futures are two strongly related types. Every promise has a future.\nA promise is a write-only entity that has the ability to complete (or fail) it's Future counterpart.\n\n\nFutures are a read-only entity that can have a successful or error case. Successful cases are called the \"Expectation\".\n\n\nFutures can be used to register callbacks to, which will always executed in the order of registration. Promises can only be completed once. If a promise is completed more than once the input will be \nignored\n.\n\n\nBasics\n\u00b6\n\n\nCreating a promise is when the result is returned in the future at an unknown time.\nFor the sake of demonstration, however, the promise will be completed at a predefined point in time and execution.\n\n\nWithin the \n.do\n block you may not throw an error or return a result.\n\n\nlet\n \npromise\n \n=\n \nPromise\n<\nString\n>()\n\n\nlet\n \nfuture\n \n=\n \npromise\n.\nfuture\n \n// Future\n\n\n\nfuture\n.\ndo\n \n{\n \nstring\n \nin\n\n \nprint\n(\nstring\n)\n\n\n}\n\n\n\npromise\n.\ncomplete\n(\n\"Hello\"\n)\n\n\n\n\n\n\nThe above code prints \"Hello\" in the console.\n\n\nErrors\n\u00b6\n\n\nWhen running the above code, you may have noticed a warning pop up. This is because the \n.do\n block only handles successful completions. If we were to replace the completion with the following code the \n.do\n block would never get run:\n\n\nstruct\n \nMyError\n:\n \nError\n \n{}\n\n\n\npromise\n.\nfail\n(\nMyError\n())\n\n\n\n\n\n\nInstead, a \n.catch\n block will be triggered.\n\n\nfuture\n.\ndo\n \n{\n \nstring\n \nin\n\n \nprint\n(\nstring\n)\n\n\n}.\ncatch\n \n{\n \nerror\n \nin\n\n \nprint\n(\n\"Error '\n\\(\nerror\n)\n' occurred\"\n)\n\n\n}\n\n\n\n\n\n\nIn this scenario the test \"Error 'MyError' occurred\" will appear.\n\n\nWithin the \n.catch\n block you may not throw an error or return a result.\n\n\nBasic Transformations\n\u00b6\n\n\nTransformations are one of the more critical parts of Vapor 3's future system. They assist in reducing the complexity of futures and keep code isolated and readable. You can use the \n.map\n function to transform the future expectation to another future of the same or a different type. You need to explicitly state which type will be returned in the mapping closure.\n\n\nThe mapping closure(s) will \nonly\n be executed if an expectation has been received in the previous step. If at any point a transformation function throws an error, execution stops there and the \n.catch\n block will be executed.\n\n\nIf the promise that was mapped failed to begin with, the \n.catch\n block will also be executed \nwithout\n triggering \nany\n mapping closures.\n\n\nlet\n \npromise\n \n=\n \nPromise\n<\nInt\n>()\n\n\n\npromise\n.\nfuture\n.\ndo\n \n{\n \nint\n \nin\n\n \nprint\n(\nint\n)\n\n\n}.\nmap\n(\nto\n:\n \nInt\n.\nself\n)\n \n{\n \nint\n \nin\n\n \nreturn\n \nint\n \n+\n \n4\n\n\n}.\nmap\n(\nto\n:\n \nString\n.\nself\n)\n \n{\n \nint\n \nin\n\n \nreturn\n \nint\n.\ndescription\n\n\n}.\ndo\n \n{\n \nstring\n \nin\n\n \nprint\n(\nstring\n)\n\n\n}.\ncatch\n \n{\n \nerror\n \nin\n\n \nprint\n(\n\"Error '\n\\(\nerror\n)\n' occurred\"\n)\n\n\n}\n\n\n\npromise\n.\ncomplete\n(\n3\n)\n\n\n\n\n\n\nThe above code will print the inputted integer. Then map the input to \n(integer + 4) == 7\n.\nThen the textual representation of the integer is returned as a \nString\n which will be printed.\n\n\nThis results in the following console output:\n\n\n3\n\n\n7\n\n\n\n\n\n\nRecursive futures\n\u00b6\n\n\nIn the above \nmap\n function we returned a new result synchronously. In some situations, however, you'll need to dispatch another asynchronous call based on the result of a previous call.\n\n\nFirst, let's see how this would work out using \nmap\n by exaggerating synchronous code as if it were an asynchronous call.\n\n\n\n\nWarning\n\n\nDo not use this implementation, use the next one instead. This is an unnecessarily complicated way of nesting futures.\n\n\n\n\nlet\n \npromise\n \n=\n \nPromise\n<\nInt\n>()\n\n\n\npromise\n.\nmap\n(\nto\n:\n \nFuture\n<\nInt\n>.\nself\n)\n \n{\n \nint\n \nin\n\n \nreturn\n \nFuture\n(\nint\n \n+\n \n4\n)\n\n\n}.\nmap\n(\nto\n:\n \nFuture\n<\nFuture\n<\nString\n>\n>\n.\nself\n)\n \n{\n \nfutureInt\n \nin\n\n \nreturn\n \nfutureInt\n.\nmap\n(\nto\n:\n \nFuture\n<\nString\n.\nself\n>)\n \n{\n \nint\n \nin\n\n \nreturn\n \nFuture\n(\nint\n.\ndescription\n)\n\n \n}\n\n\n}.\ndo\n \n{\n \ndoubleFutureString\n \nin\n \n// Future>\n\n \ndoubleFutureString\n.\ndo\n \n{\n \nfutureString\n \nin\n \n// Future\n\n \nfutureString\n.\ndo\n \n{\n \nstring\n \nin\n\n \nprint\n(\nstring\n)\n\n \n}.\ncatch\n \n{\n \nerror\n \nin\n\n \nprint\n(\n\"Error '\n\\(\nerror\n)\n' occurred\"\n)\n\n \n}\n\n \n}.\ncatch\n \n{\n \nerror\n \nin\n\n \nprint\n(\n\"Error '\n\\(\nerror\n)\n' occurred\"\n)\n\n \n}\n\n\n}.\ncatch\n \n{\n \nerror\n \nin\n\n \nprint\n(\n\"Error '\n\\(\nerror\n)\n' occurred\"\n)\n\n\n}\n\n\n\npromise\n.\ncomplete\n(\n3\n)\n\n\n\n\n\n\nTo flatten this asynchronous recursion, instead, we recommend using \nflatMap\n.\nThe type supplied in the \nto:\n argument is implied to be wrapped in a \nFuture<>\n.\n\n\nlet\n \npromise\n \n=\n \nPromise\n<\nInt\n>()\n\n\n\npromise\n.\nflatMap\n(\nto\n:\n \nInt\n.\nself\n)\n \n{\n \nint\n \nin\n\n \nreturn\n \nFuture\n<\nInt\n>(\nint\n \n+\n \n4\n)\n\n\n}.\nflatMap\n(\nto\n:\n \nString\n.\nself\n)\n \n{\n \nint\n \nin\n\n \nreturn\n \nFuture\n(\nint\n.\ndescription\n)\n\n\n}.\ndo\n \n{\n \nstring\n \nin\n\n \nprint\n(\nstring\n)\n\n\n}.\ncatch\n \n{\n \nerror\n \nin\n\n \nprint\n(\n\"Error '\n\\(\nerror\n)\n' occurred\"\n)\n\n\n}\n\n\n\npromise\n.\ncomplete\n(\n3\n)\n\n\n\n\n\n\nAlways\n\u00b6\n\n\nSometimes you want to always execute a function as part of the cleanup phase.\nYou can use the \n.always\n block to execute a block of code after the future has been successfully executed (and mapped if applicable) or when an error occurs. Please do consider that \nalways\n also will be executed in the order in which it has been registered, like all other closures.\n\n\nvar\n \ni\n \n=\n \n0\n\n\n\nlet\n \npromise\n \n=\n \nPromise\n<\nInt\n>()\n\n\nlet\n \nfuture\n \n=\n \npromise\n.\nfuture\n \n// Future\n\n\n\nfuture\n.\ndo\n \n{\n \nint\n \nin\n\n \ni\n \n+=\n \nint\n \n*\n \n3\n\n\n}.\ndo\n \n{\n \nint\n \nin\n\n \ni\n \n+=\n \n(\nint\n \n-\n \n1\n)\n\n\n}.\nalways\n \n{\n\n \nprint\n(\ni\n)\n\n \ni\n \n=\n \n0\n\n\n}.\ncatch\n \n{\n \n_\n \nin\n\n \ni\n \n=\n \n-\n1\n\n\n}\n\n\n\n\n\n\nAt the end of the above function, \ni\n will \nalways\n be 0. If the promise is completed with the successful result \ni\n, the number \"11\" will be printed. On error, \"-1\" will be printed.\n\n\nSignals\n\u00b6\n\n\nSignals, or \nFuture\n is a Future that can contain either an Error or Void (the Expectation). \nFuture\n is often used to indicate the successful or unsuccessful completion of a task.", - "title": "Futures" - }, - { - "location": "/getting-started/futures/#futures", - "text": "You may have noticed some APIs in Vapor expect or return a Future type.\nIf this is your first time hearing about futures, they might seem a little confusing at first.\nBut don't worry, Vapor makes them easy to use. Promises and Futures are two strongly related types. Every promise has a future.\nA promise is a write-only entity that has the ability to complete (or fail) it's Future counterpart. Futures are a read-only entity that can have a successful or error case. Successful cases are called the \"Expectation\". Futures can be used to register callbacks to, which will always executed in the order of registration. Promises can only be completed once. If a promise is completed more than once the input will be ignored .", - "title": "Futures" - }, - { - "location": "/getting-started/futures/#basics", - "text": "Creating a promise is when the result is returned in the future at an unknown time.\nFor the sake of demonstration, however, the promise will be completed at a predefined point in time and execution. Within the .do block you may not throw an error or return a result. let promise = Promise < String >() let future = promise . future // Future future . do { string in \n print ( string ) } promise . complete ( \"Hello\" ) The above code prints \"Hello\" in the console.", - "title": "Basics" - }, - { - "location": "/getting-started/futures/#errors", - "text": "When running the above code, you may have noticed a warning pop up. This is because the .do block only handles successful completions. If we were to replace the completion with the following code the .do block would never get run: struct MyError : Error {} promise . fail ( MyError ()) Instead, a .catch block will be triggered. future . do { string in \n print ( string ) }. catch { error in \n print ( \"Error ' \\( error ) ' occurred\" ) } In this scenario the test \"Error 'MyError' occurred\" will appear. Within the .catch block you may not throw an error or return a result.", - "title": "Errors" - }, - { - "location": "/getting-started/futures/#basic-transformations", - "text": "Transformations are one of the more critical parts of Vapor 3's future system. They assist in reducing the complexity of futures and keep code isolated and readable. You can use the .map function to transform the future expectation to another future of the same or a different type. You need to explicitly state which type will be returned in the mapping closure. The mapping closure(s) will only be executed if an expectation has been received in the previous step. If at any point a transformation function throws an error, execution stops there and the .catch block will be executed. If the promise that was mapped failed to begin with, the .catch block will also be executed without triggering any mapping closures. let promise = Promise < Int >() promise . future . do { int in \n print ( int ) }. map ( to : Int . self ) { int in \n return int + 4 }. map ( to : String . self ) { int in \n return int . description }. do { string in \n print ( string ) }. catch { error in \n print ( \"Error ' \\( error ) ' occurred\" ) } promise . complete ( 3 ) The above code will print the inputted integer. Then map the input to (integer + 4) == 7 .\nThen the textual representation of the integer is returned as a String which will be printed. This results in the following console output: 3 7", - "title": "Basic Transformations" - }, - { - "location": "/getting-started/futures/#recursive-futures", - "text": "In the above map function we returned a new result synchronously. In some situations, however, you'll need to dispatch another asynchronous call based on the result of a previous call. First, let's see how this would work out using map by exaggerating synchronous code as if it were an asynchronous call. Warning Do not use this implementation, use the next one instead. This is an unnecessarily complicated way of nesting futures. let promise = Promise < Int >() promise . map ( to : Future < Int >. self ) { int in \n return Future ( int + 4 ) }. map ( to : Future < Future < String > > . self ) { futureInt in \n return futureInt . map ( to : Future < String . self >) { int in \n return Future ( int . description ) \n } }. do { doubleFutureString in // Future> \n doubleFutureString . do { futureString in // Future \n futureString . do { string in \n print ( string ) \n }. catch { error in \n print ( \"Error ' \\( error ) ' occurred\" ) \n } \n }. catch { error in \n print ( \"Error ' \\( error ) ' occurred\" ) \n } }. catch { error in \n print ( \"Error ' \\( error ) ' occurred\" ) } promise . complete ( 3 ) To flatten this asynchronous recursion, instead, we recommend using flatMap .\nThe type supplied in the to: argument is implied to be wrapped in a Future<> . let promise = Promise < Int >() promise . flatMap ( to : Int . self ) { int in \n return Future < Int >( int + 4 ) }. flatMap ( to : String . self ) { int in \n return Future ( int . description ) }. do { string in \n print ( string ) }. catch { error in \n print ( \"Error ' \\( error ) ' occurred\" ) } promise . complete ( 3 )", - "title": "Recursive futures" - }, - { - "location": "/getting-started/futures/#always", - "text": "Sometimes you want to always execute a function as part of the cleanup phase.\nYou can use the .always block to execute a block of code after the future has been successfully executed (and mapped if applicable) or when an error occurs. Please do consider that always also will be executed in the order in which it has been registered, like all other closures. var i = 0 let promise = Promise < Int >() let future = promise . future // Future future . do { int in \n i += int * 3 }. do { int in \n i += ( int - 1 ) }. always { \n print ( i ) \n i = 0 }. catch { _ in \n i = - 1 } At the end of the above function, i will always be 0. If the promise is completed with the successful result i , the number \"11\" will be printed. On error, \"-1\" will be printed.", - "title": "Always" - }, - { - "location": "/getting-started/futures/#signals", - "text": "Signals, or Future is a Future that can contain either an Error or Void (the Expectation). Future is often used to indicate the successful or unsuccessful completion of a task.", - "title": "Signals" - }, - { - "location": "/getting-started/cloud/", - "text": "Deployment\n\u00b6\n\n\nDeploying code is the process of making your Vapor project publically available. \nIt can be one of the most difficult aspects of web development. Fortunately, there\nare services to help.\n\n\nVapor Cloud\n\u00b6\n\n\nThe best way to deploy your application is through Vapor Cloud. It's a cloud platform built\nspecifically for the Vapor web framework. This means it's incredibly easy to deploy your\nproject quickly and be confident that it will be fast and stable.\n\n\nDeploying your project to Vapor Cloud is simple, it's built right into the \nVapor Toolbox\n.\nJust run this command from within the root directory of your project.\n\n\nvapor cloud deploy\n\n\n\n\n\nFor a detailed guide, visit \nVapor Cloud \u2192 Quick Start\n.\n\n\nOther Options\n\u00b6\n\n\nVapor can be deployed anywhere that supports Ubuntu (basically everywhere). To learn more about\ndeploying your code, checkout \nDeploy \u2192 Getting Started", - "title": "Deployment" - }, - { - "location": "/getting-started/cloud/#deployment", - "text": "Deploying code is the process of making your Vapor project publically available. \nIt can be one of the most difficult aspects of web development. Fortunately, there\nare services to help.", - "title": "Deployment" - }, - { - "location": "/getting-started/cloud/#vapor-cloud", - "text": "The best way to deploy your application is through Vapor Cloud. It's a cloud platform built\nspecifically for the Vapor web framework. This means it's incredibly easy to deploy your\nproject quickly and be confident that it will be fast and stable. Deploying your project to Vapor Cloud is simple, it's built right into the Vapor Toolbox .\nJust run this command from within the root directory of your project. vapor cloud deploy For a detailed guide, visit Vapor Cloud \u2192 Quick Start .", - "title": "Vapor Cloud" - }, - { - "location": "/getting-started/cloud/#other-options", - "text": "Vapor can be deployed anywhere that supports Ubuntu (basically everywhere). To learn more about\ndeploying your code, checkout Deploy \u2192 Getting Started", - "title": "Other Options" - }, - { - "location": "/concepts/vapor/", - "text": "What is Vapor?\n\u00b6\n\n\nVapor 3 is an \nasynchronous\n, codable and protocol oriented framework. This document will outline the major lines how vapor is designed and why.\n\n\nAsync\n\u00b6\n\n\nVapor 3 async is a framework consisting of two basic principles. \nFutures\n and \nReactive Streams\n. Both have their ideal use cases and strengths.\n\n\nPerformance\n\u00b6\n\n\nVapor's high performance is achieved by a combination of an \nasynchronous architecture\n, Copy on Write mechanics and highly optimized lazy parsers. These three techniques combined with Swift's compiler ensure that our performance is comparable to Go.\n\n\nType safety\n\u00b6\n\n\nVapor is designed around type-safety and compile-time checks, ensuring that code doesn't behave in unexpected ways and shows you most problems at compile time. Vapor achieves this by leveraging Swift and its Codable protocol.\n\n\nWe believe type-safety is critical to both security and developer productivity.\n\n\nEasy to use\n\u00b6\n\n\nCreating a new Vapor project takes only a few minutes.\n\n\nWe've got you covered with our thorough documentation and have \na helpful community\n to back it up!", - "title": "Vapor" - }, - { - "location": "/concepts/vapor/#what-is-vapor", - "text": "Vapor 3 is an asynchronous , codable and protocol oriented framework. This document will outline the major lines how vapor is designed and why.", - "title": "What is Vapor?" - }, - { - "location": "/concepts/vapor/#async", - "text": "Vapor 3 async is a framework consisting of two basic principles. Futures and Reactive Streams . Both have their ideal use cases and strengths.", - "title": "Async" - }, - { - "location": "/concepts/vapor/#performance", - "text": "Vapor's high performance is achieved by a combination of an asynchronous architecture , Copy on Write mechanics and highly optimized lazy parsers. These three techniques combined with Swift's compiler ensure that our performance is comparable to Go.", - "title": "Performance" - }, - { - "location": "/concepts/vapor/#type-safety", - "text": "Vapor is designed around type-safety and compile-time checks, ensuring that code doesn't behave in unexpected ways and shows you most problems at compile time. Vapor achieves this by leveraging Swift and its Codable protocol. We believe type-safety is critical to both security and developer productivity.", - "title": "Type safety" - }, - { - "location": "/concepts/vapor/#easy-to-use", - "text": "Creating a new Vapor project takes only a few minutes. We've got you covered with our thorough documentation and have a helpful community to back it up!", - "title": "Easy to use" - }, - { - "location": "/concepts/services/", - "text": "Services\n\u00b6\n\n\nServices is a framework for creating things you need in your application in a type-safe fashion with protocol and environment support.\n\n\nThe Services framework is designed to be thread unsafe. The framework aims to guarantee that a service exists on the same \nEventLoop\n it was created from and will be used on.\n\n\nGlossary\n\u00b6\n\n\nContainer\n\u00b6\n\n\nContainers are \nEventLoops\n that can create and cache services.\n\n\nRequest\n is the most common \nContainer\n type, which can be accessed in every \nRoute\n.\n\n\nContainers cache instances of a given service (keyed by the requested protocol) on a per-container basis.\n\n\n\n\nAny given container has its own cache. No two containers will ever share a service instance, whether singleton or not.\n\n\nA singleton service is chosen and cached only by which interface(s) it supports and the service tag.\nThere will only ever be one instance of a singleton service per-container, regardless of what requested it.\n\n\nA normal service is chosen and cached by which interface(s) it supports, the service tag, and the requesting client interface.\nThere will be as many instances of a normal service per-container as there are unique clients requesting it.\n(Remembering that clients are also interface types, not instances - that's the \nfor:\n parameter to \n.make()\n)\n\n\n\n\nEphemeralContainer\n\u00b6\n\n\nEphemeralContainers are containers that are short-lived.\nTheir cache does not stretch beyond a short lifecycle.\nThe most common EphemeralContainer is an \nHTTP Request\n which lives for the duration of the route handler.\n\n\nService\n\u00b6\n\n\nServices are a type that can be requested from a Container. They are registered as part of the application setup.\n\n\nServices are registered to a matching type or protocol it can represent, including it's own concrete type.\n\n\nServices are registered to a blueprint before the \nApplication\n is initialized. Together they make up the blueprint that Containers use to create an individual Service.\n\n\nEnvironment\n\u00b6\n\n\nEnvironments indicate the type of deployment/situation in which an application is ran. Environments can be used to change database credentials or API tokens per environment automatically.\n\n\nRegistering\n\u00b6\n\n\nServices are registered as a concrete (singleton) type or factories. Singleton types should be a struct, but can be a class.\n\n\nTo create an empty list of Services you can call the initializer without parameters\n\n\nvar\n \nservices\n \n=\n \nServices\n()\n\n\n\n\n\n\nThe Vapor framework has a default setup with the most common (and officially supported) Services already registered.\n\n\nvar\n \nservices\n \n=\n \nServices\n.\ndefault\n()\n\n\n\n\n\n\nConcrete implementations\n\u00b6\n\n\nA common use case for registering a struct is for registering configurations.\nVapor 3 configurations are \nalways\n a concrete struct type. Registering a concrete type is simple:\n\n\nstruct\n \nEmptyService\n \n{}\n\n\n\nservices\n.\ninstance\n(\nEmptyService\n())\n\n\n\n\n\n\nSingletons\n\u00b6\n\n\nSingleton services (which declare themselves, or were registered, as such) are cached on a per-container basis, but the singleton cache ignores which Client is requesting the service (whereas the normal cache does not).\n\n\nSingleton classes \nmust\n be thread-safe to prevent crashes. If you want your class to be a singleton type (across all threads):\n\n\nfinal\n \nclass\n \nSingletonService\n \n{\n\n \ninit\n()\n \n{}\n\n\n}\n\n\n\nservices\n.\ninstance\n(\nisSingleton\n:\n \ntrue\n,\n \nSingletonService\n())\n\n\n\n\n\n\nAssuming the above service, you can now make this service from a container. The global container in Vapor is \nApplication\n which \nmust not\n be used within routes.\n\n\nlet\n \napp\n \n=\n \ntry\n \nApplication\n(\nservices\n:\n \nservices\n)\n\n\nlet\n \nemptyService\n \n=\n \napp\n.\nmake\n(\nEmptyService\n.\nself\n)\n\n\n\n\n\n\nProtocol conforming services\n\u00b6\n\n\nOften times when registering a service is conforms to one or more protocols for which it can be used. This is one of the more widely used use cases for Services.\n\n\nenum\n \nLevel\n \n{\n\n \ncase\n \nverbose\n,\n \nerror\n\n\n}\n\n\n\nprotocol\n \nLogger\n \n{\n\n \nfunc\n \nlog\n(\n_\n \nmessage\n:\n \nString\n,\n \nlevel\n:\n \nLevel\n)\n\n\n}\n\n\n\nstruct\n \nPrintLogger\n:\n \nLogger\n \n{\n\n \ninit\n()\n \n{}\n\n\n \nfunc\n \nlog\n(\n_\n \nmessage\n:\n \nString\n,\n \nlevel\n:\n \nLevel\n)\n \n{\n\n \nprint\n(\nmessage\n)\n\n \n}\n\n\n}\n\n\n\nservices\n.\ninstance\n(\nLogger\n.\nself\n,\n \nPrintLogger\n())\n\n\n\n\n\n\nThe above can be combined with \nisSingleton: true\n\n\nRegistering multiple conformances\n\u00b6\n\n\nA single type can conform to multiple protocols, and you might want to register a single service for all those conforming situations.\n\n\nprotocol\n \nConsole\n \n{\n\n \nfunc\n \nwrite\n(\n_\n \nmessage\n:\n \nString\n,\n \ncolor\n:\n \nAnsiColor\n)\n\n\n}\n\n\n\nstruct\n \nPrintConsole\n:\n \nConsole\n,\n \nLogger\n \n{\n\n \nfunc\n \nwrite\n(\n_\n \nmessage\n:\n \nString\n,\n \ncolor\n:\n \nAnsiColor\n)\n \n{\n\n \nprint\n(\nmessage\n)\n\n \n}\n\n\n \nfunc\n \nlog\n(\n_\n \nmessage\n:\n \nString\n,\n \nlevel\n:\n \nLevel\n)\n \n{\n\n \nprint\n(\nmessage\n)\n\n \n}\n\n\n \ninit\n()\n \n{}\n\n\n}\n\n\n\nservices\n.\ninstance\n(\n\n \nsupports\n:\n \n[\nLogger\n.\nself\n,\n \nConsole\n.\nself\n],\n\n \nErrorLogger\n()\n\n\n)\n\n\n\n\n\n\nRegistering for a specific requester\n\u00b6\n\n\nSometimes, the implementation should change depending on the user. A database connector might need to run over a VPN tunnel, redis might use an optimized local loopback whilst the default implementation is a normal TCP socket.\n\n\nOther times, you simply want to change the log destination depending on the type that's logging (such as logging HTTP errors differently from database errors).\n\n\nThis comes in useful when changing configurations per situation, too.\n\n\nstruct\n \nVerboseLogger\n:\n \nLogger\n \n{\n\n \ninit\n()\n \n{}\n\n\n \nfunc\n \nlog\n(\n_\n \nmessage\n:\n \nString\n,\n \nlevel\n:\n \nLevel\n)\n \n{\n\n \nprint\n(\nmessage\n)\n\n \n}\n\n\n}\n\n\n\nstruct\n \nErrorLogger\n:\n \nLogger\n \n{\n\n \ninit\n()\n \n{}\n\n\n \nfunc\n \nlog\n(\n_\n \nmessage\n:\n \nString\n,\n \nlevel\n:\n \nLevel\n)\n \n{\n\n \nif\n \nlevel\n \n==\n \n.\nerror\n \n{\n\n \nprint\n(\nmessage\n)\n\n \n}\n\n \n}\n\n\n}\n\n\n\n// Only log errors\n\n\nservices\n.\ninstance\n(\nLogger\n.\nself\n,\n \nErrorLogger\n())\n\n\n\n// Except the router, do log not found errors verbosely\n\n\nservices\n.\ninstance\n(\nLogger\n.\nself\n,\n \nPrintLogger\n(),\n \nfor\n:\n \nRouter\n.\nself\n)\n\n\n\n\n\n\nFactorized services\n\u00b6\n\n\nSome services have dependencies. An extremly useful use case is TLS, where the implementation is separated from the protocol. This allows users to create a TLS socket to connect to another host with without relying on a specific implementation. Vapor uses this to better integrate with the operating system by changing the default TLS implementation from OpenSSL on Linux to the Transport Security Framework on macOS and iOS.\n\n\nFactorized services get access to the event loop to factorize dependencies.\n\n\nservices\n.\nregister\n \n{\n \ncontainer\n \n->\n \nGithubClient\n \nin\n\n \n// Create an HTTP client for our GithubClient\n\n \nlet\n \nclient\n \n=\n \ntry\n \ncontainer\n.\nmake\n(\nClient\n.\nself\n,\n \nfor\n:\n \nGithubClient\n.\nself\n)\n\n \ntry\n \nclient\n.\nconnect\n(\nhostname\n:\n \n\"github.com\"\n,\n \nssl\n:\n \ntrue\n)\n\n\n \nreturn\n \nGithubClient\n(\nusing\n:\n \nclient\n)\n\n\n}\n\n\n\n\n\n\nPlease do note that we explicitly stated that the \nGithubClient\n requests an (HTTP) Client. We recommend doing this at all times, so that you leave configuration options open.\n\n\nEnvironments\n\u00b6\n\n\nVapor 3 supports (custom) environments. By default we recommend (and support) the \n.production\n, \n.development\n and \n.testing\n environments.\n\n\nYou can create a custom environment type as \n.custom()\n.\n\n\nlet\n \nenvironment\n \n=\n \nEnvironment\n.\ncustom\n(\n\"staging\"\n)\n\n\n\n\n\n\nContainers give access to the current environment, so libraries may change behaviour depending on the environment.\n\n\nChanging configurations per environment\n\u00b6\n\n\nFor easy of development, some parameters may and should change for easy of debugging.\nPassword hashes can be made intentionally weaker in development scenarios to compensate for debug compilation performance, or API tokens may change to the correct one for your environment.\n\n\nservices\n.\nregister\n \n{\n \ncontainer\n \n->\n \nBCryptConfig\n \nin\n\n \nlet\n \ncost\n:\n \nInt\n\n\n \nswitch\n \ncontainer\n.\nenvironment\n \n{\n\n \ncase\n \n.\nproduction\n:\n\n \ncost\n \n=\n \n12\n\n \ndefault\n:\n\n \ncost\n \n=\n \n4\n\n \n}\n\n\n \nreturn\n \nBCryptConfig\n(\ncost\n:\n \ncost\n)\n\n\n}\n\n\n\n\n\n\nGetting a Service\n\u00b6\n\n\nTo get a service you need an existing container matching the current EventLoop.\nIf you're processing a \nRequest\n, you should almost always use the Request as a Container type.\n\n\n// ErrorLogger\n\n\nlet\n \nerrorLogger\n \n=\n \nmyContainerType\n.\nmake\n(\nLogger\n.\nself\n,\n \nfor\n:\n \nRequest\n.\nself\n)\n\n\n\n// PrintLogger\n\n\nlet\n \nprintLogger\n \n=\n \nmyContainerType\n.\nmake\n(\nLogger\n.\nself\n,\n \nfor\n:\n \nRouter\n.\nself\n)", - "title": "Services" - }, - { - "location": "/concepts/services/#services", - "text": "Services is a framework for creating things you need in your application in a type-safe fashion with protocol and environment support. The Services framework is designed to be thread unsafe. The framework aims to guarantee that a service exists on the same EventLoop it was created from and will be used on.", - "title": "Services" - }, - { - "location": "/concepts/services/#glossary", - "text": "", - "title": "Glossary" - }, - { - "location": "/concepts/services/#container", - "text": "Containers are EventLoops that can create and cache services. Request is the most common Container type, which can be accessed in every Route . Containers cache instances of a given service (keyed by the requested protocol) on a per-container basis. Any given container has its own cache. No two containers will ever share a service instance, whether singleton or not. A singleton service is chosen and cached only by which interface(s) it supports and the service tag.\nThere will only ever be one instance of a singleton service per-container, regardless of what requested it. A normal service is chosen and cached by which interface(s) it supports, the service tag, and the requesting client interface.\nThere will be as many instances of a normal service per-container as there are unique clients requesting it.\n(Remembering that clients are also interface types, not instances - that's the for: parameter to .make() )", - "title": "Container" - }, - { - "location": "/concepts/services/#ephemeralcontainer", - "text": "EphemeralContainers are containers that are short-lived.\nTheir cache does not stretch beyond a short lifecycle.\nThe most common EphemeralContainer is an HTTP Request which lives for the duration of the route handler.", - "title": "EphemeralContainer" - }, - { - "location": "/concepts/services/#service", - "text": "Services are a type that can be requested from a Container. They are registered as part of the application setup. Services are registered to a matching type or protocol it can represent, including it's own concrete type. Services are registered to a blueprint before the Application is initialized. Together they make up the blueprint that Containers use to create an individual Service.", - "title": "Service" - }, - { - "location": "/concepts/services/#environment", - "text": "Environments indicate the type of deployment/situation in which an application is ran. Environments can be used to change database credentials or API tokens per environment automatically.", - "title": "Environment" - }, - { - "location": "/concepts/services/#registering", - "text": "Services are registered as a concrete (singleton) type or factories. Singleton types should be a struct, but can be a class. To create an empty list of Services you can call the initializer without parameters var services = Services () The Vapor framework has a default setup with the most common (and officially supported) Services already registered. var services = Services . default ()", - "title": "Registering" - }, - { - "location": "/concepts/services/#concrete-implementations", - "text": "A common use case for registering a struct is for registering configurations.\nVapor 3 configurations are always a concrete struct type. Registering a concrete type is simple: struct EmptyService {} services . instance ( EmptyService ())", - "title": "Concrete implementations" - }, - { - "location": "/concepts/services/#singletons", - "text": "Singleton services (which declare themselves, or were registered, as such) are cached on a per-container basis, but the singleton cache ignores which Client is requesting the service (whereas the normal cache does not). Singleton classes must be thread-safe to prevent crashes. If you want your class to be a singleton type (across all threads): final class SingletonService { \n init () {} } services . instance ( isSingleton : true , SingletonService ()) Assuming the above service, you can now make this service from a container. The global container in Vapor is Application which must not be used within routes. let app = try Application ( services : services ) let emptyService = app . make ( EmptyService . self )", - "title": "Singletons" - }, - { - "location": "/concepts/services/#protocol-conforming-services", - "text": "Often times when registering a service is conforms to one or more protocols for which it can be used. This is one of the more widely used use cases for Services. enum Level { \n case verbose , error } protocol Logger { \n func log ( _ message : String , level : Level ) } struct PrintLogger : Logger { \n init () {} \n\n func log ( _ message : String , level : Level ) { \n print ( message ) \n } } services . instance ( Logger . self , PrintLogger ()) The above can be combined with isSingleton: true", - "title": "Protocol conforming services" - }, - { - "location": "/concepts/services/#registering-multiple-conformances", - "text": "A single type can conform to multiple protocols, and you might want to register a single service for all those conforming situations. protocol Console { \n func write ( _ message : String , color : AnsiColor ) } struct PrintConsole : Console , Logger { \n func write ( _ message : String , color : AnsiColor ) { \n print ( message ) \n } \n\n func log ( _ message : String , level : Level ) { \n print ( message ) \n } \n\n init () {} } services . instance ( \n supports : [ Logger . self , Console . self ], \n ErrorLogger () )", - "title": "Registering multiple conformances" - }, - { - "location": "/concepts/services/#registering-for-a-specific-requester", - "text": "Sometimes, the implementation should change depending on the user. A database connector might need to run over a VPN tunnel, redis might use an optimized local loopback whilst the default implementation is a normal TCP socket. Other times, you simply want to change the log destination depending on the type that's logging (such as logging HTTP errors differently from database errors). This comes in useful when changing configurations per situation, too. struct VerboseLogger : Logger { \n init () {} \n\n func log ( _ message : String , level : Level ) { \n print ( message ) \n } } struct ErrorLogger : Logger { \n init () {} \n\n func log ( _ message : String , level : Level ) { \n if level == . error { \n print ( message ) \n } \n } } // Only log errors services . instance ( Logger . self , ErrorLogger ()) // Except the router, do log not found errors verbosely services . instance ( Logger . self , PrintLogger (), for : Router . self )", - "title": "Registering for a specific requester" - }, - { - "location": "/concepts/services/#factorized-services", - "text": "Some services have dependencies. An extremly useful use case is TLS, where the implementation is separated from the protocol. This allows users to create a TLS socket to connect to another host with without relying on a specific implementation. Vapor uses this to better integrate with the operating system by changing the default TLS implementation from OpenSSL on Linux to the Transport Security Framework on macOS and iOS. Factorized services get access to the event loop to factorize dependencies. services . register { container -> GithubClient in \n // Create an HTTP client for our GithubClient \n let client = try container . make ( Client . self , for : GithubClient . self ) \n try client . connect ( hostname : \"github.com\" , ssl : true ) \n\n return GithubClient ( using : client ) } Please do note that we explicitly stated that the GithubClient requests an (HTTP) Client. We recommend doing this at all times, so that you leave configuration options open.", - "title": "Factorized services" - }, - { - "location": "/concepts/services/#environments", - "text": "Vapor 3 supports (custom) environments. By default we recommend (and support) the .production , .development and .testing environments. You can create a custom environment type as .custom() . let environment = Environment . custom ( \"staging\" ) Containers give access to the current environment, so libraries may change behaviour depending on the environment.", - "title": "Environments" - }, - { - "location": "/concepts/services/#changing-configurations-per-environment", - "text": "For easy of development, some parameters may and should change for easy of debugging.\nPassword hashes can be made intentionally weaker in development scenarios to compensate for debug compilation performance, or API tokens may change to the correct one for your environment. services . register { container -> BCryptConfig in \n let cost : Int \n\n switch container . environment { \n case . production : \n cost = 12 \n default : \n cost = 4 \n } \n\n return BCryptConfig ( cost : cost ) }", - "title": "Changing configurations per environment" - }, - { - "location": "/concepts/services/#getting-a-service", - "text": "To get a service you need an existing container matching the current EventLoop.\nIf you're processing a Request , you should almost always use the Request as a Container type. // ErrorLogger let errorLogger = myContainerType . make ( Logger . self , for : Request . self ) // PrintLogger let printLogger = myContainerType . make ( Logger . self , for : Router . self )", - "title": "Getting a Service" - }, - { - "location": "/concepts/http/", - "text": "HTTP\n\u00b6\n\n\nAt the heart of the web lies HTTP. HTTP (or HyperText Transfer Protocol) is used for communicating multiple types of media between a client and a server.\n\n\nHTTP comes in two major versions. \nHTTP/1\n and \nHTTP/2\n. Vapor comes with HTTP/1 support by default but has an official package for HTTP/2, too.\n\n\nWhat is the difference?\n\u00b6\n\n\nHTTP/1 is a protocol designed in the '90s for the then new and rapidly evolving internet. The protocol is designed around simplicity above security and functionality.\n\n\nHTTP/2 is a protocol with security and performance in mind. Designed with experience of the past 20 years of internet in addition to modern standards such as a high bandwidth and many resources per page.\n\n\nHow it works\n\u00b6\n\n\nAt the heart of HTTP lie the \nRequest\n and \nResponse\n types. Both of them are \"HTTP Messages\". Both HTTP messages consists of \nHeaders\n and a \nbody\n.\n\n\nHTTP clients connect to an HTTP server. The clients can send a request to which the server will send a response.\n\n\nBodies contain the concrete information being transferred. Think of the web-page, images, videos, JSON and \nforms\n.\n\n\nHeaders contain metadata, meaning they carry information describing the HTTP message, it's context and it's content.\n\n\nCookies\n are context metadata about the client that, for example, can be used for identifying users after they've (successfully) logged in. One of these methods are \nsession tokens\n.\n\n\nAnother type of metadata that is often used to define the type of content transferred in the body is the \nContent-Type\n header\n\n\nRequest\n\u00b6\n\n\nRequests have two additional properties in addition to all properties of a Message. The \nMethod\n and \npath\n.\n\n\nThe path is used to specify the resource being accessed. Although there are conventions, there are no rules/limitations to how you structure your paths except their format. Paths consist of \ncomponents\n. The components are separated by a forward slash (\n/\n). All components must be encoded with percent encoding, affecting special characters only.\n\n\nThe \nmethod\n indicated the operation to this resource. \nGET\n is used for reading a resource where \nDELETE\n will (attempt to) remove the resource. This does not mean you need to blindly comply. If a user doesn't have the permissions for said operation, you can emit a response indicating this.\n\n\nResponse\n\u00b6\n\n\nResponses have one additional property in addition to the message's properties. This is \nthe status code\n. The status code is used to indicate to the client what the status/result is of a Request. If a client was not authenticated, for example, you would return a status 401 or 403 for \"Unauthorized\" or \"Forbidden\" respectively. \nMore about status codes here.\n\n\nHandling requests\n\u00b6\n\n\nRequests in Vapor will be handled by a \nrouter\n. This allows registering a path to a method. For example, registering \n.get(\"users\")\n will register the path \n/users/\n to the method \nGET\n. The responder/closure associated with this route can then handle requests sent to \n/users/\n with the \nGET\n method.\n\n\nTypes of endpoints\n\u00b6\n\n\nIn the web we usually define two types of endpoints. Either a website or an API. Websites are HTML pages, usually with associated styling, code and images. APIs are endpoints that communicate with raw information rather than types and user friendly information. APIs are aimed to developers and their applications.\n\n\niOS and Android apps usually communicate with an API, where a web browser such as Safari, Firefox, Chrome or Edge will usually communicate with a website.\n\n\nWebsites\n\u00b6\n\n\nWebsites 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.\n\n\nServer rendered pages make use of a templating system such as \nleaf\n whereas client rendered pages communicate with an API.\n\n\nAPI\n\u00b6\n\n\nAPIs are endpoints that sometimes receive but always reply with raw data. The raw data can be in any format. Most commonly, APIs communicate with \nJSON\n. Sometimes, they communicate with XML or other data types. Vapor can flexibly switch between supported formats, both by official or by community made libraries.\n\n\nAPIs in Vapor are (almost) always created using a \"MVC\" or \"Model View Controller\" model \nwhich we explain here.\n\n\nDesigning an API in Vapor is really simple. \nWe dive into this from here.", - "title": "HTTP" - }, - { - "location": "/concepts/http/#http", - "text": "At the heart of the web lies HTTP. HTTP (or HyperText Transfer Protocol) is used for communicating multiple types of media between a client and a server. HTTP comes in two major versions. HTTP/1 and HTTP/2 . Vapor comes with HTTP/1 support by default but has an official package for HTTP/2, too.", - "title": "HTTP" - }, - { - "location": "/concepts/http/#what-is-the-difference", - "text": "HTTP/1 is a protocol designed in the '90s for the then new and rapidly evolving internet. The protocol is designed around simplicity above security and functionality. HTTP/2 is a protocol with security and performance in mind. Designed with experience of the past 20 years of internet in addition to modern standards such as a high bandwidth and many resources per page.", - "title": "What is the difference?" - }, - { - "location": "/concepts/http/#how-it-works", - "text": "At the heart of HTTP lie the Request and Response types. Both of them are \"HTTP Messages\". Both HTTP messages consists of Headers and a body . 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 and forms . Headers contain metadata, meaning they carry information describing the HTTP message, it's context and it's content. Cookies are context metadata about the client that, for example, can be used for identifying users after they've (successfully) logged in. One of these methods are session tokens . Another type of metadata that is often used to define the type of content transferred in the body is the Content-Type header", - "title": "How it works" - }, - { - "location": "/concepts/http/#request", - "text": "Requests have two additional properties in addition to all properties of a Message. The Method and path . The path is used to specify the resource being accessed. Although there are conventions, there are no rules/limitations to how you structure your paths except their format. Paths consist of components . The components are separated by a forward slash ( / ). All components must be encoded with percent encoding, affecting special characters only. The method indicated the operation to this resource. GET is used for reading a resource where DELETE will (attempt to) remove the resource. This does not mean you need to blindly comply. If a user doesn't have the permissions for said operation, you can emit a response indicating this.", - "title": "Request" - }, - { - "location": "/concepts/http/#response", - "text": "Responses have one additional property in addition to the message's properties. This is the status code . The status code is used to indicate to the client what the status/result is of a Request. If a client was not authenticated, for example, you would return a status 401 or 403 for \"Unauthorized\" or \"Forbidden\" respectively. More about status codes here.", - "title": "Response" - }, - { - "location": "/concepts/http/#handling-requests", - "text": "Requests in Vapor will be handled by a router . 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.", - "title": "Handling requests" - }, - { - "location": "/concepts/http/#types-of-endpoints", - "text": "In the web we usually define two types of endpoints. Either a website or an API. Websites are HTML pages, usually with associated styling, code and images. APIs are endpoints that communicate with raw information rather than types and user friendly information. APIs are aimed to developers and their applications. iOS and Android apps usually communicate with an API, where a web browser such as Safari, Firefox, Chrome or Edge will usually communicate with a website.", - "title": "Types of endpoints" - }, - { - "location": "/concepts/http/#websites", - "text": "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 whereas client rendered pages communicate with an API.", - "title": "Websites" - }, - { - "location": "/concepts/http/#api", - "text": "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 . 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 created using a \"MVC\" or \"Model View Controller\" model which we explain here. Designing an API in Vapor is really simple. We dive into this from here.", - "title": "API" - }, - { - "location": "/concepts/code-contributions/", - "text": "API Design\n\u00b6\n\n\nFor contributing code we have guidelines that we strive for, and requirements.\nAccepted code \nmust\n comply to all requirements and \nshould\n follow all guidelines.\n\n\nRequirements\n\u00b6\n\n\nThe requirements stated here \nmust\n be followed for all Vapor 3 code starting with the beta.\nWe designed all of Vapor 3's APIs (including the Async library) to require little to no breaking changes over the coming years.\n\n\nEnums\n\u00b6\n\n\nenum\ns should only be used where adding a new case should result in a breaking change. Specifically since exhaustively switching on an \nenum\n will no longer compile when a new case is added. For things like errors, this is obviously undesirable as supporting a new error type results in code no longer compiling. However, for something like a supported data type, \nenum\ns make sense because the errors help you track down all of places that need support for that new type added. Most use cases for \nenum\ns are when the enum is internal to the current module.\n\n\nClasses\n\u00b6\n\n\nAlways mark classes as \nfinal\n. If you plan on using a \npublic\n class, look into a protocol or generics oriented approach.\n\n\nLow-level APIs\n\u00b6\n\n\nLow level APIs such as sockets, SSL, cryptography and HTTP should be oriented towards simplicity, maintainability and correctness.\nIf you feel an API becomes more complex for end-users, you can add high level APIs that rely on the low-level ones.\n\n\nTests\n\u00b6\n\n\nThe unit tests must have a minimum of 80% code coverage.\n\n\nUniformity\n\u00b6\n\n\nStick to the familiar API patterns. If connecting a socket has the following signature:\n\n\ntry\n \nsocket\n.\nconnect\n(\nto\n:\n \n\"example.com\"\n,\n \nport\n:\n \n80\n)\n\n\n\n\n\n\nCopy the signature, rather than adding an extra case.\nIf you need more metadata for connecting, consider setting them in the initializer or as a variable on the type.\n\n\nBinary data\n\u00b6\n\n\nBinary data should be passed around in 3 formats;\n\n\n\n\nByteBuffer\n\n\nData\n\n\n[UInt8]\n\n\n\n\nYou should use \nByteBuffer\n when working with Streams to limit copies and improve the stream chaining applicability.\n\nData\n for larger sets of data (>1000 bytes) and \n[UInt8]\n for smaller data sets (<=1000 bytes).\n\n\nDictionaries and Arrays\n\u00b6\n\n\nWhere you'd normally use a dictionary (such as HTTP headers) you \nshould\n create a separate struct instead.\nThis improves the freedom for future optimizations and keeps the implementation separate from the end user API.\nThis results in better future-proofing and helps building better (more specialized) APIs.\n\n\nGuidelines\n\u00b6\n\n\nPerformance\n\u00b6\n\n\nPerformance regression is acceptable to some degree but should be limited.\nHere are some tips on achieving high performance code or ensuring more performance can be added in the future.\n\n\nAccess modifiers\n\u00b6\n\n\nTry to use the \npublic\n keyword as little as possible. More APIs adds more complexity than freedom.\nSpecialized use cases are always free to build their own modules, and a \npublic\n keyword can be added but not undone.\n\n\nComments\n\u00b6\n\n\nGreen is the future, and we believe in that, too! I'm not talking about green energy, but the often green coloured code comments.\nCode comments important for the users of an API. Do not add meaningless comments, though.\n\n\nDocumentation\n\u00b6\n\n\nEvery \npublic\n function, type and variable \nshould\n have a link to the documentation describing it's use case(s) with examples.\n\n\nArgument labels\n\u00b6\n\n\nAlong with uniformity described above, you should try to keep argument labels short and descriptive.\n\n\nArgument count\n\u00b6\n\n\nWe strive for functions with a maximum of 3 parameters, although certain use cases permit for more arguments.\nOften called functions should strive for less arguments.\n\n\nInitializer complexity\n\u00b6\n\n\nInitializers are complex enough, it is recommended to \nnot\n put optional arguments in an initializer since they can be modified on the entity itself.\nTry to limit the initializer to what really matters, and put the rest in \nfunc\ntions.", - "title": "Code Contributions" - }, - { - "location": "/concepts/code-contributions/#api-design", - "text": "For contributing code we have guidelines that we strive for, and requirements.\nAccepted code must comply to all requirements and should follow all guidelines.", - "title": "API Design" - }, - { - "location": "/concepts/code-contributions/#requirements", - "text": "The requirements stated here must be followed for all Vapor 3 code starting with the beta.\nWe designed all of Vapor 3's APIs (including the Async library) to require little to no breaking changes over the coming years.", - "title": "Requirements" - }, - { - "location": "/concepts/code-contributions/#enums", - "text": "enum s should only be used where adding a new case should result in a breaking change. Specifically since exhaustively switching on an enum will no longer compile when a new case is added. For things like errors, this is obviously undesirable as supporting a new error type results in code no longer compiling. However, for something like a supported data type, enum s make sense because the errors help you track down all of places that need support for that new type added. Most use cases for enum s are when the enum is internal to the current module.", - "title": "Enums" - }, - { - "location": "/concepts/code-contributions/#classes", - "text": "Always mark classes as final . If you plan on using a public class, look into a protocol or generics oriented approach.", - "title": "Classes" - }, - { - "location": "/concepts/code-contributions/#low-level-apis", - "text": "Low level APIs such as sockets, SSL, cryptography and HTTP should be oriented towards simplicity, maintainability and correctness.\nIf you feel an API becomes more complex for end-users, you can add high level APIs that rely on the low-level ones.", - "title": "Low-level APIs" - }, - { - "location": "/concepts/code-contributions/#tests", - "text": "The unit tests must have a minimum of 80% code coverage.", - "title": "Tests" - }, - { - "location": "/concepts/code-contributions/#uniformity", - "text": "Stick to the familiar API patterns. If connecting a socket has the following signature: try socket . connect ( to : \"example.com\" , port : 80 ) Copy the signature, rather than adding an extra case.\nIf you need more metadata for connecting, consider setting them in the initializer or as a variable on the type.", - "title": "Uniformity" - }, - { - "location": "/concepts/code-contributions/#binary-data", - "text": "Binary data should be passed around in 3 formats; ByteBuffer Data [UInt8] You should use ByteBuffer when working with Streams to limit copies and improve the stream chaining applicability. Data for larger sets of data (>1000 bytes) and [UInt8] for smaller data sets (<=1000 bytes).", - "title": "Binary data" - }, - { - "location": "/concepts/code-contributions/#dictionaries-and-arrays", - "text": "Where you'd normally use a dictionary (such as HTTP headers) you should create a separate struct instead.\nThis improves the freedom for future optimizations and keeps the implementation separate from the end user API.\nThis results in better future-proofing and helps building better (more specialized) APIs.", - "title": "Dictionaries and Arrays" - }, - { - "location": "/concepts/code-contributions/#guidelines", - "text": "", - "title": "Guidelines" - }, - { - "location": "/concepts/code-contributions/#performance", - "text": "Performance regression is acceptable to some degree but should be limited.\nHere are some tips on achieving high performance code or ensuring more performance can be added in the future.", - "title": "Performance" - }, - { - "location": "/concepts/code-contributions/#access-modifiers", - "text": "Try to use the public keyword as little as possible. More APIs adds more complexity than freedom.\nSpecialized use cases are always free to build their own modules, and a public keyword can be added but not undone.", - "title": "Access modifiers" - }, - { - "location": "/concepts/code-contributions/#comments", - "text": "Green is the future, and we believe in that, too! I'm not talking about green energy, but the often green coloured code comments.\nCode comments important for the users of an API. Do not add meaningless comments, though.", - "title": "Comments" - }, - { - "location": "/concepts/code-contributions/#documentation", - "text": "Every public function, type and variable should have a link to the documentation describing it's use case(s) with examples.", - "title": "Documentation" - }, - { - "location": "/concepts/code-contributions/#argument-labels", - "text": "Along with uniformity described above, you should try to keep argument labels short and descriptive.", - "title": "Argument labels" - }, - { - "location": "/concepts/code-contributions/#argument-count", - "text": "We strive for functions with a maximum of 3 parameters, although certain use cases permit for more arguments.\nOften called functions should strive for less arguments.", - "title": "Argument count" - }, - { - "location": "/concepts/code-contributions/#initializer-complexity", - "text": "Initializers are complex enough, it is recommended to not put optional arguments in an initializer since they can be modified on the entity itself.\nTry to limit the initializer to what really matters, and put the rest in func tions.", - "title": "Initializer complexity" - }, - { - "location": "/async/getting-started/", - "text": "Using Async\n\u00b6\n\n\nAsync is a library revolving around two main concepts:\n\n\n\n\nPromises and Futures\n\n\n(Reactive) Streams\n\n\nEventLoops\n\n\n\n\nTogether they form the foundation of Vapor 3's data flow.\n\n\nWith Vapor\n\u00b6\n\n\nThis package is included with Vapor by default, just add:\n\n\nimport\n \nAsync\n\n\n\n\n\n\nWithout Vapor\n\u00b6\n\n\nAsync is a powerful library for any Swift project. To include it in your package, add the following to your \nPackage.swift\n file.\n\n\n// swift-tools-version:4.0\n\n\nimport\n \nPackageDescription\n\n\n\nlet\n \npackage\n \n=\n \nPackage\n(\n\n \nname\n:\n \n\"Project\"\n,\n\n \ndependencies\n:\n \n[\n\n \n...\n\n \n.\npackage\n(\nurl\n:\n \n\"https://github.com/vapor/async.git\"\n,\n \n.\nrevision\n(\n\"beta\"\n)),\n\n \n],\n\n \ntargets\n:\n \n[\n\n \n.\ntarget\n(\nname\n:\n \n\"Project\"\n,\n \ndependencies\n:\n \n[\n\"Async\"\n,\n \n...\n \n])\n\n \n]\n\n\n)\n\n\n\n\n\n\nUse \nimport Async\n to access Async's APIs.", - "title": "Getting Started" - }, - { - "location": "/async/getting-started/#using-async", - "text": "Async is a library revolving around two main concepts: Promises and Futures (Reactive) Streams EventLoops Together they form the foundation of Vapor 3's data flow.", - "title": "Using Async" - }, - { - "location": "/async/getting-started/#with-vapor", - "text": "This package is included with Vapor by default, just add: import Async", - "title": "With Vapor" - }, - { - "location": "/async/getting-started/#without-vapor", - "text": "Async is a powerful library for any Swift project. To include it in your package, add the following to your Package.swift file. // swift-tools-version:4.0 import PackageDescription let package = Package ( \n name : \"Project\" , \n dependencies : [ \n ... \n . package ( url : \"https://github.com/vapor/async.git\" , . revision ( \"beta\" )), \n ], \n targets : [ \n . target ( name : \"Project\" , dependencies : [ \"Async\" , ... ]) \n ] ) Use import Async to access Async's APIs.", - "title": "Without Vapor" - }, - { - "location": "/async/futures/", - "text": "Futures\n\u00b6\n\n\nFutures are used throughout Vapor 3.\n\n\nThey aim to provide a more simplified but high performance interface to process\ndata actions and transformations.\n\n\nWe explain the future basics and use cases \nhere\n.", - "title": "Futures" - }, - { - "location": "/async/futures/#futures", - "text": "Futures are used throughout Vapor 3. They aim to provide a more simplified but high performance interface to process\ndata actions and transformations. We explain the future basics and use cases here .", - "title": "Futures" - }, - { - "location": "/async/streams/", - "text": "Streams\n\u00b6\n\n\nStreams are a mechanism that process any information efficiently, \nreactively\n and asynchronously without bloat. They make asynchronous data flows easier to deal with.\n\n\nStreams are designed to limit memory usage and copies. They are used in all domains of Vapor 3, be it sockets, be it (larger) database operations.\n\n\nDraining streams\n\u00b6\n\n\nIn this example we print the string representation of the TCP connnection's incoming data.\n\n\nSince this socket is reactive we need to first request data before we can expect a result.\nAfter requesting data we need to set up the output\n\n\nimport\n \nAsync\n\n\nimport\n \nFoundation\n\n\n\n...\n\n\n\ntcpSocket\n.\ndrain\n \n{\n \nupstream\n \nin\n\n \nupstream\n.\nrequest\n()\n\n\n}.\noutput\n \n{\n \nbuffer\n \nin\n\n \nprint\n(\nString\n(\nbytes\n:\n \nbuffer\n,\n \nencoding\n:\n \n.\nutf8\n))\n\n \ntcpSocket\n.\nrequest\n()\n\n\n}.\ncatch\n \n{\n \nerror\n \nin\n\n \nprint\n(\n\"Error occurred \n\\(\nerror\n)\n\"\n)\n\n\n}.\nfinally\n \n{\n\n \nprint\n(\n\"TCP socket closed\"\n)\n\n\n}\n\n\n\n\n\n\nIn the above implementation we explicitly request more information from the socket after receiving output.\n\n\nEmitting output\n\u00b6\n\n\nEmitter streams are useful if you don't want to create your own reactive stream implementation.\n\n\nThey allow emitting output easily which can then be used like any other stream.\n\n\nlet\n \nemitter\n \n=\n \nEmitterStream\n<\nInt\n>()\n\n\n\nemitter\n.\ndrain\n \n{\n \nupstream\n \nin\n\n \nupstream\n.\nrequest\n()\n\n\n}.\noutput\n \n{\n \nnumber\n \nin\n\n \nprint\n(\nnumber\n)\n\n \nemitter\n.\nrequest\n()\n\n\n}\n\n\n\nemitter\n.\nemit\n(\n3\n)\n\n\nemitter\n.\nemit\n(\n4\n)\n\n\nemitter\n.\nemit\n(\n3\n)\n\n\nemitter\n.\nemit\n(\n5\n)\n\n\n\n\n\n\nMapping Streams\n\u00b6\n\n\nTo transform a string to another type you can map it similarly to futures.\nThe following assumes \nstream\n contains a stream of \nInt\n as defined in the above emitter.\n\n\nlet\n \nstringStream\n \n=\n \nemitter\n.\nmap\n(\nto\n:\n \nString\n.\nself\n)\n \n{\n \nnumber\n \nin\n\n \nreturn\n \nnumber\n.\ndescription\n\n\n}\n\n\n\n\n\n\nImplementing custom streams\n\u00b6\n\n\nComing soon", - "title": "Streams" - }, - { - "location": "/async/streams/#streams", - "text": "Streams are a mechanism that process any information efficiently, reactively and asynchronously without bloat. They make asynchronous data flows easier to deal with. Streams are designed to limit memory usage and copies. They are used in all domains of Vapor 3, be it sockets, be it (larger) database operations.", - "title": "Streams" - }, - { - "location": "/async/streams/#draining-streams", - "text": "In this example we print the string representation of the TCP connnection's incoming data. Since this socket is reactive we need to first request data before we can expect a result.\nAfter requesting data we need to set up the output import Async import Foundation ... tcpSocket . drain { upstream in \n upstream . request () }. output { buffer in \n print ( String ( bytes : buffer , encoding : . utf8 )) \n tcpSocket . request () }. catch { error in \n print ( \"Error occurred \\( error ) \" ) }. finally { \n print ( \"TCP socket closed\" ) } In the above implementation we explicitly request more information from the socket after receiving output.", - "title": "Draining streams" - }, - { - "location": "/async/streams/#emitting-output", - "text": "Emitter streams are useful if you don't want to create your own reactive stream implementation. They allow emitting output easily which can then be used like any other stream. let emitter = EmitterStream < Int >() emitter . drain { upstream in \n upstream . request () }. output { number in \n print ( number ) \n emitter . request () } emitter . emit ( 3 ) emitter . emit ( 4 ) emitter . emit ( 3 ) emitter . emit ( 5 )", - "title": "Emitting output" - }, - { - "location": "/async/streams/#mapping-streams", - "text": "To transform a string to another type you can map it similarly to futures.\nThe following assumes stream contains a stream of Int as defined in the above emitter. let stringStream = emitter . map ( to : String . self ) { number in \n return number . description }", - "title": "Mapping Streams" - }, - { - "location": "/async/streams/#implementing-custom-streams", - "text": "Coming soon", - "title": "Implementing custom streams" - }, - { - "location": "/async/eventloop/", - "text": "EventLoop\n\u00b6\n\n\nEvent loops are at the heart of Vapor's non-blocking concurrency model. There is usually one event loop per logical core in your computer's CPU. The event loop's main purpose is to detect when data is ready to be read from or written to a socket. By detecting socket events before actually attempting to read or write data, Vapor can avoid making function calls that may block. Avoiding blocking calls is critical for performance as it allows Vapor to aggressively re-use threads, making your app very fast and efficient.\n\n\nIn addition to the above, they're also able to run single tasks inbetween listening for events.\n\n\nThere are three main forms of eventloops.\n\n\nDispatchEventLoop\n is based on Dispatch, \nKQueueEventLoop\n is a macOS-only eventloop that is more performant than Dispatch.\n\n\nThe third one (currently work in progress) is the \nEPollEventLoop\n, a Linux only eventloop that is also more performant than Dispatch.\n\n\nWorkers\n\u00b6\n\n\nTo carry around context, the \nWorker\n protocol exists to indicate the current eventloop context.\n\n\nprint\n(\nworker\n.\neventLoop\n)\n \n// EventLoop\n\n\n\n\n\n\nWhen looking for a worker, the most common ones you'll come across are the \nRequest\n and \nResponse\n.\n\n\nFuture changes during beta\n\u00b6\n\n\nIt is likely that we'll start inferring the current EventLoop using the Thread Local Storage, removing the need for Workers and passing EventLoop as an argument.\n\n\nSources\n\u00b6\n\n\nTo add/remove listeners from EventLoops you can ask for a readable or writable source or \nEventSource\n. EventSources are a handle which can be resumed, suspended and cancelled.\nWhen requesting said handle on an EventLoop you must provide a closure which calls back with the notification.\n\n\nThis notification indicates that data is available for work in the provided descriptor. This includes the descriptor being closed.\n\n\nlet\n \nsourceHandle\n \n=\n \neventLoop\n.\nonReadable\n(\ndescriptor\n:\n \nsocket\n.\ndescriptor\n)\n \n{\n \ncancelled\n \nin\n\n \nif\n \ncancelled\n \n{\n\n \nprint\n(\n\"descriptor closed\"\n)\n\n \n}\n \nelse\n \n{\n\n \nprint\n(\n\"Data is readable\"\n)\n\n \n}\n\n\n}\n\n\n\n\n\n\nWrite sources are the opposite of a Source in that they notify the ability to write data. They should be suspended after the first write so that they do not call back every loop.\n\n\nWhilst Sources indicate the availability of data, Drains\n\n\nSockets\n\u00b6\n\n\nAs part of the EventLoops in Vapor 3, we also centralized the asynchronous part of Sockets, simplifying the APIs for I/O and improving it's asynchronous usability. It is recommended for (raw) TCP, (raw) UDP and SSL implementations to conform to the \nSocket\n protocol.\n\n\nSocketSink and SocketSource\n\u00b6\n\n\nSocketSink\n is a helper that assists with writing data to sockets reactively.\n\nSocketSource\n is a helper that functions as the Sink's counterpart with reading data from sockets reactively.\n\n\nlet\n \nsink\n \n=\n \nsocket\n.\nsink\n(\non\n:\n \neventLoop\n)", - "title": "EventLoop" - }, - { - "location": "/async/eventloop/#eventloop", - "text": "Event loops are at the heart of Vapor's non-blocking concurrency model. There is usually one event loop per logical core in your computer's CPU. The event loop's main purpose is to detect when data is ready to be read from or written to a socket. By detecting socket events before actually attempting to read or write data, Vapor can avoid making function calls that may block. Avoiding blocking calls is critical for performance as it allows Vapor to aggressively re-use threads, making your app very fast and efficient. In addition to the above, they're also able to run single tasks inbetween listening for events. There are three main forms of eventloops. DispatchEventLoop is based on Dispatch, KQueueEventLoop is a macOS-only eventloop that is more performant than Dispatch. The third one (currently work in progress) is the EPollEventLoop , a Linux only eventloop that is also more performant than Dispatch.", - "title": "EventLoop" - }, - { - "location": "/async/eventloop/#workers", - "text": "To carry around context, the Worker protocol exists to indicate the current eventloop context. print ( worker . eventLoop ) // EventLoop When looking for a worker, the most common ones you'll come across are the Request and Response .", - "title": "Workers" - }, - { - "location": "/async/eventloop/#future-changes-during-beta", - "text": "It is likely that we'll start inferring the current EventLoop using the Thread Local Storage, removing the need for Workers and passing EventLoop as an argument.", - "title": "Future changes during beta" - }, - { - "location": "/async/eventloop/#sources", - "text": "To add/remove listeners from EventLoops you can ask for a readable or writable source or EventSource . EventSources are a handle which can be resumed, suspended and cancelled.\nWhen requesting said handle on an EventLoop you must provide a closure which calls back with the notification. This notification indicates that data is available for work in the provided descriptor. This includes the descriptor being closed. let sourceHandle = eventLoop . onReadable ( descriptor : socket . descriptor ) { cancelled in \n if cancelled { \n print ( \"descriptor closed\" ) \n } else { \n print ( \"Data is readable\" ) \n } } Write sources are the opposite of a Source in that they notify the ability to write data. They should be suspended after the first write so that they do not call back every loop. Whilst Sources indicate the availability of data, Drains", - "title": "Sources" - }, - { - "location": "/async/eventloop/#sockets", - "text": "As part of the EventLoops in Vapor 3, we also centralized the asynchronous part of Sockets, simplifying the APIs for I/O and improving it's asynchronous usability. It is recommended for (raw) TCP, (raw) UDP and SSL implementations to conform to the Socket protocol.", - "title": "Sockets" - }, - { - "location": "/async/eventloop/#socketsink-and-socketsource", - "text": "SocketSink is a helper that assists with writing data to sockets reactively. SocketSource is a helper that functions as the Sink's counterpart with reading data from sockets reactively. let sink = socket . sink ( on : eventLoop )", - "title": "SocketSink and SocketSource" - }, - { - "location": "/async/reactive/", - "text": "Reactive Programming\n\u00b6\n\n\nAs part of the Vapor 3 ecosystem we embrace reactiveness.\n\n\nReactiveness means your Vapor applications will be more resilient to the producer-consumer problem that has long plagued web applications. Vapor achieves this by moving from \"push\" streams (aka, fire hose streams) to \"pull\" streams. At a high level, this means better performance and less memory usage during peak demand.\n\n\nLearn more at \nreactive-streams.org\n.\n\n\nAs part of our API design we strive to minimize the impact on code.\n\n\nReducing code impact\n\u00b6\n\n\nMost of our code impact is reduced using protocols.\nBy conforming Futures and Streams to the \nCodable\n protocol we can use reactiveness throughout all components of Vapor 3.\n\n\nThis allows for reactive templating with less code than before reactiveness was introduced.\n\n\nRules\n\u00b6\n\n\nThe following rules are critical to reactive programming with Vapor 3:\n\n\nInformation flow\n\u00b6\n\n\nStream data must be asynchronously available. This means that when input is received, the information stays intact until new data is requested or the sending stream is cancelled/closed.\n\n\nOutput to another stream must stay intact (in the case of \nByteBuffer\n, must not be deallocated or reused) until a new request for data has been made.\n\n\nUpstream\n\u00b6\n\n\nRequesting data from upstream must only be done if you do not have enough information to complete a request from downstream.\n\n\nDownstream\n\u00b6\n\n\nYou \nmust not\n feed more data to downstream than was requested.\n\n\nBlocking\n\u00b6\n\n\nYou \nmust not\n block within a stream using \nsleep\n or blocking sockets.", - "title": "Reactive Programming" - }, - { - "location": "/async/reactive/#reactive-programming", - "text": "As part of the Vapor 3 ecosystem we embrace reactiveness. Reactiveness means your Vapor applications will be more resilient to the producer-consumer problem that has long plagued web applications. Vapor achieves this by moving from \"push\" streams (aka, fire hose streams) to \"pull\" streams. At a high level, this means better performance and less memory usage during peak demand. Learn more at reactive-streams.org . As part of our API design we strive to minimize the impact on code.", - "title": "Reactive Programming" - }, - { - "location": "/async/reactive/#reducing-code-impact", - "text": "Most of our code impact is reduced using protocols.\nBy conforming Futures and Streams to the Codable protocol we can use reactiveness throughout all components of Vapor 3. This allows for reactive templating with less code than before reactiveness was introduced.", - "title": "Reducing code impact" - }, - { - "location": "/async/reactive/#rules", - "text": "The following rules are critical to reactive programming with Vapor 3:", - "title": "Rules" - }, - { - "location": "/async/reactive/#information-flow", - "text": "Stream data must be asynchronously available. This means that when input is received, the information stays intact until new data is requested or the sending stream is cancelled/closed. Output to another stream must stay intact (in the case of ByteBuffer , must not be deallocated or reused) until a new request for data has been made.", - "title": "Information flow" - }, - { - "location": "/async/reactive/#upstream", - "text": "Requesting data from upstream must only be done if you do not have enough information to complete a request from downstream.", - "title": "Upstream" - }, - { - "location": "/async/reactive/#downstream", - "text": "You must not feed more data to downstream than was requested.", - "title": "Downstream" - }, - { - "location": "/async/reactive/#blocking", - "text": "You must not block within a stream using sleep or blocking sockets.", - "title": "Blocking" - }, - { - "location": "/http/getting-started/", - "text": "Using HTTP\n\u00b6\n\n\nHTTP is a module as part of the \nEngine\n library containing all HTTP related APIs.\n\n\nWith Vapor\n\u00b6\n\n\nThis package is included with Vapor by default, just add:\n\n\nimport\n \nHTTP\n\n\n\n\n\n\nWithout Vapor\n\u00b6\n\n\nTo include it in your package, add the following to your \nPackage.swift\n file.\n\n\n// swift-tools-version:4.0\n\n\nimport\n \nPackageDescription\n\n\n\nlet\n \npackage\n \n=\n \nPackage\n(\n\n \nname\n:\n \n\"Project\"\n,\n\n \ndependencies\n:\n \n[\n\n \n...\n\n \n.\npackage\n(\nurl\n:\n \n\"https://github.com/vapor/engine.git\"\n,\n \n.\nupToNextMajor\n(\nfrom\n:\n \n\"3.0.0\"\n)),\n\n \n],\n\n \ntargets\n:\n \n[\n\n \n.\ntarget\n(\nname\n:\n \n\"Project\"\n,\n \ndependencies\n:\n \n[\n\"HTTP\"\n,\n \n...\n \n])\n\n \n]\n\n\n)\n\n\n\n\n\n\nIf this is your first time adding a dependency, you should read our introduction to \nPackage.swift\n.\n\n\nUse \nimport HTTP\n to access HTTP's APIs.", - "title": "Getting Started" - }, - { - "location": "/http/getting-started/#using-http", - "text": "HTTP is a module as part of the Engine library containing all HTTP related APIs.", - "title": "Using HTTP" - }, - { - "location": "/http/getting-started/#with-vapor", - "text": "This package is included with Vapor by default, just add: import HTTP", - "title": "With Vapor" - }, - { - "location": "/http/getting-started/#without-vapor", - "text": "To include it in your package, add the following to your Package.swift file. // swift-tools-version:4.0 import PackageDescription let package = Package ( \n name : \"Project\" , \n dependencies : [ \n ... \n . package ( url : \"https://github.com/vapor/engine.git\" , . upToNextMajor ( from : \"3.0.0\" )), \n ], \n targets : [ \n . target ( name : \"Project\" , dependencies : [ \"HTTP\" , ... ]) \n ] ) If this is your first time adding a dependency, you should read our introduction to Package.swift . Use import HTTP to access HTTP's APIs.", - "title": "Without Vapor" - }, - { - "location": "/http/body/", - "text": "HTTPBody\n\u00b6\n\n\nHTTPBody is a type that contains the raw representation of a \nRequest\n or \nResponse\n. It's contents are related to the \nContent-Type\n header.\n\n\nBody can contain, but is not limited to \nData\n, \nString\n, \nDispatchData\n or binary streams.\nBinary streams will be chunk-encoded in HTTP/1.\n\n\nCreating a Body\n\u00b6\n\n\nEmpty bodies can be created using the empty initializer \nBody()\n.\nAlternatively you can provide \nData\n or \nDispatchData\n as the content of the body.\n\n\nHTTPBodyRepresentable\n\u00b6\n\n\nIf you want to serialize your data to a body using a predefined format such as JSON or XML, look into \nContent\n first.\n\n\nWhen adding a new struct/class that can be serialized to a raw Body as part of a Request or Response you can consider implementing the \nHTTPBodyRepresentable\n protocol.\n\n\nBelow is how String is implemented.\n\n\n/// String can be represented as an HTTP body.\n\n\nextension\n \nString\n:\n \nHTTPBodyRepresentable\n \n{\n\n \n/// See BodyRepresentable.makeBody()\n\n \npublic\n \nfunc\n \nmakeBody\n()\n \nthrows\n \n->\n \nHTTPBody\n \n{\n\n\n \n}\n\n\n}\n\n\n\n\n\n\nAlthough often unnecessary it is possible to throw an error here if the creation of the body failed.", - "title": "Body" - }, - { - "location": "/http/body/#httpbody", - "text": "HTTPBody is a type that contains the raw representation of a Request or Response . It's contents are related to the Content-Type header. Body can contain, but is not limited to Data , String , DispatchData or binary streams.\nBinary streams will be chunk-encoded in HTTP/1.", - "title": "HTTPBody" - }, - { - "location": "/http/body/#creating-a-body", - "text": "Empty bodies can be created using the empty initializer Body() .\nAlternatively you can provide Data or DispatchData as the content of the body.", - "title": "Creating a Body" - }, - { - "location": "/http/body/#httpbodyrepresentable", - "text": "If you want to serialize your data to a body using a predefined format such as JSON or XML, look into Content first. When adding a new struct/class that can be serialized to a raw Body as part of a Request or Response you can consider implementing the HTTPBodyRepresentable protocol. Below is how String is implemented. /// String can be represented as an HTTP body. extension String : HTTPBodyRepresentable { \n /// See BodyRepresentable.makeBody() \n public func makeBody () throws -> HTTPBody { \n\n } } Although often unnecessary it is possible to throw an error here if the creation of the body failed.", - "title": "HTTPBodyRepresentable" - }, - { - "location": "/http/client/", - "text": "HTTP Client\n\u00b6\n\n\nHTTP Clients are often used to communicate with external APIs such as PayPal, Stripe or Mailgun.\n\n\nConnecting\n\u00b6\n\n\nConnecting 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 \ntrue\n.\n\n\nHTTP clients require an eventloop to run on. The \nEventLoop\n is described in \nthe async concepts introduction\n.\n\n\n// Future\n\n \nlet\n \nclient\n \n=\n \ntry\n \nHTTPClient\n.\nconnect\n(\n\n \nto\n:\n \n\"example.com\"\n,\n\n \nssl\n:\n \ntrue\n,\n\n \non\n:\n \neventLoop\n\n \n)\n\n\n\n\n\n\nYou can override the port by specifying a custom port using the following parameters:\n\n\n// Future\n\n \nlet\n \nclient\n \n=\n \ntry\n \nHTTPClient\n.\nconnect\n(\n\n \nto\n:\n \n\"localhost\"\n,\n\n \nport\n:\n \n8080\n,\n\n \nssl\n:\n \nfalse\n,\n\n \non\n:\n \neventLoop\n\n \n)\n\n\n\n\n\n\nSending Requests\n\u00b6\n\n\nFrom here, you can send \nRequests\n. You can only send one request at a time. Sending a request before a \nResponse\n has been received has unpredictable consequences.\n\n\n// Future\n\n\nlet\n \nresponse\n \n=\n \nclient\n.\nflatMap\n(\nto\n:\n \nResponse\n.\nself\n)\n \n{\n \nconnectedClient\n \nin\n\n \nlet\n \nrequest\n \n=\n \nRequest\n(\n\n \nmethod\n:\n \n.\nget\n,\n\n \nuri\n:\n \n\"https://example.com/\"\n\n \n)\n\n\n \nreturn\n \nconnectedClient\n.\nsend\n(\nrequest\n:\n \nrequest\n)\n\n\n}", - "title": "Client" - }, - { - "location": "/http/client/#http-client", - "text": "HTTP Clients are often used to communicate with external APIs such as PayPal, Stripe or Mailgun.", - "title": "HTTP Client" - }, - { - "location": "/http/client/#connecting", - "text": "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 an eventloop to run on. The EventLoop is described in the async concepts introduction . // Future \n let client = try HTTPClient . connect ( \n to : \"example.com\" , \n ssl : true , \n on : eventLoop \n ) You can override the port by specifying a custom port using the following parameters: // Future \n let client = try HTTPClient . connect ( \n to : \"localhost\" , \n port : 8080 , \n ssl : false , \n on : eventLoop \n )", - "title": "Connecting" - }, - { - "location": "/http/client/#sending-requests", - "text": "From here, you can send Requests . You can only send one request at a time. Sending a request before a Response has been received has unpredictable consequences. // Future let response = client . flatMap ( to : Response . self ) { connectedClient in \n let request = Request ( \n method : . get , \n uri : \"https://example.com/\" \n ) \n\n return connectedClient . send ( request : request ) }", - "title": "Sending Requests" - }, - { - "location": "/http/cookies/", - "text": "Cookies\n\u00b6\n\n\nCookies are used to store data on the client-side between multiple requests. They're often used for keeping track of a user for various reasons. One of the more important purposes is to store a session cookie containing identification of an account.\n\n\nCreating cookies\n\u00b6\n\n\nVapor has three related objects for Cookies.\n\n\nThe \nCookies\n object is an array of multiple Cookie objects.\n\n\nThe \nCookie\n object is a single key-value pair. Where the key is the Cookie name.\n\n\nThe \nValue\n object contains a String representing the Cookie's Value and optional attributes with metadata such as the expiration date of the Cookie.\n\n\nValues\n\u00b6\n\n\nValues can be initialized with a String Literal.\n\n\nvar\n \nvalue\n:\n \nCookie\n.\nValue\n \n=\n \n\"String Literal\"\n\n\n\n\n\n\nThey can be manipulated to add other properties.\n\n\n// Expires in one day\n\n\nvalue\n.\nexpires\n \n=\n \nDate\n().\naddingTimeInterval\n(\n24\n \n*\n \n3600\n)\n\n\n\n\n\n\nA single Cookie\n\u00b6\n\n\nCreating a \nCookie\n requires a name and a Value.\n\n\nlet\n \ncookie\n \n=\n \nCookie\n(\nnamed\n:\n \n\"session\"\n,\n \nvalue\n:\n \nvalue\n)\n\n\n\n\n\n\nMultiple cookies\n\u00b6\n\n\nCookies\n can be initialized with a dictionary literal.\n\n\nlet\n \ncookies\n:\n \nCookies\n \n=\n \n[\n\n \n\"session\"\n:\n \n\"String Literal\"\n\n\n]\n\n\n\n\n\n\nThe above will create a single cookie named \"session\" with a value of \"String Literal\".", - "title": "Cookies" - }, - { - "location": "/http/cookies/#cookies", - "text": "Cookies are used to store data on the client-side between multiple requests. They're often used for keeping track of a user for various reasons. One of the more important purposes is to store a session cookie containing identification of an account.", - "title": "Cookies" - }, - { - "location": "/http/cookies/#creating-cookies", - "text": "Vapor has three related objects for Cookies. The Cookies object is an array of multiple Cookie objects. The Cookie object is a single key-value pair. Where the key is the Cookie name. The Value object contains a String representing the Cookie's Value and optional attributes with metadata such as the expiration date of the Cookie.", - "title": "Creating cookies" - }, - { - "location": "/http/cookies/#values", - "text": "Values can be initialized with a String Literal. var value : Cookie . Value = \"String Literal\" They can be manipulated to add other properties. // Expires in one day value . expires = Date (). addingTimeInterval ( 24 * 3600 )", - "title": "Values" - }, - { - "location": "/http/cookies/#a-single-cookie", - "text": "Creating a Cookie requires a name and a Value. let cookie = Cookie ( named : \"session\" , value : value )", - "title": "A single Cookie" - }, - { - "location": "/http/cookies/#multiple-cookies", - "text": "Cookies can be initialized with a dictionary literal. let cookies : Cookies = [ \n \"session\" : \"String Literal\" ] The above will create a single cookie named \"session\" with a value of \"String Literal\".", - "title": "Multiple cookies" - }, - { - "location": "/http/headers/", - "text": "Headers\n\u00b6\n\n\nHTTP Headers are the metadata of a request/response. They can provide a wide variety of information.\n\n\nHeaders\n are an array of key-value pairs. As such it's possible, but not common for multiple pairs to have the same key.\n\n\nCreating a Headers object\n\u00b6\n\n\nThe most common syntax for creating Headers is a dictionary literal.\n\n\nlet\n \nheaders\n:\n \nHeaders\n \n=\n \n[\n\n \n.\ncontentType\n:\n \n\"text/html\"\n\n\n]\n\n\n\n\n\n\nThe left hand side (key) is a \nHeader.Name\n.\n\n\nA name can also be initialized with a String literal.\n\n\nlet\n \nheaders\n:\n \nHeaders\n \n=\n \n[\n\n \n\"Content-Type\"\n:\n \n\"text/html\"\n\n\n]\n\n\n\n\n\n\nAccessing headers\n\u00b6\n\n\nThere are two ways to access Headers. Either by accessing a single (the first) value, or all values.\n\n\nA single value example:\n\u00b6\n\n\nlet\n \nheaders\n:\n \nHeaders\n \n=\n \n[\n\n \n.\ncontentType\n:\n \n\"text/html\"\n\n\n]\n\n\n\nprint\n(\nheaders\n[.\ncontentType\n])\n \n// prints \"text/html\"\n\n\n\n\n\n\nAccessing all values example:\n\u00b6\n\n\nlet\n \nheaders\n:\n \nHeaders\n \n=\n \n[\n\n \n.\nsetCookie\n:\n \n\"session=afasfwrw3qr241j4qwmdsijfo13k43\"\n,\n\n \n.\nsetCookie\n:\n \n\"awesome=true\"\n\n\n]\n\n\n\n// prints [\"session=afasfwrw3qr241j4qwmdsijfo13k43\", \"awesome=true\"]\n\n\nprint\n(\nheaders\n[\nvaluesFor\n:\n \n.\ncontentType\n])", - "title": "Headers" - }, - { - "location": "/http/headers/#headers", - "text": "HTTP Headers are the metadata of a request/response. They can provide a wide variety of information. Headers are an array of key-value pairs. As such it's possible, but not common for multiple pairs to have the same key.", - "title": "Headers" - }, - { - "location": "/http/headers/#creating-a-headers-object", - "text": "The most common syntax for creating Headers is a dictionary literal. let headers : Headers = [ \n . contentType : \"text/html\" ] The left hand side (key) is a Header.Name . A name can also be initialized with a String literal. let headers : Headers = [ \n \"Content-Type\" : \"text/html\" ]", - "title": "Creating a Headers object" - }, - { - "location": "/http/headers/#accessing-headers", - "text": "There are two ways to access Headers. Either by accessing a single (the first) value, or all values.", - "title": "Accessing headers" - }, - { - "location": "/http/headers/#a-single-value-example", - "text": "let headers : Headers = [ \n . contentType : \"text/html\" ] print ( headers [. contentType ]) // prints \"text/html\"", - "title": "A single value example:" - }, - { - "location": "/http/headers/#accessing-all-values-example", - "text": "let headers : Headers = [ \n . setCookie : \"session=afasfwrw3qr241j4qwmdsijfo13k43\" , \n . setCookie : \"awesome=true\" ] // prints [\"session=afasfwrw3qr241j4qwmdsijfo13k43\", \"awesome=true\"] print ( headers [ valuesFor : . contentType ])", - "title": "Accessing all values example:" - }, - { - "location": "/http/method/", - "text": "Method\n\u00b6\n\n\nMethods are used to indicate the type of operation requested for a route. They're part exclusively in \nHTTP Requests\n and are required.\n\n\n\n\n\n\n\n\nMethod\n\n\nPurpose\n\n\n\n\n\n\n\n\n\n\n.get\n\n\nUsed for retrieving content\n\n\n\n\n\n\n.head\n\n\nUsed for retrieving content metadata\n\n\n\n\n\n\n.put\n\n\nUsed for replacing content\n\n\n\n\n\n\n.post\n\n\nUsed for creating content\n\n\n\n\n\n\n.delete\n\n\nUsed for deleting content\n\n\n\n\n\n\n\n\nA \npath\n is used for specifying a specific resource/content. The method influences the type of interaction with this resource/content.", - "title": "Methods" - }, - { - "location": "/http/method/#method", - "text": "Methods are used to indicate the type of operation requested for a route. They're part exclusively in HTTP Requests and are required. Method Purpose .get Used for retrieving content .head Used for retrieving content metadata .put Used for replacing content .post Used for creating content .delete Used for deleting content A path is used for specifying a specific resource/content. The method influences the type of interaction with this resource/content.", - "title": "Method" - }, - { - "location": "/http/middleware/", - "text": "Middleware\n\u00b6\n\n\nMiddleware are a step in Vapor's responder chain. They're capable of modifying Requests/Responses, preventing the chain from continuing and transforming the data flow.\n\n\nThey can be employed for authorization checks, logging and a wide range of other functionalities.\n\n\nImplementing a Middleware\n\u00b6\n\n\nThe following example is a middleware that will prevent all \nrequests\n from going to their respective \nresponder\n unless the origin has a special header set. In the case of a missing header, \nstatus code\n 404 (not found) will be returned.\n\n\nDon't secure your APIs using this example code, it's very unsafe and exclusively to be used as a test.\n\n\npublic\n \nfinal\n \nclass\n \nSpecialHeaderCheckMiddleware\n:\n \nMiddleware\n \n{\n\n \npublic\n \nfunc\n \nrespond\n(\nto\n \nrequest\n:\n \nRequest\n,\n \nchainingTo\n \nnext\n:\n \nResponder\n)\n \nthrows\n \n->\n \nFuture\n<\nResponse\n>\n \n{\n\n \nguard\n \nrequest\n.\nheaders\n[\n\"Secret-Header\"\n]\n \n==\n \n\"MagicK3y\"\n \nelse\n \n{\n\n \nreturn\n \nResponse\n(\nstatus\n:\n \n.\nnotFound\n)\n\n \n}\n\n\n \nreturn\n \ntry\n \nnext\n.\nrespond\n(\nto\n:\n \nrequest\n)\n\n \n}\n\n\n}\n\n\n\n\n\n\nIntercepting/transforming Responses\n\u00b6\n\n\nThe following example demonstrates a middleware that creates a session token for new users.\n\n\n// For the random number\n\n\nimport\n \nCrypto\n\n\n\nstruct\n \nInvalidString\n \n:\n \nSwift\n.\nError\n \n{}\n\n\n\npublic\n \nfinal\n \nclass\n \nSessionTokenMiddleware\n:\n \nMiddleware\n \n{\n\n \nfunc\n \ngenerateSessionToken\n()\n \nthrows\n \n->\n \nString\n \n{\n\n \n// Generate token here ...\n\n \nlet\n \nbase64\n \n=\n \nBase64Encoder\n.\nencode\n(\nOSRandom\n().\ndata\n(\ncount\n:\n \n32\n))\n\n\n \n// Convert to a String\n\n \nguard\n \nlet\n \nstring\n \n=\n \nString\n(\nbytes\n:\n \nbase64\n,\n \nencoding\n:\n \n.\nutf8\n)\n \nelse\n \n{\n\n \n// This can never happen, but throw an error anyways\n\n \nthrow\n \nInvalidString\n()\n\n \n}\n\n\n \nreturn\n \nstring\n\n \n}\n\n\n \npublic\n \nfunc\n \nrespond\n(\nto\n \nrequest\n:\n \nRequest\n,\n \nchainingTo\n \nnext\n:\n \nResponder\n)\n \nthrows\n \n->\n \nFuture\n<\nResponse\n>\n \n{\n\n \nlet\n \nresponse\n \n=\n \ntry\n \nnext\n.\nrespond\n(\nto\n:\n \nrequest\n)\n\n\n \n// If the session cookie is not set\n\n \nguard\n \nrequest\n.\ncookies\n[\n\"session\"\n]\n \n!=\n \nnil\n \nelse\n \n{\n\n \n// Set a new session token\n\n \nresponse\n.\ncookies\n[\n\"session\"\n]\n \n=\n \ntry\n \ngenerateSessionToken\n()\n\n\n \nreturn\n \nresponse\n\n \n}\n\n\n \nreturn\n \nresponse\n\n \n}\n\n\n}", - "title": "Middleware" - }, - { - "location": "/http/middleware/#middleware", - "text": "Middleware are a step in Vapor's responder chain. They're capable of modifying Requests/Responses, preventing the chain from continuing and transforming the data flow. They can be employed for authorization checks, logging and a wide range of other functionalities.", - "title": "Middleware" - }, - { - "location": "/http/middleware/#implementing-a-middleware", - "text": "The following example is a middleware that will prevent all requests from going to their respective responder unless the origin has a special header set. In the case of a missing header, status code 404 (not found) will be returned. Don't secure your APIs using this example code, it's very unsafe and exclusively to be used as a test. public final class SpecialHeaderCheckMiddleware : Middleware { \n public func respond ( to request : Request , chainingTo next : Responder ) throws -> Future < Response > { \n guard request . headers [ \"Secret-Header\" ] == \"MagicK3y\" else { \n return Response ( status : . notFound ) \n } \n\n return try next . respond ( to : request ) \n } }", - "title": "Implementing a Middleware" - }, - { - "location": "/http/middleware/#interceptingtransforming-responses", - "text": "The following example demonstrates a middleware that creates a session token for new users. // For the random number import Crypto struct InvalidString : Swift . Error {} public final class SessionTokenMiddleware : Middleware { \n func generateSessionToken () throws -> String { \n // Generate token here ... \n let base64 = Base64Encoder . encode ( OSRandom (). data ( count : 32 )) \n\n // Convert to a String \n guard let string = String ( bytes : base64 , encoding : . utf8 ) else { \n // This can never happen, but throw an error anyways \n throw InvalidString () \n } \n\n return string \n } \n\n public func respond ( to request : Request , chainingTo next : Responder ) throws -> Future < Response > { \n let response = try next . respond ( to : request ) \n\n // If the session cookie is not set \n guard request . cookies [ \"session\" ] != nil else { \n // Set a new session token \n response . cookies [ \"session\" ] = try generateSessionToken () \n\n return response \n } \n\n return response \n } }", - "title": "Intercepting/transforming Responses" - }, - { - "location": "/http/multipart/", - "text": "Multipart Forms\n\u00b6\n\n\nMultipart is a module that is primarily used with Forms. Multipart is used for complex forms containing one or more files, input fields and other HTML form data.\n\n\nExtracting from a request\n\u00b6\n\n\nMultipartForm\n is a type of \nContent\n and can be extracted like any type of Content.\n\n\nlet\n \nform\n \n=\n \ntry\n \nMultipartForm\n.\ndecode\n(\nfrom\n:\n \nrequest\n)\n \n// Future\n\n\n\n\n\n\nA future is returned because of streaming/reactive body parsing.\n\n\nParsing a multipart form\n\u00b6\n\n\nMultipart forms can be parsed using \nMultipartParser\n provided a body and boundary to read.\n\n\nlet\n \nform\n \n=\n \ntry\n \nMultipartParser\n(\nbody\n:\n \nhttpBody\n,\n \nboundary\n:\n \nboundaryBytes\n).\nparse\n()\n\n\n\n\n\n\nReading forms\n\u00b6\n\n\nThe parsed form is an array of \nPart\n instances.\nEach of them contains data and headers.\n\n\nYou can read a part using either manually or using the \nMultipartForm\n's helpers.\n\n\nlet\n \npictureData\n \n=\n \ntry\n \nform\n.\ngetFile\n(\nnamed\n:\n \n\"profile-picture\"\n)\n\n\n\n\n\n\nlet\n \nnewPassword\n \n=\n \ntry\n \nform\n.\ngetString\n(\nnamed\n:\n \n\"password\"\n)", - "title": "Multipart" - }, - { - "location": "/http/multipart/#multipart-forms", - "text": "Multipart is a module that is primarily used with Forms. Multipart is used for complex forms containing one or more files, input fields and other HTML form data.", - "title": "Multipart Forms" - }, - { - "location": "/http/multipart/#extracting-from-a-request", - "text": "MultipartForm is a type of Content and can be extracted like any type of Content. let form = try MultipartForm . decode ( from : request ) // Future A future is returned because of streaming/reactive body parsing.", - "title": "Extracting from a request" - }, - { - "location": "/http/multipart/#parsing-a-multipart-form", - "text": "Multipart forms can be parsed using MultipartParser provided a body and boundary to read. let form = try MultipartParser ( body : httpBody , boundary : boundaryBytes ). parse ()", - "title": "Parsing a multipart form" - }, - { - "location": "/http/multipart/#reading-forms", - "text": "The parsed form is an array of Part instances.\nEach of them contains data and headers. You can read a part using either manually or using the MultipartForm 's helpers. let pictureData = try form . getFile ( named : \"profile-picture\" ) let newPassword = try form . getString ( named : \"password\" )", - "title": "Reading forms" - }, - { - "location": "/http/status/", - "text": "Status codes\n\u00b6\n\n\nStatus codes are exclusively part of the \nHTTP Response\n and are required.\n\n\nStatus codes are a 3 digit number.\n\n\nThe first of the 3 numbers indicated the type of response.\n\n\n\n\n\n\n\n\n_xx\n\n\nMeaning\n\n\n\n\n\n\n\n\n\n\n1xx\n\n\nInformational response\n\n\n\n\n\n\n2xx\n\n\nSuccess\n\n\n\n\n\n\n3xx\n\n\nRedirection\n\n\n\n\n\n\n4xx\n\n\nClient error\n\n\n\n\n\n\n5xx\n\n\nServer error\n\n\n\n\n\n\n\n\nThe other 2 numbers in a status code are used to define a specific code.\n\n\nSelecting a status code\n\u00b6\n\n\nThe enum \nStatus\n has all supported status codes. It can be accessed using a \n.\n or created using an integer literal.\n\n\nlet\n \nok\n \n=\n \nStatus\n.\nok\n\n\nlet\n \nnotFound\n \n=\n \nStatus\n.\nnotFound\n\n\n\n\n\n\nlet\n \nok\n:\n \nStatus\n \n=\n \n200\n\n\nlet\n \nnotFound\n:\n \nStatus\n \n=\n \n404\n\n\n\n\n\n\nInformational responses\n\u00b6\n\n\nInformational responses indicate a \nRequest\n was received and understood.\n\n\n101 - switching protocols\n\u00b6\n\n\nSwitching Protocols is a status code used to upgrade the connection to a different protocol. Commonly used by \nWebSocket\n or HTTP/2.\n\n\nSuccess responses\n\u00b6\n\n\nSuccess responses indicate that the request was received, understood, accepted and processed.\n\n\n200 - OK\n\u00b6\n\n\n200, or \"OK\" is the most common status code. It's used to indicate successful processing of the Request.\n\n\nRedirection responses\n\u00b6\n\n\nRedirection responses indicate the client must take additional action to complete the request. Many of these status codes are used in URL redirection.\n\n\nClient error responses\n\u00b6\n\n\nClient errors indicate an error was caused by the client.\n\n\n400 - Bad Request\n\u00b6\n\n\nThe error was caused by the client sending an invalid request.\n\n\nFor example an invalid message, malformed request syntax or too large request size.\n\n\n403 - Forbidden\n\u00b6\n\n\nThe client does not have the permissions to execute this operation on the specified resource.\n\n\n404 - Not found\n\u00b6\n\n\nThe requested resource does not exist.\n\n\nServer error responses\n\u00b6\n\n\nServer errors occur when the an error occurred on the server side.\n\n\n500 - Internal Server Error\n\u00b6\n\n\nInternal server errors are almost exclusively used when an error occurred on the server.", - "title": "Status codes" - }, - { - "location": "/http/status/#status-codes", - "text": "Status codes are exclusively part of the HTTP Response and are required. Status codes are a 3 digit number. The first of the 3 numbers indicated the type of response. _xx Meaning 1xx Informational response 2xx Success 3xx Redirection 4xx Client error 5xx Server error The other 2 numbers in a status code are used to define a specific code.", - "title": "Status codes" - }, - { - "location": "/http/status/#selecting-a-status-code", - "text": "The enum Status has all supported status codes. It can be accessed using a . or created using an integer literal. let ok = Status . ok let notFound = Status . notFound let ok : Status = 200 let notFound : Status = 404", - "title": "Selecting a status code" - }, - { - "location": "/http/status/#informational-responses", - "text": "Informational responses indicate a Request was received and understood.", - "title": "Informational responses" - }, - { - "location": "/http/status/#101-switching-protocols", - "text": "Switching Protocols is a status code used to upgrade the connection to a different protocol. Commonly used by WebSocket or HTTP/2.", - "title": "101 - switching protocols" - }, - { - "location": "/http/status/#success-responses", - "text": "Success responses indicate that the request was received, understood, accepted and processed.", - "title": "Success responses" - }, - { - "location": "/http/status/#200-ok", - "text": "200, or \"OK\" is the most common status code. It's used to indicate successful processing of the Request.", - "title": "200 - OK" - }, - { - "location": "/http/status/#redirection-responses", - "text": "Redirection responses indicate the client must take additional action to complete the request. Many of these status codes are used in URL redirection.", - "title": "Redirection responses" - }, - { - "location": "/http/status/#client-error-responses", - "text": "Client errors indicate an error was caused by the client.", - "title": "Client error responses" - }, - { - "location": "/http/status/#400-bad-request", - "text": "The error was caused by the client sending an invalid request. For example an invalid message, malformed request syntax or too large request size.", - "title": "400 - Bad Request" - }, - { - "location": "/http/status/#403-forbidden", - "text": "The client does not have the permissions to execute this operation on the specified resource.", - "title": "403 - Forbidden" - }, - { - "location": "/http/status/#404-not-found", - "text": "The requested resource does not exist.", - "title": "404 - Not found" - }, - { - "location": "/http/status/#server-error-responses", - "text": "Server errors occur when the an error occurred on the server side.", - "title": "Server error responses" - }, - { - "location": "/http/status/#500-internal-server-error", - "text": "Internal server errors are almost exclusively used when an error occurred on the server.", - "title": "500 - Internal Server Error" - }, - { - "location": "/http/uri/", - "text": "URI\n\u00b6\n\n\nURIs or \"Uniform Resource Identifiers\" are used for defining a resource.\n\n\nThey consist of the following components:\n\n\n\n\nscheme\n\n\nauthority\n\n\npath\n\n\nquery\n\n\nfragment\n\n\n\n\nCreating an URI\n\u00b6\n\n\nURIs can be created from it's initializer or from a String literal.\n\n\nlet\n \nstringLiteralURI\n:\n \nURI\n \n=\n \n\"http://localhost:8080/path\"\n\n\nlet\n \nmanualURI\n:\n \nURI\n \n=\n \nURI\n(\n\n \nscheme\n:\n \n\"http\"\n,\n\n \nhostname\n:\n \n\"localhost\"\n,\n\n \nport\n:\n \n8080\n,\n\n \npath\n:\n \n\"/path\"\n\n\n)", - "title": "URI" - }, - { - "location": "/http/uri/#uri", - "text": "URIs or \"Uniform Resource Identifiers\" are used for defining a resource. They consist of the following components: scheme authority path query fragment", - "title": "URI" - }, - { - "location": "/http/uri/#creating-an-uri", - "text": "URIs can be created from it's initializer or from a String literal. let stringLiteralURI : URI = \"http://localhost:8080/path\" let manualURI : URI = URI ( \n scheme : \"http\" , \n hostname : \"localhost\" , \n port : 8080 , \n path : \"/path\" )", - "title": "Creating an URI" - }, - { - "location": "/fluent/getting-started/", - "text": "Adding Fluent to your Project\n\u00b6\n\n\nFluent (\nvapor/fluent\n) is a type-safe, fast, and easy-to-use ORM built for Swift.\nIt takes advantage of Swift's strong type system to provide an elegant API for your database.\n\n\nDatabase\n\u00b6\n\n\nIn addition to adding Fluent to your project, you must also add a Fluent compatible database.\nFluent does not include any databases by default. All official databases have a getting started guide similar to this one.\n\n\n\n\n\n\n\n\ndatabase\n\n\nlibrary\n\n\ndriver\n\n\nguide\n\n\n\n\n\n\n\n\n\n\nPostgreSQL\n\n\nvapor/postgres\n\n\nvapor/fluent-postgres\n\n\nPostgreSQL \u2192 Package\n\n\n\n\n\n\nMySQL\n\n\nvapor/mysql\n\n\nvapor/fluent-mysql\n\n\nMySQL \u2192 Package\n\n\n\n\n\n\nSQLite\n\n\nvapor/sqlite\n\n\nvapor/fluent-sqlite\n\n\nSQLite \u2192 Package\n\n\n\n\n\n\nMongoDB\n\n\nmongokitten/mongokitten\n\n\nvapor/fluent-mongokitten\n\n\nREADME.md\n\n\n\n\n\n\n\n\n\n\nTip\n\n\nAny database can be made to work with Fluent by conforming to its \nDatabase\n protocol.\nFor a list of all compatible database types, search GitHub for the \nfluent-driver\n topic.\n\n\n\n\nFluent\n\u00b6\n\n\nAfter you have added your database driver, simply add the Fluent package to your Package manifest.\n\n\n// swift-tools-version:4.0\n\n\nimport\n \nPackageDescription\n\n\n\nlet\n \npackage\n \n=\n \nPackage\n(\n\n \nname\n:\n \n\"Project\"\n,\n\n \ndependencies\n:\n \n[\n\n \n...\n\n \n.\npackage\n(\nurl\n:\n \n\"https://github.com/vapor/fluent.git\"\n,\n \n.\nupToNextMajor\n(\nfrom\n:\n \n\"3.0.0\"\n)),\n\n \n],\n\n \ntargets\n:\n \n[\n\n \n.\ntarget\n(\nname\n:\n \n\"Project\"\n,\n \ndependencies\n:\n \n[\n\"Fluent\"\n,\n \n...\n \n])\n\n \n]\n\n\n)\n\n\n\n\n\n\nIf this is your first time adding a dependency, you should read our introduction to \nPackage.swift\n.\n\n\n\n\nNote\n\n\nUse \nimport Fluent\n to access Fluent's APIs.\n\n\n\n\nOnce you have Fluent added to your project, you are ready to configure your database(s).\n\n\nConfiguring Fluent\n\u00b6\n\n\nFluent integrates seamlessly into your Vapor project using \nservices\n.\nIn this section we will add the Fluent service provider to your application and configure your databases.\n\n\n\n\nWarning\n\n\nThis section assumes you have added both \nFluent\n and a \nFluent database\n to your package.\n\n\n\n\nService Provider\n\u00b6\n\n\nThe first step to using Fluent, is registering it with your Vapor application.\n\n\nimport\n \nFluent\n\n\n\n...\n\n\n\ntry\n \nservices\n.\ninstance\n(\nFluentProvider\n())\n\n\n\n\n\n\nRegister the \nFluentProvider\n in the \nconfigure section\n of your application.\n\n\n\n\nQuestion\n\n\nLearn more about how service providers work in \nGetting Started: Application\n\nand \nConcepts: Services\n.\n\n\n\n\nConfig\n\u00b6\n\n\nOnce the service provider has been added, we can configure one or more databases\nto be used with Fluent.\n\n\nIdentifier\n\u00b6\n\n\nEach database you use with Fluent must have a unique identifier. The easiest way to\nkeep track of this identifier is to add it as static extension to \nDatabaseIdentifier\n.\n\n\nimport\n \nFluent\n\n\nimport\n \nFluentMySQL\n\n\nimport\n \nFluentSQLite\n\n\n\nextension\n \nDatabaseIdentifier\n \n{\n\n \nstatic\n \nvar\n \nfoo\n:\n \nDatabaseIdentifier\n<\nSQLiteDatabase\n>\n \n{\n\n \nreturn\n \n.\ninit\n(\n\"foo\"\n)\n\n \n}\n\n\n \nstatic\n \nvar\n \nbar\n:\n \nDatabaseIdentifier\n<\nMySQLDatabase\n>\n \n{\n\n \nreturn\n \n.\ninit\n(\n\"bar\"\n)\n\n \n}\n\n\n}\n\n\n\n\n\n\nNow we can use the identifier anywhere in our project:\n\n\nreq\n.\ndatabase\n(.\nfoo\n)\n \n{\n \n...\n \n}\n\n\n\n\n\n\nThe \nconfigure section\n of your project is a good place to put this extension.\n\n\nDatabases\n\u00b6\n\n\nNow that we have created a unique identifier for our database, we can register it\nto our application using \nDatabaseConfig\n. A good place to do this is in the\n\nconfigure section\n of your project.\n\n\nYou can add databases to the \nDatabaseConfig\n using either a type (\n.self\n) or an instance.\n\n\nType\n\u00b6\n\n\nIf you register a database type (like \nSQLiteDatabase.self\n), Fluent will ask the application\nto create an instance of your database at boot.\n\n\nimport\n \nFluent\n\n\nimport\n \nFluentSQLite\n\n\n\n...\n\n\n\nvar\n \ndatabaseConfig\n \n=\n \nDatabaseConfig\n()\n\n\n\ndatabaseConfig\n.\nadd\n(\ndatabase\n:\n \nSQLiteDatabase\n.\nself\n,\n \nas\n:\n \n.\nfoo\n)\n\n\n\nservices\n.\ninstance\n(\ndatabaseConfig\n)\n\n\n\n\n\n\nInstance\n\u00b6\n\n\nYou can also register a pre-initialized database. This is especially useful if you'd\nlike to configure two instances of the same database type.\n\n\nimport\n \nFluent\n\n\nimport\n \nFluentMySQL\n\n\n\n...\n\n\n\nvar\n \ndatabaseConfig\n \n=\n \nDatabaseConfig\n()\n\n\n\nlet\n \nmysql\n \n=\n \nMySQLDatabase\n(...)\n\n\ndatabaseConfig\n.\nadd\n(\ndatabase\n:\n \nmysql\n,\n \nas\n:\n \n.\nbar\n)\n\n\n\nservices\n.\ninstance\n(\ndatabaseConfig\n)\n\n\n\n\n\n\nMigrations\n\u00b6\n\n\nIf your database uses schemas (most SQL databases do, whereas NoSQL databases don't), you will also want to configure\nyour migrations using \nMigrationConfig\n.\n\n\nimport\n \nFluent\n\n\n\n...\n\n\n\nvar\n \nmigrationConfig\n \n=\n \nMigrationConfig\n()\n\n\n\nmigrationConfig\n.\nadd\n(\nmigration\n:\n \nUser\n.\nself\n,\n \ndatabase\n:\n \n.\nfoo\n)\n\n\n\nservices\n.\ninstance\n(\nmigrationConfig\n)\n\n\n\n\n\n\nYou can read more about migrations in \nFluent: Migrations\n.\n\n\nDone\n\u00b6\n\n\nYou should now be able to compile and run your application. The next step is to create your models.", - "title": "Getting Started" - }, - { - "location": "/fluent/getting-started/#adding-fluent-to-your-project", - "text": "Fluent ( vapor/fluent ) is a type-safe, fast, and easy-to-use ORM built for Swift.\nIt takes advantage of Swift's strong type system to provide an elegant API for your database.", - "title": "Adding Fluent to your Project" - }, - { - "location": "/fluent/getting-started/#database", - "text": "In addition to adding Fluent to your project, you must also add a Fluent compatible database.\nFluent does not include any databases by default. All official databases have a getting started guide similar to this one. database library driver guide PostgreSQL vapor/postgres vapor/fluent-postgres PostgreSQL \u2192 Package MySQL vapor/mysql vapor/fluent-mysql MySQL \u2192 Package SQLite vapor/sqlite vapor/fluent-sqlite SQLite \u2192 Package MongoDB mongokitten/mongokitten vapor/fluent-mongokitten README.md Tip Any database can be made to work with Fluent by conforming to its Database protocol.\nFor a list of all compatible database types, search GitHub for the fluent-driver topic.", - "title": "Database" - }, - { - "location": "/fluent/getting-started/#fluent", - "text": "After you have added your database driver, simply add the Fluent package to your Package manifest. // swift-tools-version:4.0 import PackageDescription let package = Package ( \n name : \"Project\" , \n dependencies : [ \n ... \n . package ( url : \"https://github.com/vapor/fluent.git\" , . upToNextMajor ( from : \"3.0.0\" )), \n ], \n targets : [ \n . target ( name : \"Project\" , dependencies : [ \"Fluent\" , ... ]) \n ] ) If this is your first time adding a dependency, you should read our introduction to Package.swift . 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).", - "title": "Fluent" - }, - { - "location": "/fluent/getting-started/#configuring-fluent", - "text": "Fluent integrates seamlessly into your Vapor project using services .\nIn this section we will add the Fluent service provider to your application and configure your databases. Warning This section assumes you have added both Fluent and a Fluent database to your package.", - "title": "Configuring Fluent" - }, - { - "location": "/fluent/getting-started/#service-provider", - "text": "The first step to using Fluent, is registering it with your Vapor application. import Fluent ... try services . instance ( FluentProvider ()) Register the FluentProvider in the configure section of your application. Question Learn more about how service providers work in Getting Started: Application \nand Concepts: Services .", - "title": "Service Provider" - }, - { - "location": "/fluent/getting-started/#config", - "text": "Once the service provider has been added, we can configure one or more databases\nto be used with Fluent.", - "title": "Config" - }, - { - "location": "/fluent/getting-started/#identifier", - "text": "Each database you use with Fluent must have a unique identifier. The easiest way to\nkeep track of this identifier is to add it as static extension to DatabaseIdentifier . import Fluent import FluentMySQL import FluentSQLite extension DatabaseIdentifier { \n static var foo : DatabaseIdentifier < SQLiteDatabase > { \n return . init ( \"foo\" ) \n } \n\n static var bar : DatabaseIdentifier < MySQLDatabase > { \n return . init ( \"bar\" ) \n } } Now we can use the identifier anywhere in our project: req . database (. foo ) { ... } The configure section of your project is a good place to put this extension.", - "title": "Identifier" - }, - { - "location": "/fluent/getting-started/#databases", - "text": "Now that we have created a unique identifier for our database, we can register it\nto our application using DatabaseConfig . A good place to do this is in the configure section of your project. You can add databases to the DatabaseConfig using either a type ( .self ) or an instance.", - "title": "Databases" - }, - { - "location": "/fluent/getting-started/#type", - "text": "If you register a database type (like SQLiteDatabase.self ), Fluent will ask the application\nto create an instance of your database at boot. import Fluent import FluentSQLite ... var databaseConfig = DatabaseConfig () databaseConfig . add ( database : SQLiteDatabase . self , as : . foo ) services . instance ( databaseConfig )", - "title": "Type" - }, - { - "location": "/fluent/getting-started/#instance", - "text": "You can also register a pre-initialized database. This is especially useful if you'd\nlike to configure two instances of the same database type. import Fluent import FluentMySQL ... var databaseConfig = DatabaseConfig () let mysql = MySQLDatabase (...) databaseConfig . add ( database : mysql , as : . bar ) services . instance ( databaseConfig )", - "title": "Instance" - }, - { - "location": "/fluent/getting-started/#migrations", - "text": "If your database uses schemas (most SQL databases do, whereas NoSQL databases don't), you will also want to configure\nyour migrations using MigrationConfig . import Fluent ... var migrationConfig = MigrationConfig () migrationConfig . add ( migration : User . self , database : . foo ) services . instance ( migrationConfig ) You can read more about migrations in Fluent: Migrations .", - "title": "Migrations" - }, - { - "location": "/fluent/getting-started/#done", - "text": "You should now be able to compile and run your application. The next step is to create your models.", - "title": "Done" - }, - { - "location": "/fluent/models/", - "text": "Getting Started with Models\n\u00b6\n\n\nModels are the heart of Fluent. Unlike ORMs in other languages, Fluent doesn't return untyped\narrays or dictionaries for queries. Instead, you query the database using models. This allows the\nSwift compiler to catch many errors that have burdened ORM users for ages.\n\n\nIn this guide, we will cover the creation of a basic \nUser\n model.\n\n\nClass\n\u00b6\n\n\nEvery Fluent model starts with a \nCodable\n class. You can make any \nCodable\n class a Fluent model,\neven ones that come from a different module. All you have to do is conform to \nModel\n.\n\n\nimport\n \nFoundation\n\n\nimport\n \nVapor\n\n\n\nfinal\n \nclass\n \nUser\n:\n \nContent\n \n{\n\n \nvar\n \nid\n:\n \nUUID\n?\n\n \nvar\n \nname\n:\n \nString\n\n \nvar\n \nage\n:\n \nInt\n\n\n}\n\n\n\n\n\n\nAlthough it's not necessary, adding \nfinal\n to your Swift classes can make them more performant\nand also make adding \ninit\n methods in extensions easier.\n\n\nConforming to Model\n\u00b6\n\n\nNow that we have our \nUser\n class, let's conform it to \nModel\n.\n\n\nimport\n \nFluentMySQL\n\n\n\nextension\n \nUser\n:\n \nModel\n \n{\n\n\n\n}\n\n\n\n\n\n\nOnce you add this conformance requirement, Swift will tell you that it does not yet conform.\nLet's add the necessary items to make \nUser\n conform to model.\n\n\n\n\nTip\n\n\nWe recommend adding \nModel\n conformance in an extension to help keep your code clean.\n\n\n\n\nDatabase\n\u00b6\n\n\nThe first step to conforming to \nModel\n is to let Fluent know which type of database you plan\non using this model with. This allows Fluent to enable database-specific features wherever you\nuse this model.\n\n\nimport\n \nFluentMySQL\n\n\n\nextension\n \nUser\n:\n \nModel\n \n{\n\n \n...\n\n\n \n/// See Model.Database\n\n \ntypealias\n \nDatabase\n \n=\n \nMySQLDatabase\n\n\n}\n\n\n\n\n\n\nID\n\u00b6\n\n\nNow we can tell Fluent what type of ID this model uses. In this example, our \nUser\n model\nhas an ID property of type \nUUID\n named \nid\n.\n\n\nimport\n \nFluentMySQL\n\n\nimport\n \nFoundation\n\n\n\nextension\n \nUser\n:\n \nModel\n \n{\n\n \n...\n\n\n \n/// See Model.ID\n\n \ntypealias\n \nID\n \n=\n \nUUID\n\n\n \n/// See Model.idKey\n\n \nstatic\n \nvar\n \nidKey\n:\n \nIDKey\n \n{\n\n \nreturn\n \n\\\n.\nid\n\n \n}\n\n\n}\n\n\n\n\n\n\nYou can use any type that conforms to \nIDType\n as a Fluent ID. See \nFluent \u2192 Model \u2192 ID\n for more information.\nYou can also use any property name you'd like for the id.\n\n\n\n\nWarning\n\n\nSome databases require certain ID keys. For example, MongoDB requires \n_id\n.\n\n\n\n\nExample\n\u00b6\n\n\nWe now have a fully-conformed Fluent model!\n\n\nimport\n \nFluentMySQL\n\n\nimport\n \nFoundation\n\n\nimport\n \nVapor\n\n\n\nfinal\n \nclass\n \nUser\n:\n \nCodable\n \n{\n\n \nvar\n \nid\n:\n \nUUID\n?\n\n \nvar\n \nname\n:\n \nString\n\n \nvar\n \nage\n:\n \nInt\n\n\n}\n\n\n\nextension\n \nUser\n:\n \nModel\n \n{\n\n \n/// See Model.Database\n\n \ntypealias\n \nDatabase\n \n=\n \nMySQLDatabase\n\n\n \n/// See Model.ID\n\n \ntypealias\n \nID\n \n=\n \nUUID\n\n\n \n/// See Model.idKey\n\n \nstatic\n \nvar\n \nidKey\n:\n \nIDKey\n \n{\n\n \nreturn\n \n\\\n.\nid\n\n \n}\n\n\n}\n\n\n\n\n\n\nDone\n\u00b6\n\n\nNow that you have a working Fluent model, you can move onto \nquerying\n your model.\nHowever, if your database uses schemas, you may need to create a \nmigration\n for your model first.", - "title": "Models" - }, - { - "location": "/fluent/models/#getting-started-with-models", - "text": "Models are the heart of Fluent. Unlike ORMs in other languages, Fluent doesn't return untyped\narrays or dictionaries for queries. Instead, you query the database using models. This allows the\nSwift compiler to catch many errors that have burdened ORM users for ages. In this guide, we will cover the creation of a basic User model.", - "title": "Getting Started with Models" - }, - { - "location": "/fluent/models/#class", - "text": "Every Fluent model starts with a Codable class. You can make any Codable class a Fluent model,\neven ones that come from a different module. All you have to do is conform to Model . import Foundation import Vapor final class User : Content { \n var id : UUID ? \n var name : String \n var age : Int } Although it's not necessary, adding final to your Swift classes can make them more performant\nand also make adding init methods in extensions easier.", - "title": "Class" - }, - { - "location": "/fluent/models/#conforming-to-model", - "text": "Now that we have our User class, let's conform it to Model . import FluentMySQL extension User : Model { } Once you add this conformance requirement, Swift will tell you that it does not yet conform.\nLet's add the necessary items to make User conform to model. Tip We recommend adding Model conformance in an extension to help keep your code clean.", - "title": "Conforming to Model" - }, - { - "location": "/fluent/models/#database", - "text": "The first step to conforming to Model is to let Fluent know which type of database you plan\non using this model with. This allows Fluent to enable database-specific features wherever you\nuse this model. import FluentMySQL extension User : Model { \n ... \n\n /// See Model.Database \n typealias Database = MySQLDatabase }", - "title": "Database" - }, - { - "location": "/fluent/models/#id", - "text": "Now we can tell Fluent what type of ID this model uses. In this example, our User model\nhas an ID property of type UUID named id . import FluentMySQL import Foundation extension User : Model { \n ... \n\n /// See Model.ID \n typealias ID = UUID \n\n /// See Model.idKey \n static var idKey : IDKey { \n return \\ . id \n } } You can use any type that conforms to IDType as a Fluent ID. See Fluent \u2192 Model \u2192 ID for more information.\nYou can also use any property name you'd like for the id. Warning Some databases require certain ID keys. For example, MongoDB requires _id .", - "title": "ID" - }, - { - "location": "/fluent/models/#example", - "text": "We now have a fully-conformed Fluent model! import FluentMySQL import Foundation import Vapor final class User : Codable { \n var id : UUID ? \n var name : String \n var age : Int } extension User : Model { \n /// See Model.Database \n typealias Database = MySQLDatabase \n\n /// See Model.ID \n typealias ID = UUID \n\n /// See Model.idKey \n static var idKey : IDKey { \n return \\ . id \n } }", - "title": "Example" - }, - { - "location": "/fluent/models/#done", - "text": "Now that you have a working Fluent model, you can move onto querying your model.\nHowever, if your database uses schemas, you may need to create a migration for your model first.", - "title": "Done" - }, - { - "location": "/fluent/migrations/", - "text": "Getting Started with Migrations\n\u00b6\n\n\nMigrations are a way of making organized, testable, and reliable changes to your database's structure--\neven while it's in production!\n\n\nMigrations are often used for preparing a database schema for your models. However, they can also be used to \nmake normal queries to your database.\n\n\nIn this guide we will cover creating both types of migrations.\n\n\nModel Schema\n\u00b6\n\n\nLet's take a look at how we can prepare a schema supporting database to accept the \n\nUser\n model from the \nprevious section\n.\n\n\nJust like we did with the \nModel\n protocol, we will conform our \nUser\n to \nMigration\n.\n\n\nimport\n \nFluent\n\n\n\nextension\n \nUser\n:\n \nMigration\n \n{\n\n\n\n}\n\n\n\n\n\n\nSwift will inform us that \nUser\n does not yet conform. Let's add the required methods!\n\n\nPrepare\n\u00b6\n\n\nThe first method to implement is \nprepare\n. This method is where you make any of your \ndesired changes to the database.\n\n\nFor our \nUser\n model, we simply want to create a table that can store one or more users. To do this,\nwe will use the \n.create(...)\n function on the supplied database connection.\n\n\nextension\n \nUser\n:\n \nMigration\n \n{\n\n \n/// See Migration.prepare\n\n \nstatic\n \nfunc\n \nprepare\n(\non\n \nconnection\n:\n \nMySQLConnection\n)\n \n->\n \nFuture\n<\nVoid\n>\n \n{\n\n \nreturn\n \nconnection\n.\ncreate\n(\nself\n)\n \n{\n \nbuilder\n \nin\n\n \ntry\n \nbuilder\n.\nfield\n(\nfor\n:\n \n\\\n.\nid\n)\n\n \ntry\n \nbuilder\n.\nfield\n(\nfor\n:\n \n\\\n.\nname\n)\n\n \ntry\n \nbuilder\n.\nfield\n(\nfor\n:\n \n\\\n.\nage\n)\n\n \n}\n\n \n}\n\n\n}\n\n\n\n\n\n\nWe pass \nself\n (shorthand for \nUser.self\n since this is a static method) as the first argument to the \n.create\n method. This indicates\nto Fluent that we would like to create a schema for the \nUser\n model.\n\n\nNext, we pass a closure that accepts a \nSchemaBuilder\n for our \nUser\n model.\nWe can then call \n.field\non this builder to describe what fields we'd like our table to have.\n\n\nSince we are passing key paths to our \nUser\n model (indicated by \n\\.\n), Fluent can see what type those properties are.\nFor most common types (\nString\n, \nInt\n, \nDouble\n, etc) Fluent will automatically be able to determine the best\ndatabase field type to use.\n\n\nYou can also choose to manually select which database field type to use for a given field.\n\n\ntry\n \nbuilder\n.\nfield\n(\ntype\n:\n \n.\ntext\n,\n \nfor\n:\n \n\\\n.\nname\n)\n\n\n\n\n\n\nLearn more about creating, updating, and deleting schemas in \nFluent \u2192 Schema Builder\n.\n\n\nRevert\n\u00b6\n\n\nRevert is the opposite of prepare. It's job is to undo anything that was done in prepare. It is used when you boot your \napp with the \n--revert\n option. See \nFluent \u2192 Migration\n for more information.\n\n\nTo implement \nrevert\n for our model, we simply use \n.delete\n to indicate that we would like to delete the schema created for \nUser\n.\n\n\nextension\n \nUser\n:\n \nMigration\n \n{\n\n \n/// See Migration.revert\n\n \nstatic\n \nfunc\n \nrevert\n(\non\n \nconnection\n:\n \nMySQLConnection\n)\n \n->\n \nFuture\n<\nVoid\n>\n \n{\n\n \nreturn\n \nconnection\n.\ndelete\n(\nself\n)\n\n \n}\n\n\n}\n\n\n\n\n\n\nExample\n\u00b6\n\n\nWe now have a fully functioning model with migration!\n\n\nextension\n \nTestUser\n:\n \nMigration\n \n{\n\n \n/// See Migration.prepare\n\n \nstatic\n \nfunc\n \nprepare\n(\non\n \nconnection\n:\n \nSQLiteConnection\n)\n \n->\n \nFuture\n<\nVoid\n>\n \n{\n\n \nreturn\n \nconnection\n.\ncreate\n(\nself\n)\n \n{\n \nbuilder\n \nin\n\n \ntry\n \nbuilder\n.\nfield\n(\nfor\n:\n \n\\\n.\nid\n)\n\n \ntry\n \nbuilder\n.\nfield\n(\nfor\n:\n \n\\\n.\nname\n)\n\n \ntry\n \nbuilder\n.\nfield\n(\nfor\n:\n \n\\\n.\nage\n)\n\n \n}\n\n \n}\n\n\n \n/// See Migration.revert\n\n \nstatic\n \nfunc\n \nrevert\n(\non\n \nconnection\n:\n \nSQLiteConnection\n)\n \n->\n \nFuture\n<\nVoid\n>\n \n{\n\n \nreturn\n \nconnection\n.\ndelete\n(\nself\n)\n\n \n}\n\n\n}\n\n\n\n\n\n\nDone\n\u00b6\n\n\nNow that you have a working Fluent model and migration, you can move onto \nquerying\n your model.", - "title": "Migrations" - }, - { - "location": "/fluent/migrations/#getting-started-with-migrations", - "text": "Migrations are a way of making organized, testable, and reliable changes to your database's structure--\neven while it's in production! Migrations are often used for preparing a database schema for your models. However, they can also be used to \nmake normal queries to your database. In this guide we will cover creating both types of migrations.", - "title": "Getting Started with Migrations" - }, - { - "location": "/fluent/migrations/#model-schema", - "text": "Let's take a look at how we can prepare a schema supporting database to accept the User model from the previous section . Just like we did with the Model protocol, we will conform our User to Migration . import Fluent extension User : Migration { } Swift will inform us that User does not yet conform. Let's add the required methods!", - "title": "Model Schema" - }, - { - "location": "/fluent/migrations/#prepare", - "text": "The first method to implement is prepare . This method is where you make any of your \ndesired changes to the database. For our User model, we simply want to create a table that can store one or more users. To do this,\nwe will use the .create(...) function on the supplied database connection. extension User : Migration { \n /// See Migration.prepare \n static func prepare ( on connection : MySQLConnection ) -> Future < Void > { \n return connection . create ( self ) { builder in \n try builder . field ( for : \\ . id ) \n try builder . field ( for : \\ . name ) \n try builder . field ( for : \\ . age ) \n } \n } } We pass self (shorthand for User.self since this is a static method) as the first argument to the .create method. This indicates\nto Fluent that we would like to create a schema for the User model. Next, we pass a closure that accepts a SchemaBuilder for our User model.\nWe can then call .field on this builder to describe what fields we'd like our table to have. Since we are passing key paths to our User model (indicated by \\. ), Fluent can see what type those properties are.\nFor most common types ( String , Int , Double , etc) Fluent will automatically be able to determine the best\ndatabase field type to use. You can also choose to manually select which database field type to use for a given field. try builder . field ( type : . text , for : \\ . name ) Learn more about creating, updating, and deleting schemas in Fluent \u2192 Schema Builder .", - "title": "Prepare" - }, - { - "location": "/fluent/migrations/#revert", - "text": "Revert is the opposite of prepare. It's job is to undo anything that was done in prepare. It is used when you boot your \napp with the --revert option. See Fluent \u2192 Migration for more information. To implement revert for our model, we simply use .delete to indicate that we would like to delete the schema created for User . extension User : Migration { \n /// See Migration.revert \n static func revert ( on connection : MySQLConnection ) -> Future < Void > { \n return connection . delete ( self ) \n } }", - "title": "Revert" - }, - { - "location": "/fluent/migrations/#example", - "text": "We now have a fully functioning model with migration! extension TestUser : Migration { \n /// See Migration.prepare \n static func prepare ( on connection : SQLiteConnection ) -> Future < Void > { \n return connection . create ( self ) { builder in \n try builder . field ( for : \\ . id ) \n try builder . field ( for : \\ . name ) \n try builder . field ( for : \\ . age ) \n } \n } \n\n /// See Migration.revert \n static func revert ( on connection : SQLiteConnection ) -> Future < Void > { \n return connection . delete ( self ) \n } }", - "title": "Example" - }, - { - "location": "/fluent/migrations/#done", - "text": "Now that you have a working Fluent model and migration, you can move onto querying your model.", - "title": "Done" - }, - { - "location": "/fluent/querying/", - "text": "Querying Models\n\u00b6\n\n\nOnce you have a \nmodel\n (and optionally a \nmigration\n) you can start\nquerying your database to create, read, update, and delete data.\n\n\nConnection\n\u00b6\n\n\nThe first thing you need to query your database, is a connection to it. Luckily, they are easy to get.\n\n\nYou can use either the application or an incoming request to create a database connection. You just need\naccess to the \nDatabaseIdentifier\n.\n\n\nRequest\n\u00b6\n\n\nThe preferred method for getting access to a database connection is via an incoming request.\n\n\nrouter\n.\nget\n(...)\n \n{\n \nreq\n \nin\n\n \nreturn\n \nreq\n.\nwithConnection\n(\nto\n:\n \n.\nfoo\n)\n \n{\n \ndb\n \nin\n\n \n// use the db here\n\n \n}\n\n\n}\n\n\n\n\n\n\nThe first parameter is the database's \nidentifier\n. The second parameter is a closure\nthat accepts a connection to that database.\n\n\n\n\nTip\n\n\nAlthough the closure to \n.withConnection(to: ...)\n accepts a database \nconnection\n, we often use just \ndb\n for short.\n\n\n\n\nThe closure is expected to return a \nFuture\n. When this future is completed, the connection will be released\nback into Fluent's connection pool. This is usually acheived by simply returning the query as we will soon see.\n\n\nApplication\n\u00b6\n\n\nYou can also create a database connection using the application. This is useful for cases where you must access\nthe database from outside a request/response event.\n\n\nlet\n \nres\n \n=\n \napp\n.\nwithConnection\n(\nto\n:\n \n.\nfoo\n)\n \n{\n \ndb\n \nin\n\n \n// use the db here\n\n\n}\n\n\nprint\n(\nres\n)\n \n// Future\n\n\n\n\n\n\nThis is usually done in the \nboot section\n of your application.\n\n\n\n\nWarning\n\n\nDo not use database connections created by the application in a route closure (when responding to a request).\nAlways use the incoming request to create a connection to avoid threading issues.\n\n\n\n\nCreate\n\u00b6\n\n\nTo create (save) a model to the database, first initialize an instance of your model, then call \n.save(on: )\n.\n\n\nrouter\n.\npost\n(...)\n \n{\n \nreq\n \nin\n\n \nreturn\n \nreq\n.\nwithConnection\n(\nto\n:\n \n.\nfoo\n)\n \n{\n \ndb\n \n->\n \nFuture\n<\nUser\n>\n \nin\n\n \nlet\n \nuser\n \n=\n \nUser\n(\nname\n:\n \n\"Vapor\"\n,\n \nage\n:\n \n3\n)\n\n \nreturn\n \nuser\n.\nsave\n(\non\n:\n \ndb\n).\ntransform\n(\nto\n:\n \nuser\n)\n \n// Future\n\n \n}\n\n\n}\n\n\n\n\n\n\nResponse\n\u00b6\n\n\n.save(on: )\n returns a \nFuture\n that completes when the user has finished saving. In this example, we then\nmap that \nFuture\n to a \nFuture\n by calling \n.map\n and passing in the recently-saved user.\n\n\nYou can also use \n.map\n to return a simple success response.\n\n\nrouter\n.\npost\n(...)\n \n{\n \nreq\n \nin\n\n \nreturn\n \nreq\n.\nwithConnection\n(\nto\n:\n \n.\nfoo\n)\n \n{\n \ndb\n \n->\n \nFuture\n<\nHTTPResponse\n>\n \nin\n\n \nlet\n \nuser\n \n=\n \nUser\n(\nname\n:\n \n\"Vapor\"\n,\n \nage\n:\n \n3\n)\n\n \nreturn\n \nuser\n.\nsave\n(\non\n:\n \ndb\n).\nmap\n(\nto\n:\n \nHTTPResponse\n.\nself\n)\n \n{\n\n \nreturn\n \nHTTPResponse\n(\nstatus\n:\n \n.\ncreated\n)\n\n \n}\n\n \n}\n\n\n}\n\n\n\n\n\n\nMultiple\n\u00b6\n\n\nIf you have multiple instances to save, do so using an array. Arrays containing only futures behave like futures.\n\n\nrouter\n.\npost\n(...)\n \n{\n \nreq\n \nin\n\n \nreturn\n \nreq\n.\nwithConnection\n(\nto\n:\n \n.\nfoo\n)\n \n{\n \ndb\n \n->\n \nFuture\n<\nHTTPResponse\n>\n \nin\n\n \nlet\n \nmarie\n \n=\n \nUser\n(\nname\n:\n \n\"Marie Curie\"\n,\n \nage\n:\n \n66\n)\n\n \nlet\n \ncharles\n \n=\n \nUser\n(\nname\n:\n \n\"Charles Darwin\"\n,\n \nage\n:\n \n73\n)\n\n \nreturn\n \n[\n\n \nmarie\n.\nsave\n(\non\n:\n \ndb\n),\n\n \ncharles\n.\nsave\n(\non\n:\n \ndb\n)\n\n \n].\nmap\n(\nto\n:\n \nHTTPResponse\n.\nself\n)\n \n{\n\n \nreturn\n \nHTTPResponse\n(\nstatus\n:\n \n.\ncreated\n)\n\n \n}\n\n \n}\n\n\n}\n\n\n\n\n\n\nRead\n\u00b6\n\n\nTo read models from the database, use \n.query()\n on the database connection to create a \nQueryBuilder\n.\n\n\nAll\n\u00b6\n\n\nFetch all instances of a model from the database using \n.all()\n.\n\n\nrouter\n.\nget\n(...)\n \n{\n \nreq\n \nin\n\n \nreturn\n \nreq\n.\nwithConnection\n(\nto\n:\n \n.\nfoo\n)\n \n{\n \ndb\n \n->\n \nFuture\n<\n[\nUser\n]\n>\n \nin\n\n \nreturn\n \ndb\n.\nquery\n(\nUser\n.\nself\n).\nall\n()\n\n \n}\n\n\n}\n\n\n\n\n\n\nFilter\n\u00b6\n\n\nUse \n.filter(...)\n to apply \nfilters\n to your query.\n\n\nrouter\n.\nget\n(...)\n \n{\n \nreq\n \nin\n\n \nreturn\n \nreq\n.\nwithConnection\n(\nto\n:\n \n.\nfoo\n)\n \n{\n \ndb\n \n->\n \nFuture\n<\n[\nUser\n]\n>\n \nin\n\n \nreturn\n \ntry\n \ndb\n.\nquery\n(\nUser\n.\nself\n).\nfilter\n(\n\\\nUser\n.\nage\n \n>\n \n50\n).\nall\n()\n\n \n}\n\n\n}\n\n\n\n\n\n\nFirst\n\u00b6\n\n\nYou can also use \n.first()\n to just get the first result.\n\n\nrouter\n.\nget\n(...)\n \n{\n \nreq\n \nin\n\n \nreturn\n \nreq\n.\nwithConnection\n(\nto\n:\n \n.\nfoo\n)\n \n{\n \ndb\n \n->\n \nFuture\n<\nUser\n>\n \nin\n\n \nreturn\n \ntry\n \ndb\n.\nquery\n(\nUser\n.\nself\n).\nfilter\n(\n\\\nUser\n.\nname\n \n==\n \n\"Vapor\"\n).\nfirst\n().\nmap\n(\nto\n:\n \nUser\n.\nself\n)\n \n{\n \nuser\n \nin\n\n \nguard\n \nlet\n \nuser\n \n=\n \nuser\n \nelse\n \n{\n\n \nthrow\n \nAbort\n(.\nnotFound\n,\n \nreason\n:\n \n\"Could not find user.\"\n)\n\n \n}\n\n\n \nreturn\n \nuser\n\n \n}\n\n \n}\n\n\n}\n\n\n\n\n\n\nNotice we use \n.map(to:)\n here to convert the optional user returned by \n.first()\n to a non-optional\nuser, or we throw an error.\n\n\nUpdate\n\u00b6\n\n\nrouter\n.\nput\n(...)\n \n{\n \nreq\n \nin\n\n \nreturn\n \nreq\n.\nwithConnection\n(\nto\n:\n \n.\nfoo\n)\n \n{\n \ndb\n \n->\n \nFuture\n<\nUser\n>\n \nin\n\n \nreturn\n \ndb\n.\nquery\n(\nUser\n.\nself\n).\nfirst\n().\nmap\n(\nto\n:\n \nUser\n.\nself\n)\n \n{\n \nuser\n \nin\n\n \nguard\n \nlet\n \nuser\n \n=\n \n$0\n \nelse\n \n{\n\n \nthrow\n \nAbort\n(.\nnotFound\n,\n \nreason\n:\n \n\"Could not find user.\"\n)\n\n \n}\n\n\n \nreturn\n \nuser\n\n \n}.\nflatMap\n(\nto\n:\n \nUser\n.\nself\n)\n \n{\n \nuser\n \nin\n\n \nuser\n.\nage\n \n+=\n \n1\n\n \nreturn\n \nuser\n.\nupdate\n(\non\n:\n \ndb\n).\nmap\n(\nto\n:\n \nUser\n.\nself\n)\n \n{\n \nuser\n \n}\n\n \n}\n\n \n}\n\n\n}\n\n\n\n\n\n\nNotice we use \n.map(to:)\n here to convert the optional user returned by \n.first()\n to a non-optional\nuser, or we throw an error.\n\n\nDelete\n\u00b6\n\n\nrouter\n.\ndelete\n(...)\n \n{\n \nreq\n \nin\n\n \nreturn\n \nreq\n.\nwithConnection\n(\nto\n:\n \n.\nfoo\n)\n \n{\n \ndb\n \n->\n \nFuture\n<\nUser\n>\n \nin\n\n \nreturn\n \ndb\n.\nquery\n(\nUser\n.\nself\n).\nfirst\n().\nmap\n(\nto\n:\n \nUser\n.\nself\n)\n \n{\n \nuser\n \nin\n\n \nguard\n \nlet\n \nuser\n \n=\n \n$0\n \nelse\n \n{\n\n \nthrow\n \nAbort\n(.\nnotFound\n,\n \nreason\n:\n \n\"Could not find user.\"\n)\n\n \n}\n\n \nreturn\n \nuser\n\n \n}.\nflatMap\n(\nto\n:\n \nUser\n.\nself\n)\n \n{\n \nuser\n \nin\n\n \nreturn\n \nuser\n.\ndelete\n(\non\n:\n \ndb\n).\ntransfom\n(\nto\n:\n \nuser\n)\n\n \n}\n\n \n}\n\n\n}\n\n\n\n\n\n\nNotice we use \n.map(to:)\n here to convert the optional user returned by \n.first()\n to a non-optional\nuser, or we throw an error.", - "title": "Querying" - }, - { - "location": "/fluent/querying/#querying-models", - "text": "Once you have a model (and optionally a migration ) you can start\nquerying your database to create, read, update, and delete data.", - "title": "Querying Models" - }, - { - "location": "/fluent/querying/#connection", - "text": "The first thing you need to query your database, is a connection to it. Luckily, they are easy to get. You can use either the application or an incoming request to create a database connection. You just need\naccess to the DatabaseIdentifier .", - "title": "Connection" - }, - { - "location": "/fluent/querying/#request", - "text": "The preferred method for getting access to a database connection is via an incoming request. router . get (...) { req in \n return req . withConnection ( to : . foo ) { db in \n // use the db here \n } } The first parameter is the database's identifier . The second parameter is a closure\nthat accepts a connection to that database. Tip Although the closure to .withConnection(to: ...) accepts a database connection , we often use just db for short. The closure is expected to return a Future . When this future is completed, the connection will be released\nback into Fluent's connection pool. This is usually acheived by simply returning the query as we will soon see.", - "title": "Request" - }, - { - "location": "/fluent/querying/#application", - "text": "You can also create a database connection using the application. This is useful for cases where you must access\nthe database from outside a request/response event. let res = app . withConnection ( to : . foo ) { db in \n // use the db here } print ( res ) // Future This is usually done in the boot section of your application. Warning Do not use database connections created by the application in a route closure (when responding to a request).\nAlways use the incoming request to create a connection to avoid threading issues.", - "title": "Application" - }, - { - "location": "/fluent/querying/#create", - "text": "To create (save) a model to the database, first initialize an instance of your model, then call .save(on: ) . router . post (...) { req in \n return req . withConnection ( to : . foo ) { db -> Future < User > in \n let user = User ( name : \"Vapor\" , age : 3 ) \n return user . save ( on : db ). transform ( to : user ) // Future \n } }", - "title": "Create" - }, - { - "location": "/fluent/querying/#response", - "text": ".save(on: ) returns a Future that completes when the user has finished saving. In this example, we then\nmap that Future to a Future by calling .map and passing in the recently-saved user. You can also use .map to return a simple success response. router . post (...) { req in \n return req . withConnection ( to : . foo ) { db -> Future < HTTPResponse > in \n let user = User ( name : \"Vapor\" , age : 3 ) \n return user . save ( on : db ). map ( to : HTTPResponse . self ) { \n return HTTPResponse ( status : . created ) \n } \n } }", - "title": "Response" - }, - { - "location": "/fluent/querying/#multiple", - "text": "If you have multiple instances to save, do so using an array. Arrays containing only futures behave like futures. router . post (...) { req in \n return req . withConnection ( to : . foo ) { db -> Future < HTTPResponse > in \n let marie = User ( name : \"Marie Curie\" , age : 66 ) \n let charles = User ( name : \"Charles Darwin\" , age : 73 ) \n return [ \n marie . save ( on : db ), \n charles . save ( on : db ) \n ]. map ( to : HTTPResponse . self ) { \n return HTTPResponse ( status : . created ) \n } \n } }", - "title": "Multiple" - }, - { - "location": "/fluent/querying/#read", - "text": "To read models from the database, use .query() on the database connection to create a QueryBuilder .", - "title": "Read" - }, - { - "location": "/fluent/querying/#all", - "text": "Fetch all instances of a model from the database using .all() . router . get (...) { req in \n return req . withConnection ( to : . foo ) { db -> Future < [ User ] > in \n return db . query ( User . self ). all () \n } }", - "title": "All" - }, - { - "location": "/fluent/querying/#filter", - "text": "Use .filter(...) to apply filters to your query. router . get (...) { req in \n return req . withConnection ( to : . foo ) { db -> Future < [ User ] > in \n return try db . query ( User . self ). filter ( \\ User . age > 50 ). all () \n } }", - "title": "Filter" - }, - { - "location": "/fluent/querying/#first", - "text": "You can also use .first() to just get the first result. router . get (...) { req in \n return req . withConnection ( to : . foo ) { db -> Future < User > in \n return try db . query ( User . self ). filter ( \\ User . name == \"Vapor\" ). first (). map ( to : User . self ) { user in \n guard let user = user else { \n throw Abort (. notFound , reason : \"Could not find user.\" ) \n } \n\n return user \n } \n } } Notice we use .map(to:) here to convert the optional user returned by .first() to a non-optional\nuser, or we throw an error.", - "title": "First" - }, - { - "location": "/fluent/querying/#update", - "text": "router . put (...) { req in \n return req . withConnection ( to : . foo ) { db -> Future < User > in \n return db . query ( User . self ). first (). map ( to : User . self ) { user in \n guard let user = $0 else { \n throw Abort (. notFound , reason : \"Could not find user.\" ) \n } \n\n return user \n }. flatMap ( to : User . self ) { user in \n user . age += 1 \n return user . update ( on : db ). map ( to : User . self ) { user } \n } \n } } Notice we use .map(to:) here to convert the optional user returned by .first() to a non-optional\nuser, or we throw an error.", - "title": "Update" - }, - { - "location": "/fluent/querying/#delete", - "text": "router . delete (...) { req in \n return req . withConnection ( to : . foo ) { db -> Future < User > in \n return db . query ( User . self ). first (). map ( to : User . self ) { user in \n guard let user = $0 else { \n throw Abort (. notFound , reason : \"Could not find user.\" ) \n } \n return user \n }. flatMap ( to : User . self ) { user in \n return user . delete ( on : db ). transfom ( to : user ) \n } \n } } Notice we use .map(to:) here to convert the optional user returned by .first() to a non-optional\nuser, or we throw an error.", - "title": "Delete" - }, - { - "location": "/fluent/query-builder/", - "text": "Fluent Query Builder\n\u00b6\n\n\nComing soon.\n\n\nFilter\n\u00b6\n\n\nComing soon.\n\n\nCompare\n\u00b6\n\n\nComing soon.\n\n\nGroup\n\u00b6\n\n\nComing soon.\n\n\nSubset\n\u00b6\n\n\nComing soon.\n\n\nJoin\n\u00b6\n\n\nComing soon.\n\n\nRange\n\u00b6\n\n\nComing soon.\n\n\nSort\n\u00b6\n\n\nComing soon.\n\n\nExecute\n\u00b6\n\n\nComing soon.\n\n\nAggregate\n\u00b6\n\n\nComing soon.\n\n\nAll\n\u00b6\n\n\nComing soon.\n\n\nFirst\n\u00b6\n\n\nComing soon.\n\n\nQuery\n\u00b6\n\n\nComing soon.", - "title": "Query Builder" - }, - { - "location": "/fluent/query-builder/#fluent-query-builder", - "text": "Coming soon.", - "title": "Fluent Query Builder" - }, - { - "location": "/fluent/query-builder/#filter", - "text": "Coming soon.", - "title": "Filter" - }, - { - "location": "/fluent/query-builder/#compare", - "text": "Coming soon.", - "title": "Compare" - }, - { - "location": "/fluent/query-builder/#group", - "text": "Coming soon.", - "title": "Group" - }, - { - "location": "/fluent/query-builder/#subset", - "text": "Coming soon.", - "title": "Subset" - }, - { - "location": "/fluent/query-builder/#join", - "text": "Coming soon.", - "title": "Join" - }, - { - "location": "/fluent/query-builder/#range", - "text": "Coming soon.", - "title": "Range" - }, - { - "location": "/fluent/query-builder/#sort", - "text": "Coming soon.", - "title": "Sort" - }, - { - "location": "/fluent/query-builder/#execute", - "text": "Coming soon.", - "title": "Execute" - }, - { - "location": "/fluent/query-builder/#aggregate", - "text": "Coming soon.", - "title": "Aggregate" - }, - { - "location": "/fluent/query-builder/#all", - "text": "Coming soon.", - "title": "All" - }, - { - "location": "/fluent/query-builder/#first", - "text": "Coming soon.", - "title": "First" - }, - { - "location": "/fluent/query-builder/#query", - "text": "Coming soon.", - "title": "Query" - }, - { - "location": "/fluent/schema-builder/", - "text": "Fluent Schema Builder\n\u00b6\n\n\nComing soon.\n\n\nCreate\n\u00b6\n\n\nComing soon.\n\n\nUpdate\n\u00b6\n\n\nComing soon.\n\n\nDelete\n\u00b6\n\n\nComing soon.\n\n\nReferences\n\u00b6\n\n\nComing soon.", - "title": "Schema Builder" - }, - { - "location": "/fluent/schema-builder/#fluent-schema-builder", - "text": "Coming soon.", - "title": "Fluent Schema Builder" - }, - { - "location": "/fluent/schema-builder/#create", - "text": "Coming soon.", - "title": "Create" - }, - { - "location": "/fluent/schema-builder/#update", - "text": "Coming soon.", - "title": "Update" - }, - { - "location": "/fluent/schema-builder/#delete", - "text": "Coming soon.", - "title": "Delete" - }, - { - "location": "/fluent/schema-builder/#references", - "text": "Coming soon.", - "title": "References" - }, - { - "location": "/fluent/migration/", - "text": "Fluent Migration\n\u00b6\n\n\nComing soon.\n\n\nProtocol\n\u00b6\n\n\nComing soon.\n\n\nConfig\n\u00b6\n\n\nComing soon.", - "title": "Migration" - }, - { - "location": "/fluent/migration/#fluent-migration", - "text": "Coming soon.", - "title": "Fluent Migration" - }, - { - "location": "/fluent/migration/#protocol", - "text": "Coming soon.", - "title": "Protocol" - }, - { - "location": "/fluent/migration/#config", - "text": "Coming soon.", - "title": "Config" - }, - { - "location": "/fluent/relations/", - "text": "Fluent Relations\n\u00b6\n\n\nComing soon.\n\n\nParent / Child\n\u00b6\n\n\nComing soon.\n\n\nSiblings\n\u00b6\n\n\nComing soon.", - "title": "Relations" - }, - { - "location": "/fluent/relations/#fluent-relations", - "text": "Coming soon.", - "title": "Fluent Relations" - }, - { - "location": "/fluent/relations/#parent-child", - "text": "Coming soon.", - "title": "Parent / Child" - }, - { - "location": "/fluent/relations/#siblings", - "text": "Coming soon.", - "title": "Siblings" - }, - { - "location": "/fluent/pivot/", - "text": "Fluent Pivot\n\u00b6\n\n\nComing soon.", - "title": "Pivot" - }, - { - "location": "/fluent/pivot/#fluent-pivot", - "text": "Coming soon.", - "title": "Fluent Pivot" - }, - { - "location": "/fluent/transaction/", - "text": "Fluent Transactions\n\u00b6\n\n\nComing soon.", - "title": "Transaction" - }, - { - "location": "/fluent/transaction/#fluent-transactions", - "text": "Coming soon.", - "title": "Fluent Transactions" - }, - { - "location": "/fluent/database/", - "text": "Fluent Database\n\u00b6\n\n\nComing soon.\n\n\nConnection\n\u00b6\n\n\nComing soon.\n\n\nLogger\n\u00b6\n\n\nComing soon.", - "title": "Database" - }, - { - "location": "/fluent/database/#fluent-database", - "text": "Coming soon.", - "title": "Fluent Database" - }, - { - "location": "/fluent/database/#connection", - "text": "Coming soon.", - "title": "Connection" - }, - { - "location": "/fluent/database/#logger", - "text": "Coming soon.", - "title": "Logger" - }, - { - "location": "/databases/mongodb/getting-started/", - "text": "Using MongoDB\n\u00b6\n\n\nThe \nvapor/mongodb\n package is an extremely performant, reactive and pure swift MonogDB driver. It provides a simple interface to MongoDB for powerful features.\n\n\nOn top of \nvapor/mysql\n, we have built \nvapor/fluent-sqlite\n which allows SQLite databases to be used with Fluent.\n\n\nJust MongoDB\n\u00b6\n\n\nThis package works really well with \nand\n without Fluent. To include this package in your project, simply add it to your Package manifest.\n\n\n// swift-tools-version:4.0\n\n\nimport\n \nPackageDescription\n\n\n\nlet\n \npackage\n \n=\n \nPackage\n(\n\n \nname\n:\n \n\"Project\"\n,\n\n \ndependencies\n:\n \n[\n\n \n...\n\n \n.\npackage\n(\nurl\n:\n \n\"https://github.com/OpenKitten/MongoKitten.git\"\n,\n \n.\nrevision\n(\n\"master/5.0\"\n)),\n\n \n],\n\n \ntargets\n:\n \n[\n\n \n.\ntarget\n(\nname\n:\n \n\"Project\"\n,\n \ndependencies\n:\n \n[\n\"MongoKitten\"\n,\n \n...\n \n])\n\n \n]\n\n\n)\n\n\n\n\n\n\nIf this is your first time adding a dependency, you should read our introduction to \nPackage.swift\n.\n\n\nIf this is your first time using MongoDB, have a look at the \nsetup guides\n.\n\n\nThe official documentation assumed either the MongoDB shell or one of their more popular (official) drivers.\nPlease refer to \nthe MongoKitten interpretation guide\n before reading the \nofficial documentation\n.\n\n\nUse \nimport MongoKitten\n to access the APIs.\n\n\nWith Fluent\n\u00b6\n\n\nTo use MongoDB with Fluent, you just need to make sure to add the \nvapor/fluent-mongodb\n package to your project.\n\n\nTo do this, add the Fluent MySQL package to your Package manifest.\n\n\n// swift-tools-version:4.0\n\n\nimport\n \nPackageDescription\n\n\n\nlet\n \npackage\n \n=\n \nPackage\n(\n\n \nname\n:\n \n\"Project\"\n,\n\n \ndependencies\n:\n \n[\n\n \n...\n\n \n.\npackage\n(\nurl\n:\n \n\"https://github.com/vapor/fluent-mongodb.git\"\n,\n \n.\nrevision\n(\n\"beta\"\n)),\n\n \n],\n\n \ntargets\n:\n \n[\n\n \n.\ntarget\n(\nname\n:\n \n\"Project\"\n,\n \ndependencies\n:\n \n[\n\"FluentMongoDB\"\n,\n \n...\n \n])\n\n \n]\n\n\n)\n\n\n\n\n\n\nIf this is your first time adding a dependency, you should read our introduction to \nPackage.swift\n.\n\n\nUse \nimport FluentMongoDB\n to access MongoDB's Fluent compatible APIs. \nLearn more about the Fluent APIs here\n\n\n\n\n\n\n\n\nMore info on working with Fluent can be found \nhere\n.", - "title": "Getting Started" - }, - { - "location": "/databases/mongodb/getting-started/#using-mongodb", - "text": "The vapor/mongodb package is an extremely performant, reactive and pure swift MonogDB driver. It provides a simple interface to MongoDB for powerful features. On top of vapor/mysql , we have built vapor/fluent-sqlite which allows SQLite databases to be used with Fluent.", - "title": "Using MongoDB" - }, - { - "location": "/databases/mongodb/getting-started/#just-mongodb", - "text": "This package works really well with and without Fluent. To include this package in your project, simply add it to your Package manifest. // swift-tools-version:4.0 import PackageDescription let package = Package ( \n name : \"Project\" , \n dependencies : [ \n ... \n . package ( url : \"https://github.com/OpenKitten/MongoKitten.git\" , . revision ( \"master/5.0\" )), \n ], \n targets : [ \n . target ( name : \"Project\" , dependencies : [ \"MongoKitten\" , ... ]) \n ] ) If this is your first time adding a dependency, you should read our introduction to Package.swift . If this is your first time using MongoDB, have a look at the setup guides . The official documentation assumed either the MongoDB shell or one of their more popular (official) drivers.\nPlease refer to the MongoKitten interpretation guide before reading the official documentation . Use import MongoKitten to access the APIs.", - "title": "Just MongoDB" - }, - { - "location": "/databases/mongodb/getting-started/#with-fluent", - "text": "To use MongoDB with Fluent, you just need to make sure to add the vapor/fluent-mongodb package to your project. To do this, add the Fluent MySQL package to your Package manifest. // swift-tools-version:4.0 import PackageDescription let package = Package ( \n name : \"Project\" , \n dependencies : [ \n ... \n . package ( url : \"https://github.com/vapor/fluent-mongodb.git\" , . revision ( \"beta\" )), \n ], \n targets : [ \n . target ( name : \"Project\" , dependencies : [ \"FluentMongoDB\" , ... ]) \n ] ) If this is your first time adding a dependency, you should read our introduction to Package.swift . Use import FluentMongoDB to access MongoDB's Fluent compatible APIs. Learn more about the Fluent APIs here More info on working with Fluent can be found here .", - "title": "With Fluent" - }, - { - "location": "/databases/mongodb/bson/", - "text": "BSON\n\u00b6\n\n\nBSON is a performant (not compact) format for storing data in MongoDB.\nThe BSON module used here is extremely performant and support Codable.\n\n\nPrimitives\n\u00b6\n\n\nMongoDB has a set of supported primitives. At the root of any BSON data lies a \nDocument\n.\n\n\n\n\nDouble\n\n\nString\n\n\nDocument (Array and Dictionary)\n\n\nObjectId\n\n\nBool\n\n\nInt32\n\n\nInt (Int64)\n\n\nBinary\n\n\nDecimal128 \n(not supported)\n\n\nJavascriptCode\n\n\nNull (not nil)\n\n\nDate (from Foundation)\n\n\nMinKey\n\n\nMaxKey\n\n\nRegularExpression (BSON Type)\n\n\n\n\nDocument\n\u00b6\n\n\nDocument is a type that comes in two representations. Array and Dictionary-like.\nYou should see Document as \n[(String, Primitive)]\n.\n\n\nArray-like Documents ignore the key (\nString\n) whilst Dictionary-like Documents require using it.\nFor this reason, both \nDocument\n variants are the same struct type and behave the same way.\n\n\nYou can subscript a dictionary-like document with an integer, and an array-like document by it's key.\n\n\nUsage\n\u00b6\n\n\nThe root type of any BSON structure is a \nDocument\n, please note that MongoDB entities \nmust\n be a dictionary-like.\n\n\nTo create a dictionary-like BSON Document:\n\n\n// Dictionary document by default\n\n\nvar\n \ndocument\n \n=\n \nDocument\n()\n\n\n\n\n\n\nYou can also use a dictionary or array literal. This creates the respective BSON document type.\n\n\nvar\n \narrayDocument\n:\n \nDocument\n \n=\n \n[]\n\n\nvar\n \ndictionaryDocument\n:\n \nDocument\n \n=\n \n[:]\n\n\n\n\n\n\nAccessing Dictionary Documents\n\u00b6\n\n\nTo access a dictionary document you must subscript with a key:\n\n\nlet\n \nusername\n \n=\n \ndictionaryDocument\n[\n\"username\"\n]\n \n// Primitive?\n\n\n\n\n\n\nThe return type is a Primitive type, which is a protocol.\n\n\nAccessing Array Documents\n\u00b6\n\n\nTo a\n\n\nPrimitives\n\u00b6\n\n\nTo access the concrete type of the primitive you must either cast the primitive to a concrete type or loosely unwrap the type.\n\n\nFor the purpose of demonstration we're assuming the following Document:\n\n\nvar\n \ndoc\n:\n \nDocument\n \n=\n \n[\n\n \n\"_id\"\n:\n \nObjectId\n(),\n\n \n\"username\"\n:\n \n\"Joannis\"\n,\n\n \n\"admin\"\n:\n \ntrue\n,\n\n \n\"year\"\n:\n \n2018\n\n\n]\n\n\n\n\n\n\nCasting\n\u00b6\n\n\nCasting is used when you want exactly that type. A good example is a \nString\n.\n\n\nlet\n \nusername\n \n=\n \ndoc\n[\n\"username\"\n]\n \nas\n?\n \nString\n \n// String?\n\n\nprint\n(\nusername\n)\n \n// Optional(\"Joannis\")\n\n\n\n\n\n\nThe following will be nil because they're not a \nString\n.\n\n\nlet\n \n_id\n:\n \n=\n \ndoc\n[\n\"_id\"\n]\n \nas\n?\n \nString\n \n// String?\n\n\nprint\n(\n_id\n)\n \n// nil\n\n\n\nlet\n \nadmin\n \n=\n \ndoc\n[\n\"admin\"\n]\n \nas\n?\n \nString\n \n// String?\n\n\nprint\n(\nadmin\n)\n \n// nil\n\n\n\nlet\n \nyear\n \n=\n \ndoc\n[\n\"year\"\n]\n \nas\n?\n \nString\n \n// String?\n\n\nprint\n(\nyear\n)\n \n// nil\n\n\n\n\n\n\nLoosely Converting\n\u00b6\n\n\nConverting is useful when you don't care about the specifics.\nFor example, when exposing data over JSON.\n\n\nlet\n \nusername\n \n=\n \nString\n(\nlossy\n:\n \ndoc\n[\n\"username\"\n])\n \n// String?\n\n\nprint\n(\nusername\n)\n \n// Optional(\"Joannis\")\n\n\n\n\n\n\nThis converts types to a String when it's sensible:\n\n\nlet\n \n_id\n:\n \n=\n \ndoc\n[\n\"_id\"\n]\n \nas\n?\n \nString\n \n// String?\n\n\nprint\n(\n_id\n)\n \n// Optional(\"afafafafafafafafafafafaf\")\n\n\n\nlet\n \nadmin\n \n=\n \ndoc\n[\n\"admin\"\n]\n \nas\n?\n \nString\n \n// String?\n\n\nprint\n(\nadmin\n)\n \n// Optional(\"true\")\n\n\n\nlet\n \nyear\n \n=\n \ndoc\n[\n\"year\"\n]\n \nas\n?\n \nString\n \n// String?\n\n\nprint\n(\nyear\n)\n \n// Optional(\"2018\")\n\n\n\n\n\n\nCodable\n\u00b6\n\n\nBSON has highly optimized support for Codable through \nBSONEncoder\n and \nBSONDecoder\n.\n\n\nstruct\n \nUser\n:\n \nCodable\n \n{\n\n \nvar\n \n_id\n \n=\n \nObjectId\n()\n\n \nvar\n \nusername\n:\n \nString\n\n \nvar\n \nadmin\n:\n \nBool\n \n=\n \nfalse\n\n \nvar\n \nyear\n:\n \nInt\n\n\n \ninit\n(\nnamed\n \nname\n:\n \nString\n,\n \nyear\n:\n \nInt\n)\n \n{\n\n \nself\n.\nusername\n \n=\n \nname\n\n \nself\n.\nyear\n \n=\n \nyear\n\n \n}\n\n\n}\n\n\n\nlet\n \nuser\n \n=\n \nUser\n(\nnamed\n:\n \n\"Joannis\"\n,\n \nyear\n:\n \n2018\n)\n\n\n\nlet\n \nuserDocument\n \n=\n \ntry\n \nBSONEncoder\n().\nencode\n(\nuser\n)\n\n\n\nlet\n \nusername\n \n=\n \nuserDocument\n[\n\"username\"\n]\n \nas\n?\n \nString\n\n\nprint\n(\nusername\n)\n \n// Optional(\"Joannis\")\n\n\n\nlet\n \nsameUser\n \n=\n \ntry\n \nBSONDecoder\n().\ndecode\n(\nUser\n.\nself\n,\n \nfrom\n:\n \nuserDocument\n)\n\n\n\nprint\n(\nsameUser\n.\nusername\n)\n \n// \"Joannis\"", - "title": "BSON" - }, - { - "location": "/databases/mongodb/bson/#bson", - "text": "BSON is a performant (not compact) format for storing data in MongoDB.\nThe BSON module used here is extremely performant and support Codable.", - "title": "BSON" - }, - { - "location": "/databases/mongodb/bson/#primitives", - "text": "MongoDB has a set of supported primitives. At the root of any BSON data lies a Document . Double String Document (Array and Dictionary) ObjectId Bool Int32 Int (Int64) Binary Decimal128 (not supported) JavascriptCode Null (not nil) Date (from Foundation) MinKey MaxKey RegularExpression (BSON Type)", - "title": "Primitives" - }, - { - "location": "/databases/mongodb/bson/#document", - "text": "Document is a type that comes in two representations. Array and Dictionary-like.\nYou should see Document as [(String, Primitive)] . Array-like Documents ignore the key ( String ) whilst Dictionary-like Documents require using it.\nFor this reason, both Document variants are the same struct type and behave the same way. You can subscript a dictionary-like document with an integer, and an array-like document by it's key.", - "title": "Document" - }, - { - "location": "/databases/mongodb/bson/#usage", - "text": "The root type of any BSON structure is a Document , please note that MongoDB entities must be a dictionary-like. To create a dictionary-like BSON Document: // Dictionary document by default var document = Document () You can also use a dictionary or array literal. This creates the respective BSON document type. var arrayDocument : Document = [] var dictionaryDocument : Document = [:]", - "title": "Usage" - }, - { - "location": "/databases/mongodb/bson/#accessing-dictionary-documents", - "text": "To access a dictionary document you must subscript with a key: let username = dictionaryDocument [ \"username\" ] // Primitive? The return type is a Primitive type, which is a protocol.", - "title": "Accessing Dictionary Documents" - }, - { - "location": "/databases/mongodb/bson/#accessing-array-documents", - "text": "To a", - "title": "Accessing Array Documents" - }, - { - "location": "/databases/mongodb/bson/#primitives_1", - "text": "To access the concrete type of the primitive you must either cast the primitive to a concrete type or loosely unwrap the type. For the purpose of demonstration we're assuming the following Document: var doc : Document = [ \n \"_id\" : ObjectId (), \n \"username\" : \"Joannis\" , \n \"admin\" : true , \n \"year\" : 2018 ]", - "title": "Primitives" - }, - { - "location": "/databases/mongodb/bson/#casting", - "text": "Casting is used when you want exactly that type. A good example is a String . let username = doc [ \"username\" ] as ? String // String? print ( username ) // Optional(\"Joannis\") The following will be nil because they're not a String . let _id : = doc [ \"_id\" ] as ? String // String? print ( _id ) // nil let admin = doc [ \"admin\" ] as ? String // String? print ( admin ) // nil let year = doc [ \"year\" ] as ? String // String? print ( year ) // nil", - "title": "Casting" - }, - { - "location": "/databases/mongodb/bson/#loosely-converting", - "text": "Converting is useful when you don't care about the specifics.\nFor example, when exposing data over JSON. let username = String ( lossy : doc [ \"username\" ]) // String? print ( username ) // Optional(\"Joannis\") This converts types to a String when it's sensible: let _id : = doc [ \"_id\" ] as ? String // String? print ( _id ) // Optional(\"afafafafafafafafafafafaf\") let admin = doc [ \"admin\" ] as ? String // String? print ( admin ) // Optional(\"true\") let year = doc [ \"year\" ] as ? String // String? print ( year ) // Optional(\"2018\")", - "title": "Loosely Converting" - }, - { - "location": "/databases/mongodb/bson/#codable", - "text": "BSON has highly optimized support for Codable through BSONEncoder and BSONDecoder . struct User : Codable { \n var _id = ObjectId () \n var username : String \n var admin : Bool = false \n var year : Int \n\n init ( named name : String , year : Int ) { \n self . username = name \n self . year = year \n } } let user = User ( named : \"Joannis\" , year : 2018 ) let userDocument = try BSONEncoder (). encode ( user ) let username = userDocument [ \"username\" ] as ? String print ( username ) // Optional(\"Joannis\") let sameUser = try BSONDecoder (). decode ( User . self , from : userDocument ) print ( sameUser . username ) // \"Joannis\"", - "title": "Codable" - }, - { - "location": "/databases/mongodb/basics/", - "text": "MongoDB Basics\n\u00b6\n\n\nimport MongoKitten\n in your file(s) using MongoKitten. This will automatically also import the \nBSON\n APIs.\n\n\nConnecting to MongoDB\n\u00b6\n\n\nTo connect to MongoDB you need to provide your connection URI and the database you want to access.\nThe database will also need access to your \nworker\n.\n\n\nlet\n \ndatabase\n \n=\n \nDatabase\n.\nconnect\n(\nserver\n:\n \n\"mongodb://localhost:27017\"\n,\n \ndatabase\n:\n \n\"test-db\"\n,\n \nworker\n:\n \nworker\n)\n \n// Future\n\n\n\n\n\n\nYou cannot use the connection globally. If you plan to use this connection globally you'll need to use a lock on it for querying since the connection is not thread safe. This will be added in a later stage of MongoKitten 5's beta.\n\n\nConnection as a Service\n\u00b6\n\n\nIf you want to use the connection within a Vapor 3 route you'll need to register this connection to your \nServices\n.\n\n\nservices\n.\nregister\n \n{\n \ncontainer\n \n->\n \nClientSettings\n \nin\n\n \nreturn\n \n\"mongodb://localhost:27017\"\n \n// Your MongoDB URI here\n\n\n}\n\n\n\nservices\n.\nregister\n \n{\n \ncontainer\n \n->\n \nDatabase\n \nin\n\n \nlet\n \nserver\n \n=\n \ntry\n \ncontainer\n.\nmake\n(\nClientSettings\n.\nself\n,\n \nfor\n:\n \nDatabase\n.\nself\n)\n\n\n \n// Future\n\n \nlet\n \ndb\n \n=\n \ntry\n \nDatabase\n.\nconnect\n(\nsever\n:\n \nserver\n,\n \ndatabase\n:\n \n\"my-database\"\n,\n \nworker\n:\n \ncontainer\n)\n\n\n \n// Await here, it doesn't hurt performant much and it only needs to wait once per worker\n\n \nreturn\n \ntry\n \ndb\n.\nawait\n(\non\n:\n \ncontainer\n)\n\n\n}\n\n\n\n\n\n\nCRUD\n\u00b6\n\n\nBefore applying a CRUD operation you need to select a Collection first. This is the MongoDB equivalent of a table.\n\n\nYou can subscript a database with a string to get a collection with that name. You do not need to set up a schema first.\n\n\nrouter\n.\nget\n(\n\"users\"\n)\n \n{\n \nrequest\n \nin\n\n \nlet\n \ndatabase\n \n=\n \ntry\n \nrequest\n.\nmake\n(\nDatabase\n.\nself\n)\n\n \nlet\n \nusers\n \n=\n \ndatabase\n[\n\"users\"\n]\n \n// Collection\n\n\n \n...\n\n\n}\n\n\n\n\n\n\nSubscripting will give you a \nCollection\n. If you want to read the collection as a different (Codable) type you'll need to \nmap\n the collection to a different type.\n\n\nstruct\n \nUser\n:\n \nCodable\n \n{\n\n \nvar\n \n_id\n \n=\n \nObjectId\n()\n\n \nvar\n \nusername\n:\n \nString\n\n \nvar\n \nage\n:\n \nInt\n\n\n \ninit\n(\nnamed\n \nname\n:\n \nString\n,\n \nage\n:\n \nInt\n)\n \n{\n\n \nself\n.\nusername\n \n=\n \nnamed\n\n \nself\n.\nage\n \n=\n \nage\n\n \n}\n\n\n}\n\n\n\nlet\n \nusers\n \n=\n \ndatabase\n[\n\"users\"\n].\nmap\n(\nto\n:\n \nUser\n.\nself\n)\n\n\n\n\n\n\nCRUD operations\n\n\nInsert\n\u00b6\n\n\nInserting entities is done by using \n.insert\n with either one or a sequence of the entity.\n\n\nusers\n.\ninsert\n(\nuser\n)\n\n\nusers\n.\ninsertAll\n([\nuser1\n,\n \nuser2\n,\n \nuser3\n])\n\n\n\n\n\n\nInsert returns a \nFuture\n which you can use to determine if one (or more) inserts failed. Is \nreply.ok != 1\n the future will not be completed but failed with the reply, instead.\n\n\nlet\n \nreply\n \n=\n \nusers\n.\ninsertAll\n([\nuser1\n,\n \nuser2\n,\n \nuser3\n])\n\n\n\nreply\n.\ndo\n \n{\n \nsuccess\n \nin\n\n \nprint\n(\n\"\n\\(\nsuccess\n.\nn\n)\n users inserted\"\n)\n\n\n}.\ncatch\n \n{\n \nerror\n \nin\n\n \n// Insert failed!\n\n\n}\n\n\n\n\n\n\nFind\n\u00b6\n\n\nUsing \nfind\n on a collection returns a \nCursor\n where \nC\n is the type nested in the collection as demonstrated above.\n\n\nfind\n can take 4 arguments. A filter, range, sort and projection.\n\n\nThere is also \nfindOne\n, which is useful when you want to exactly one object. This does not support a range expression.\n\n\nFilter\n\u00b6\n\n\nThe filter is a MongoKitten \nQuery\n object.\n\n\n// User?\n\n\nguard\n \nlet\n \njoannis\n \n=\n \nusers\n.\nfindOne\n(\n\"username\"\n \n==\n \n\"Joannis\"\n \n&&\n \n\"active\"\n \n==\n \ntrue\n)\n \nelse\n \n{\n\n \nreturn\n\n\n}\n\n\n\n\n\n\nRange\n\u00b6\n\n\nA range is any native swift Range. It is used for \nskip\n and \nlimit\n.\n\n\n// Cursor with the first 10 users\n\n\n\nlet\n \nusers1\n \n=\n \nusers\n.\nfind\n(\nin\n:\n \n..<\n10\n)\n \n// Cursor\n\n\nlet\n \nusers2\n \n=\n \nusers\n.\nfind\n(\nin\n:\n \n10.\n.<\n20\n)\n \n// Cursor\n\n\nlet\n \nusers3\n \n=\n \nusers\n.\nfind\n(\nin\n:\n \n30.\n..)\n \n// Cursor\n\n\n\n\n\n\nSort\n\u00b6\n\n\nThe \nSort\n is used to sort entities in either ascending or descending order based on one or more fields.\n\n\nlet\n \nmaleUsers\n \n=\n \nusers\n.\nfind\n(\n\"gender\"\n \n==\n \n\"male\"\n,\n \nsortedBy\n:\n \n[\n\"age\"\n:\n \n.\nascending\n])\n \n// Cursor\n\n\n\n\n\n\nProjection\n\u00b6\n\n\nThe \nprojection\n ensures only the specifies subset of fields are returned. Projections can also be computed fields. Remember that the projection must fit within the Collection's Codable type. If this is not a dictionary(-like) type such as \nDocument\n it will be required that the Document fetched matches the Codable type.\n\n\nlet\n \nadults\n \n=\n \nusers\n.\nfind\n(\n\"age\"\n \n>=\n \n21\n,\n \nprojecting\n:\n \n[\n\"_id\"\n:\n \n.\nexcluded\n,\n \n\"username\"\n:\n \n.\nincluded\n])\n \n// Cursor\n\n\n\n\n\n\nCount\n\u00b6\n\n\nCount is similar to a find operation in that it is a read-only operation. Since it does not return the actual data (only an integer of the total entities found) it does not support projections and sorting. It does support a range expression, although it's not often used.\n\n\nlet\n \ntotalUsers\n \n=\n \nusers\n.\ncount\n()\n \n// Future\n\n\nlet\n \nfemaleUsers\n \n=\n \nusers\n.\ncount\n(\n\"gender\"\n \n==\n \n\"female\"\n)\n \n// Future\n\n\n\n\n\n\nUpdate\n\u00b6\n\n\nUpdating can be done on one or all entities. Updates can either update the entire entity or only a subset of fields.\n\n\nlet\n \nuser\n \n=\n \nUser\n(\nnamed\n:\n \n\"Joannis\"\n,\n \nage\n:\n \n111\n)\n\n\n\nusers\n.\nupdate\n(\n\"username\"\n \n==\n \n\"Joannis\"\n,\n \nto\n:\n \nuser\n)\n\n\n\n\n\n\nUpdate also indicates a success state in the same way \ninsert\n does.\n\n\nUpdating fields\n\u00b6\n\n\nUpdating a subset of fields can be done more efficiently using\n\n\n// Rename `Joannis` -> `JoannisO`\n\n\nusers\n.\nupdate\n(\n\"username\"\n \n==\n \n\"Joannis\"\n,\n \nfields\n:\n \n[\n\n \n\"username\"\n:\n \n\"JoannisO\"\n\n\n])\n\n\n\n// Migrate all users to require a password update\n\n\nusers\n.\nupdate\n(\nfields\n:\n \n[\n\n \n\"resetPassword\"\n:\n \ntrue\n\n\n])\n\n\n\n\n\n\nUpsert\n\u00b6\n\n\nIf you don't know if an entity exists but want it inserted/updated accordingly you should use \nupsert\n.\n\n\nlet\n \nuser\n \n=\n \nUser\n(\nnamed\n:\n \n\"Joannis\"\n,\n \nage\n:\n \n111\n)\n\n\n\nusers\n.\nupsert\n(\n\"_id\"\n \n==\n \nuser\n.\n_id\n,\n \nto\n:\n \nuser\n)\n\n\n\n\n\n\nRemove\n\u00b6\n\n\nRemove removes the first or all entities matching a query.\n\n\n// Remove me!\n\n\nusers\n.\nremove\n(\n\"username\"\n \n==\n \n\"Joannis\"\n)\n\n\n\n// Remove all Dutch users\n\n\nusers\n.\nremoveAll\n(\n\"country\"\n \n==\n \n\"NL\"\n)\n\n\n\n\n\n\nTroubleshooting\n\u00b6\n\n\nAmbiguous naming\n\u00b6\n\n\nIn some situations you may find that MongoKitten's \nDatabase\n or \nClientSettings\n are ambiguous with another library. The following lines will help get rid of that.\n\n\ntypealias\n \nMongoDB\n \n=\n \nMongoKitten\n.\nDatabase\n\n\ntypealias\n \nMongoDBSettings\n \n=\n \nMongoKitten\n.\nClientSettings\n\n\n\n\n\n\nIn the above case you'll have to use the aliases instead of the normal references to \nDatabase\n and/or \nClientSettings\n. Alternatively you can prefix the occurences of those instances with \nMongoKitten.\n, indicating you want the \nDatabase\n object of the MongoKitten module.", - "title": "Basics" - }, - { - "location": "/databases/mongodb/basics/#mongodb-basics", - "text": "import MongoKitten in your file(s) using MongoKitten. This will automatically also import the BSON APIs.", - "title": "MongoDB Basics" - }, - { - "location": "/databases/mongodb/basics/#connecting-to-mongodb", - "text": "To connect to MongoDB you need to provide your connection URI and the database you want to access.\nThe database will also need access to your worker . let database = Database . connect ( server : \"mongodb://localhost:27017\" , database : \"test-db\" , worker : worker ) // Future You cannot use the connection globally. If you plan to use this connection globally you'll need to use a lock on it for querying since the connection is not thread safe. This will be added in a later stage of MongoKitten 5's beta.", - "title": "Connecting to MongoDB" - }, - { - "location": "/databases/mongodb/basics/#connection-as-a-service", - "text": "If you want to use the connection within a Vapor 3 route you'll need to register this connection to your Services . services . register { container -> ClientSettings in \n return \"mongodb://localhost:27017\" // Your MongoDB URI here } services . register { container -> Database in \n let server = try container . make ( ClientSettings . self , for : Database . self ) \n\n // Future \n let db = try Database . connect ( sever : server , database : \"my-database\" , worker : container ) \n\n // Await here, it doesn't hurt performant much and it only needs to wait once per worker \n return try db . await ( on : container ) }", - "title": "Connection as a Service" - }, - { - "location": "/databases/mongodb/basics/#crud", - "text": "Before applying a CRUD operation you need to select a Collection first. This is the MongoDB equivalent of a table. You can subscript a database with a string to get a collection with that name. You do not need to set up a schema first. router . get ( \"users\" ) { request in \n let database = try request . make ( Database . self ) \n let users = database [ \"users\" ] // Collection \n\n ... } Subscripting will give you a Collection . If you want to read the collection as a different (Codable) type you'll need to map the collection to a different type. struct User : Codable { \n var _id = ObjectId () \n var username : String \n var age : Int \n\n init ( named name : String , age : Int ) { \n self . username = named \n self . age = age \n } } let users = database [ \"users\" ]. map ( to : User . self ) CRUD operations", - "title": "CRUD" - }, - { - "location": "/databases/mongodb/basics/#insert", - "text": "Inserting entities is done by using .insert with either one or a sequence of the entity. users . insert ( user ) users . insertAll ([ user1 , user2 , user3 ]) Insert returns a Future which you can use to determine if one (or more) inserts failed. Is reply.ok != 1 the future will not be completed but failed with the reply, instead. let reply = users . insertAll ([ user1 , user2 , user3 ]) reply . do { success in \n print ( \" \\( success . n ) users inserted\" ) }. catch { error in \n // Insert failed! }", - "title": "Insert" - }, - { - "location": "/databases/mongodb/basics/#find", - "text": "Using find on a collection returns a Cursor where C is the type nested in the collection as demonstrated above. find can take 4 arguments. A filter, range, sort and projection. There is also findOne , which is useful when you want to exactly one object. This does not support a range expression.", - "title": "Find" - }, - { - "location": "/databases/mongodb/basics/#filter", - "text": "The filter is a MongoKitten Query object. // User? guard let joannis = users . findOne ( \"username\" == \"Joannis\" && \"active\" == true ) else { \n return }", - "title": "Filter" - }, - { - "location": "/databases/mongodb/basics/#range", - "text": "A range is any native swift Range. It is used for skip and limit . // Cursor with the first 10 users let users1 = users . find ( in : ..< 10 ) // Cursor let users2 = users . find ( in : 10. .< 20 ) // Cursor let users3 = users . find ( in : 30. ..) // Cursor", - "title": "Range" - }, - { - "location": "/databases/mongodb/basics/#sort", - "text": "The Sort is used to sort entities in either ascending or descending order based on one or more fields. let maleUsers = users . find ( \"gender\" == \"male\" , sortedBy : [ \"age\" : . ascending ]) // Cursor", - "title": "Sort" - }, - { - "location": "/databases/mongodb/basics/#projection", - "text": "The projection ensures only the specifies subset of fields are returned. Projections can also be computed fields. Remember that the projection must fit within the Collection's Codable type. If this is not a dictionary(-like) type such as Document it will be required that the Document fetched matches the Codable type. let adults = users . find ( \"age\" >= 21 , projecting : [ \"_id\" : . excluded , \"username\" : . included ]) // Cursor", - "title": "Projection" - }, - { - "location": "/databases/mongodb/basics/#count", - "text": "Count is similar to a find operation in that it is a read-only operation. Since it does not return the actual data (only an integer of the total entities found) it does not support projections and sorting. It does support a range expression, although it's not often used. let totalUsers = users . count () // Future let femaleUsers = users . count ( \"gender\" == \"female\" ) // Future", - "title": "Count" - }, - { - "location": "/databases/mongodb/basics/#update", - "text": "Updating can be done on one or all entities. Updates can either update the entire entity or only a subset of fields. let user = User ( named : \"Joannis\" , age : 111 ) users . update ( \"username\" == \"Joannis\" , to : user ) Update also indicates a success state in the same way insert does.", - "title": "Update" - }, - { - "location": "/databases/mongodb/basics/#updating-fields", - "text": "Updating a subset of fields can be done more efficiently using // Rename `Joannis` -> `JoannisO` users . update ( \"username\" == \"Joannis\" , fields : [ \n \"username\" : \"JoannisO\" ]) // Migrate all users to require a password update users . update ( fields : [ \n \"resetPassword\" : true ])", - "title": "Updating fields" - }, - { - "location": "/databases/mongodb/basics/#upsert", - "text": "If you don't know if an entity exists but want it inserted/updated accordingly you should use upsert . let user = User ( named : \"Joannis\" , age : 111 ) users . upsert ( \"_id\" == user . _id , to : user )", - "title": "Upsert" - }, - { - "location": "/databases/mongodb/basics/#remove", - "text": "Remove removes the first or all entities matching a query. // Remove me! users . remove ( \"username\" == \"Joannis\" ) // Remove all Dutch users users . removeAll ( \"country\" == \"NL\" )", - "title": "Remove" - }, - { - "location": "/databases/mongodb/basics/#troubleshooting", - "text": "", - "title": "Troubleshooting" - }, - { - "location": "/databases/mongodb/basics/#ambiguous-naming", - "text": "In some situations you may find that MongoKitten's Database or ClientSettings are ambiguous with another library. The following lines will help get rid of that. typealias MongoDB = MongoKitten . Database typealias MongoDBSettings = MongoKitten . ClientSettings In the above case you'll have to use the aliases instead of the normal references to Database and/or ClientSettings . Alternatively you can prefix the occurences of those instances with MongoKitten. , indicating you want the Database object of the MongoKitten module.", - "title": "Ambiguous naming" - }, - { - "location": "/databases/mongodb/interpreting/", - "text": "Interpreting official MongoDB tutorials\n\u00b6\n\n\nhttps://docs.mongodb.com/manual/reference/ has a lot of tutorials on every detail of MongoDB.\nUsing them in MongoKitten can be a bit tricky. This guide will explain how they are best applied.\n\n\nDocuments/BSON Data\n\u00b6\n\n\nMongoDB writes Documents in a JSON-like syntax. The following Documents are taken from the documentation on the MongoDB website.\n\n\nvar\n \ndocument0\n \n=\n \n{\n\n \n\"_id\"\n \n:\n \nObjectId\n(\n\"512bc95fe835e68f199c8686\"\n),\n\n \n\"author\"\n \n:\n \n\"dave\"\n,\n\n \n\"score\"\n \n:\n \n80\n,\n\n \n\"views\"\n \n:\n \nNumberLong\n(\n100\n),\n\n \n\"awesome\"\n:\n \ntrue\n,\n\n \n\"users\"\n:\n \n[\n\n \n{\n \n_id\n:\n \n2\n,\n \nuser\n:\n \n\"ijk123\"\n,\n \nstatus\n:\n \n\"A\"\n \n},\n\n \n{\n \n_id\n:\n \n3\n,\n \nuser\n:\n \n\"xyz123\"\n,\n \nstatus\n:\n \n\"P\"\n \n},\n\n \n{\n \n_id\n:\n \n4\n,\n \nuser\n:\n \n\"mop123\"\n,\n \nstatus\n:\n \n\"P\"\n \n}\n\n \n]\n\n\n}\n\n\n\n\n\n\nThese Documents read to this equivalent in BSON.\n\n\nlet\n \ndocument0\n:\n \nDoucment\n \n=\n \n[\n\n \n\"_id\"\n:\n \ntry\n \nObjectId\n(\n\"512bc95fe835e68f199c8686\"\n)\n\n \n\"author\"\n:\n \n\"dave\"\n,\n\n \n\"score\"\n:\n \nInt32\n(\n80\n),\n\n \n\"views\"\n:\n \n100\n,\n\n \n\"aweosme\"\n:\n \ntrue\n,\n\n \n\"users\"\n:\n \n[\n\n \n[\n\"_id\"\n:\n \n2\n,\n \n\"user\"\n:\n \n\"ijk123\"\n,\n \n\"status\"\n:\n \n\"A\"\n],\n\n \n[\n\"_id\"\n:\n \n3\n,\n \n\"user\"\n:\n \n\"xyz123\"\n,\n \n\"status\"\n:\n \n\"P\"\n],\n\n \n[\n\"_id\"\n:\n \n4\n,\n \n\"user\"\n:\n \n\"mop123\"\n,\n \n\"status\"\n:\n \n\"P\"\n]\n\n \n]\n\n\n]\n\n\n\n\n\n\nAs you see, ObjectId works similarly but initializing an creating ObjectId from a String can throw an error.\nMore information about BSON, the MongoDB data type \non this page\n.\n\n\nIntegers in MongoDB are Int32 by default, MongoKitten uses Int64 by default.\nMongoDB and MongoKitten can often use the 2 interchangeably with the exception of large numbers that do not fit in an Int32 (and are requested as Int32).\n\n\nIn Swift your dictionary literals don\u2019t start with \n{\n and don\u2019t end with \n}\n but instead also use \n[\n and \n]\n for dictionaries.\n\n\nDictionaries in Swift require a \nString\n to have quotes (\n\"\n) around them. In JavaScript syntax this is optional.\n\n\nSelecting databases/collections\n\u00b6\n\n\nIn the shell \nuse mydatabase\n selects the database named \"mydatabase\". From here you can use \ndb.mycollection.find()\n to fetch all data in the collection.\n\n\nIn MongoKitten, you can subscript the server and database.\nSubscripting the server selects a database, and subscripting the database selects a collection.\nUsually you'll be working with a single database, so the following code connects to a single database named \"example-db\".\n\n\n// Connect to the localhost database `example-db` without credentials\n\n\nlet\n \ndatabase\n \n=\n \ntry\n \nDatabase\n.\nconnect\n(\nserver\n:\n \n\"mongodb://localhost:27017\"\n,\n \ndatabase\n:\n \n\"mongokitten-example-db\"\n,\n \nworker\n:\n \neventLoop\n).\nawait\n(\non\n:\n \neventLoop\n)\n\n\n\n// Select a collection\n\n\nlet\n \nmycollection\n \n=\n \nmydatabase\n[\n\"mycollection\"\n]\n\n\n\n// Query all entities\n\n\ntry\n \nmycollection\n.\nfind\n()\n\n\n\n\n\n\nCommon operations in collections\n\u00b6\n\n\nMost operations are available on a collection object.\nBy typing \nmycollection.\n\u00a0, Swift will autocomplete a list of available methods.\n\n\nFor inserting you can use \ninsert\n which have various parameters which you can use.\nMost parameters have a default and can thus be left out of not needed.\n\n\nmyCollection\n.\ninsert\n([\n\n \n\"_id\"\n:\n \nObjectId\n(),\n\n \n\"some\"\n:\n \n\"string\"\n,\n\n \n\"date\"\n:\n \nDate\n()\n\n\n])\n\n\n\n\n\n\nAggregates\n\u00b6\n\n\nIf you followed this article you should know the basics of MongoKitten aggregates.\nIf you, at any point, need a specific stage, you can look for the stage by the MongoDB name in the documentation or make use of autocomplete.\n\n\nAll supported aggregates can be quickly accessed using by simply typing a dot (\n.\n) inside the pipeline array literal like you would for enum cases.\nThis will autocomplete to all available static functions that can help you create a stage.\n\n\nIf you miss a stage or want to create a stage manually you can do so by providing a BSON \nDocument\n like this:\n\n\nlet\n \nstage\n \n=\n \nStage\n([\n\n \n\"$match\"\n:\n \n[\n\n \n\"username\"\n:\n \n[\n\n \n\"$eq\"\n:\n \n\"Joannis\"\n\n \n]\n\n \n]\n\n\n])\n\n\n\n\n\n\nThe stage must be equal to the Document representation of the stage operators described here.\nYou can then add this stage as one of the values in the array.\n\n\nDocument Queries\n\u00b6\n\n\nMongoDB queries can work if converted to a Document.\nIf you do this, you must either use a literal query like below or write it using the query builder described here.\n\n\nlet\n \nquery\n \n=\n \nQuery\n(\ndocument\n)\n\n\nlet\n \nliteralQuery\n:\n \nQuery\n \n=\n \n[\n\n \n\"age\"\n:\n \n[\n\n \n\"$gte\"\n:\n \n15\n\n \n]\n\n\n]", - "title": "Interpreting tutorials" - }, - { - "location": "/databases/mongodb/interpreting/#interpreting-official-mongodb-tutorials", - "text": "https://docs.mongodb.com/manual/reference/ has a lot of tutorials on every detail of MongoDB.\nUsing them in MongoKitten can be a bit tricky. This guide will explain how they are best applied.", - "title": "Interpreting official MongoDB tutorials" - }, - { - "location": "/databases/mongodb/interpreting/#documentsbson-data", - "text": "MongoDB writes Documents in a JSON-like syntax. The following Documents are taken from the documentation on the MongoDB website. var document0 = { \n \"_id\" : ObjectId ( \"512bc95fe835e68f199c8686\" ), \n \"author\" : \"dave\" , \n \"score\" : 80 , \n \"views\" : NumberLong ( 100 ), \n \"awesome\" : true , \n \"users\" : [ \n { _id : 2 , user : \"ijk123\" , status : \"A\" }, \n { _id : 3 , user : \"xyz123\" , status : \"P\" }, \n { _id : 4 , user : \"mop123\" , status : \"P\" } \n ] } These Documents read to this equivalent in BSON. let document0 : Doucment = [ \n \"_id\" : try ObjectId ( \"512bc95fe835e68f199c8686\" ) \n \"author\" : \"dave\" , \n \"score\" : Int32 ( 80 ), \n \"views\" : 100 , \n \"aweosme\" : true , \n \"users\" : [ \n [ \"_id\" : 2 , \"user\" : \"ijk123\" , \"status\" : \"A\" ], \n [ \"_id\" : 3 , \"user\" : \"xyz123\" , \"status\" : \"P\" ], \n [ \"_id\" : 4 , \"user\" : \"mop123\" , \"status\" : \"P\" ] \n ] ] As you see, ObjectId works similarly but initializing an creating ObjectId from a String can throw an error.\nMore information about BSON, the MongoDB data type on this page . Integers in MongoDB are Int32 by default, MongoKitten uses Int64 by default.\nMongoDB and MongoKitten can often use the 2 interchangeably with the exception of large numbers that do not fit in an Int32 (and are requested as Int32). In Swift your dictionary literals don\u2019t start with { and don\u2019t end with } but instead also use [ and ] for dictionaries. Dictionaries in Swift require a String to have quotes ( \" ) around them. In JavaScript syntax this is optional.", - "title": "Documents/BSON Data" - }, - { - "location": "/databases/mongodb/interpreting/#selecting-databasescollections", - "text": "In the shell use mydatabase selects the database named \"mydatabase\". From here you can use db.mycollection.find() to fetch all data in the collection. In MongoKitten, you can subscript the server and database.\nSubscripting the server selects a database, and subscripting the database selects a collection.\nUsually you'll be working with a single database, so the following code connects to a single database named \"example-db\". // Connect to the localhost database `example-db` without credentials let database = try Database . connect ( server : \"mongodb://localhost:27017\" , database : \"mongokitten-example-db\" , worker : eventLoop ). await ( on : eventLoop ) // Select a collection let mycollection = mydatabase [ \"mycollection\" ] // Query all entities try mycollection . find ()", - "title": "Selecting databases/collections" - }, - { - "location": "/databases/mongodb/interpreting/#common-operations-in-collections", - "text": "Most operations are available on a collection object.\nBy typing mycollection. \u00a0, Swift will autocomplete a list of available methods. For inserting you can use insert which have various parameters which you can use.\nMost parameters have a default and can thus be left out of not needed. myCollection . insert ([ \n \"_id\" : ObjectId (), \n \"some\" : \"string\" , \n \"date\" : Date () ])", - "title": "Common operations in collections" - }, - { - "location": "/databases/mongodb/interpreting/#aggregates", - "text": "If you followed this article you should know the basics of MongoKitten aggregates.\nIf you, at any point, need a specific stage, you can look for the stage by the MongoDB name in the documentation or make use of autocomplete. All supported aggregates can be quickly accessed using by simply typing a dot ( . ) inside the pipeline array literal like you would for enum cases.\nThis will autocomplete to all available static functions that can help you create a stage. If you miss a stage or want to create a stage manually you can do so by providing a BSON Document like this: let stage = Stage ([ \n \"$match\" : [ \n \"username\" : [ \n \"$eq\" : \"Joannis\" \n ] \n ] ]) The stage must be equal to the Document representation of the stage operators described here.\nYou can then add this stage as one of the values in the array.", - "title": "Aggregates" - }, - { - "location": "/databases/mongodb/interpreting/#document-queries", - "text": "MongoDB queries can work if converted to a Document.\nIf you do this, you must either use a literal query like below or write it using the query builder described here. let query = Query ( document ) let literalQuery : Query = [ \n \"age\" : [ \n \"$gte\" : 15 \n ] ]", - "title": "Document Queries" - }, - { - "location": "/databases/mysql/getting-started/", - "text": "Using MySQL\n\u00b6\n\n\nThe \nvapor/mysql\n package is a lightweight, reactive, async and pure swift MySQL/MariaDB driver. It provides an intuitive interface for working with MySQL that can be used with any Swift project.\n\n\nOn top of \nvapor/mysql\n, we have built \nvapor/fluent-sqlite\n which allows SQLite databases to be used with Fluent.\n\n\nJust MySQL\n\u00b6\n\n\nThis 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.\n\n\n// swift-tools-version:4.0\n\n\nimport\n \nPackageDescription\n\n\n\nlet\n \npackage\n \n=\n \nPackage\n(\n\n \nname\n:\n \n\"Project\"\n,\n\n \ndependencies\n:\n \n[\n\n \n...\n\n \n.\npackage\n(\nurl\n:\n \n\"https://github.com/vapor/mysql.git\"\n,\n \n.\nrevision\n(\n\"beta\"\n)),\n\n \n],\n\n \ntargets\n:\n \n[\n\n \n.\ntarget\n(\nname\n:\n \n\"Project\"\n,\n \ndependencies\n:\n \n[\n\"MySQL\"\n,\n \n...\n \n])\n\n \n]\n\n\n)\n\n\n\n\n\n\nIf this is your first time adding a dependency, you should read our introduction to \nPackage.swift\n.\n\n\nUse \nimport MySQL\n to access the Swift MySQL APIs.\n\n\nWith Fluent\n\u00b6\n\n\nMySQL works well Fluent, you just need to make sure to add the \nvapor/fluent-mysql\n package to your project.\n\n\nTo do this, add the Fluent MySQL package to your Package manifest.\n\n\n// swift-tools-version:4.0\n\n\nimport\n \nPackageDescription\n\n\n\nlet\n \npackage\n \n=\n \nPackage\n(\n\n \nname\n:\n \n\"Project\"\n,\n\n \ndependencies\n:\n \n[\n\n \n...\n\n \n.\npackage\n(\nurl\n:\n \n\"https://github.com/vapor/fluent-mysql.git\"\n,\n \n.\nrevision\n(\n\"beta\"\n)),\n\n \n],\n\n \ntargets\n:\n \n[\n\n \n.\ntarget\n(\nname\n:\n \n\"Project\"\n,\n \ndependencies\n:\n \n[\n\"FluentMySQL\"\n,\n \n...\n \n])\n\n \n]\n\n\n)\n\n\n\n\n\n\nIf this is your first time adding a dependency, you should read our introduction to \nPackage.swift\n.\n\n\nUse \nimport FluentMySQL\n to access MySQL's Fluent compatible APIs. \nLearn more about the Fluent APIs here\n\n\nSetting up services\n\u00b6\n\n\nIn order to set up MySQL with Fluent you first need to register the Fluent provider:\n\n\ntry\n \nservices\n.\nprovider\n(\nFluentProvider\n())\n\n\n\n\n\n\nAfter this, you need to register the MySQL config and database.\n\n\nservices\n.\ninstance\n(\nFluentMySQLConfig\n())\n\n\n\nvar\n \ndatabaseConfig\n \n=\n \nDatabaseConfig\n()\n\n\n\nlet\n \nusername\n \n=\n \n\"\"\n\n\nlet\n \npassword\n \n=\n \n\"\"\n\n\nlet\n \ndatabase\n \n=\n \n\"\"\n\n\n\nlet\n \ndb\n \n=\n \nMySQLDatabase\n(\nhostname\n:\n \n\"localhost\"\n,\n \nuser\n:\n \nusername\n,\n \npassword\n:\n \npassword\n,\n \ndatabase\n:\n \ndatabase\n)\n\n\ndatabaseConfig\n.\nadd\n(\ndatabase\n:\n \ndb\n,\n \nas\n:\n \n.\nmysql\n)\n\n\nservices\n.\ninstance\n(\ndatabaseConfig\n)\n\n\n\n\n\n\nNotice the variable \nmysqlDB\n. This is an identifier for the MySQL database.\nYou need to create an identifier for each database you attach to Fluent.\n\n\nThe following identifer can be global, or as a static variable in an extension on \nDatabaseIdentifier\n.\n\n\nextension\n \nDatabaseIdentifier\n \n{\n\n \nstatic\n \nvar\n \nmysql\n:\n \nDatabaseIdentifier\n<\nMySQLDatabase\n>\n \n{\n\n \nreturn\n \n.\ninit\n(\n\"foo\"\n)\n\n \n}\n\n\n}\n\n\n\n\n\n\nLast, you can specify migrations for Fluent to execute. This can be used for creating and altering schemas and migrating data within the table.\n\n\nFluent \nModel\ns that conform to the protocol \nMigration\n automatically inherit a migration for schema creation. Nothing needs to be programmed for this.\n\n\nvar\n \nmigrationConfig\n \n=\n \nMigrationConfig\n()\n\n\nmigrationConfig\n.\nadd\n(\nmodel\n:\n \nMyModel\n.\nself\n,\n \ndatabase\n:\n \n.\nmysql\n)\n\n\nservices\n.\ninstance\n(\nmigrationConfig\n)\n\n\n\n\n\n\nMore info on working with Fluent can be found \nhere\n.", - "title": "Getting Started" - }, - { - "location": "/databases/mysql/getting-started/#using-mysql", - "text": "The vapor/mysql package is a lightweight, reactive, async and pure swift MySQL/MariaDB driver. It provides an intuitive interface for working with MySQL that can be used with any Swift project. On top of vapor/mysql , we have built vapor/fluent-sqlite which allows SQLite databases to be used with Fluent.", - "title": "Using MySQL" - }, - { - "location": "/databases/mysql/getting-started/#just-mysql", - "text": "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-tools-version:4.0 import PackageDescription let package = Package ( \n name : \"Project\" , \n dependencies : [ \n ... \n . package ( url : \"https://github.com/vapor/mysql.git\" , . revision ( \"beta\" )), \n ], \n targets : [ \n . target ( name : \"Project\" , dependencies : [ \"MySQL\" , ... ]) \n ] ) If this is your first time adding a dependency, you should read our introduction to Package.swift . Use import MySQL to access the Swift MySQL APIs.", - "title": "Just MySQL" - }, - { - "location": "/databases/mysql/getting-started/#with-fluent", - "text": "MySQL works well Fluent, you just need to make sure to add the vapor/fluent-mysql package to your project. To do this, add the Fluent MySQL package to your Package manifest. // swift-tools-version:4.0 import PackageDescription let package = Package ( \n name : \"Project\" , \n dependencies : [ \n ... \n . package ( url : \"https://github.com/vapor/fluent-mysql.git\" , . revision ( \"beta\" )), \n ], \n targets : [ \n . target ( name : \"Project\" , dependencies : [ \"FluentMySQL\" , ... ]) \n ] ) If this is your first time adding a dependency, you should read our introduction to Package.swift . Use import FluentMySQL to access MySQL's Fluent compatible APIs. Learn more about the Fluent APIs here", - "title": "With Fluent" - }, - { - "location": "/databases/mysql/getting-started/#setting-up-services", - "text": "In order to set up MySQL with Fluent you first need to register the Fluent provider: try services . provider ( FluentProvider ()) After this, you need to register the MySQL config and database. services . instance ( FluentMySQLConfig ()) var databaseConfig = DatabaseConfig () let username = \"\" let password = \"\" let database = \"\" let db = MySQLDatabase ( hostname : \"localhost\" , user : username , password : password , database : database ) databaseConfig . add ( database : db , as : . mysql ) services . instance ( databaseConfig ) Notice the variable mysqlDB . This is an identifier for the MySQL database.\nYou need to create an identifier for each database you attach to Fluent. The following identifer can be global, or as a static variable in an extension on DatabaseIdentifier . extension DatabaseIdentifier { \n static var mysql : DatabaseIdentifier < MySQLDatabase > { \n return . init ( \"foo\" ) \n } } Last, you can specify migrations for Fluent to execute. This can be used for creating and altering schemas and migrating data within the table. Fluent Model s that conform to the protocol Migration automatically inherit a migration for schema creation. Nothing needs to be programmed for this. var migrationConfig = MigrationConfig () migrationConfig . add ( model : MyModel . self , database : . mysql ) services . instance ( migrationConfig ) More info on working with Fluent can be found here .", - "title": "Setting up services" - }, - { - "location": "/databases/mysql/basics/", - "text": "MySQL Basics\n\u00b6\n\n\nThis guide assumes you've set up MySQL and are connected to MySQL using a connection pool as described in \nthe getting started guide\n.\n\n\nType safety\n\u00b6\n\n\nThe MySQL driver is written to embrace type-safety and Codable. We currently \nonly\n expose Codable based results.\n\n\nQueries\n\u00b6\n\n\nQueries are any type conforming to the protocol \nQuery\n, which requires being convertible to a \nString\n.\n\nString\n is a Query by default.\n\n\nYou can receive results from Queries in 4 kinds of formats.\n\n\n\n\nStream\n\n\nFuture\n\n\nforEach\n\n\nFuture\n\n\n\n\nAll examples assume the following model:\n\n\nstruct\n \nUser\n:\n \nCodable\n \n{\n\n \nvar\n \nusername\n:\n \nString\n\n \nvar\n \npasswordHash\n:\n \nString\n\n \nvar\n \nage\n:\n \nInt\n\n\n}\n\n\n\n\n\n\nFutures\n\u00b6\n\n\nFutures are often easier to use but significantly heavier on your memory and thus performance. \nThey are thoroughly described here\n\n\nQuerying a database for a future is achieved through the \nall\n function and requires specifying the \nDecodable\n type that the results need to be deserialized into.\n\n\n \n// Future<[User]>\n\n\nlet\n \nusers\n \n=\n \nconnection\n.\nall\n(\nUser\n.\nself\n,\n \nin\n:\n \n\"SELECT * FROM users\"\n)\n\n\n\n\n\n\nFor partial results (\nSELECT username, age FROM\n) it is recommended to create a second decodable struct specifically for this query to ensure correctness and type-safety.\n\n\nstruct\n \nUserLoginDetails\n:\n \nCodable\n \n{\n\n \nvar\n \nusername\n:\n \nString\n\n \nvar\n \nage\n:\n \nInt\n\n\n}\n\n\n\n\n\n\nStreams\n\u00b6\n\n\nStreams, \nas described on this page\n, 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 \nthis is described here.\n\n\nQuerying a database for a stream of results is achieved through the \nstream\n function and requires specifying the \nDecodable\n type that the results need to be deserialized into.\n\n\ntry\n \nconnection\n.\nstream\n(\nUser\n.\nself\n,\n \nin\n:\n \n\"SELECT * FROM users\"\n,\n \nto\n:\n \ninputStream\n)\n\n\n\n\n\n\nThis will put all Users into the \ninputStream\n. This requires \ninputStream\n to be an \nInputStream\n accepting \nUser\n as Input.\n\n\nForEach\n\u00b6\n\n\nIf you don't need to stream complex results to a third party such as using an HTTP Response you can use \nforEach\n. 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.\n\n\nconnection\n.\nforEach\n(\nUser\n.\nself\n,\n \nin\n:\n \n\"SELECT * FROM users\"\n)\n \n{\n \nuser\n \nin\n\n \nprint\n(\nuser\n.\nusername\n)\n\n\n}\n\n\n\n\n\n\nforEach\n returns a future that you can optionally capture. It will be completed when all users have been processed.\n\n\nlet\n \ncompleted\n \n=\n \nconnection\n.\nforEach\n(\nUser\n.\nself\n,\n \nin\n:\n \n\"SELECT * FROM users\"\n)\n \n{\n \nuser\n \nin\n\n \nprint\n(\nuser\n.\nusername\n)\n\n\n}\n\n\n\ncompleted\n.\ndo\n \n{\n\n \nprint\n(\n\"All users printed\"\n)\n\n\n}.\ncatch\n \n{\n \nerror\n \nin\n\n \nprint\n(\n\"An error occurred while printing the users: \n\\(\nerror\n)\n\"\n)\n\n\n}\n\n\n\n\n\n\nSingle column rows\n\u00b6\n\n\nWhen the query returns only one column, you can decode resulting rows as a single value.\n\n\nlet\n \nusernames\n \n=\n \nconnection\n.\nall\n(\nString\n.\nself\n,\n \nin\n:\n \n\"SELECT username FROM users\"\n)\n \n// Future<[String]>\n\n\n\n\n\n\nResultless queries\n\u00b6\n\n\nSome queries (mostly administrative queries) do not require/return a response. Instead, they only indicate success or error.\n\n\nYou can execute these queries using the \nadministrativeQuery\n command.\n\n\nconnection\n.\nadministrativeQuery\n(\n\"DROP TABLE users\"\n)\n\n\n\n\n\n\nYou can handle success or response using the returned future.\n\n\nconnection\n.\nadministrativeQuery\n(\n\"DROP TABLE users\"\n).\nthen\n \n{\n\n \nprint\n(\n\"success\"\n)\n\n\n}.\ncatch\n \n{\n\n \nprint\n(\n\"failure\"\n)\n\n\n}", - "title": "Basics" - }, - { - "location": "/databases/mysql/basics/#mysql-basics", - "text": "This guide assumes you've set up MySQL and are connected to MySQL using a connection pool as described in the getting started guide .", - "title": "MySQL Basics" - }, - { - "location": "/databases/mysql/basics/#type-safety", - "text": "The MySQL driver is written to embrace type-safety and Codable. We currently only expose Codable based results.", - "title": "Type safety" - }, - { - "location": "/databases/mysql/basics/#queries", - "text": "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 4 kinds of formats. Stream Future forEach Future All examples assume the following model: struct User : Codable { \n var username : String \n var passwordHash : String \n var age : Int }", - "title": "Queries" - }, - { - "location": "/databases/mysql/basics/#futures", - "text": "Futures are often easier to use but significantly heavier on your memory and thus performance. They are thoroughly described here 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. // 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. struct UserLoginDetails : Codable { \n var username : String \n var age : Int }", - "title": "Futures" - }, - { - "location": "/databases/mysql/basics/#streams", - "text": "Streams, as described on this page , 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. 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. try connection . stream ( User . self , in : \"SELECT * FROM users\" , to : inputStream ) This will put all Users into the inputStream . This requires inputStream to be an InputStream accepting User as Input.", - "title": "Streams" - }, - { - "location": "/databases/mysql/basics/#foreach", - "text": "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. connection . forEach ( User . self , in : \"SELECT * FROM users\" ) { user in \n print ( user . username ) } forEach returns a future that you can optionally capture. It will be completed when all users have been processed. let completed = connection . forEach ( User . self , in : \"SELECT * FROM users\" ) { user in \n print ( user . username ) } completed . do { \n print ( \"All users printed\" ) }. catch { error in \n print ( \"An error occurred while printing the users: \\( error ) \" ) }", - "title": "ForEach" - }, - { - "location": "/databases/mysql/basics/#single-column-rows", - "text": "When the query returns only one column, you can decode resulting rows as a single value. let usernames = connection . all ( String . self , in : \"SELECT username FROM users\" ) // Future<[String]>", - "title": "Single column rows" - }, - { - "location": "/databases/mysql/basics/#resultless-queries", - "text": "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 administrativeQuery command. connection . administrativeQuery ( \"DROP TABLE users\" ) You can handle success or response using the returned future. connection . administrativeQuery ( \"DROP TABLE users\" ). then { \n print ( \"success\" ) }. catch { \n print ( \"failure\" ) }", - "title": "Resultless queries" - }, - { - "location": "/databases/mysql/prepared-statements/", - "text": "Prepared statements\n\u00b6\n\n\nPreparing statements is important in many SQL operations to prevent SQL injection.\n\n\nYou first have to set up your query to make use of statement binding.\n\n\nTo design your query for preparation you must replace all user inputted values with a \n?\n such as the following statement:\n\n\nSELECT\n \n*\n \nFROM\n \nusers\n \nWHERE\n \nusername\n \n=\n \n?\n\n\n\n\n\n\nPreparing a statement\n\u00b6\n\n\nTo prepare a statement from a query you call the \nwithPreparation\n function on \nConnection\n.\n\n\ntry\n \nconnection\n.\nwithPreparation\n(\nstatement\n:\n \n\"SELECT * FROM users WHERE username = ?\"\n)\n \n{\n \nstatement\n \nin\n\n \n// Bind\n\n\n}\n\n\n\n\n\n\nBinding to a statement\n\u00b6\n\n\nThe statement can be bound by calling the \nbind\n function on \nstatement\n. This will provide you with a temporary binding context.\n\n\nYou must bind the same or a similar type to the one related to the field being bound to. Bindings must be added in the same order they appear in the query.\n\n\ntry\n \nstatement\n.\nbind\n \n{\n \nbinding\n \nin\n\n \ntry\n \nbinding\n.\nbind\n(\n\"ExampleUser\"\n)\n\n\n}\n\n\n\n\n\n\nBindings will throw an error if the inputted value did not meet the query's required type.\n\n\nBinding encodable data\n\u00b6\n\n\nWhen inserting entities, often you need to encode all of the values to the query, preferably with type safety.\n\n\ntry\n \nbinding\n.\nwithEncoder\n \n{\n \nencoder\n \nin\n\n \ntry\n \nmodel\n.\nencode\n(\nto\n:\n \nencoder\n)\n\n\n}\n\n\n\n\n\n\nYou can use this \nwithEncoder\n closure to bind multiple structures, too.\n\n\nReading the query's results\n\u00b6\n\n\nYou can then use the \nFuture or Streaming query functions as described in the basics\n to receive the queried results from the prepared and bound \nstatement\n object.", - "title": "Prepared Statements" - }, - { - "location": "/databases/mysql/prepared-statements/#prepared-statements", - "text": "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: SELECT * FROM users WHERE username = ?", - "title": "Prepared statements" - }, - { - "location": "/databases/mysql/prepared-statements/#preparing-a-statement", - "text": "To prepare a statement from a query you call the withPreparation function on Connection . try connection . withPreparation ( statement : \"SELECT * FROM users WHERE username = ?\" ) { statement in \n // Bind }", - "title": "Preparing a statement" - }, - { - "location": "/databases/mysql/prepared-statements/#binding-to-a-statement", - "text": "The statement can be bound by calling the bind function on statement . This will provide you with a temporary binding context. You must bind the same or a similar type to the one related to the field being bound to. Bindings must be added in the same order they appear in the query. try statement . bind { binding in \n try binding . bind ( \"ExampleUser\" ) } Bindings will throw an error if the inputted value did not meet the query's required type.", - "title": "Binding to a statement" - }, - { - "location": "/databases/mysql/prepared-statements/#binding-encodable-data", - "text": "When inserting entities, often you need to encode all of the values to the query, preferably with type safety. try binding . withEncoder { encoder in \n try model . encode ( to : encoder ) } You can use this withEncoder closure to bind multiple structures, too.", - "title": "Binding encodable data" - }, - { - "location": "/databases/mysql/prepared-statements/#reading-the-querys-results", - "text": "You can then use the Future or Streaming query functions as described in the basics to receive the queried results from the prepared and bound statement object.", - "title": "Reading the query's results" - }, - { - "location": "/leaf/getting-started/", - "text": "Leaf\n\u00b6\n\n\nLeaf is a templating language that integrates with Futures, Reactive Streams and Codable. This section outlines how to import the Leaf package into a Vapor project.\n\n\nExample Folder Structure\n\u00b6\n\n\nHello\n\u251c\u2500\u2500 Package.resolved\n\u251c\u2500\u2500 Package.swift\n\u251c\u2500\u2500 Public\n\u251c\u2500\u2500 Resources\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 Views\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 hello.leaf\n\u251c\u2500\u2500 Public\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 images (images resources)\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 styles (css resources)\n\u251c\u2500\u2500 Sources\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 App\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 boot.swift\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 configure.swift\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 routes.swift\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 Run\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 main.swift\n\u251c\u2500\u2500 Tests\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 AppTests\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 AppTests.swift\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 LinuxMain.swift\n\u2514\u2500\u2500 LICENSE\n\n\n\n\n\nAdding Leaf to your project\n\u00b6\n\n\nThe easiest way to use Leaf with Vapor is to include the Leaf repository as a dependency in Package.swift:\n\n\n// swift-tools-version:4.0\n\n\nimport\n \nPackageDescription\n\n\n\nlet\n \npackage\n \n=\n \nPackage\n(\n\n \nname\n:\n \n\"project1\"\n,\n\n \ndependencies\n:\n \n[\n\n \n// \ud83d\udca7 A server-side Swift web framework.\n\n \n.\npackage\n(\nurl\n:\n \n\"https://github.com/vapor/vapor.git\"\n,\n \n.\nbranch\n(\n\"beta\"\n)),\n\n \n.\npackage\n(\nurl\n:\n \n\"https://github.com/vapor/leaf.git\"\n,\n \n.\nbranch\n(\n\"beta\"\n)),\n\n \n],\n\n \ntargets\n:\n \n[\n\n \n.\ntarget\n(\n\n \nname\n:\n \n\"App\"\n,\n\n \ndependencies\n:\n \n[\n\"Vapor\"\n,\n \n\"Leaf\"\n]\n\n \n),\n\n \n.\ntarget\n(\nname\n:\n \n\"Run\"\n,\n \ndependencies\n:\n \n[\n\"App\"\n]),\n\n \n.\ntestTarget\n(\nname\n:\n \n\"AppTests\"\n,\n \ndependencies\n:\n \n[\n\"App\"\n]),\n\n \n]\n\n\n)\n\n\n\n\n\n\nThe Leaf package adds Leaf to your project, but to configure it for use you must modify configure.swift:\n\n\n\n\nAdd \nimport Leaf\n to the top of the file so that Leaf is available to use. You will also need to add this to any file that will render templates.\n\n\nAdd \ntry services.provider(LeafProvider())\n to the \nconfigure()\n function so that routes may render Leaf templates as needed.\n\n\n\n\nSyntax Highlighting\n\u00b6\n\n\nYou may also wish to install one these third-party packages that provide support for syntax highlighting in Leaf templates.\n\n\nAtom\n\u00b6\n\n\nlanguage-leaf\n by ButkiewiczP\n\n\nXcode\n\u00b6\n\n\nIt is not currently possible to implement Leaf Syntax Highlighting in Xcode, however, using Xcode's HTML Syntax Coloring can help a bit. Select one or more Leaf files and then choose Editor > Syntax Coloring > HTML. Your selected Leaf files will now use Xcode's HTML Syntax Coloring. Unfortunately the usefulness of this is limited because this association will be removed when \nvapor xcode\n is run.\n\n\nThere appears to be a way to \nmake Xcode file associations persist\n but that requires a bit more kung-fu.\n\n\nVS Code\n\u00b6\n\n\nhtml-leaf\n by FranciscoAmado\n\n\nCLion & AppCode\n\u00b6\n\n\nSome preliminary work has been done to implement a Leaf Plugin for CLion & AppCode but lack of skill and interest in Java has slowed progress! If you have IntelliJ SDK experience and want to help with this, message Tom Holland on \nVapor Slack", - "title": "Getting Started" - }, - { - "location": "/leaf/getting-started/#leaf", - "text": "Leaf is a templating language that integrates with Futures, Reactive Streams and Codable. This section outlines how to import the Leaf package into a Vapor project.", - "title": "Leaf" - }, - { - "location": "/leaf/getting-started/#example-folder-structure", - "text": "Hello\n\u251c\u2500\u2500 Package.resolved\n\u251c\u2500\u2500 Package.swift\n\u251c\u2500\u2500 Public\n\u251c\u2500\u2500 Resources\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 Views\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 hello.leaf\n\u251c\u2500\u2500 Public\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 images (images resources)\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 styles (css resources)\n\u251c\u2500\u2500 Sources\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 App\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 boot.swift\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 configure.swift\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 routes.swift\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 Run\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 main.swift\n\u251c\u2500\u2500 Tests\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 AppTests\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 AppTests.swift\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 LinuxMain.swift\n\u2514\u2500\u2500 LICENSE", - "title": "Example Folder Structure" - }, - { - "location": "/leaf/getting-started/#adding-leaf-to-your-project", - "text": "The easiest way to use Leaf with Vapor is to include the Leaf repository as a dependency in Package.swift: // swift-tools-version:4.0 import PackageDescription let package = Package ( \n name : \"project1\" , \n dependencies : [ \n // \ud83d\udca7 A server-side Swift web framework. \n . package ( url : \"https://github.com/vapor/vapor.git\" , . branch ( \"beta\" )), \n . package ( url : \"https://github.com/vapor/leaf.git\" , . branch ( \"beta\" )), \n ], \n targets : [ \n . target ( \n name : \"App\" , \n dependencies : [ \"Vapor\" , \"Leaf\" ] \n ), \n . target ( name : \"Run\" , dependencies : [ \"App\" ]), \n . testTarget ( name : \"AppTests\" , dependencies : [ \"App\" ]), \n ] ) The Leaf package adds Leaf to your project, but to configure it for use you must modify configure.swift: Add import Leaf to the top of the file so that Leaf is available to use. You will also need to add this to any file that will render templates. Add try services.provider(LeafProvider()) to the configure() function so that routes may render Leaf templates as needed.", - "title": "Adding Leaf to your project" - }, - { - "location": "/leaf/getting-started/#syntax-highlighting", - "text": "You may also wish to install one these third-party packages that provide support for syntax highlighting in Leaf templates.", - "title": "Syntax Highlighting" - }, - { - "location": "/leaf/getting-started/#atom", - "text": "language-leaf by ButkiewiczP", - "title": "Atom" - }, - { - "location": "/leaf/getting-started/#xcode", - "text": "It is not currently possible to implement Leaf Syntax Highlighting in Xcode, however, using Xcode's HTML Syntax Coloring can help a bit. Select one or more Leaf files and then choose Editor > Syntax Coloring > HTML. Your selected Leaf files will now use Xcode's HTML Syntax Coloring. Unfortunately the usefulness of this is limited because this association will be removed when vapor xcode is run. There appears to be a way to make Xcode file associations persist but that requires a bit more kung-fu.", - "title": "Xcode" - }, - { - "location": "/leaf/getting-started/#vs-code", - "text": "html-leaf by FranciscoAmado", - "title": "VS Code" - }, - { - "location": "/leaf/getting-started/#clion-appcode", - "text": "Some preliminary work has been done to implement a Leaf Plugin for CLion & AppCode but lack of skill and interest in Java has slowed progress! If you have IntelliJ SDK experience and want to help with this, message Tom Holland on Vapor Slack", - "title": "CLion & AppCode" - }, - { - "location": "/leaf/basics/", - "text": "Basics\n\u00b6\n\n\nWelcome to Leaf. Leaf's goal is to be a simple templating language that can make generating views easier. There are plenty of great templating languages, so use what's best for you \u2013 maybe that's Leaf! The goals of Leaf are:\n\n\n\n\nSmall set of strictly enforced rules\n\n\nConsistency\n\n\nParser first mentality\n\n\nExtensibility\n\n\nAsynchronous and reactive\n\n\n\n\nRendering a template\n\u00b6\n\n\nOnce you have Leaf installed, you should create a directory called \u201cResources\u201d inside your project folder, and inside that create another directory called \u201cViews\u201d. This Resources/Views directory is the default location for Leaf templates, although you can change it if you want.\n\n\nTo render a basic Leaf template from a route, add this code:\n\n\nrouter\n.\nget\n \n{\n \nreq\n \n->\n \nFuture\n<\nView\n>\n \nin\n\n \nlet\n \nleaf\n \n=\n \ntry\n \nreq\n.\nmake\n(\nLeafRenderer\n.\nself\n)\n\n \nlet\n \ncontext\n \n=\n \n[\nString\n:\n \nString\n]()\n\n \nreturn\n \ntry\n \nleaf\n.\nmake\n(\n\"home\"\n,\n \ncontext\n)\n\n\n}\n\n\n\n\n\n\nThat will load home.leaf in the Resources/Views directory and render it. The \ncontext\n dictionary is there to let you provide custom data to render inside the template, but you might find it easier to use codable structs instead because they provide extra type safety. For example:\n\n\nstruct\n \nHomePage\n:\n \nCodable\n \n{\n\n \nvar\n \ntitle\n:\n \nString\n\n \nvar\n \ncontent\n:\n \nString\n\n\n}\n\n\n\n\n\n\nAsync\n\u00b6\n\n\nLeaf's engine is completely \nreactive\n, supporting both \nstreams\n and \nfutures\n. One of the only ones of it's kind.\n\n\nWhen working with Future results, simply pass the \nFuture\n in your template context.\nStreams that carry an encodable type need to be encoded before they're usable within Leaf.\n\n\nstruct\n \nProfile\n:\n \nCodable\n \n{\n\n \nvar\n \nfriends\n:\n \nEncodableStream\n\n \nvar\n \ncurrentUser\n:\n \nFuture\n<\nUser\n>\n\n\n}\n\n\n\n\n\n\nIn the above context, the \ncurrentUser\n variable in Leaf will behave as being a \nUser\n type. Leaf will not read the user Future if it's not used during rendering.\n\n\nEncodableStream\n will behave as an array of LeafData, only with lower memory impact and better performance. It is recommended to use \nEncodableStream\n for (large) database queries.\n\n\nYour name is #(currentUser.name).\n\n#for(friend in friends) {\n #(friend.name) is a friend of you.\n}\n\n\n\n\n\nTemplate syntax\n\u00b6\n\n\nStructure\n\u00b6\n\n\nLeaf tags are made up of four elements:\n\n\n\n\nToken: \n#\n is the token\n\n\nName: A \nstring\n that identifies the tag\n\n\nParameter List: \n()\n May accept 0 or more arguments\n\n\nBody (optional): \n{}\n Must be separated from the parameter list by a space\n\n\n\n\nThere can be many different usages of these four elements depending on the tag's implementation. Let's look at a few examples of how Leaf's built-in tags might be used:\n\n\n\n\n#()\n\n\n#(variable)\n\n\n#embed(\"template\")\n\n\n#set(\"title\") { Welcome to Vapor }\n\n\n#count(friends)\n\n\n#for(friend in friends) {
  • #(friend.name)
  • }\n\n\n\n\nWorking with context\n\u00b6\n\n\nIn our Swift example from earlier, we used an empty \n[String: String]\n dictionary for context, which passes no custom data to Leaf. To try rendering content, use this code instead:\n\n\nlet\n \ncontext\n \n=\n \n[\n\"title\"\n:\n \n\"Welcome\"\n,\n \n\"message\"\n:\n \n\"Vapor and Leaf work hand in hand\"\n]\n\n\nreturn\n \ntry\n \nleaf\n.\nmake\n(\n\"home\"\n,\n \ncontext\n)\n\n\n\n\n\n\nThat will expose \ntitle\n and \nmessage\n to our Leaf template, which can then be used inside tags. For example:\n\n\n

    #(title)

    \n

    #(message)

    \n\n\n\n\n\nChecking conditions\n\u00b6\n\n\nLeaf is able to evaluate a range of conditions using its \n#if\n tag. For example, if you provide a variable it will check that variable exists in its context:\n\n\n#if(title) {\n The title is #(title)\n} else {\n No title was provided.\n}\n\n\n\n\n\nYou can also write comparisons, for example:\n\n\n#if(title == \"Welcome\") {\n This is a friendly web page.\n} else {\n No strangers allowed!\n}\n\n\n\n\n\nIf you want to use another tag as part of your condition, you should omit the \n#\n for the inner tag. For example:\n\n\n#if(lowercase(title) == \"welcome\") {\n This is a friendly web page.\n} else {\n No strangers allowed!\n}\n\n\n\n\n\nLoops\n\u00b6\n\n\nIf you provide an array of items, Leaf can loop over them and let you manipulate each item individually using its \n#for\n tag. For example, we could update our Swift code to provide a list of names in a team:\n\n\nlet\n \ncontext\n \n=\n \n[\n\"team\"\n:\n \n[\n\"Malcolm\"\n,\n \n\"Kaylee\"\n,\n \n\"Jayne\"\n]]\n\n\n\n\n\n\nWe could then loop over them in Leaf like this:\n\n\n#for(name in team) {\n

    #(name) is in the team.

    \n}\n\n\n\n\n\nLeaf provides some extra variables inside a \n#for\n loop to give you more information about the loop's progress:\n\n\n\n\nThe \nloop.isFirst\n variable is true when the current iteration is the first one.\n\n\nThe \nloop.isLast\n variable is true when it's the last iteration.\n\n\nThe \nloop.index\n variable will be set to the number of the current iteration, counting from 0.\n\n\n\n\nEmbedding templates\n\u00b6\n\n\nLeaf\u2019s \n#embed\n tag allows you to copy the contents of one template into another. When use this, you should always omit the template file's .leaf extension.\n\n\nEmbedding is useful for copying in a standard piece of content, for example a page footer or advert code:\n\n\n#embed(\"footer\")\n\n\n\n\n\nThis tag is also useful for building one template on top of another. For example, you might have a master.leaf file that includes all the code required to lay out your website \u2013\u00a0HTML structure, CSS and JavaScript \u2013\u00a0with some gaps in place that represent where page content varies.\n\n\nUsing this approach, you would construct a child template that fills in its unique content, then embeds the parent template that places the content appropriately.\n\n\nFor example, you might create a child.leaf template like this:\n\n\n#set(\"body\") {\n

    Welcome to Vapor!

    \n}\n\n#embed(\"master\")\n\n\n\n\n\nThat configures one item of context, \nbody\n, but doesn\u2019t display it directly. Instead, it embeds master.leaf, which can render \nbody\n along with any other context variables passed in from Swift. For example, master.leaf might look like this:\n\n\n\n#(title)\n#(body)\n\n\n\n\n\n\nWhen given the context \n[\"title\": \"Hi there!\"]\n, child.leaf will render as follows:\n\n\n\nHi there!\n

    Welcome to Vapor!

    \n\n\n\n\n\n\nOther tags\n\u00b6\n\n\n#capitalize\n\u00b6\n\n\nThe \n#capitalize\n tag uppercases the first letter of any string. For example, \u201ctaylor\u201d will become \u201cTaylor\u201d.\n\n\n#capitalize(name)\n\n\n\n\n\n#contains\n\u00b6\n\n\nThe \n#contains\n tag accepts an array and a value as its two parameters, and returns true if the array in parameter one contains the value in parameter two. For example, given the array \nteam\n:\n\n\n#if(contains(team, \"Jayne\")) {\n You're all set!\n} else {\n You need someone to do PR.\n}\n\n\n\n\n\n#count\n\u00b6\n\n\nThe \n#count\n tag returns the number of items in an array. For example:\n\n\nYour search matched #count(matches) pages.\n\n\n\n\n\n#lowercase\n\u00b6\n\n\nThe \n#lowercase\n tag lowercases all letters in a string. For example, \u201cTaylor\u201d will become \u201ctaylor\u201d.\n\n\n#lowercase(name)\n\n\n\n\n\n#uppercase\n\u00b6\n\n\nThe \n#uppercase\n tag uppercases all letters in a string. For example, \u201cTaylor\u201d will become \u201cTAYLOR\u201d.\n\n\n#uppercase(name)", - "title": "Basics" - }, - { - "location": "/leaf/basics/#basics", - "text": "Welcome to Leaf. Leaf's goal is to be a simple templating language that can make generating views easier. There are plenty of great templating languages, so use what's best for you \u2013 maybe that's Leaf! The goals of Leaf are: Small set of strictly enforced rules Consistency Parser first mentality Extensibility Asynchronous and reactive", - "title": "Basics" - }, - { - "location": "/leaf/basics/#rendering-a-template", - "text": "Once you have Leaf installed, you should create a directory called \u201cResources\u201d inside your project folder, and inside that create another directory called \u201cViews\u201d. This Resources/Views directory is the default location for Leaf templates, although you can change it if you want. To render a basic Leaf template from a route, add this code: router . get { req -> Future < View > in \n let leaf = try req . make ( LeafRenderer . self ) \n let context = [ String : String ]() \n return try leaf . make ( \"home\" , context ) } That will load home.leaf in the Resources/Views directory and render it. The context dictionary is there to let you provide custom data to render inside the template, but you might find it easier to use codable structs instead because they provide extra type safety. For example: struct HomePage : Codable { \n var title : String \n var content : String }", - "title": "Rendering a template" - }, - { - "location": "/leaf/basics/#async", - "text": "Leaf's engine is completely reactive , supporting both streams and futures . One of the only ones of it's kind. When working with Future results, simply pass the Future in your template context.\nStreams that carry an encodable type need to be encoded before they're usable within Leaf. struct Profile : Codable { \n var friends : EncodableStream \n var currentUser : Future < User > } In the above context, the currentUser variable in Leaf will behave as being a User type. Leaf will not read the user Future if it's not used during rendering. EncodableStream will behave as an array of LeafData, only with lower memory impact and better performance. It is recommended to use EncodableStream for (large) database queries. Your name is #(currentUser.name).\n\n#for(friend in friends) {\n #(friend.name) is a friend of you.\n}", - "title": "Async" - }, - { - "location": "/leaf/basics/#template-syntax", - "text": "", - "title": "Template syntax" - }, - { - "location": "/leaf/basics/#structure", - "text": "Leaf tags are made up of four elements: Token: # is the token Name: A string that identifies the tag Parameter List: () May accept 0 or more arguments Body (optional): {} Must be separated from the parameter list by a space There can be many different usages of these four elements depending on the tag's implementation. Let's look at a few examples of how Leaf's built-in tags might be used: #() #(variable) #embed(\"template\") #set(\"title\") { Welcome to Vapor } #count(friends) #for(friend in friends) {
  • #(friend.name)
  • }", - "title": "Structure" - }, - { - "location": "/leaf/basics/#working-with-context", - "text": "In our Swift example from earlier, we used an empty [String: String] dictionary for context, which passes no custom data to Leaf. To try rendering content, use this code instead: let context = [ \"title\" : \"Welcome\" , \"message\" : \"Vapor and Leaf work hand in hand\" ] return try leaf . make ( \"home\" , context ) That will expose title and message to our Leaf template, which can then be used inside tags. For example:

    #(title)

    \n

    #(message)

    ", - "title": "Working with context" - }, - { - "location": "/leaf/basics/#checking-conditions", - "text": "Leaf is able to evaluate a range of conditions using its #if tag. For example, if you provide a variable it will check that variable exists in its context: #if(title) {\n The title is #(title)\n} else {\n No title was provided.\n} You can also write comparisons, for example: #if(title == \"Welcome\") {\n This is a friendly web page.\n} else {\n No strangers allowed!\n} If you want to use another tag as part of your condition, you should omit the # for the inner tag. For example: #if(lowercase(title) == \"welcome\") {\n This is a friendly web page.\n} else {\n No strangers allowed!\n}", - "title": "Checking conditions" - }, - { - "location": "/leaf/basics/#loops", - "text": "If you provide an array of items, Leaf can loop over them and let you manipulate each item individually using its #for tag. For example, we could update our Swift code to provide a list of names in a team: let context = [ \"team\" : [ \"Malcolm\" , \"Kaylee\" , \"Jayne\" ]] We could then loop over them in Leaf like this: #for(name in team) {\n

    #(name) is in the team.

    \n} Leaf provides some extra variables inside a #for loop to give you more information about the loop's progress: The loop.isFirst variable is true when the current iteration is the first one. The loop.isLast variable is true when it's the last iteration. The loop.index variable will be set to the number of the current iteration, counting from 0.", - "title": "Loops" - }, - { - "location": "/leaf/basics/#embedding-templates", - "text": "Leaf\u2019s #embed tag allows you to copy the contents of one template into another. When use this, you should always omit the template file's .leaf extension. Embedding is useful for copying in a standard piece of content, for example a page footer or advert code: #embed(\"footer\") This tag is also useful for building one template on top of another. For example, you might have a master.leaf file that includes all the code required to lay out your website \u2013\u00a0HTML structure, CSS and JavaScript \u2013\u00a0with some gaps in place that represent where page content varies. Using this approach, you would construct a child template that fills in its unique content, then embeds the parent template that places the content appropriately. For example, you might create a child.leaf template like this: #set(\"body\") {\n

    Welcome to Vapor!

    \n}\n\n#embed(\"master\") That configures one item of context, body , but doesn\u2019t display it directly. Instead, it embeds master.leaf, which can render body along with any other context variables passed in from Swift. For example, master.leaf might look like this: \n#(title)\n#(body)\n When given the context [\"title\": \"Hi there!\"] , child.leaf will render as follows: \nHi there!\n

    Welcome to Vapor!

    \n", - "title": "Embedding templates" - }, - { - "location": "/leaf/basics/#other-tags", - "text": "", - "title": "Other tags" - }, - { - "location": "/leaf/basics/#capitalize", - "text": "The #capitalize tag uppercases the first letter of any string. For example, \u201ctaylor\u201d will become \u201cTaylor\u201d. #capitalize(name)", - "title": "#capitalize" - }, - { - "location": "/leaf/basics/#contains", - "text": "The #contains tag accepts an array and a value as its two parameters, and returns true if the array in parameter one contains the value in parameter two. For example, given the array team : #if(contains(team, \"Jayne\")) {\n You're all set!\n} else {\n You need someone to do PR.\n}", - "title": "#contains" - }, - { - "location": "/leaf/basics/#count", - "text": "The #count tag returns the number of items in an array. For example: Your search matched #count(matches) pages.", - "title": "#count" - }, - { - "location": "/leaf/basics/#lowercase", - "text": "The #lowercase tag lowercases all letters in a string. For example, \u201cTaylor\u201d will become \u201ctaylor\u201d. #lowercase(name)", - "title": "#lowercase" - }, - { - "location": "/leaf/basics/#uppercase", - "text": "The #uppercase tag uppercases all letters in a string. For example, \u201cTaylor\u201d will become \u201cTAYLOR\u201d. #uppercase(name)", - "title": "#uppercase" - }, - { - "location": "/leaf/custom-tags/", - "text": "Custom Tags\n\u00b6\n\n\nYou can extend Leaf to provide your own tags that add custom functionality. To demonstrate this, let's look at a basic example by recreating \n#uppercase\n together. This tag will take one argument, which is the string to uppercase.\n\n\nWhen working with custom tags, there are four important things to know:\n\n\n\n\nYou should call \nrequireParameterCount()\n with the number of parameters you expect to receive. This will throw an error if your tag is used incorrectly.\n\n\nIf you do or do not require a body, you should use either \nrequireBody()\n or \nrequireNoBody()\n. Again, this will throw an error if your tag is used incorrectly.\n\n\nYou can read individual parameters using the \nparameters\n array. Each parameter will be of type \nLeafData\n, which you can convert to concrete data types using properties such as \n.string\n, \n.dictionary\n, and so on.\n\n\nYou must return a \nFuture\n containing what should be rendered. In the example below we wrap the resulting uppercase string in a \nLeafData\n string, then send that back wrapped in a future.\n\n\n\n\nHere\u2019s example code for a \nCustomUppercase\n Leaf tag:\n\n\nimport\n \nAsync\n\n\nimport\n \nLeaf\n\n\n\npublic\n \nfinal\n \nclass\n \nCustomUppercase\n:\n \nLeaf\n.\nLeafTag\n \n{\n\n \npublic\n \ninit\n()\n \n{}\n\n \npublic\n \nfunc\n \nrender\n(\nparsed\n:\n \nParsedTag\n,\n \ncontext\n:\n \nLeafContext\n,\n \nrenderer\n:\n \nLeafRenderer\n)\n \nthrows\n \n->\n \nFuture\n<\nLeafData\n?\n>\n \n{\n\n \n// ensure we receive precisely one parameter\n\n \ntry\n \nparsed\n.\nrequireParameterCount\n(\n1\n)\n\n\n \n// pull out our lone parameter as a string then uppercase it, or use an empty string\n\n \nlet\n \nstring\n \n=\n \nparsed\n.\nparameters\n[\n0\n].\nstring\n?.\nuppercased\n()\n \n??\n \n\"\"\n\n\n \n// send it back wrapped in a LeafData\n\n \nreturn\n \nFuture\n(.\nstring\n(\nstring\n))\n\n \n}\n\n\n}\n\n\n\n\n\n\nWe can now register this Tag in our \nconfigure.swift\n file with:\n\n\nservices\n.\nregister\n \n{\n \ncontainer\n \n->\n \nLeafConfig\n \nin\n\n \n// take a copy of Leaf's default tags\n\n \nvar\n \ntags\n \n=\n \ndefaultTags\n\n\n \n// add our custom tag\n\n \ntags\n[\n\"customuppercase\"\n]\n \n=\n \nCustomUppercase\n()\n\n\n \n// find the location of our Resources/Views directory\n\n \nlet\n \ndirectoryConfig\n \n=\n \ntry\n \ncontainer\n.\nmake\n(\nDirectoryConfig\n.\nself\n,\n \nfor\n:\n \nLeafRenderer\n.\nself\n)\n\n \nlet\n \nviewsDirectory\n \n=\n \ndirectoryConfig\n.\nworkDir\n \n+\n \n\"Resources/Views\"\n\n\n \n// put all that into a new Leaf configuration and return it\n\n \nreturn\n \nLeafConfig\n(\ntags\n:\n \ntags\n,\n \nviewsDir\n:\n \nviewsDirectory\n)\n\n\n}\n\n\n\n\n\n\nOnce that is complete, you can use \n#customuppercase(some_variable)\n to run your custom code.\n\n\n\n\nNote: Use of non-alphanumeric characters in tag names is \nstrongly discouraged\n and may be disallowed in future versions of Leaf.", - "title": "Custom tags" - }, - { - "location": "/leaf/custom-tags/#custom-tags", - "text": "You can extend Leaf to provide your own tags that add custom functionality. To demonstrate this, let's look at a basic example by recreating #uppercase together. This tag will take one argument, which is the string to uppercase. When working with custom tags, there are four important things to know: You should call requireParameterCount() with the number of parameters you expect to receive. This will throw an error if your tag is used incorrectly. If you do or do not require a body, you should use either requireBody() or requireNoBody() . Again, this will throw an error if your tag is used incorrectly. You can read individual parameters using the parameters array. Each parameter will be of type LeafData , which you can convert to concrete data types using properties such as .string , .dictionary , and so on. You must return a Future containing what should be rendered. In the example below we wrap the resulting uppercase string in a LeafData string, then send that back wrapped in a future. Here\u2019s example code for a CustomUppercase Leaf tag: import Async import Leaf public final class CustomUppercase : Leaf . LeafTag { \n public init () {} \n public func render ( parsed : ParsedTag , context : LeafContext , renderer : LeafRenderer ) throws -> Future < LeafData ? > { \n // ensure we receive precisely one parameter \n try parsed . requireParameterCount ( 1 ) \n\n // pull out our lone parameter as a string then uppercase it, or use an empty string \n let string = parsed . parameters [ 0 ]. string ?. uppercased () ?? \"\" \n\n // send it back wrapped in a LeafData \n return Future (. string ( string )) \n } } We can now register this Tag in our configure.swift file with: services . register { container -> LeafConfig in \n // take a copy of Leaf's default tags \n var tags = defaultTags \n\n // add our custom tag \n tags [ \"customuppercase\" ] = CustomUppercase () \n\n // find the location of our Resources/Views directory \n let directoryConfig = try container . make ( DirectoryConfig . self , for : LeafRenderer . self ) \n let viewsDirectory = directoryConfig . workDir + \"Resources/Views\" \n\n // put all that into a new Leaf configuration and return it \n return LeafConfig ( tags : tags , viewsDir : viewsDirectory ) } Once that is complete, you can use #customuppercase(some_variable) to run your custom code. Note: Use of non-alphanumeric characters in tag names is strongly discouraged and may be disallowed in future versions of Leaf.", - "title": "Custom Tags" - }, - { - "location": "/redis/getting-started/", - "text": "Redis\n\u00b6\n\n\nRedis is a Redis client library that can communicate with a Redis database.\n\n\nWhat is Redis?\n\u00b6\n\n\nRedis is an in-memory data store used as a database, cache and message broker. It supports most common data structures. Redis is most commonly used for caching data such as sessions and notifications (between multiple servers).\n\n\nRedis works as a key-value store, but allows querying the keys, unlike most databases.\n\n\nWith and without Vapor\n\u00b6\n\n\nTo include it in your package, add the following to your \nPackage.swift\n file.\n\n\n// swift-tools-version:4.0\n\n\nimport\n \nPackageDescription\n\n\n\nlet\n \npackage\n \n=\n \nPackage\n(\n\n \nname\n:\n \n\"Project\"\n,\n\n \ndependencies\n:\n \n[\n\n \n...\n\n \n.\npackage\n(\nurl\n:\n \n\"https://github.com/vapor/redis.git\"\n,\n \n.\nupToNextMajor\n(\nfrom\n:\n \n\"3.0.0\"\n)),\n\n \n],\n\n \ntargets\n:\n \n[\n\n \n.\ntarget\n(\nname\n:\n \n\"Project\"\n,\n \ndependencies\n:\n \n[\n\"Redis\"\n,\n \n...\n \n])\n\n \n]\n\n\n)\n\n\n\n\n\n\nIf this is your first time adding a dependency, you should read our introduction to \nPackage.swift\n.\n\n\nUse \nimport Redis\n to access Redis' APIs.", - "title": "Getting Started" - }, - { - "location": "/redis/getting-started/#redis", - "text": "Redis is a Redis client library that can communicate with a Redis database.", - "title": "Redis" - }, - { - "location": "/redis/getting-started/#what-is-redis", - "text": "Redis is an in-memory data store used as a database, cache and message broker. It supports most common data structures. Redis is most commonly used for caching data such as sessions and notifications (between multiple servers). Redis works as a key-value store, but allows querying the keys, unlike most databases.", - "title": "What is Redis?" - }, - { - "location": "/redis/getting-started/#with-and-without-vapor", - "text": "To include it in your package, add the following to your Package.swift file. // swift-tools-version:4.0 import PackageDescription let package = Package ( \n name : \"Project\" , \n dependencies : [ \n ... \n . package ( url : \"https://github.com/vapor/redis.git\" , . upToNextMajor ( from : \"3.0.0\" )), \n ], \n targets : [ \n . target ( name : \"Project\" , dependencies : [ \"Redis\" , ... ]) \n ] ) If this is your first time adding a dependency, you should read our introduction to Package.swift . Use import Redis to access Redis' APIs.", - "title": "With and without Vapor" - }, - { - "location": "/redis/basics/", - "text": "Redis basic usage\n\u00b6\n\n\nTo interact with Redis, you first need to construct a Redis client.\nThe Redis library primarily supports \nTCP sockets\n.\n\n\nThis requires a hostname, port and \nWorker\n. The eventloop will be used for Redis' Socket. The hostname and port have a default. The hostname is defaulted to \nlocalhost\n, and the port to Redis' default port \n6379\n.\n\n\nlet\n \nclient\n \n=\n \ntry\n \nRedisClient\n.\nconnect\n(\non\n:\n \nworker\n)\n \n// Future\n\n\n\n\n\n\nThe \nconnect\n method will return a \nFuture\n containing the TCP based Redis Client.\n\n\nRedis Data Types\n\u00b6\n\n\nRedis has 6 data types:\n\n\n\n\nnull\n\n\nInt\n\n\nError\n\n\nArray\n\n\nBasic String (used for command names and basic replies only)\n\n\nBulk String (used for Strings and binary data blobs)\n\n\n\n\nYou can instantiate one from the static functions and variables on \nRedisData\n.\n\n\nlet\n \nnull\n \n=\n \nRedisData\n.\nnull\n\n\n\nlet\n \nhelloWorld\n \n=\n \nRedisData\n.\nbulkString\n(\n\"Hello World\"\n)\n\n\n\nlet\n \nthree\n \n=\n \nRedisData\n.\ninteger\n(\n3\n)\n\n\n\nlet\n \noneThroughTen\n \n=\n \nRedisData\n.\narray\n([\n\n \n.\ninteger\n(\n1\n),\n\n \n.\ninteger\n(\n2\n),\n\n \n.\ninteger\n(\n3\n),\n\n \n.\ninteger\n(\n4\n),\n\n \n.\ninteger\n(\n5\n),\n\n \n.\ninteger\n(\n6\n),\n\n \n.\ninteger\n(\n7\n),\n\n \n.\ninteger\n(\n8\n),\n\n \n.\ninteger\n(\n9\n),\n\n \n.\ninteger\n(\n10\n)\n\n\n])\n\n\n\n\n\n\nThe above is the explicit way of defining Redis Types. You can also use literals in most scenarios:\n\n\nlet\n \narray\n \n=\n \nRedisData\n.\narray\n([\n\n \n[\n\n \n1\n,\n \n2\n,\n \n3\n,\n \n4\n,\n \n5\n,\n \n6\n,\n \n7\n,\n \n8\n,\n \n9\n,\n \n10\n\n \n],\n\n \n\"Hello World\"\n,\n\n \n\"One\"\n,\n\n \n\"Two\"\n,\n\n \n.\nnull\n,\n\n \n.\nnull\n,\n\n \n\"test\"\n\n\n])\n\n\n\n\n\n\nCRUD using Redis\n\u00b6\n\n\nFrom here on it is assumed that your client has been successfully created and is available in the variable \nclient\n as a \nRedisClient\n.\n\n\nCreating a record\n\u00b6\n\n\nCreating a record is done using a \nRedisData\n for a value and a key.\n\n\nclient\n.\nset\n(\n\"world\"\n,\n \nforKey\n:\n \n\"hello\"\n)\n\n\n\n\n\n\nThis returns a future that'll indicate successful or unsuccessful insertion.\n\n\nReading a record\n\u00b6\n\n\nReading a record is similar, only you'll get a warning if you don't use the returned future.\n\n\nThe \nFuture\n for the key \"hello\" will be \"world\" if you created the record as shown above.\n\n\nlet\n \nfutureRecord\n \n=\n \nclient\n.\ngetData\n(\nforKey\n:\n \n\"hello\"\n)\n \n// Future\n\n\n\n\n\n\nDeleting a record\n\u00b6\n\n\nDeleting a record is similar but allows querying the keys, too.\n\n\nclient\n.\ndelete\n(\nkeys\n:\n \n[\n\"hello\"\n])\n\n\n\n\n\n\nWhere the above command will remove the key \"hello\", the next command will delete \nall\n keys from the Redis database.\n\n\nclient\n.\ndelete\n(\nkeys\n:\n \n[\n\"*\"\n])", - "title": "Basics" - }, - { - "location": "/redis/basics/#redis-basic-usage", - "text": "To interact with Redis, you first need to construct a Redis client.\nThe Redis library primarily supports TCP sockets . This requires a hostname, port and Worker . The eventloop 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 . let client = try RedisClient . connect ( on : worker ) // Future The connect method will return a Future containing the TCP based Redis Client.", - "title": "Redis basic usage" - }, - { - "location": "/redis/basics/#redis-data-types", - "text": "Redis has 6 data types: null Int Error Array Basic String (used for command names and basic replies only) Bulk String (used for Strings and binary data blobs) You can instantiate one from the static functions and variables on RedisData . let null = RedisData . null let helloWorld = RedisData . bulkString ( \"Hello World\" ) let three = RedisData . integer ( 3 ) let oneThroughTen = RedisData . array ([ \n . integer ( 1 ), \n . integer ( 2 ), \n . integer ( 3 ), \n . integer ( 4 ), \n . integer ( 5 ), \n . integer ( 6 ), \n . integer ( 7 ), \n . integer ( 8 ), \n . integer ( 9 ), \n . integer ( 10 ) ]) The above is the explicit way of defining Redis Types. You can also use literals in most scenarios: let array = RedisData . array ([ \n [ \n 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 \n ], \n \"Hello World\" , \n \"One\" , \n \"Two\" , \n . null , \n . null , \n \"test\" ])", - "title": "Redis Data Types" - }, - { - "location": "/redis/basics/#crud-using-redis", - "text": "From here on it is assumed that your client has been successfully created and is available in the variable client as a RedisClient .", - "title": "CRUD using Redis" - }, - { - "location": "/redis/basics/#creating-a-record", - "text": "Creating a record is done using a RedisData for a value and a key. client . set ( \"world\" , forKey : \"hello\" ) This returns a future that'll indicate successful or unsuccessful insertion.", - "title": "Creating a record" - }, - { - "location": "/redis/basics/#reading-a-record", - "text": "Reading a record is similar, only you'll get a warning if you don't use the returned future. The Future for the key \"hello\" will be \"world\" if you created the record as shown above. let futureRecord = client . getData ( forKey : \"hello\" ) // Future", - "title": "Reading a record" - }, - { - "location": "/redis/basics/#deleting-a-record", - "text": "Deleting a record is similar but allows querying the keys, too. client . delete ( keys : [ \"hello\" ]) Where the above command will remove the key \"hello\", the next command will delete all keys from the Redis database. client . delete ( keys : [ \"*\" ])", - "title": "Deleting a record" - }, - { - "location": "/redis/custom-commands/", - "text": "Custom commands\n\u00b6\n\n\nMany commands are not (yet) implemented by the driver using a convenience function. This does not mean the feature/command is not usable.\n\n\n(Almost) all functions listed here\n work out of the box using custom commands.\n\n\nUsage\n\u00b6\n\n\nThe Redis client has a \nrun\n function that allows you to run these commands.\n\n\nThe following code demonstrates a \"custom\" implementation for \nGET\n.\n\n\nlet\n \nfuture\n \n=\n \nclient\n.\nrun\n(\ncommand\n:\n \n\"GET\"\n,\n \narguments\n:\n \n[\n\"my-key\"\n])\n \n// Future\n\n\n\n\n\n\nThis future will contain the result as specified in the article on the redis command page or an error.\n\n\nThe future can be used as described in the \nAsync API\n.", - "title": "Custom commands" - }, - { - "location": "/redis/custom-commands/#custom-commands", - "text": "Many commands are not (yet) implemented by the driver using a convenience function. This does not mean the feature/command is not usable. (Almost) all functions listed here work out of the box using custom commands.", - "title": "Custom commands" - }, - { - "location": "/redis/custom-commands/#usage", - "text": "The Redis client has a run function that allows you to run these commands. The following code demonstrates a \"custom\" implementation for GET . let future = client . run ( command : \"GET\" , arguments : [ \"my-key\" ]) // Future This future will contain the result as specified in the article on the redis command page or an error. The future can be used as described in the Async API .", - "title": "Usage" - }, - { - "location": "/redis/pub-sub/", - "text": "Publish & Subscribe\n\u00b6\n\n\nRedis' Publish and Subscribe model is really useful for notifications.\n\n\nUse cases\n\u00b6\n\n\nPub/sub is used for notifying subscribers of an event.\nA simple and common event for example would be a chat message.\n\n\nA channel consists of a name and group of listeners. Think of it as being \n[String: [Listener]]\n.\nWhen you send a notification to a channel you need to provide a payload.\nEach listener will get a notification consisting of this payload.\n\n\nChannels must be a string. For chat groups, for example, you could use the database identifier.\n\n\nPublishing\n\u00b6\n\n\nYou cannot get a list of listeners, but sending a payload will emit the amount of listeners that received the notification.\nSending (publishing) an event is done like so:\n\n\n// Any redis data\n\n\nlet\n \nnotification\n:\n \nRedisData\n \n=\n \n\"My-Notification\"\n\n\n\nclient\n.\npublish\n(\nnotification\n,\n \nto\n:\n \n\"my-channel\"\n)\n\n\n\n\n\n\nIf you want access to the listener count:\n\n\nlet\n \nnotifiedCount\n \n=\n \nclient\n.\npublish\n(\nnotification\n,\n \nto\n:\n \n\"my-channel\"\n)\n \n// Future\n\n\n\n\n\n\nSubscribing\n\u00b6\n\n\nTo subscribe for notifications you're rendering an entire Redis Client useless in exchange for listening to events.\n\n\nA single client can listen to one or more channels, which is provided using a set of unique channel names. The result of subscribing is a \nSubscriptionStream\n.\n\n\nlet\n \nnotifications\n \n=\n \nclient\n.\nsubscribe\n(\nto\n:\n \n[\n\"some-notification-channel\"\n,\n \n\"other-notification-channel\"\n])\n\n\n\n\n\n\nIf you try to use the client after subscribing, all operations will fail. These errors are usually emitted through the Future.\n\n\nThis stream will receive messages asynchronously from the point of \ndraining\n. This works like \nany other async stream\n\n\nNotifications consist of the channel and payload.\n\n\nnotifications\n.\ndrain\n \n{\n \nnotification\n \nin\n\n \nprint\n(\nnotification\n.\nchannel\n)\n\n\n \nlet\n \npayload\n \n=\n \nnotification\n.\npayload\n\n\n \n// \nTODO:\n Process the payload\n\n\n}", - "title": "Publish and Subscribe" - }, - { - "location": "/redis/pub-sub/#publish-subscribe", - "text": "Redis' Publish and Subscribe model is really useful for notifications.", - "title": "Publish & Subscribe" - }, - { - "location": "/redis/pub-sub/#use-cases", - "text": "Pub/sub is used for notifying subscribers of an event.\nA simple and common event for example would be a chat message. A channel consists of a name and group of listeners. Think of it as being [String: [Listener]] .\nWhen you send a notification to a channel you need to provide a payload.\nEach listener will get a notification consisting of this payload. Channels must be a string. For chat groups, for example, you could use the database identifier.", - "title": "Use cases" - }, - { - "location": "/redis/pub-sub/#publishing", - "text": "You cannot get a list of listeners, but sending a payload will emit the amount of listeners that received the notification.\nSending (publishing) an event is done like so: // Any redis data let notification : RedisData = \"My-Notification\" client . publish ( notification , to : \"my-channel\" ) If you want access to the listener count: let notifiedCount = client . publish ( notification , to : \"my-channel\" ) // Future", - "title": "Publishing" - }, - { - "location": "/redis/pub-sub/#subscribing", - "text": "To subscribe for notifications you're rendering an entire Redis Client useless in exchange for listening to events. A single client can listen to one or more channels, which is provided using a set of unique channel names. The result of subscribing is a SubscriptionStream . let notifications = client . subscribe ( to : [ \"some-notification-channel\" , \"other-notification-channel\" ]) If you try to use the client after subscribing, all operations will fail. These errors are usually emitted through the Future. This stream will receive messages asynchronously from the point of draining . This works like any other async stream Notifications consist of the channel and payload. notifications . drain { notification in \n print ( notification . channel ) \n\n let payload = notification . payload \n\n // TODO: Process the payload }", - "title": "Subscribing" - }, - { - "location": "/redis/pipeline/", - "text": "Pipelining\n\u00b6\n\n\nPipelining is used for sending multiple commands at once. The performance advantages become apparent when sending a large number of queries. Redis' pipelining cuts down latency by reducing the RTT (Round Trip Time) between the client and server. Pipelining also reduces the amount of IO operations Redis has to perform, this increases the amount of queries per second Redis can handle. \n\n\nUse cases\n\u00b6\n\n\nSometimes multiple commands need to be executed at once. Instead of sending those commands individually in a loop, pipelining allows the commands to be batched and sent in one request. A common scenario might be needing to set a key and increment a count, pipelining those commands would be ideal.\n\n\nEnqueuing Commands\n\u00b6\n\n\n \nlet\n \npipeline\n \n=\n \nconnection\n.\nmakePipeline\n()\n\n \nlet\n \nresult\n \n=\n \ntry\n \npipeline\n\n \n.\nenqueue\n(\ncommand\n:\n \n\"SET\"\n,\n \narguments\n:\n \n[\n\"KEY\"\n,\n \n\"VALUE\"\n])\n\n \n.\nenqueue\n(\ncommand\n:\n \n\"INCR\"\n,\n \narguments\n:\n \n[\n\"COUNT\"\n])\n\n \n.\nexecute\n()\n \n// Future<[RedisData]>\n\n\n\n\n\n\nNote: Commands will not be executed until execute is called.", - "title": "Pipeline" - }, - { - "location": "/redis/pipeline/#pipelining", - "text": "Pipelining is used for sending multiple commands at once. The performance advantages become apparent when sending a large number of queries. Redis' pipelining cuts down latency by reducing the RTT (Round Trip Time) between the client and server. Pipelining also reduces the amount of IO operations Redis has to perform, this increases the amount of queries per second Redis can handle.", - "title": "Pipelining" - }, - { - "location": "/redis/pipeline/#use-cases", - "text": "Sometimes multiple commands need to be executed at once. Instead of sending those commands individually in a loop, pipelining allows the commands to be batched and sent in one request. A common scenario might be needing to set a key and increment a count, pipelining those commands would be ideal.", - "title": "Use cases" - }, - { - "location": "/redis/pipeline/#enqueuing-commands", - "text": "let pipeline = connection . makePipeline () \n let result = try pipeline \n . enqueue ( command : \"SET\" , arguments : [ \"KEY\" , \"VALUE\" ]) \n . enqueue ( command : \"INCR\" , arguments : [ \"COUNT\" ]) \n . execute () // Future<[RedisData]> Note: Commands will not be executed until execute is called.", - "title": "Enqueuing Commands" - }, - { - "location": "/websocket/websocket/", - "text": "WebSocket\n\u00b6\n\n\nWebSockets 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.\n\n\nVapor 3 supports both WebSocket Clients and server-side sockets.\n\n\nServer side Sockets\n\u00b6\n\n\nVapor 3 adds a helper to routing that helps accepting clients.\n\n\nimport\n \nWebSocket\n\n\n\nrouting\n.\nwebsocket\n(\n\"api/v1/websocket\"\n)\n \n{\n \nreq\n,\n \nwebsocket\n \nin\n\n \n// set up the websocket\n\n\n}\n\n\n\n\n\n\nClient side sockets\n\u00b6\n\n\nConnecting to a remote WebSocket is relatively painless. You can provide a URL and Vapor 3 will attempt to set up a connection.\n\n\nFor creating an SSL connection, however, a \ncontainer\n must be provided.\n\n\n\n\nWarning\n\n\nVapor does not retain the WebSocket. You need to keep the WebSocket active by means of strong references and pings.\n\n\n\n\nlet\n \nfutureWebSocket\n \n=\n \ntry\n \nWebSocket\n.\nconnect\n(\nto\n:\n \n\"ws://localhost/path\"\n,\n \nusing\n:\n \ncontainer\n)\n \n// Future\n\n\n\n\n\n\nUsing websockets\n\u00b6\n\n\nSending strings\n\u00b6\n\n\nSending a \nString\n using a WebSocket sends it to the remote.\n\n\nwebSocket\n.\nsend\n(\nstring\n:\n \nstring\n)\n\n\n\n\n\n\nReceiving strings\n\u00b6\n\n\nString data can be read using the following function. Only one closure can read at a time.\n\n\nwebSocket\n.\nonString\n \n{\n \nstring\n \nin\n\n \n// use the `String`\n\n\n}\n\n\n\n\n\n\nSending binary data\n\u00b6\n\n\nSending a \nData\n or \nByteBuffer\n using a WebSocket sends it to the remote.\n\n\nwebSocket\n.\nsend\n(\nbytes\n:\n \nbyteBuffer\n)\n\n\nwebSocket\n.\nsend\n(\ndata\n:\n \ndata\n)\n\n\n\n\n\n\nReceiving binary data\n\u00b6\n\n\nBinary data can be read as a \nByteBuffer\n using the following function. Only one closure can read at a time.\n\n\nwebSocket\n.\nonByteBuffer\n \n{\n \nbyteBuffer\n \nin\n\n \n// use the `ByteBuffer`\n\n\n}\n\n\n\n\n\n\nBinary data can also, instead, be used as Foundation's \nData\n. This is less efficient than \nByteBuffer\n but is often easier to use.\n\n\nwebSocket\n.\nonData\n \n{\n \ndata\n \nin\n\n \n// use the `Data`\n\n\n}\n\n\n\n\n\n\nSetting a listener will override all previous listeners. You need to split into multiple listeners manually.\n\n\nOn close\n\u00b6\n\n\nWhen requesting data using the \nonString\n, \nonByteBuffer\n and/or \nonData\n functions, you'll receive a handle to that stream.\nThis can be used for catching errors and detecting the websocket being closed.\n\n\nwebSocket\n.\nonString\n \n{\n \nwebsocket\n,\n \nstring\n \nin\n\n \nprint\n(\nstring\n)\n\n\n}.\ncatch\n \n{\n \nerror\n \nin\n\n \nprint\n(\n\"Error occurred: \n\\(\nerror\n)\n\"\n)\n\n\n}.\nfinally\n \n{\n\n \nprint\n(\n\"closed\"\n)\n\n\n}\n\n\n\n\n\n\nErrors\n\u00b6\n\n\nAny error in a WebSocket will close the connection. This notification will be received on the binary \nand\n text streams.", - "title": "WebSocket" - }, - { - "location": "/websocket/websocket/#websocket", - "text": "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 Clients and server-side sockets.", - "title": "WebSocket" - }, - { - "location": "/websocket/websocket/#server-side-sockets", - "text": "Vapor 3 adds a helper to routing that helps accepting clients. import WebSocket routing . websocket ( \"api/v1/websocket\" ) { req , websocket in \n // set up the websocket }", - "title": "Server side Sockets" - }, - { - "location": "/websocket/websocket/#client-side-sockets", - "text": "Connecting to a remote WebSocket is relatively painless. You can provide a URL and Vapor 3 will attempt to set up a connection. For creating an SSL connection, however, a container must be provided. Warning Vapor does not retain the WebSocket. You need to keep the WebSocket active by means of strong references and pings. let futureWebSocket = try WebSocket . connect ( to : \"ws://localhost/path\" , using : container ) // Future", - "title": "Client side sockets" - }, - { - "location": "/websocket/websocket/#using-websockets", - "text": "", - "title": "Using websockets" - }, - { - "location": "/websocket/websocket/#sending-strings", - "text": "Sending a String using a WebSocket sends it to the remote. webSocket . send ( string : string )", - "title": "Sending strings" - }, - { - "location": "/websocket/websocket/#receiving-strings", - "text": "String data can be read using the following function. Only one closure can read at a time. webSocket . onString { string in \n // use the `String` }", - "title": "Receiving strings" - }, - { - "location": "/websocket/websocket/#sending-binary-data", - "text": "Sending a Data or ByteBuffer using a WebSocket sends it to the remote. webSocket . send ( bytes : byteBuffer ) webSocket . send ( data : data )", - "title": "Sending binary data" - }, - { - "location": "/websocket/websocket/#receiving-binary-data", - "text": "Binary data can be read as a ByteBuffer using the following function. Only one closure can read at a time. webSocket . onByteBuffer { byteBuffer in \n // use the `ByteBuffer` } Binary data can also, instead, be used as Foundation's Data . This is less efficient than ByteBuffer but is often easier to use. webSocket . onData { data in \n // use the `Data` } Setting a listener will override all previous listeners. You need to split into multiple listeners manually.", - "title": "Receiving binary data" - }, - { - "location": "/websocket/websocket/#on-close", - "text": "When requesting data using the onString , onByteBuffer and/or onData functions, you'll receive a handle to that stream.\nThis can be used for catching errors and detecting the websocket being closed. webSocket . onString { websocket , string in \n print ( string ) }. catch { error in \n print ( \"Error occurred: \\( error ) \" ) }. finally { \n print ( \"closed\" ) }", - "title": "On close" - }, - { - "location": "/websocket/websocket/#errors", - "text": "Any error in a WebSocket will close the connection. This notification will be received on the binary and text streams.", - "title": "Errors" - }, - { - "location": "/services/getting-started/", - "text": "TODO\n\u00b6\n\n\nRefer to the concepts/services for now", - "title": "Services" - }, - { - "location": "/services/getting-started/#todo", - "text": "Refer to the concepts/services for now", - "title": "TODO" - }, - { - "location": "/crypto/getting-started/", - "text": "Using Crypto\n\u00b6\n\n\nCrypto is a library containing all common APIs related to cryptography and security.\n\n\nThis project does \nnot\n support TLS. For that, please see \nthe TLS package\n.\n\n\nWith Vapor\n\u00b6\n\n\nThis package is included with Vapor by default, just add:\n\n\nimport\n \nCrypto\n\n\n\n\n\n\nWithout Vapor\n\u00b6\n\n\nTo include it in your package, add the following to your \nPackage.swift\n file.\n\n\n// swift-tools-version:4.0\n\n\nimport\n \nPackageDescription\n\n\n\nlet\n \npackage\n \n=\n \nPackage\n(\n\n \nname\n:\n \n\"Project\"\n,\n\n \ndependencies\n:\n \n[\n\n \n...\n\n \n.\npackage\n(\nurl\n:\n \n\"https://github.com/vapor/crypto.git\"\n,\n \n.\nupToNextMajor\n(\nfrom\n:\n \n\"x.0.0\"\n)),\n\n \n],\n\n \ntargets\n:\n \n[\n\n \n.\ntarget\n(\nname\n:\n \n\"Project\"\n,\n \ndependencies\n:\n \n[\n\"Crypto\"\n,\n \n...\n \n])\n\n \n]\n\n\n)\n\n\n\n\n\n\nUse \nimport Crypto\n to access Crypto's APIs.", - "title": "Getting Started" - }, - { - "location": "/crypto/getting-started/#using-crypto", - "text": "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 .", - "title": "Using Crypto" - }, - { - "location": "/crypto/getting-started/#with-vapor", - "text": "This package is included with Vapor by default, just add: import Crypto", - "title": "With Vapor" - }, - { - "location": "/crypto/getting-started/#without-vapor", - "text": "To include it in your package, add the following to your Package.swift file. // swift-tools-version:4.0 import PackageDescription let package = Package ( \n name : \"Project\" , \n dependencies : [ \n ... \n . package ( url : \"https://github.com/vapor/crypto.git\" , . upToNextMajor ( from : \"x.0.0\" )), \n ], \n targets : [ \n . target ( name : \"Project\" , dependencies : [ \"Crypto\" , ... ]) \n ] ) Use import Crypto to access Crypto's APIs.", - "title": "Without Vapor" - }, - { - "location": "/crypto/base64/", - "text": "Base64\n\u00b6\n\n\nComing soon", - "title": "Base64" - }, - { - "location": "/crypto/base64/#base64", - "text": "Coming soon", - "title": "Base64" - }, - { - "location": "/crypto/hash/", - "text": "Hash\n\u00b6\n\n\nHashes are a one-directional encryption that is commonly used for validating files or one-way securing data such as passwords.\n\n\nAvailable hashes\n\u00b6\n\n\nCrypto currently supports a few hashes.\n\n\n\n\nMD5\n\n\nSHA1\n\n\nSHA2 (all variants)\n\n\n\n\nMD5 and SHA1 are generally used for file validation or legacy (weak) passwords. They're performant and lightweight.\n\n\nEvery Hash type has a set of helpers that you can use.\n\n\nHashing blobs of data\n\u00b6\n\n\nEvery \nHash\n has a static method called \nhash\n that can be used for hashing the entire contents of \nFoundation.Data\n, \nByteBuffer\n or \nString\n.\n\n\nThe result is \nData\n containing the resulting hash. The hash's length is according to spec and defined in the static variable \ndigestSize\n.\n\n\n// MD5 with `Data`\n\n\nlet\n \nfileData\n \n=\n \nData\n()\n\n\nlet\n \nfileMD5\n \n=\n \nMD5\n.\nhash\n(\nfileData\n)\n\n\n\n// SHA1 with `ByteBuffer`\n\n\nlet\n \nfileBuffer\n:\n \nByteBuffer\n \n=\n \n...\n\n\nlet\n \nfileSHA1\n \n=\n \nSHA1\n.\nhash\n(\nfileBuffer\n)\n\n\n\n// SHA2 variants with String\n\n\nlet\n \nstaticUnsafeToken\n:\n \nString\n \n=\n \n\"rsadd14ndmasidfm12i4j\"\n\n\n\nlet\n \ntokenHashSHA224\n \n=\n \nSHA224\n.\nhash\n(\nstaticUnsafeToken\n)\n\n\nlet\n \ntokenHashSHA256\n \n=\n \nSHA256\n.\nhash\n(\nstaticUnsafeToken\n)\n\n\nlet\n \ntokenHashSHA384\n \n=\n \nSHA384\n.\nhash\n(\nstaticUnsafeToken\n)\n\n\nlet\n \ntokenHashSHA512\n \n=\n \nSHA512\n.\nhash\n(\nstaticUnsafeToken\n)\n\n\n\n\n\n\nIncremental hashes (manual)\n\u00b6\n\n\nTo incrementally process hashes you can create an instance of the Hash. This will set up a context.\n\n\nAll hash context initializers are empty:\n\n\n// Create an MD5 context\n\n\nlet\n \nmd5Context\n \n=\n \nMD5\n()\n\n\n\n\n\n\nTo process a single chunk of data, you can call the \nupdate\n function on a context using any \nSequence\n of \nUInt8\n. That means \nArray\n, \nData\n and \nByteBuffer\n work alongside any other sequence of bytes.\n\n\nmd5Context\n.\nupdate\n(\ndata\n)\n\n\n\n\n\n\nThe data data need not be a specific length. Any length works.\n\n\nWhen you need the result, you can call \nmd5Context.finalize()\n. This will finish calculating the hash by appending the standard \n1\n bit, padding and message bitlength.\n\n\nYou can optionally provide a last set of data to \nfinalize()\n.\n\n\nAfter calling \nfinalize()\n, do not update the hash if you want correct results.\n\n\nFetching the results\n\u00b6\n\n\nThe context can then be accessed to extract the resulting Hash.\n\n\nlet\n \nhash\n:\n \nData\n \n=\n \nmd5Context\n.\nhash\n\n\n\n\n\n\nStreaming hashes (Async)\n\u00b6\n\n\nSometimes you need to hash the contents of a Stream, for example, when processing a file transfer. In this case you can use \nByteStreamHasher\n.\n\n\nFirst, create a new generic \nByteStreamHasher\n where \nHash\n is the hash you want to use. In this case, SHA512.\n\n\nlet\n \nstreamHasher\n \n=\n \nByteStreamHasher\n<\nSHA512\n>()\n\n\n\n\n\n\nThis stream works like any \ninputStream\n by consuming the incoming data and passing the buffers to the hash context.\n\n\nFor example, draining a TCP socket.\n\n\nlet\n \nsocket\n:\n \nTCP\n.\nSocket\n \n=\n \n...\n\n\n\nsocket\n.\ndrain\n(\ninto\n:\n \nstreamHasher\n)\n\n\n\n\n\n\nThis will incrementally update the hash using the provided TCP socket's data.\n\n\nWhen the hash has been completely accumulated, you can \ncomplete\n the hash.\n\n\nlet\n \nhash\n \n=\n \nstreamHasher\n.\ncomplete\n()\n \n// Foundation `Data`\n\n\n\n\n\n\nThis will reset the hash's context to the default configuration, ready to start over.", - "title": "Hashes" - }, - { - "location": "/crypto/hash/#hash", - "text": "Hashes are a one-directional encryption that is commonly used for validating files or one-way securing data such as passwords.", - "title": "Hash" - }, - { - "location": "/crypto/hash/#available-hashes", - "text": "Crypto currently supports a few hashes. MD5 SHA1 SHA2 (all variants) MD5 and SHA1 are generally used for file validation or legacy (weak) passwords. They're performant and lightweight. Every Hash type has a set of helpers that you can use.", - "title": "Available hashes" - }, - { - "location": "/crypto/hash/#hashing-blobs-of-data", - "text": "Every Hash has a static method called hash that can be used for hashing the entire contents of Foundation.Data , ByteBuffer or String . The result is Data containing the resulting hash. The hash's length is according to spec and defined in the static variable digestSize . // MD5 with `Data` let fileData = Data () let fileMD5 = MD5 . hash ( fileData ) // SHA1 with `ByteBuffer` let fileBuffer : ByteBuffer = ... let fileSHA1 = SHA1 . hash ( fileBuffer ) // SHA2 variants with String let staticUnsafeToken : String = \"rsadd14ndmasidfm12i4j\" let tokenHashSHA224 = SHA224 . hash ( staticUnsafeToken ) let tokenHashSHA256 = SHA256 . hash ( staticUnsafeToken ) let tokenHashSHA384 = SHA384 . hash ( staticUnsafeToken ) let tokenHashSHA512 = SHA512 . hash ( staticUnsafeToken )", - "title": "Hashing blobs of data" - }, - { - "location": "/crypto/hash/#incremental-hashes-manual", - "text": "To incrementally process hashes you can create an instance of the Hash. This will set up a context. All hash context initializers are empty: // Create an MD5 context let md5Context = MD5 () To process a single chunk of data, you can call the update function on a context using any Sequence of UInt8 . That means Array , Data and ByteBuffer work alongside any other sequence of bytes. md5Context . update ( data ) The data data need not be a specific length. Any length works. When you need the result, you can call md5Context.finalize() . This will finish calculating the hash by appending the standard 1 bit, padding and message bitlength. You can optionally provide a last set of data to finalize() . After calling finalize() , do not update the hash if you want correct results.", - "title": "Incremental hashes (manual)" - }, - { - "location": "/crypto/hash/#fetching-the-results", - "text": "The context can then be accessed to extract the resulting Hash. let hash : Data = md5Context . hash", - "title": "Fetching the results" - }, - { - "location": "/crypto/hash/#streaming-hashes-async", - "text": "Sometimes you need to hash the contents of a Stream, for example, when processing a file transfer. In this case you can use ByteStreamHasher . First, create a new generic ByteStreamHasher where Hash is the hash you want to use. In this case, SHA512. let streamHasher = ByteStreamHasher < SHA512 >() This stream works like any inputStream by consuming the incoming data and passing the buffers to the hash context. For example, draining a TCP socket. let socket : TCP . Socket = ... socket . drain ( into : streamHasher ) This will incrementally update the hash using the provided TCP socket's data. When the hash has been completely accumulated, you can complete the hash. let hash = streamHasher . complete () // Foundation `Data` This will reset the hash's context to the default configuration, ready to start over.", - "title": "Streaming hashes (Async)" - }, - { - "location": "/crypto/mac/", - "text": "Message authentication\n\u00b6\n\n\nMessage authentication is used for verifying message authenticity and validity.\n\n\nCommon use cases are JSON Web Tokens.\n\n\nFor message authentication, Vapor only supports HMAC.\n\n\nUsing HMAC\n\u00b6\n\n\nTo use HMAC you first need to select the used hashing algorithm for authentication. This works using generics.\n\n\nlet\n \nhash\n \n=\n \nHMAC\n<\nSHA224\n>.\nauthenticate\n(\nmessage\n,\n \nwithKey\n:\n \nauthenticationKey\n)", - "title": "Message authentication" - }, - { - "location": "/crypto/mac/#message-authentication", - "text": "Message authentication is used for verifying message authenticity and validity. Common use cases are JSON Web Tokens. For message authentication, Vapor only supports HMAC.", - "title": "Message authentication" - }, - { - "location": "/crypto/mac/#using-hmac", - "text": "To use HMAC you first need to select the used hashing algorithm for authentication. This works using generics. let hash = HMAC < SHA224 >. authenticate ( message , withKey : authenticationKey )", - "title": "Using HMAC" - }, - { - "location": "/crypto/passwords/", - "text": "Password hashing\n\u00b6\n\n\nPassword management is critical for good user security and doesn't need to cost a lot of effort. No software is perfect. Even if your software is perfect, other software on the same server likely isn't. Good password encryption security prevents users' passwords from leaking out in case of a hypothetical future data breach.\n\n\nFor password hashing Vapor supports PBKDF2 and BCrypt.\n\n\nWe recommend using BCrypt over PBKDF2 for almost all scenarios. Whilst PBKDF2 is a proven standard, it's much more easily brute-forced than BCrypt and is less future-proof.\n\n\nBCrypt\n\u00b6\n\n\nBCrypt is an algorithm specifically designed for password hashing. It's easy to store and verify.\n\n\nDeriving a key\n\u00b6\n\n\nUnlike PBKDF2 you don't need to generate and store a salt, that's part of the BCrypt hashing and verification process.\n\n\nThe output is a combination of the BCrypt \"cost\" factor, salt and resulting hash. Meaning that the derived output contains all information necessary for verification, simplifying the database access.\n\n\nlet\n \nresult\n:\n \nData\n \n=\n \ntry\n \nBCrypt\n.\nmake\n(\nmessage\n:\n \n\"MyPassword\"\n)\n\n\n\nguard\n \ntry\n \nBCrypt\n.\nverify\n(\nmessage\n:\n \n\"MyPassword\"\n,\n \nmatches\n:\n \nresult\n)\n \nelse\n \n{\n\n \nfatalError\n(\n\"This never triggers, since the verification process will always be successful for the same password and conditions\"\n)\n\n\n}\n\n\n\n\n\n\nThe default cost factor is \n12\n, based on the official recommendations.\n\n\nStoring the derived key as a String\n\u00b6\n\n\nBCrypt always outputs valid ASCII/UTF-8 for the resulting hash.\n\n\nThis means you can convert the output \nData\n to a \nString\n as such:\n\n\nguard\n \nlet\n \nstring\n \n=\n \nString\n(\nbytes\n:\n \nresult\n,\n \nencoding\n:\n \n.\nutf8\n)\n \nelse\n \n{\n\n \n// This must never trigger\n\n\n}\n\n\n\n\n\n\nPBKDF2\n\u00b6\n\n\nPBKDF2 is an algorithm that is almost always (and in Vapor, exclusively) used with HMAC for message authentication.\n\n\nPBKDF2 can be paired up with any hashing algorithm and is simple to implement. PBKDF2 is used all over the world through the WPA2 standard, securing WiFi connections. But we still recommend PBKDF2 above any normal hashing function.\n\n\nFor PBKDF2 you also select the Hash using generics.\n\n\nDeriving a key\n\u00b6\n\n\nIn the following example:\n\n\n\n\npassword\n is either a \nString\n or \nData\n\n\nThe \nsalt\n is \nData\n\n\nIterations is defaulted to \n10_000\n iterations\n\n\nThe keySize is equivalent to 1 hash's length.\n\n\n\n\n// Generate a random salt\n\n\nlet\n \nsalt\n:\n \nData\n \n=\n \nOSRandom\n().\ndata\n(\ncount\n:\n \n32\n)\n\n\n\nlet\n \nhash\n \n=\n \ntry\n \nPBKDF2\n<\nSHA256\n>.\nderive\n(\nfromPassword\n:\n \npassword\n,\n \nsalt\n:\n \nsalt\n)\n\n\n\n\n\n\nYou can optionally configure PBKDF2 to use a different iteration count and output keysize.\n\n\n// Iterates 20'000 times and outputs 100 bytes\n\n\nlet\n \nhash\n \n=\n \ntry\n \nPBKDF2\n<\nSHA256\n>.\nderive\n(\nfromPassword\n:\n \npassword\n,\n \nsalt\n:\n \nsalt\n,\n \niterating\n:\n \n20_000\n,\n \nderivedKeyLength\n:\n \n100\n)\n\n\n\n\n\n\nStoring the results\n\u00b6\n\n\nWhen you're storing the PBKDF2 results, be sure to also store the Salt. Without the original salt, iteration count and other parameters you cannot reproduce the same hash for validation or authentication.", - "title": "Password hashing" - }, - { - "location": "/crypto/passwords/#password-hashing", - "text": "Password management is critical for good user security and doesn't need to cost a lot of effort. No software is perfect. Even if your software is perfect, other software on the same server likely isn't. Good password encryption security prevents users' passwords from leaking out in case of a hypothetical future data breach. For password hashing Vapor supports PBKDF2 and BCrypt. We recommend using BCrypt over PBKDF2 for almost all scenarios. Whilst PBKDF2 is a proven standard, it's much more easily brute-forced than BCrypt and is less future-proof.", - "title": "Password hashing" - }, - { - "location": "/crypto/passwords/#bcrypt", - "text": "BCrypt is an algorithm specifically designed for password hashing. It's easy to store and verify.", - "title": "BCrypt" - }, - { - "location": "/crypto/passwords/#deriving-a-key", - "text": "Unlike PBKDF2 you don't need to generate and store a salt, that's part of the BCrypt hashing and verification process. The output is a combination of the BCrypt \"cost\" factor, salt and resulting hash. Meaning that the derived output contains all information necessary for verification, simplifying the database access. let result : Data = try BCrypt . make ( message : \"MyPassword\" ) guard try BCrypt . verify ( message : \"MyPassword\" , matches : result ) else { \n fatalError ( \"This never triggers, since the verification process will always be successful for the same password and conditions\" ) } The default cost factor is 12 , based on the official recommendations.", - "title": "Deriving a key" - }, - { - "location": "/crypto/passwords/#storing-the-derived-key-as-a-string", - "text": "BCrypt always outputs valid ASCII/UTF-8 for the resulting hash. This means you can convert the output Data to a String as such: guard let string = String ( bytes : result , encoding : . utf8 ) else { \n // This must never trigger }", - "title": "Storing the derived key as a String" - }, - { - "location": "/crypto/passwords/#pbkdf2", - "text": "PBKDF2 is an algorithm that is almost always (and in Vapor, exclusively) used with HMAC for message authentication. PBKDF2 can be paired up with any hashing algorithm and is simple to implement. PBKDF2 is used all over the world through the WPA2 standard, securing WiFi connections. But we still recommend PBKDF2 above any normal hashing function. For PBKDF2 you also select the Hash using generics.", - "title": "PBKDF2" - }, - { - "location": "/crypto/passwords/#deriving-a-key_1", - "text": "In the following example: password is either a String or Data The salt is Data Iterations is defaulted to 10_000 iterations The keySize is equivalent to 1 hash's length. // Generate a random salt let salt : Data = OSRandom (). data ( count : 32 ) let hash = try PBKDF2 < SHA256 >. derive ( fromPassword : password , salt : salt ) You can optionally configure PBKDF2 to use a different iteration count and output keysize. // Iterates 20'000 times and outputs 100 bytes let hash = try PBKDF2 < SHA256 >. derive ( fromPassword : password , salt : salt , iterating : 20_000 , derivedKeyLength : 100 )", - "title": "Deriving a key" - }, - { - "location": "/crypto/passwords/#storing-the-results", - "text": "When you're storing the PBKDF2 results, be sure to also store the Salt. Without the original salt, iteration count and other parameters you cannot reproduce the same hash for validation or authentication.", - "title": "Storing the results" - }, - { - "location": "/crypto/random/", - "text": "Random\n\u00b6\n\n\nCrypto has two primary random number generators.\n\n\nOSRandom generates random numbers by calling the operating system's random number generator.\n\n\nURandom generates random numbers by reading from \n/dev/urandom\n.\n\n\nAccessing random numbers\n\u00b6\n\n\nFirst, create an instance of the preferred random number generator:\n\n\nlet\n \nrandom\n \n=\n \nOSRandom\n()\n\n\n\n\n\n\nor\n\n\nlet\n \nrandom\n \n=\n \ntry\n \nURandom\n()\n\n\n\n\n\n\nReading integers\n\u00b6\n\n\nFor every Swift integer a random number function exists.\n\n\nlet\n \nint8\n:\n \nInt8\n \n=\n \ntry\n \nrandom\n.\nmakeInt8\n()\n\n\nlet\n \nuint8\n:\n \nUInt8\n \n=\n \ntry\n \nrandom\n.\nmakeUInt8\n()\n\n\nlet\n \nint16\n:\n \nInt16\n \n=\n \ntry\n \nrandom\n.\nmakeInt16\n()\n\n\nlet\n \nuint16\n:\n \nUInt16\n \n=\n \ntry\n \nrandom\n.\nmakeUInt16\n()\n\n\nlet\n \nint32\n:\n \nInt32\n \n=\n \ntry\n \nrandom\n.\nmakeInt32\n()\n\n\nlet\n \nuint32\n:\n \nUInt32\n \n=\n \ntry\n \nrandom\n.\nmakeUInt32\n()\n\n\nlet\n \nint64\n:\n \nInt64\n \n=\n \ntry\n \nrandom\n.\nmakeInt64\n()\n\n\nlet\n \nuint64\n:\n \nUInt64\n \n=\n \ntry\n \nrandom\n.\nmakeUInt64\n()\n\n\nlet\n \nint\n:\n \nInt\n \n=\n \ntry\n \nrandom\n.\nmakeInt\n()\n\n\nlet\n \nuint\n:\n \nUInt\n \n=\n \ntry\n \nrandom\n.\nmakeUInt\n()\n\n\n\n\n\n\nReading random data\n\u00b6\n\n\nRandom buffers of data are useful when, for example, generating tokens or other unique strings/blobs.\n\n\nTo generate a buffer of random data:\n\n\n// generates 20 random bytes\n\n\nlet\n \ndata\n:\n \nData\n \n=\n \nrandom\n.\ndata\n(\ncount\n:\n \n20\n)", - "title": "Random" - }, - { - "location": "/crypto/random/#random", - "text": "Crypto has two primary random number generators. OSRandom generates random numbers by calling the operating system's random number generator. URandom generates random numbers by reading from /dev/urandom .", - "title": "Random" - }, - { - "location": "/crypto/random/#accessing-random-numbers", - "text": "First, create an instance of the preferred random number generator: let random = OSRandom () or let random = try URandom ()", - "title": "Accessing random numbers" - }, - { - "location": "/crypto/random/#reading-integers", - "text": "For every Swift integer a random number function exists. let int8 : Int8 = try random . makeInt8 () let uint8 : UInt8 = try random . makeUInt8 () let int16 : Int16 = try random . makeInt16 () let uint16 : UInt16 = try random . makeUInt16 () let int32 : Int32 = try random . makeInt32 () let uint32 : UInt32 = try random . makeUInt32 () let int64 : Int64 = try random . makeInt64 () let uint64 : UInt64 = try random . makeUInt64 () let int : Int = try random . makeInt () let uint : UInt = try random . makeUInt ()", - "title": "Reading integers" - }, - { - "location": "/crypto/random/#reading-random-data", - "text": "Random buffers of data are useful when, for example, generating tokens or other unique strings/blobs. To generate a buffer of random data: // generates 20 random bytes let data : Data = random . data ( count : 20 )", - "title": "Reading random data" - }, - { - "location": "/version/1_5/", - "text": "Redirecting...\n\u00b6", - "title": "1.5" - }, - { - "location": "/version/1_5/#redirecting", - "text": "", - "title": "Redirecting..." - }, - { - "location": "/version/2_0/", - "text": "Redirecting...\n\u00b6", - "title": "2.0" - }, - { - "location": "/version/2_0/#redirecting", - "text": "", - "title": "Redirecting..." - }, - { - "location": "/version/3_0/", - "text": "Redirecting...\n\u00b6", - "title": "3.0-alpha" - }, - { - "location": "/version/3_0/#redirecting", - "text": "", - "title": "Redirecting..." - }, - { - "location": "/version/support/", - "text": "Version Support\n\u00b6\n\n\n\n\nVapor 3.0 is currently in beta.\n\n\nVapor 2.0 is currently active.\n\n\nVapor 1.5 is being maintained until November 2017.\n\n\n\n\nWarning\n\n\nDashed blocks and lines represent the team's goals and are not yet guaranteed. \nVapor 3's release and the beginning of Vapor 2's maintenance phase have not yet been decided.\n\n\n\n\nCore\n\u00b6\n\n\nAll packages in the \nVapor GitHub\n are maintained according to the following rules.\n\n\nActive\n\u00b6\n\n\nWhile a version is active, reported security issues and bugs are fixed.\n\n\nAdditionally, new features and optimizations may be added. If new API is added, the minor version number will be incremented. At no point can existing API be removed or broken during an active version. Semver is strictly followed and tested.\n\n\nMaintenance\n\u00b6\n\n\nWhen a new version of Vapor is released, the previous version will enter a maintenance phase which lasts for six months. During this phase, critical security issues and bugs will be fixed. No new features will be added.\n\n\n\n\nNote\n\n\nOnly the latest minor version will be maintained.\n\n\n\n\nUnstable\n\u00b6\n\n\nThe master branch is the latest development version of Vapor and is constantly changing. Before a new version of Vapor is released, there may be unstable alpha and beta phases in which you are welcome to test and provide feedback on the changes.\n\n\nCommunity\n\u00b6\n\n\nAll packages in the \nVapor Community GitHub\n are maintained in strict accordance of semver. Violations of semver should be reported as issues to the offending package's GitHub page.", - "title": "Support" - }, - { - "location": "/version/support/#version-support", - "text": "Vapor 3.0 is currently in beta. Vapor 2.0 is currently active. Vapor 1.5 is being maintained until November 2017. Warning Dashed blocks and lines represent the team's goals and are not yet guaranteed. \nVapor 3's release and the beginning of Vapor 2's maintenance phase have not yet been decided.", - "title": "Version Support" - }, - { - "location": "/version/support/#core", - "text": "All packages in the Vapor GitHub are maintained according to the following rules.", - "title": "Core" - }, - { - "location": "/version/support/#active", - "text": "While a version is active, reported security issues and bugs are fixed. Additionally, new features and optimizations may be added. If new API is added, the minor version number will be incremented. At no point can existing API be removed or broken during an active version. Semver is strictly followed and tested.", - "title": "Active" - }, - { - "location": "/version/support/#maintenance", - "text": "When a new version of Vapor is released, the previous version will enter a maintenance phase which lasts for six months. During this phase, critical security issues and bugs will be fixed. No new features will be added. Note Only the latest minor version will be maintained.", - "title": "Maintenance" - }, - { - "location": "/version/support/#unstable", - "text": "The master branch is the latest development version of Vapor and is constantly changing. Before a new version of Vapor is released, there may be unstable alpha and beta phases in which you are welcome to test and provide feedback on the changes.", - "title": "Unstable" - }, - { - "location": "/version/support/#community", - "text": "All packages in the Vapor Community GitHub are maintained in strict accordance of semver. Violations of semver should be reported as issues to the offending package's GitHub page.", - "title": "Community" - } - ] -} \ No newline at end of file diff --git a/build/3.0/site/services/getting-started/index.html b/build/3.0/site/services/getting-started/index.html deleted file mode 100644 index 17fb972c..00000000 --- a/build/3.0/site/services/getting-started/index.html +++ /dev/null @@ -1,1500 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Services - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    - -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/stylesheets/extra.css b/build/3.0/site/stylesheets/extra.css deleted file mode 100644 index 27fff6f2..00000000 --- a/build/3.0/site/stylesheets/extra.css +++ /dev/null @@ -1,48 +0,0 @@ -.md-header { - box-shadow: 0px 0px 8px #657590!important; -} - -.md-main__inner { - padding-top: 0; -} - -.md-nav__button img { - width: 90%; -} - -.md-logo { - padding: 6px; -} - -.md-sidebar__inner .md-logo { - width: 60px!important; -} - -.md-header-nav__title { - line-height: 4.6rem; -} - -@media only screen and (min-width:75em) { - .md-header-nav__button img { - width: 28px!important; - height: 28px!important; - } - - .md-main__inner { - padding-top: 1rem; - } -} - -@media only screen and (min-width:100em) { - .md-header-nav__button img { - width: 32px!important; - height: 32px!important; - } -} - -@media only screen and (min-width:125em) { - .md-header-nav__button img { - width: 38px!important; - height: 38px!important; - } -} diff --git a/build/3.0/site/version/1_5/index.html b/build/3.0/site/version/1_5/index.html deleted file mode 100644 index 13aba0e1..00000000 --- a/build/3.0/site/version/1_5/index.html +++ /dev/null @@ -1,1506 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1.5 - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Redirecting...

    -

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/version/2_0/index.html b/build/3.0/site/version/2_0/index.html deleted file mode 100644 index 0ab40f72..00000000 --- a/build/3.0/site/version/2_0/index.html +++ /dev/null @@ -1,1506 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2.0 - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Redirecting...

    -

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/version/3_0/index.html b/build/3.0/site/version/3_0/index.html deleted file mode 100644 index 05032495..00000000 --- a/build/3.0/site/version/3_0/index.html +++ /dev/null @@ -1,1506 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 3.0-alpha - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Redirecting...

    -

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/version/support/index.html b/build/3.0/site/version/support/index.html deleted file mode 100644 index 427f442f..00000000 --- a/build/3.0/site/version/support/index.html +++ /dev/null @@ -1,1624 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Support - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    Version Support

    -

    Support Matrix

    -

    Vapor 3.0 is currently in beta.

    -

    Vapor 2.0 is currently active.

    -

    Vapor 1.5 is being maintained until November 2017.

    -
    -

    Warning

    -

    Dashed blocks and lines represent the team's goals and are not yet guaranteed. -Vapor 3's release and the beginning of Vapor 2's maintenance phase have not yet been decided.

    -
    -

    Core

    -

    All packages in the Vapor GitHub are maintained according to the following rules.

    -

    Active

    -

    While a version is active, reported security issues and bugs are fixed.

    -

    Additionally, new features and optimizations may be added. If new API is added, the minor version number will be incremented. At no point can existing API be removed or broken during an active version. Semver is strictly followed and tested.

    -

    Maintenance

    -

    When a new version of Vapor is released, the previous version will enter a maintenance phase which lasts for six months. During this phase, critical security issues and bugs will be fixed. No new features will be added.

    -
    -

    Note

    -

    Only the latest minor version will be maintained.

    -
    -

    Unstable

    -

    The master branch is the latest development version of Vapor and is constantly changing. Before a new version of Vapor is released, there may be unstable alpha and beta phases in which you are welcome to test and provide feedback on the changes.

    -

    Community

    -

    All packages in the Vapor Community GitHub are maintained in strict accordance of semver. Violations of semver should be reported as issues to the offending package's GitHub page.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/site/websocket/websocket/index.html b/build/3.0/site/websocket/websocket/index.html deleted file mode 100644 index cec6a6e0..00000000 --- a/build/3.0/site/websocket/websocket/index.html +++ /dev/null @@ -1,1740 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - WebSocket - Vapor Docs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - -

    WebSocket

    -

    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 Clients and server-side sockets.

    -

    Server side Sockets

    -

    Vapor 3 adds a helper to routing that helps accepting clients.

    -
    import WebSocket
    -
    -routing.websocket("api/v1/websocket") { req, websocket in
    -  // set up the websocket
    -}
    -
    - - -

    Client side sockets

    -

    Connecting to a remote WebSocket is relatively painless. You can provide a URL and Vapor 3 will attempt to set up a connection.

    -

    For creating an SSL connection, however, a container must be provided.

    -
    -

    Warning

    -

    Vapor does not retain the WebSocket. You need to keep the WebSocket active by means of strong references and pings.

    -
    -
    let futureWebSocket = try WebSocket.connect(to: "ws://localhost/path", using: container) // Future<WebSocket>
    -
    - - -

    Using websockets

    -

    Sending strings

    -

    Sending a String using a WebSocket sends it to the remote.

    -
    webSocket.send(string: string)
    -
    - - -

    Receiving strings

    -

    String data can be read using the following function. Only one closure can read at a time.

    -
    webSocket.onString { string in
    -  // use the `String`
    -}
    -
    - - -

    Sending binary data

    -

    Sending a Data or ByteBuffer using a WebSocket sends it to the remote.

    -
    webSocket.send(bytes: byteBuffer)
    -webSocket.send(data: data)
    -
    - - -

    Receiving binary data

    -

    Binary data can be read as a ByteBuffer using the following function. Only one closure can read at a time.

    -
    webSocket.onByteBuffer { byteBuffer in
    -  // use the `ByteBuffer`
    -}
    -
    - - -

    Binary data can also, instead, be used as Foundation's Data. This is less efficient than ByteBuffer but is often easier to use.

    -
    webSocket.onData { data in
    -  // use the `Data`
    -}
    -
    - - -

    Setting a listener will override all previous listeners. You need to split into multiple listeners manually.

    -

    On close

    -

    When requesting data using the onString, onByteBuffer and/or onData functions, you'll receive a handle to that stream. -This can be used for catching errors and detecting the websocket being closed.

    -
    webSocket.onString { websocket, string in
    -    print(string)
    -}.catch { error in
    -    print("Error occurred: \(error)")
    -}.finally {
    -    print("closed")
    -}
    -
    - - -

    Errors

    -

    Any error in a WebSocket will close the connection. This notification will be received on the binary and text streams.

    - - - - - - - -
    -
    -
    -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/3.0/sitemap.xml b/build/3.0/sitemap.xml index 329f6968..44cb1c65 100644 --- a/build/3.0/sitemap.xml +++ b/build/3.0/sitemap.xml @@ -4,7 +4,7 @@ / - 2018-01-02 + 2018-01-03 daily @@ -13,13 +13,13 @@ /install/macos/ - 2018-01-02 + 2018-01-03 daily /install/ubuntu/ - 2018-01-02 + 2018-01-03 daily @@ -29,67 +29,67 @@ /getting-started/hello-world/ - 2018-01-02 + 2018-01-03 daily /getting-started/toolbox/ - 2018-01-02 + 2018-01-03 daily /getting-started/spm/ - 2018-01-02 + 2018-01-03 daily /getting-started/xcode/ - 2018-01-02 + 2018-01-03 daily /getting-started/structure/ - 2018-01-02 + 2018-01-03 daily /getting-started/application/ - 2018-01-02 + 2018-01-03 daily /getting-started/controllers/ - 2018-01-02 + 2018-01-03 daily /getting-started/routing/ - 2018-01-02 + 2018-01-03 daily /getting-started/content/ - 2018-01-02 + 2018-01-03 daily /getting-started/futures/ - 2018-01-02 + 2018-01-03 daily /getting-started/cloud/ - 2018-01-02 + 2018-01-03 daily @@ -99,25 +99,25 @@ /concepts/vapor/ - 2018-01-02 + 2018-01-03 daily /concepts/services/ - 2018-01-02 + 2018-01-03 daily /concepts/http/ - 2018-01-02 + 2018-01-03 daily /concepts/code-contributions/ - 2018-01-02 + 2018-01-03 daily @@ -127,31 +127,31 @@ /async/getting-started/ - 2018-01-02 + 2018-01-03 daily /async/futures/ - 2018-01-02 + 2018-01-03 daily /async/streams/ - 2018-01-02 + 2018-01-03 daily /async/eventloop/ - 2018-01-02 + 2018-01-03 daily /async/reactive/ - 2018-01-02 + 2018-01-03 daily @@ -161,61 +161,61 @@ /http/getting-started/ - 2018-01-02 + 2018-01-03 daily /http/body/ - 2018-01-02 + 2018-01-03 daily /http/client/ - 2018-01-02 + 2018-01-03 daily /http/cookies/ - 2018-01-02 + 2018-01-03 daily /http/headers/ - 2018-01-02 + 2018-01-03 daily /http/method/ - 2018-01-02 + 2018-01-03 daily /http/middleware/ - 2018-01-02 + 2018-01-03 daily /http/multipart/ - 2018-01-02 + 2018-01-03 daily /http/status/ - 2018-01-02 + 2018-01-03 daily /http/uri/ - 2018-01-02 + 2018-01-03 daily @@ -225,67 +225,67 @@ /fluent/getting-started/ - 2018-01-02 + 2018-01-03 daily /fluent/models/ - 2018-01-02 + 2018-01-03 daily /fluent/migrations/ - 2018-01-02 + 2018-01-03 daily /fluent/querying/ - 2018-01-02 + 2018-01-03 daily /fluent/query-builder/ - 2018-01-02 + 2018-01-03 daily /fluent/schema-builder/ - 2018-01-02 + 2018-01-03 daily /fluent/migration/ - 2018-01-02 + 2018-01-03 daily /fluent/relations/ - 2018-01-02 + 2018-01-03 daily /fluent/pivot/ - 2018-01-02 + 2018-01-03 daily /fluent/transaction/ - 2018-01-02 + 2018-01-03 daily /fluent/database/ - 2018-01-02 + 2018-01-03 daily @@ -311,19 +311,19 @@ /leaf/getting-started/ - 2018-01-02 + 2018-01-03 daily /leaf/basics/ - 2018-01-02 + 2018-01-03 daily /leaf/custom-tags/ - 2018-01-02 + 2018-01-03 daily @@ -333,31 +333,31 @@ /redis/getting-started/ - 2018-01-02 + 2018-01-03 daily /redis/basics/ - 2018-01-02 + 2018-01-03 daily /redis/custom-commands/ - 2018-01-02 + 2018-01-03 daily /redis/pub-sub/ - 2018-01-02 + 2018-01-03 daily /redis/pipeline/ - 2018-01-02 + 2018-01-03 daily @@ -366,7 +366,7 @@ /websocket/websocket/ - 2018-01-02 + 2018-01-03 daily @@ -374,7 +374,7 @@ /services/getting-started/ - 2018-01-02 + 2018-01-03 daily @@ -383,37 +383,37 @@ /crypto/getting-started/ - 2018-01-02 + 2018-01-03 daily /crypto/base64/ - 2018-01-02 + 2018-01-03 daily /crypto/hash/ - 2018-01-02 + 2018-01-03 daily /crypto/mac/ - 2018-01-02 + 2018-01-03 daily /crypto/passwords/ - 2018-01-02 + 2018-01-03 daily /crypto/random/ - 2018-01-02 + 2018-01-03 daily @@ -423,25 +423,25 @@ /version/1_5/ - 2018-01-02 + 2018-01-03 daily /version/2_0/ - 2018-01-02 + 2018-01-03 daily /version/3_0/ - 2018-01-02 + 2018-01-03 daily /version/support/ - 2018-01-02 + 2018-01-03 daily