├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_EN.md ├── analysis_options.yaml ├── example └── sparky_example.dart ├── lib ├── sparky.dart └── src │ ├── cache │ └── cache_manager.dart │ ├── errors │ └── sparky_error.dart │ ├── extensions │ └── http_request.dart │ ├── jwt │ └── auth_jwt.dart │ ├── logs │ └── logs.dart │ ├── pipeline │ └── pipeline.dart │ ├── response │ └── response.dart │ ├── route │ ├── route_base.dart │ ├── route_http.dart │ └── route_web_socket.dart │ ├── sparky_server.dart │ ├── sparky_server_base.dart │ └── types │ └── sparky_types.dart ├── pubspec.yaml └── test └── sparky_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # https://dart.dev/guides/libraries/private-files 2 | # Created by `dart pub` 3 | .dart_tool/ 4 | 5 | # Avoid committing pubspec.lock for library packages; see 6 | # https://dart.dev/guides/libraries/private-files#pubspeclock. 7 | pubspec.lock 8 | stress_test_todo 9 | logs.txt -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.1.1 2 | 3 | - update packages!!! 4 | 5 | ## 1.1.0 6 | 7 | - Cache Adicionado!!! 8 | 9 | ## 1.0.15 10 | 11 | - Foram adicionados os metodos de requisição web que faltavam. 12 | 13 | ## 1.0.14 14 | 15 | - export necessario para usar criar rotas com herança de route. 16 | 17 | ## 1.0.13 18 | 19 | - Correção de bug em websocket quando ela recebe uma request get,post,put ou delete. 20 | 21 | ## 1.0.12 22 | 23 | - Melhoria nos construtores de rotas. 24 | 25 | ## 1.0.11 26 | 27 | - Topico novo adicionado no pubspec, e pequenas correções em construtores e nomes. 28 | 29 | ## 1.0.10 30 | 31 | - Topicos adicionados no pubspec. 32 | 33 | ## 1.0.9 34 | 35 | - Melhoria na doc da Pagina inicial. 36 | 37 | ## 1.0.8 38 | 39 | - Mais opções de responses com contrutores com status prontos, melhoria no código de exemplo a parte do login jwt não estava de maneira adequada antes. 40 | 41 | ## 1.0.7 42 | 43 | - Melhoria na função de validação de rotas repetidas. 44 | 45 | ## 1.0.6 46 | 47 | - A vesão anterior foi com um commit a menos 😅 48 | 49 | ## 1.0.5 50 | 51 | - Melhoria no sistema de busca de rotas antes a cada request ele fazia um lop de complexidade O(N) para achar a rota correta e executada, agora todas rotas são pré-carregadas no início da função em um map e acessadas diretamente com a complexidade O(1) o que entrega mais performance é mais perceptível em casos de grandes números de rotas. 52 | 53 | ## 1.0.4 54 | 55 | - Melhoria no sistema de logs, agora ele não fecha o arquivo e reabre toda vez que precisar salvar uma nova informação. 56 | 57 | ## 1.0.3 58 | 59 | - Correção de exemplo. 60 | 61 | ## 1.0.2 62 | 63 | - Add doc english. 64 | 65 | ## 1.0.1 66 | 67 | - Uptade doc. 68 | 69 | ## 1.0.0 70 | 71 | - Initial version. 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Sparky 2 | 3 | [Switch to English](README_EN.md) 4 | 5 | Sparky é pacote que ajuda na construção de apis rest de forma simples com suporte a websocket a autenticação jwt. 6 | 7 | ## Features 8 | 9 | - Cache automático nas rotas 10 | - Sistema de logs. 11 | - Suporte a webSocket. 12 | - Autenticação JWT. 13 | - Pipeline antes e depois do middleware principal. 14 | 15 | ## Como Usar 16 | 17 | ## Criando uma rota simples 18 | 19 | você pode usar esse construtor personalizado para aceitar apenas metodo get ou pode 20 | usar o construtor normal e personalizar. 21 | 22 | ```dart 23 | import 'dart:io'; 24 | import 'package:sparky/sparky.dart'; 25 | 26 | void main(){ 27 | // Criação da rota passando um middleware que recebe todos os dados da request, e precisa retornar uma response. 28 | 29 | final route1 = RouteHttp.get('/teste', middleware: (request) async { 30 | return Response.ok(body: 'Olá mundo'); 31 | }); 32 | 33 | // inicialização do Sparky passando uma lista de rotas. 34 | Sparky.server(routes: [route1]); 35 | } 36 | ``` 37 | 38 | ## Criando uma rota apartir de uma classe 39 | 40 | ao criar uma rota apartir de uma classe você pode definir se será uma rota de websocket ou não 41 | pode definir se ela vai aceitar somente um metodo get ou outros. 42 | 43 | ```dart 44 | import 'dart:io'; 45 | import 'package:sparky/sparky.dart'; 46 | 47 | final class RouteTest extends Route { 48 | RouteTest() 49 | : super('/test', middleware: (request) async { 50 | return Response.ok(body: 'test'); 51 | }, acceptedMethods: [ 52 | AcceptedMethods.get, 53 | AcceptedMethods.post, 54 | ]); 55 | } 56 | 57 | final class RouteSocket extends Route { 58 | RouteSocket() 59 | : super('/socket', middlewareWebSocket: (WebSocket webSocket) async { 60 | webSocket.listen((event) { 61 | print(event); 62 | }, onDone: () { 63 | webSocket.close(); 64 | }); 65 | }); 66 | } 67 | 68 | void main(){ 69 | // inicialização do Sparky passando uma lista de rotas. 70 | Sparky.server(routes: [RouteTest(),RouteSocket()]); 71 | } 72 | ``` 73 | 74 | ## Como personalizar o ip e porta 75 | 76 | ```dart 77 | import 'dart:io'; 78 | import 'package:sparky/sparky.dart'; 79 | void main(){ 80 | Sparky.server( 81 | routes: [...], 82 | ip: '0.0.0.0', 83 | port: 8080, 84 | ); 85 | } 86 | ``` 87 | 88 | ## Como Criar pipeline 89 | 90 | Você pode criar Adicionar N middlewares nas pipilenes . 91 | 92 | ```dart 93 | import 'dart:io'; 94 | import 'package:sparky/sparky.dart'; 95 | void main(){ 96 | Sparky.server( 97 | routes: [...], 98 | // Executa depois de executar a rota. 99 | pipelineAfter: Pipeline()..add((request)async => null)..add((request)async => null), 100 | // Executa antes de executar a rota, pode retornar null ou uma Response, se for retornado uma response ele não executa a rota principal. 101 | pipelineBefore: Pipeline()..add((request) async { 102 | ...... 103 | }), 104 | ); 105 | } 106 | ``` 107 | 108 | ## Sistema de logs 109 | 110 | Ele por padrão implicitamente tem essa configuração mas você pode mudar esse enum para mostrar só logs de erros, pode escolher entre só mostrar ou salvar em um arquivo 'logs.txt' 111 | 112 | ```dart 113 | import 'dart:io'; 114 | import 'package:sparky/sparky.dart'; 115 | void main(){ 116 | Sparky.server( 117 | routes: [...], 118 | logConfig: LogConfig.showAndWriteLogs, 119 | logType: LogType.all 120 | ); 121 | } 122 | ``` 123 | 124 | ## Como usar WebSockets 125 | 126 | Uma rota webSocket é uma Criada com essa classe RouteWebSocket e passada na lista de rotas como todas as outras, ela recebe um socket onde você pode ouvir e lidar com todos dados enviados e recebidos no socket. 127 | 128 | ```dart 129 | import 'dart:io'; 130 | import 'package:sparky/sparky.dart'; 131 | void main(){ 132 | final websocket = RouteWebSocket( 133 | '/test', 134 | middlewareWebSocket: (WebSocket socket) async { 135 | socket.add('Hello World'); 136 | socket.listen( 137 | (event) { 138 | print(event); 139 | }, 140 | onDone: () { 141 | socket.close(); 142 | }, 143 | ); 144 | }, 145 | ); 146 | Sparky.server( 147 | routes: [websocket], 148 | ); 149 | } 150 | ``` 151 | 152 | ## Como fazer um login simples com JWT 153 | 154 | Aqui é gerado um token e antes de cada request ele verifica se a requisição é para rota de login e ignora caso seja true, caso contrario ele verifica o token de login e retorna null, isso deixa ele ir para a rota principal, caso contrario ele retorna a uma resposta de não autorizado. 155 | 156 | ```dart 157 | import 'dart:io'; 158 | import 'package:sparky/sparky.dart'; 159 | void main(){ 160 | final authJwt = AuthJwt(secretKey: 'secretKey'); 161 | 162 | final login = RouteHttp.get('/login', middleware: (HttpRequest request) async { 163 | final token = authJwt.generateToken({'username': 'username'}); 164 | return Response.ok(body: '{"token":"$token"}'); 165 | }); 166 | 167 | Sparky.server( 168 | routes: [login], 169 | pipelineBefore: Pipeline() 170 | ..add((HttpRequest request) async { 171 | if (request.requestedUri.path == '/login') { 172 | return null; 173 | } else { 174 | if (request.headers['token'] != null) { 175 | if (request.headers['token'] != null && 176 | authJwt.verifyToken(request.headers['token']!.first)) { 177 | return null; 178 | } else { 179 | return Response.unauthorized(body: 'Não autorizado'); 180 | } 181 | } else { 182 | return Response.unauthorized(body: 'Envie o token no header'); 183 | } 184 | } 185 | }), 186 | ); 187 | } 188 | ``` 189 | 190 | ## Como compilar para usar da maneira mais performática 191 | 192 | O dart é uma linguagem compilada que compila para qualquer plataforma, com o comando abaixo passando o arquivo do seu projeto você vai conseguir um executável muito mais performático. 193 | 194 | ```bash 195 | dart compile exe main.dart 196 | ``` 197 | 198 | ## Como funciona o cache 199 | 200 | por padrão depois que uma rota rodar ela já vai ter cache e sempre retornara a mesma resonse, 201 | para que o código dessa rota volte a funcionar e ele entregue um valor diferente é preciso chamar a função 202 | 'onUpdate' isso diz de forma explicita que não é para usar o cache e ele vai rodar o código da rota normalmente, 203 | assim fica mais fácil de trabalhar com cache você pode adicionar no sistema de pipeline uma lógica para controlar se 204 | o chace deve ser usado ou não, isso te permite controlar o cache de cada rota diretamente de maneira fácil. 205 | 206 | ```dart 207 | import 'dart:io'; 208 | import 'package:sparky/sparky.dart'; 209 | void main(){ 210 | 211 | 212 | final random = 213 | RouteHttp.get('/random', middleware: (HttpRequest request) async { 214 | final value = Random().nextInt(100); 215 | return Response.ok(body: '{value:$value}'); 216 | }); 217 | 218 | Sparky.server( 219 | routes: [login], 220 | pipelineBefore: Pipeline() 221 | ..add((HttpRequest request) async { 222 | random.onUpdate(); 223 | }), 224 | ); 225 | } 226 | ``` 227 | 228 | ## Visão para o futuro 229 | 230 | A ideia é sempre mante-lo simples, não adicionar complexidade a ideia é nas próximas atualizações deixar maneiras simples de fazer determinadas rotas rodar em uma isolates separada a partir de uma flag, para conseguir uma performance maior e adicionar testes, o projeto é totalmente código aberto e contribuições são muito bem vindas. 231 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # Welcome to Sparky 2 | 3 | [Switch to Portuguese](README.md) 4 | 5 | Sparky is a package that helps in building rest apis in a simple way with websocket support with jwt authentication. 6 | 7 | ## Characteristics 8 | 9 | - Automatic caching on routes 10 | - Records system. 11 | - WebSocket support. 12 | - JWT authentication. 13 | - Pipeline before and after the main middleware. 14 | 15 | ## How to use 16 | 17 | ## Creating a simple route 18 | 19 | You can use this custom constructor to accept only the GET method, or you can use the normal constructor and customize it. 20 | 21 | ```dart 22 | import 'dart:io'; 23 | import 'package:sparky/sparky.dart'; 24 | 25 | void main(){ 26 | // Creation of the route passing a middleware that receives all the request data and needs to return a response. 27 | 28 | final route1 = RouteHttp.get('/teste', middleware: (request) async { 29 | return Response.ok(body: 'Olá mundo'); 30 | }); 31 | 32 | // Initialization of Sparky by passing a list of routes. 33 | Sparky.server(routes: [route1]); 34 | } 35 | ``` 36 | 37 | ## Creating a route from a class 38 | 39 | When creating a route from a class, you can define whether it will be a WebSocket route or not, and you can specify if it will accept only the GET method or others. 40 | 41 | ```dart 42 | import 'dart:io'; 43 | import 'package:sparky/sparky.dart'; 44 | 45 | final class RouteTest extends Route { 46 | RouteTest() 47 | : super('/test', middleware: (request) async { 48 | return Response.ok(body: 'test'); 49 | }, acceptedMethods: [ 50 | AcceptedMethods.get, 51 | AcceptedMethods.post, 52 | ]); 53 | } 54 | 55 | final class RouteSocket extends Route { 56 | RouteSocket() 57 | : super('/socket', middlewareWebSocket: (WebSocket webSocket) async { 58 | webSocket.listen((event) { 59 | print(event); 60 | }, onDone: () { 61 | webSocket.close(); 62 | }); 63 | }); 64 | } 65 | 66 | void main(){ 67 | // Initialization of Sparky by passing a list of routes. 68 | Sparky.server(routes: [RouteTest(),RouteSocket()]); 69 | } 70 | ``` 71 | 72 | ## How to customize the ip and port 73 | 74 | ```dart 75 | import 'dart:io'; 76 | import 'package:sparky/sparky.dart'; 77 | void main(){ 78 | Sparky.server( 79 | routes: [...], 80 | ip: '0.0.0.0', 81 | port: 8080, 82 | ); 83 | } 84 | ``` 85 | 86 | ## How to Create pipeline 87 | 88 | You can create Add N middlewares in pipilenes. 89 | 90 | ```dart 91 | import 'dart:io'; 92 | import 'package:sparky/sparky.dart'; 93 | void main(){ 94 | Sparky.server( 95 | routes: [...], 96 | // Execute after executing the route. 97 | pipelineAfter: Pipeline()..add((request)async => null)..add((request)async => null), 98 | // Executed before executing the route, it may return null or a Response, if a response is returned it does not execute the main route. 99 | pipelineBefore: Pipeline()..add((request) async { 100 | ...... 101 | }), 102 | ); 103 | } 104 | ``` 105 | 106 | ## Records system 107 | 108 | By default it implicitly has this configuration, but you can change this enum to only show error logs, you can choose between just showing or saving in a 'logs.txt' file 109 | 110 | ```dart 111 | import 'dart:io'; 112 | import 'package:sparky/sparky.dart'; 113 | void main(){ 114 | Sparky.server( 115 | routes: [...], 116 | logConfig: LogConfig.showAndWriteLogs, 117 | logType: LogType.all 118 | ); 119 | } 120 | ``` 121 | 122 | ## How to use WebSockets 123 | 124 | A webSocket route is one created with this RouteWebSocket class and passed in the route list like all others, it is assigned a socket where you can listen and handle all data sent and received on the socket. 125 | 126 | ```dart 127 | import 'dart:io'; 128 | import 'package:sparky/sparky.dart'; 129 | void main(){ 130 | final websocket = RouteWebSocket( 131 | '/test', 132 | middlewareWebSocket: (WebSocket socket) async { 133 | socket.add('Hello World'); 134 | socket.listen( 135 | (event) { 136 | print(event); 137 | }, 138 | onDone: () { 139 | socket.close(); 140 | }, 141 | ); 142 | }, 143 | ); 144 | Sparky.server( 145 | routes: [websocket], 146 | ); 147 | } 148 | ``` 149 | 150 | ## How to do a simple login with JWT 151 | 152 | Here a token is generated and before each request it checks if the request is for login rotation and ignores it if it is true, otherwise it checks the login token and returns null, this lets it go to the main route, otherwise it returns an unauthorized response. 153 | 154 | ```dart 155 | import 'dart:io'; 156 | import 'package:sparky/sparky.dart'; 157 | void main(){ 158 | final authJwt = AuthJwt(secretKey: 'secretKey'); 159 | 160 | final login = RouteHttp.get('/login', middleware: (HttpRequest request) async { 161 | final token = authJwt.generateToken({'username': 'username'}); 162 | return Response.ok(body: '{"token":"$token"}'); 163 | }); 164 | 165 | Sparky.server( 166 | routes: [login], 167 | pipelineBefore: Pipeline() 168 | ..add((HttpRequest request) async { 169 | if (request.requestedUri.path == '/login') { 170 | return null; 171 | } else { 172 | if (request.headers['token'] != null) { 173 | if (request.headers['token'] != null && 174 | authJwt.verifyToken(request.headers['token']!.first)) { 175 | return null; 176 | } else { 177 | return Response.unauthorized(body: 'Não autorizado'); 178 | } 179 | } else { 180 | return Response.unauthorized(body: 'Envie o token no header'); 181 | } 182 | } 183 | }), 184 | ); 185 | } 186 | ``` 187 | 188 | ## How to compile to use in the most performant way 189 | 190 | Dart is a language compiled for any platform, like the command below, passing through your project file, you will get one that is possibly much more performant. 191 | 192 | ```bash 193 | dart compile exe main.dart 194 | ``` 195 | 196 | ## How does cache work 197 | 198 | By default, after a route runs, it will be cached and will always return the same response. For the code of this route to work again and deliver a different value, you need to call the 'onUpdate' function. This explicitly indicates not to use the cache, and it will run the route's code normally. This makes it easier to work with cache as you can add logic to the pipeline system to control whether the cache should be used or not, this allows you to control the cache of each route directly in an easy way. 199 | 200 | ```dart 201 | import 'dart:io'; 202 | import 'package:sparky/sparky.dart'; 203 | void main(){ 204 | 205 | 206 | final random = 207 | RouteHttp.get('/random', middleware: (HttpRequest request) async { 208 | final value = Random().nextInt(100); 209 | return Response.ok(body: '{value:$value}'); 210 | }); 211 | 212 | Sparky.server( 213 | routes: [login], 214 | pipelineBefore: Pipeline() 215 | ..add((HttpRequest request) async { 216 | random.onUpdate(); 217 | }), 218 | ); 219 | } 220 | ``` 221 | 222 | ## Vision for the future 223 | 224 | The idea is to always keep it simple, not add complexity, the idea is in the next updates to leave simple ways to make certain routes run in a separate isolate using a flag, to achieve greater performance and add tests, the project is completely code open and contributions are very welcome. 225 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the static analysis results for your project (errors, 2 | # warnings, and lints). 3 | # 4 | # This enables the 'recommended' set of lints from `package:lints`. 5 | # This set helps identify many issues that may lead to problems when running 6 | # or consuming Dart code, and enforces writing Dart using a single, idiomatic 7 | # style and format. 8 | # 9 | # If you want a smaller set of lints you can change this to specify 10 | # 'package:lints/core.yaml'. These are just the most critical lints 11 | # (the recommended set includes the core lints). 12 | # The core lints are also what is used by pub.dev for scoring packages. 13 | 14 | include: package:lints/recommended.yaml 15 | 16 | # Uncomment the following section to specify additional rules. 17 | 18 | # linter: 19 | # rules: 20 | # - camel_case_types 21 | 22 | # analyzer: 23 | # exclude: 24 | # - path/to/excluded/files/** 25 | 26 | # For more information about the core and recommended set of lints, see 27 | # https://dart.dev/go/core-lints 28 | 29 | # For additional information about configuring this file, see 30 | # https://dart.dev/guides/language/analysis-options 31 | -------------------------------------------------------------------------------- /example/sparky_example.dart: -------------------------------------------------------------------------------- 1 | // @author viniciusddrft 2 | 3 | import 'dart:io'; 4 | import 'dart:math'; 5 | import 'package:sparky/sparky.dart'; 6 | 7 | void main() { 8 | /// In this sample code, I will demonstrate the ways to create routes using custom classes 9 | /// that either inherit from Route or use the RouteHttp and RouteWebSocket classes directly. 10 | /// The example also illustrates a JWT login and the pipeline systems before and after 11 | /// the main route. 12 | 13 | final authJwt = AuthJwt(secretKey: 'senha'); 14 | 15 | final login = 16 | RouteHttp.post('/login', middleware: (HttpRequest request) async { 17 | final data = await request.getBodyParams(); 18 | 19 | final token = authJwt.generateToken( 20 | {'username': data['user'] ?? '', 'password': data['pass'] ?? ''}); 21 | 22 | return Response.ok(body: '{"token":"$token"}'); 23 | }); 24 | 25 | final todo = 26 | RouteHttp.get('/todo/list', middleware: (HttpRequest request) async { 27 | return Response.ok(body: '[0,1,2,3,4,5,6,7,8,9]'); 28 | }); 29 | 30 | final random = 31 | RouteHttp.get('/random', middleware: (HttpRequest request) async { 32 | final value = Random().nextInt(100); 33 | return Response.ok(body: '{value:$value}'); 34 | }); 35 | 36 | final web = RouteWebSocket( 37 | '/websocket', 38 | middlewareWebSocket: (WebSocket socket) async { 39 | socket.add('Hello Word'); 40 | socket.listen( 41 | (event) { 42 | print(event); 43 | }, 44 | onDone: () { 45 | socket.close(); 46 | }, 47 | ); 48 | }, 49 | ); 50 | 51 | Sparky.server( 52 | routes: [ 53 | login, 54 | todo, 55 | web, 56 | random, 57 | RouteTest(), 58 | RouteSocket(), 59 | ], 60 | pipelineBefore: Pipeline() 61 | ..add(((request) async { 62 | login.onUpdate(); 63 | 64 | if (request.uri.path == login.name) return null; 65 | 66 | final data = await request.getBodyParams(); 67 | 68 | if (data['auth'] != null && authJwt.verifyToken(data['auth']!)) { 69 | return null; 70 | } 71 | 72 | return Response.forbidden(body: 'envie o header'); 73 | })) 74 | ..add((request) async { 75 | if (request.uri.path == random.name) { 76 | random.onUpdate(); 77 | } 78 | return null; 79 | }), 80 | pipelineAfter: Pipeline() 81 | ..add((request) async { 82 | print('pipeline after 1 done'); 83 | return null; 84 | }) 85 | ..add((request) async { 86 | print('pipeline after 2 done'); 87 | return null; 88 | })); 89 | } 90 | 91 | /// creating routes in other ways with classes 92 | 93 | final class RouteTest extends Route { 94 | RouteTest() 95 | : super('/test', middleware: (request) async { 96 | return Response.ok(body: 'test'); 97 | }, acceptedMethods: [ 98 | AcceptedMethods.get, 99 | AcceptedMethods.post, 100 | ]); 101 | } 102 | 103 | final class RouteSocket extends Route { 104 | RouteSocket() 105 | : super('/socket', middlewareWebSocket: (WebSocket webSocket) async { 106 | webSocket.listen((event) { 107 | print(event); 108 | }, onDone: () { 109 | webSocket.close(); 110 | }); 111 | }); 112 | } 113 | -------------------------------------------------------------------------------- /lib/sparky.dart: -------------------------------------------------------------------------------- 1 | /// @author viniciusddrft 2 | /// Support for doing something awesome. 3 | /// More dartdocs go here. 4 | library; 5 | 6 | export 'src/sparky_server.dart'; 7 | export 'src/response/response.dart'; 8 | export 'src/route/route_http.dart'; 9 | export 'src/route/route_base.dart'; 10 | export 'src/route/route_web_socket.dart'; 11 | export 'src/jwt/auth_jwt.dart'; 12 | export 'src/types/sparky_types.dart'; 13 | export 'src/sparky_server_base.dart'; 14 | export 'src/extensions/http_request.dart'; 15 | -------------------------------------------------------------------------------- /lib/src/cache/cache_manager.dart: -------------------------------------------------------------------------------- 1 | /// Manages the caching mechanism for routes. 2 | /// 3 | /// This class provides methods to verify cache versions, retrieve cached 4 | /// responses, and save new cache entries. It ensures that the response for 5 | /// a route is served from cache if the version matches, enhancing performance 6 | /// by avoiding unnecessary processing. 7 | /// 8 | /// Usage: 9 | /// - `verifyVersionCache(route)`: Checks if the cache is valid for the given route. 10 | /// - `getCache(route)`: Retrieves the cached response for the given route. 11 | /// - `saveCache(route, response)`: Saves a new response to the cache for the given route. 12 | /// 13 | /// Author: viniciusddrft 14 | 15 | part of '../sparky_server_base.dart'; 16 | 17 | final class _CacheManager { 18 | final _cache = {}; 19 | 20 | /// Verifies if the cached version of the route matches the current version. 21 | /// 22 | /// Returns `true` if the cache is valid, otherwise `false`. 23 | bool verifyVersionCache(Route route) { 24 | return _cache.containsKey(route) && 25 | _cache[route]!.version == route.versionCache; 26 | } 27 | 28 | /// Retrieves the cached response for the given route. 29 | /// 30 | /// Assumes that the cache is valid. Ensure to call `verifyVersionCache` before using this method. 31 | Response getCache(Route route) { 32 | return _cache[route]!.response; 33 | } 34 | 35 | /// Saves a new response to the cache for the given route. 36 | /// 37 | /// Associates the route with the response and its current version. 38 | void saveCache(Route route, Response response) { 39 | _cache.addAll({ 40 | route: _CacheEntry(response: response, version: route.versionCache), 41 | }); 42 | } 43 | } 44 | 45 | /// Represents a cache entry containing the response and its version. 46 | /// 47 | /// This class is used internally by the `_CacheManager` to manage cache entries. 48 | final class _CacheEntry { 49 | final Response response; 50 | final int version; 51 | 52 | /// Constructs a `_CacheEntry` with the given response and version. 53 | const _CacheEntry({required this.response, required this.version}); 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/errors/sparky_error.dart: -------------------------------------------------------------------------------- 1 | /// Author: viniciusddrft 2 | /// 3 | /// This file defines the base class for Sparky errors and several specific error types. 4 | /// All internal errors within Sparky extend from the `SparkyError` base class. 5 | library; 6 | 7 | /// Base class for all internal Sparky errors. 8 | /// 9 | /// This class is used as the foundation for all specific error types in Sparky, 10 | /// implementing the `Exception` interface. 11 | sealed class SparkyError implements Exception {} 12 | 13 | /// Error thrown when Sparky is initialized with an empty set of routes. 14 | /// 15 | /// At least one route is required for Sparky to function properly. This error indicates 16 | /// that the provided routes list was empty. 17 | final class ErrorRouteEmpty implements SparkyError { 18 | @override 19 | String toString() => 'Exception: Sparky booted with empty routes'; 20 | } 21 | 22 | /// Error thrown when duplicate route names are detected during Sparky initialization. 23 | /// 24 | /// Sparky relies on each route having a unique name for correct operation. This error 25 | /// indicates that there were repeated route names in the provided routes list. 26 | final class RoutesRepeated implements SparkyError { 27 | @override 28 | String toString() => 29 | 'Exception: Sparky initialized with routes with repeated names'; 30 | } 31 | 32 | /// An unexpected error that occurs after duplicate route validation. 33 | /// 34 | /// This is a rare error that occurs if, after validating for duplicate routes, 35 | /// multiple routes or 'not found' errors remain unmapped. This indicates a logic 36 | /// flaw or unexpected state within Sparky's routing setup. 37 | final class SparkyUnexpectedError implements SparkyError { 38 | @override 39 | String toString() => 40 | 'Exception: This is an unusual error; it occurs after the validation of duplicate routes. Multiple routes and not found are still unmapped errors.'; 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/extensions/http_request.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | 5 | int _hash = 0; 6 | Map _cache = {}; 7 | 8 | extension RequestTools on HttpRequest { 9 | Future> getBodyParams() async { 10 | if (_hash == hashCode) return _cache; 11 | 12 | final content = await utf8.decoder.bind(this).join(); 13 | final Map values = {}; 14 | 15 | for (var param in _extractAllKeys(content)) { 16 | values[param] = _extractValue(content, param); 17 | } 18 | 19 | _cache = values; 20 | _hash = hashCode; 21 | return values; 22 | } 23 | } 24 | 25 | List _extractAllKeys(String input) { 26 | final pattern = RegExp( 27 | 'Content-Disposition: form-data; name="(.*?)"', 28 | multiLine: true, 29 | dotAll: true, 30 | ); 31 | 32 | return pattern 33 | .allMatches(input) 34 | .map((match) => match.group(1) ?? '') 35 | .toList(); 36 | } 37 | 38 | String _extractValue(String input, String param) { 39 | final pattern = RegExp( 40 | 'Content-Disposition: form-data; name="$param"s*\r?\n\r?\n(.*?)\r?\n', 41 | multiLine: true, 42 | dotAll: true, 43 | ); 44 | 45 | final match = pattern.firstMatch(input); 46 | return match != null ? match.group(1)?.trim() ?? '' : ''; 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/jwt/auth_jwt.dart: -------------------------------------------------------------------------------- 1 | /// Author: viniciusddrft 2 | /// 3 | /// This class provides the foundation for handling authentication using JWT tokens. 4 | /// It takes a secret key in the constructor, which is used to sign the JWT tokens. 5 | library; 6 | 7 | import 'dart:convert'; 8 | import 'dart:typed_data'; 9 | import 'package:crypto/crypto.dart'; 10 | 11 | final class AuthJwt { 12 | final String secretKey; 13 | 14 | /// Constructs an [AuthJwt] instance with the provided secret key. 15 | /// 16 | /// The [secretKey] is used for signing the JWT tokens. 17 | const AuthJwt({required this.secretKey}); 18 | 19 | /// Generates a JWT token from the given [payload]. 20 | /// 21 | /// The [payload] is a [Map] containing the login data. 22 | /// This function returns a signed JWT token as a [String]. 23 | String generateToken(Map payload) { 24 | final header = json.encode({'alg': 'HS256', 'typ': 'JWT'}); 25 | final encodedHeader = base64Url.encode(utf8.encode(header)); 26 | final encodedPayload = base64Url.encode(utf8.encode(json.encode(payload))); 27 | final signature = _hmacSha256('$encodedHeader.$encodedPayload', secretKey); 28 | return '$encodedHeader.$encodedPayload.$signature'; 29 | } 30 | 31 | /// Verifies the given JWT [token]. 32 | /// 33 | /// This function checks if the token is valid by comparing the signature 34 | /// with a newly computed HMAC SHA-256 signature. Returns `true` if the 35 | /// token is valid, otherwise `false`. 36 | bool verifyToken(String token) { 37 | final parts = token.split('.'); 38 | if (parts.length != 3) return false; 39 | final signature = _hmacSha256('${parts[0]}.${parts[1]}', secretKey); 40 | return parts[2] == signature; 41 | } 42 | 43 | /// Computes the HMAC SHA-256 signature for the given [input] using the [key]. 44 | /// 45 | /// This function returns the computed signature as a base64 URL-encoded [String]. 46 | String _hmacSha256(String input, String key) { 47 | final hmac = Hmac(sha256, utf8.encode(key)); 48 | final digest = hmac.convert(utf8.encode(input)); 49 | return base64Url.encode(Uint8List.fromList(digest.bytes)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/src/logs/logs.dart: -------------------------------------------------------------------------------- 1 | /// Author: viniciusddrft 2 | 3 | part of '../sparky_server.dart'; 4 | 5 | /// A mixin that provides logging functionalities for the Sparky server. 6 | /// 7 | /// This mixin allows the Sparky server to log information, errors, and request details 8 | /// based on the configuration settings. It can log messages to the console, write logs to a file, 9 | /// or both, depending on the `logConfig` and `logType` settings. 10 | base mixin Logs on SparkyBase { 11 | IOSink? _file; 12 | 13 | /// Opens the server log and logs the server startup information. 14 | /// 15 | /// This function logs the server's listening address and port to the console 16 | /// and/or a file based on the current logging configuration. 17 | void _openServerLog() { 18 | if (logType == LogType.all || logType == LogType.info) { 19 | if (logConfig == LogConfig.showLogs || 20 | logConfig == LogConfig.showAndWriteLogs) { 21 | print('-- info --> Listen on $ip:$port'); 22 | if (logConfig == LogConfig.writeLogs || 23 | logConfig == LogConfig.showAndWriteLogs) { 24 | _file = File('logs.txt').openWrite(mode: FileMode.append); 25 | _saveLogs('-- info --> Listen on $ip:$port'); 26 | } 27 | } 28 | } 29 | } 30 | 31 | /// Logs an error message. 32 | /// 33 | /// This function logs the provided error message to the console and/or a file 34 | /// based on the current logging configuration. 35 | void _errorServerLog(Object e) { 36 | if (logType == LogType.all || logType == LogType.errors) { 37 | if (logConfig == LogConfig.showLogs || 38 | logConfig == LogConfig.showAndWriteLogs) { 39 | print('-- error --> Message $e'); 40 | } 41 | if (logConfig == LogConfig.writeLogs || 42 | logConfig == LogConfig.showAndWriteLogs) { 43 | _saveLogs('-- error --> Message $e'); 44 | } 45 | } 46 | } 47 | 48 | /// Logs an HTTP request and its response. 49 | /// 50 | /// This function logs the HTTP request method, response status, request URI path, 51 | /// and the client's remote address to the console and/or a file based on the current logging configuration. 52 | void _requestServerLog(HttpRequest request, Response routeResponse) { 53 | if (logType == LogType.all || logType == LogType.info) { 54 | if (logConfig == LogConfig.showLogs || 55 | logConfig == LogConfig.showAndWriteLogs) { 56 | print( 57 | '-- info --> Method ${request.method} ${routeResponse.status} ${request.uri.path} from -> ${request.connectionInfo?.remoteAddress.host}'); 58 | } 59 | if (logConfig == LogConfig.writeLogs || 60 | logConfig == LogConfig.showAndWriteLogs) { 61 | _saveLogs( 62 | '-- info --> Method ${request.method} ${routeResponse.status} ${request.uri.path} from -> ${request.connectionInfo?.remoteAddress.host}:'); 63 | } 64 | } 65 | } 66 | 67 | /// Private function that saves log messages to a file. 68 | /// 69 | /// This function writes the provided [message] to the log file with a timestamp. 70 | void _saveLogs(String message) => 71 | _file?.write('${DateTime.now()}: $message\n'); 72 | } 73 | -------------------------------------------------------------------------------- /lib/src/pipeline/pipeline.dart: -------------------------------------------------------------------------------- 1 | /// Author: viniciusddrft 2 | /// 3 | /// This file defines the `Pipeline` class, which manages a list of middleware functions 4 | /// that can be executed before or after route processing in the Sparky server. 5 | 6 | part of '../sparky_server_base.dart'; 7 | 8 | /// A class representing a pipeline of middleware functions. 9 | /// 10 | /// The `Pipeline` class allows for the management and execution of multiple middleware functions. 11 | /// These functions can be configured to run either before or after the processing of a route. 12 | final class Pipeline { 13 | final List _mids = []; 14 | 15 | /// Returns the list of middleware functions in the pipeline. 16 | /// 17 | /// The list is modifiable, allowing for the dynamic addition of middleware functions. 18 | List get mids => _mids; 19 | 20 | /// Adds a middleware function to the pipeline. 21 | /// 22 | /// The [middleware] parameter is a function that conforms to the `MiddlewareNulable` type. 23 | /// This function will be executed as part of the pipeline. 24 | void add(MiddlewareNulable middleware) => _mids.add(middleware); 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/response/response.dart: -------------------------------------------------------------------------------- 1 | // Author: viniciusddrft 2 | 3 | import 'dart:io'; 4 | 5 | ///This class handles responses, with some constructors already configured with request code. 6 | final class Response { 7 | /// Request Success 8 | const Response.ok({required this.body, this.contentType}) 9 | : status = HttpStatus.ok; 10 | 11 | /// Request Not Found 12 | const Response.notFound({required this.body, this.contentType}) 13 | : status = HttpStatus.notFound; 14 | 15 | /// Request Method Not Allowed 16 | const Response.methodNotAllowed({required this.body, this.contentType}) 17 | : status = HttpStatus.methodNotAllowed; 18 | 19 | /// Request Bad Request 20 | const Response.badRequest({required this.body, this.contentType}) 21 | : status = HttpStatus.badRequest; 22 | 23 | /// Request Accepted 24 | const Response.accepted({required this.body, this.contentType}) 25 | : status = HttpStatus.accepted; 26 | 27 | /// Request Unauthorized 28 | const Response.unauthorized({required this.body, this.contentType}) 29 | : status = HttpStatus.unauthorized; 30 | 31 | /// Request Forbidden 32 | const Response.forbidden({required this.body, this.contentType}) 33 | : status = HttpStatus.forbidden; 34 | 35 | /// Request MovedTemporarily 36 | const Response.movedTemporarily({required this.body, this.contentType}) 37 | : status = HttpStatus.movedTemporarily; 38 | 39 | /// Request MovedPermanently 40 | const Response.movedPermanently({required this.body, this.contentType}) 41 | : status = HttpStatus.movedPermanently; 42 | 43 | /// Request AlreadyReported 44 | const Response.alreadyReported({required this.body, this.contentType}) 45 | : status = HttpStatus.alreadyReported; 46 | 47 | /// Request BadGateway 48 | const Response.badGateway({required this.body, this.contentType}) 49 | : status = HttpStatus.badGateway; 50 | 51 | /// Request Conflict 52 | const Response.conflict({required this.body, this.contentType}) 53 | : status = HttpStatus.conflict; 54 | 55 | /// Request ConnectionClosedWithoutResponse 56 | const Response.connectionClosedWithoutResponse( 57 | {required this.body, this.contentType}) 58 | : status = HttpStatus.connectionClosedWithoutResponse; 59 | 60 | /// Request Continue_ 61 | const Response.continue_({required this.body, this.contentType}) 62 | : status = HttpStatus.continue_; 63 | 64 | /// Request Created 65 | const Response.created({required this.body, this.contentType}) 66 | : status = HttpStatus.created; 67 | 68 | /// Request ExpectationFailed 69 | const Response.expectationFailed({required this.body, this.contentType}) 70 | : status = HttpStatus.expectationFailed; 71 | 72 | /// Request FailedDependency 73 | const Response.failedDependency({required this.body, this.contentType}) 74 | : status = HttpStatus.failedDependency; 75 | 76 | /// Request Found 77 | const Response.found({required this.body, this.contentType}) 78 | : status = HttpStatus.found; 79 | 80 | /// Request GatewayTimeout 81 | const Response.gatewayTimeout({required this.body, this.contentType}) 82 | : status = HttpStatus.gatewayTimeout; 83 | 84 | /// Request Gone 85 | const Response.gone({required this.body, this.contentType}) 86 | : status = HttpStatus.gone; 87 | 88 | /// Request HttpVersionNotSupported 89 | const Response.httpVersionNotSupported({required this.body, this.contentType}) 90 | : status = HttpStatus.httpVersionNotSupported; 91 | 92 | /// Request ImUsed 93 | const Response.imUsed({required this.body, this.contentType}) 94 | : status = HttpStatus.imUsed; 95 | 96 | /// Request InsufficientStorage 97 | const Response.insufficientStorage({required this.body, this.contentType}) 98 | : status = HttpStatus.insufficientStorage; 99 | 100 | /// Request InternalServerError 101 | const Response.internalServerError({required this.body, this.contentType}) 102 | : status = HttpStatus.internalServerError; 103 | 104 | /// Request LengthRequired 105 | const Response.lengthRequired({required this.body, this.contentType}) 106 | : status = HttpStatus.lengthRequired; 107 | 108 | /// Request Locked 109 | const Response.locked({required this.body, this.contentType}) 110 | : status = HttpStatus.locked; 111 | 112 | /// Request LoopDetected 113 | const Response.loopDetected({required this.body, this.contentType}) 114 | : status = HttpStatus.loopDetected; 115 | 116 | /// Request MisdirectedRequest 117 | const Response.misdirectedRequest({required this.body, this.contentType}) 118 | : status = HttpStatus.misdirectedRequest; 119 | 120 | /// Request MultiStatus 121 | const Response.multiStatus({required this.body, this.contentType}) 122 | : status = HttpStatus.multiStatus; 123 | 124 | /// Request MultipleChoices 125 | const Response.multipleChoices({required this.body, this.contentType}) 126 | : status = HttpStatus.multipleChoices; 127 | 128 | /// Request NetworkAuthenticationRequired 129 | const Response.networkAuthenticationRequired( 130 | {required this.body, this.contentType}) 131 | : status = HttpStatus.networkAuthenticationRequired; 132 | 133 | /// Request NetworkConnectTimeoutError 134 | const Response.networkConnectTimeoutError( 135 | {required this.body, this.contentType}) 136 | : status = HttpStatus.networkConnectTimeoutError; 137 | 138 | /// Request NoContent 139 | const Response.noContent({required this.body, this.contentType}) 140 | : status = HttpStatus.noContent; 141 | 142 | /// Request NonAuthoritativeInformation 143 | const Response.nonAuthoritativeInformation( 144 | {required this.body, this.contentType}) 145 | : status = HttpStatus.nonAuthoritativeInformation; 146 | 147 | /// Request NotAcceptable 148 | const Response.notAcceptable({required this.body, this.contentType}) 149 | : status = HttpStatus.notAcceptable; 150 | 151 | /// Request NotExtended 152 | const Response.notExtended({required this.body, this.contentType}) 153 | : status = HttpStatus.notExtended; 154 | 155 | /// Request NotImplemented 156 | const Response.notImplemented({required this.body, this.contentType}) 157 | : status = HttpStatus.notImplemented; 158 | 159 | /// Request NotModified 160 | const Response.notModified({required this.body, this.contentType}) 161 | : status = HttpStatus.notModified; 162 | 163 | /// Request PartialContent 164 | const Response.partialContent({required this.body, this.contentType}) 165 | : status = HttpStatus.partialContent; 166 | 167 | /// Request PaymentRequired 168 | const Response.paymentRequired({required this.body, this.contentType}) 169 | : status = HttpStatus.paymentRequired; 170 | 171 | /// Request PermanentRedirect 172 | const Response.permanentRedirect({required this.body, this.contentType}) 173 | : status = HttpStatus.permanentRedirect; 174 | 175 | /// Request PreconditionFailed 176 | const Response.preconditionFailed({required this.body, this.contentType}) 177 | : status = HttpStatus.preconditionFailed; 178 | 179 | /// Request Processing 180 | const Response.processing({required this.body, this.contentType}) 181 | : status = HttpStatus.processing; 182 | 183 | /// Request ProxyAuthenticationRequired 184 | const Response.proxyAuthenticationRequired( 185 | {required this.body, this.contentType}) 186 | : status = HttpStatus.proxyAuthenticationRequired; 187 | 188 | /// Request RequestEntityTooLarge 189 | const Response.requestEntityTooLarge({required this.body, this.contentType}) 190 | : status = HttpStatus.requestEntityTooLarge; 191 | 192 | /// Request RequestHeaderFieldsTooLarge 193 | const Response.requestHeaderFieldsTooLarge( 194 | {required this.body, this.contentType}) 195 | : status = HttpStatus.requestHeaderFieldsTooLarge; 196 | 197 | /// Request RequestTimeout 198 | const Response.requestTimeout({required this.body, this.contentType}) 199 | : status = HttpStatus.requestTimeout; 200 | 201 | /// Request RequestUriTooLong 202 | const Response.requestUriTooLong({required this.body, this.contentType}) 203 | : status = HttpStatus.requestUriTooLong; 204 | 205 | /// Request RequestedRangeNotSatisfiable 206 | const Response.requestedRangeNotSatisfiable( 207 | {required this.body, this.contentType}) 208 | : status = HttpStatus.requestedRangeNotSatisfiable; 209 | 210 | /// Request ResetContent 211 | const Response.resetContent({required this.body, this.contentType}) 212 | : status = HttpStatus.resetContent; 213 | 214 | /// Request SeeOther 215 | const Response.seeOther({required this.body, this.contentType}) 216 | : status = HttpStatus.seeOther; 217 | 218 | /// Request ServiceUnavailable 219 | const Response.serviceUnavailable({required this.body, this.contentType}) 220 | : status = HttpStatus.serviceUnavailable; 221 | 222 | /// Request SwitchingProtocols 223 | const Response.switchingProtocols({required this.body, this.contentType}) 224 | : status = HttpStatus.switchingProtocols; 225 | 226 | /// Request TemporaryRedirect 227 | const Response.temporaryRedirect({required this.body, this.contentType}) 228 | : status = HttpStatus.temporaryRedirect; 229 | 230 | /// Request TooManyRequests 231 | const Response.tooManyRequests({required this.body, this.contentType}) 232 | : status = HttpStatus.tooManyRequests; 233 | 234 | /// Request UnavailableForLegalReasons 235 | const Response.unavailableForLegalReasons( 236 | {required this.body, this.contentType}) 237 | : status = HttpStatus.unavailableForLegalReasons; 238 | 239 | /// Request UnprocessableEntity 240 | const Response.unprocessableEntity({required this.body, this.contentType}) 241 | : status = HttpStatus.unprocessableEntity; 242 | 243 | /// Request UnsupportedMediaType 244 | const Response.unsupportedMediaType({required this.body, this.contentType}) 245 | : status = HttpStatus.unsupportedMediaType; 246 | 247 | /// Request UpgradeRequired 248 | const Response.upgradeRequired({required this.body, this.contentType}) 249 | : status = HttpStatus.upgradeRequired; 250 | 251 | /// Request UseProxy 252 | const Response.useProxy({required this.body, this.contentType}) 253 | : status = HttpStatus.useProxy; 254 | 255 | /// Request VariantAlsoNegotiates 256 | const Response.variantAlsoNegotiates({required this.body, this.contentType}) 257 | : status = HttpStatus.variantAlsoNegotiates; 258 | 259 | final int status; 260 | final String body; 261 | final ContentType? contentType; 262 | } 263 | -------------------------------------------------------------------------------- /lib/src/route/route_base.dart: -------------------------------------------------------------------------------- 1 | // Author: viniciusddrft 2 | 3 | import 'package:sparky/sparky.dart'; 4 | 5 | /// Base class of a route in Spark; HTTP routes and WebSocket routes extend directly from it. 6 | base class Route { 7 | final String name; 8 | final Middleware? middleware; 9 | final MiddlewareWebSocket? middlewareWebSocket; 10 | final List? acceptedMethods; 11 | int _versionCache = 0; 12 | 13 | int get versionCache => _versionCache; 14 | 15 | Route(this.name, 16 | {this.middleware, 17 | this.middlewareWebSocket, 18 | this.acceptedMethods = const [ 19 | AcceptedMethods.get, 20 | AcceptedMethods.post, 21 | AcceptedMethods.put, 22 | AcceptedMethods.delete, 23 | AcceptedMethods.patch, 24 | AcceptedMethods.head, 25 | AcceptedMethods.options, 26 | AcceptedMethods.trace 27 | ]}); 28 | 29 | void onUpdate() { 30 | _versionCache += 1; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/route/route_http.dart: -------------------------------------------------------------------------------- 1 | // Author: viniciusddrft 2 | 3 | import '../types/sparky_types.dart'; 4 | import 'route_base.dart'; 5 | 6 | /// HTTP route class, already equipped with constructors to handle [Get], [Post], [Put], [Delete] 7 | /// [Patch], [Head], [Options], [Trace] but it's possible to customize and make your route work with more than one method. 8 | final class RouteHttp extends Route { 9 | RouteHttp(super.name, {required super.middleware}); 10 | 11 | RouteHttp.get(super.name, 12 | {required super.middleware, 13 | super.acceptedMethods = const [AcceptedMethods.get]}); 14 | 15 | RouteHttp.put(super.name, 16 | {required super.middleware, 17 | super.acceptedMethods = const [AcceptedMethods.put]}); 18 | 19 | RouteHttp.delete(super.name, 20 | {required super.middleware, 21 | super.acceptedMethods = const [AcceptedMethods.delete]}); 22 | 23 | RouteHttp.post(super.name, 24 | {required super.middleware, 25 | super.acceptedMethods = const [AcceptedMethods.post]}); 26 | 27 | RouteHttp.patch(super.name, 28 | {required super.middleware, 29 | super.acceptedMethods = const [AcceptedMethods.patch]}); 30 | 31 | RouteHttp.head(super.name, 32 | {required super.middleware, 33 | super.acceptedMethods = const [AcceptedMethods.head]}); 34 | 35 | RouteHttp.options(super.name, 36 | {required super.middleware, 37 | super.acceptedMethods = const [AcceptedMethods.options]}); 38 | 39 | RouteHttp.trace(super.name, 40 | {required super.middleware, 41 | super.acceptedMethods = const [AcceptedMethods.trace]}); 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/route/route_web_socket.dart: -------------------------------------------------------------------------------- 1 | // @author viniciusddrft 2 | 3 | import 'route_base.dart'; 4 | 5 | /// WebSocket route class 6 | final class RouteWebSocket extends Route { 7 | RouteWebSocket(super.name, {required super.middlewareWebSocket}); 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/sparky_server.dart: -------------------------------------------------------------------------------- 1 | // @author viniciusddrft 2 | 3 | import 'dart:async'; 4 | import 'dart:io'; 5 | import 'package:sparky/src/sparky_server_base.dart'; 6 | import 'package:sparky/src/types/sparky_types.dart'; 7 | import 'errors/sparky_error.dart'; 8 | import 'response/response.dart'; 9 | import 'route/route_base.dart'; 10 | 11 | part 'package:sparky/src/logs/logs.dart'; 12 | 13 | /// Main logic file of Sparky's operation. 14 | 15 | base class Sparky extends SparkyBase with Logs { 16 | Sparky.server({ 17 | required super.routes, 18 | super.port = 8080, 19 | super.ip = '0.0.0.0', 20 | super.routeNotFound, 21 | super.logConfig = LogConfig.showAndWriteLogs, 22 | super.logType = LogType.all, 23 | super.pipelineBefore, 24 | super.pipelineAfter, 25 | }) { 26 | _start(); 27 | _routeMap = {for (final route in routes) route.name: route}; 28 | } 29 | 30 | late final HttpServer _server; 31 | late final Map _routeMap; 32 | 33 | ///Private function checks for routes with duplicate names. 34 | bool _checkRepeatedRoutes(Iterable routes) { 35 | final checkedElements = {}; 36 | 37 | return routes.any((name) => !checkedElements.add(name)); 38 | } 39 | 40 | ///Private function that initializes Sparky. 41 | void _start() async { 42 | if (_checkRepeatedRoutes(routes.map((e) => e.name))) { 43 | throw RoutesRepeated(); 44 | } else if (routes.isEmpty) { 45 | throw ErrorRouteEmpty(); 46 | } 47 | 48 | _server = await HttpServer.bind(ip, port); 49 | 50 | _openServerLog(); 51 | 52 | _listenHttp( 53 | (HttpRequest request) async { 54 | final response = request.response; 55 | 56 | Response? pipelineBeforeResponse = 57 | await runPipeline(pipelineBefore, request); 58 | 59 | if (WebSocketTransformer.isUpgradeRequest(request) && 60 | _routeMap[request.uri.path] != null) { 61 | final websocket = await WebSocketTransformer.upgrade(request); 62 | 63 | _routeMap[request.uri.path]!.middlewareWebSocket!(websocket); 64 | } else { 65 | final Response routeResponse; 66 | 67 | if (pipelineBeforeResponse == null) { 68 | final route = _routeMap[request.uri.path]; 69 | if (route != null && 70 | cacheManager.verifyVersionCache(_routeMap[request.uri.path]!)) { 71 | routeResponse = 72 | cacheManager.getCache(_routeMap[request.uri.path]!); 73 | } else { 74 | routeResponse = await _internalHandler(request); 75 | if (route != null) { 76 | cacheManager.saveCache( 77 | _routeMap[request.uri.path]!, routeResponse); 78 | } 79 | } 80 | } else { 81 | routeResponse = pipelineBeforeResponse; 82 | } 83 | 84 | response 85 | ..headers.contentType = 86 | routeResponse.contentType ?? ContentType.json 87 | ..statusCode = routeResponse.status 88 | ..write(routeResponse.body); 89 | response.close(); 90 | 91 | await runPipeline(pipelineAfter, request); 92 | 93 | _requestServerLog(request, routeResponse); 94 | } 95 | }, 96 | onError: (e) { 97 | _errorServerLog(e); 98 | _file?.flush(); 99 | _file?.close(); 100 | }, 101 | onDone: () { 102 | _file?.flush(); 103 | _file?.close(); 104 | }, 105 | ); 106 | } 107 | 108 | /// Private function that handles executing the code for each route. 109 | Future _internalHandler(HttpRequest request) async { 110 | final Route? route = _routeMap[request.uri.path]; 111 | 112 | if (route != null) { 113 | final acceptedMethods = route.acceptedMethods?.map((e) => e.text); 114 | if (acceptedMethods != null && 115 | acceptedMethods.contains(request.method) && 116 | route.middleware != null) { 117 | return route.middleware!(request); 118 | } else { 119 | return Route( 120 | '/405', 121 | middleware: (request) async { 122 | return Response.notFound( 123 | body: "{'errorCode':'405','message':'Method Not Allowed'}"); 124 | }, 125 | ).middleware!(request); 126 | } 127 | } else { 128 | return Route('/404', middleware: (request) async { 129 | return await routeNotFound?.middleware!(request) ?? 130 | Response.notFound( 131 | body: "{'errorCode':'404','message':'Not Found'}"); 132 | }).middleware!(request); 133 | } 134 | } 135 | 136 | /// Private function that listens for HTTP requests. 137 | StreamSubscription _listenHttp( 138 | void Function(HttpRequest)? onData, 139 | {Function? onError, 140 | void Function()? onDone, 141 | bool? cancelOnError}) { 142 | return _server.listen(onData, 143 | onDone: onDone, onError: onError, cancelOnError: cancelOnError); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /lib/src/sparky_server_base.dart: -------------------------------------------------------------------------------- 1 | // @author viniciusddrft 2 | 3 | import 'dart:io'; 4 | import 'package:sparky/sparky.dart'; 5 | 6 | /// Main logic file of Sparky's operation. 7 | 8 | part 'package:sparky/src/pipeline/pipeline.dart'; 9 | part 'cache/cache_manager.dart'; 10 | 11 | base class SparkyBase { 12 | SparkyBase( 13 | {required this.routes, 14 | this.port = 8080, 15 | this.ip = '0.0.0.0', 16 | this.routeNotFound, 17 | this.logConfig = LogConfig.showAndWriteLogs, 18 | this.logType = LogType.all, 19 | this.pipelineAfter, 20 | this.pipelineBefore}); 21 | final List routes; 22 | final int port; 23 | final String ip; 24 | final Route? routeNotFound; 25 | final LogConfig logConfig; 26 | final LogType logType; 27 | final Pipeline? pipelineBefore, pipelineAfter; 28 | final cacheManager = _CacheManager(); 29 | 30 | Future runPipeline(Pipeline? pipeline, HttpRequest request) async { 31 | if (pipeline?.mids.isNotEmpty != null) { 32 | for (final mid in pipeline!.mids) { 33 | final response = await mid(request); 34 | if (response != null) { 35 | return response; 36 | } 37 | } 38 | } 39 | return null; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/types/sparky_types.dart: -------------------------------------------------------------------------------- 1 | // @author viniciusddrft 2 | 3 | import 'dart:io'; 4 | import 'package:sparky/src/response/response.dart'; 5 | 6 | /// In this file, there are all types used in Sparky. 7 | 8 | /// Normal middleware that receives an [HttpRequest] and returns a [Future]. 9 | typedef Middleware = Future Function(HttpRequest request); 10 | 11 | /// Nullable middleware that receives an [HttpRequest] and returns a [Future]. 12 | typedef MiddlewareNulable = Future Function(HttpRequest request); 13 | 14 | ///WebSocket middleware that receives a [WebSocket] and returns void. 15 | typedef MiddlewareWebSocket = Future Function(WebSocket webSocket); 16 | 17 | ///Enum with log configuration. 18 | enum LogConfig { showLogs, writeLogs, showAndWriteLogs, none } 19 | 20 | ///Enum with log types. 21 | enum LogType { errors, info, all, none } 22 | 23 | ///Enum with web request methods. 24 | enum AcceptedMethods { 25 | get('GET'), 26 | post('POST'), 27 | delete('DELETE'), 28 | put('PUT'), 29 | patch('PATCH'), 30 | head('HEAD'), 31 | options('OPTIONS'), 32 | trace('TRACE'); 33 | 34 | const AcceptedMethods(this.text); 35 | final String text; 36 | } 37 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sparky 2 | description: Sparky é pacote que ajuda na construção de apis rest de forma simples com suporte a websocket a autenticação jwt. 3 | version: 1.2.0 4 | repository: https://github.com/viniciusddrft/Sparky 5 | homepage: https://github.com/viniciusddrft/Sparky 6 | 7 | topics: 8 | - sparky 9 | - server 10 | - api 11 | - backend 12 | 13 | environment: 14 | sdk: ^3.6.0 15 | 16 | # Add regular dependencies here. 17 | dependencies: 18 | crypto: ^3.0.6 19 | # path: ^1.8.0 20 | 21 | dev_dependencies: 22 | lints: ^5.0.0 23 | test: ^1.25.8 24 | -------------------------------------------------------------------------------- /test/sparky_test.dart: -------------------------------------------------------------------------------- 1 | // @author viniciusddrft 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('Errors Tests', () { 6 | test('Error Routes Repeated', () {}); 7 | 8 | test('Error Routes Empty', () {}); 9 | }); 10 | } 11 | --------------------------------------------------------------------------------