Compare commits
1082 Commits
v0.8.2-can
...
use-query-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0b7a08db6 | ||
|
|
91d7a8a353 | ||
|
|
aa681ff50c | ||
|
|
1792cc6788 | ||
|
|
dc88bc68d2 | ||
|
|
37348f2595 | ||
|
|
bec3cd6cde | ||
|
|
a9c1171a14 | ||
|
|
7b7eaeee4b | ||
|
|
a96b8735cc | ||
|
|
b46a245f08 | ||
|
|
a5208e2b96 | ||
|
|
4fefbcbbb0 | ||
|
|
258c0491dd | ||
|
|
cc64243006 | ||
|
|
cb2b6a1777 | ||
|
|
8ec0d929d8 | ||
|
|
adfc529852 | ||
|
|
aebc79fe9c | ||
|
|
da6393c538 | ||
|
|
e51a002892 | ||
|
|
d73750be0c | ||
|
|
95781eb6ba | ||
|
|
afcd47569c | ||
|
|
f405b5b4df | ||
|
|
a0d7378642 | ||
|
|
60bba38919 | ||
|
|
901d1cad7e | ||
|
|
383034d1fe | ||
|
|
bd6f37a6b0 | ||
|
|
b1116b6052 | ||
|
|
85c91e2e2e | ||
|
|
0bad1f181b | ||
|
|
971e695b30 | ||
|
|
c0e1246dc0 | ||
|
|
bce9822d13 | ||
|
|
e41219944c | ||
|
|
fe0d958368 | ||
|
|
06248f6005 | ||
|
|
24b85b0108 | ||
|
|
7804d3ea77 | ||
|
|
6dca78a8ed | ||
|
|
92679cfa03 | ||
|
|
46cb60a962 | ||
|
|
012d146fd9 | ||
|
|
5015693ddd | ||
|
|
e2ab39ed75 | ||
|
|
64ced80f77 | ||
|
|
050c4f7127 | ||
|
|
86de3303bf | ||
|
|
31724c7b2a | ||
|
|
d7647ad2be | ||
|
|
0a3836b30b | ||
|
|
1fccb1dc19 | ||
|
|
7195aaea66 | ||
|
|
d1c4553ddd | ||
|
|
8ee5642fb6 | ||
|
|
7a5695105a | ||
|
|
d0008711c6 | ||
|
|
83dc0e3e2d | ||
|
|
89ab77a862 | ||
|
|
b59fbeb0a7 | ||
|
|
c012c75c87 | ||
|
|
a0615be285 | ||
|
|
4d7dc8680f | ||
|
|
9a735f6dd7 | ||
|
|
0b885c2f57 | ||
|
|
3bd030af73 | ||
|
|
9ea5d0f259 | ||
|
|
91886728cf | ||
|
|
6babea9c15 | ||
|
|
8e91c52a12 | ||
|
|
fb8a13b04c | ||
|
|
ac55bc453f | ||
|
|
fcb7884aef | ||
|
|
4ec37a5b0b | ||
|
|
68488e529f | ||
|
|
d006b0bcd9 | ||
|
|
ebc6d47832 | ||
|
|
50b597ef9f | ||
|
|
6a6f1f7b2c | ||
|
|
cfca59e6ea | ||
|
|
57cb14b1ba | ||
|
|
10a0bed302 | ||
|
|
9898c578b1 | ||
|
|
acc61d2bee | ||
|
|
7cc97a1e63 | ||
|
|
85fe7429a5 | ||
|
|
a3b5e87a96 | ||
|
|
96793d360c | ||
|
|
5d644632e7 | ||
|
|
8d2e7aba30 | ||
|
|
bbec5903d8 | ||
|
|
f99a982783 | ||
|
|
980a0a9ee2 | ||
|
|
25811cf04b | ||
|
|
05613e8559 | ||
|
|
780febd377 | ||
|
|
5619dcca97 | ||
|
|
f61ae6e3c6 | ||
|
|
92f67459df | ||
|
|
b17f9b8e70 | ||
|
|
c962b36f2f | ||
|
|
d69ca33094 | ||
|
|
9536caa073 | ||
|
|
f02cfcee00 | ||
|
|
7ab1d73f7b | ||
|
|
933e0f5847 | ||
|
|
771bd96706 | ||
|
|
2f07ae3e80 | ||
|
|
cda62ca155 | ||
|
|
7f5d1dcb4e | ||
|
|
817b760ab7 | ||
|
|
4ac527d29f | ||
|
|
b87506ae30 | ||
|
|
654d9378ad | ||
|
|
fbf9439415 | ||
|
|
4bae636085 | ||
|
|
837d3a3126 | ||
|
|
e5baf8c256 | ||
|
|
04ed4952e4 | ||
|
|
39ebadccc1 | ||
|
|
9636017262 | ||
|
|
2fc04d3f02 | ||
|
|
fb8a352349 | ||
|
|
a3a32bbbf4 | ||
|
|
d39a9e60a5 | ||
|
|
a6247fb570 | ||
|
|
9f22ff7525 | ||
|
|
cc3c1f1205 | ||
|
|
66d8aa48a7 | ||
|
|
c6ee429e61 | ||
|
|
b243da00ec | ||
|
|
38bc98755b | ||
|
|
a959ae5b58 | ||
|
|
7b102f654c | ||
|
|
4b293abc41 | ||
|
|
82fe3a0eb5 | ||
|
|
db7a3e5451 | ||
|
|
484f434d8e | ||
|
|
ddb434f847 | ||
|
|
103609cf0b | ||
|
|
8dbb0bbbdb | ||
|
|
58a68c83fd | ||
|
|
a2a5564a96 | ||
|
|
2aab361dbc | ||
|
|
a0f827a351 | ||
|
|
f4bda9f52f | ||
|
|
92abe2b0b9 | ||
|
|
62efe3f4ee | ||
|
|
17e08de16b | ||
|
|
31a88aca89 | ||
|
|
aa06d4bf1a | ||
|
|
26c8bfb215 | ||
|
|
2b0e12a08e | ||
|
|
0d8c0a16b6 | ||
|
|
a9bf1fd3a0 | ||
|
|
c0a392ac4e | ||
|
|
dcb5edc72c | ||
|
|
5b57277808 | ||
|
|
9ff878a107 | ||
|
|
87af792e41 | ||
|
|
a957d2f227 | ||
|
|
2efcb71d2e | ||
|
|
3a94cc10c1 | ||
|
|
afc77338ef | ||
|
|
948fad7996 | ||
|
|
295436c7e5 | ||
|
|
3f95ad3bbf | ||
|
|
396f7e551f | ||
|
|
2284dd8621 | ||
|
|
25d057dd90 | ||
|
|
9be0d74816 | ||
|
|
27974e4268 | ||
|
|
c700ee4b9a | ||
|
|
da2916fccf | ||
|
|
59c839c65f | ||
|
|
1341b10e57 | ||
|
|
da678523d8 | ||
|
|
34145212ad | ||
|
|
e4fd4a8990 | ||
|
|
682664bd0c | ||
|
|
384b847b39 | ||
|
|
db0cddf5ed | ||
|
|
01754dcbbd | ||
|
|
387ffc6a80 | ||
|
|
303fad2853 | ||
|
|
a1227ee37a | ||
|
|
b31e1ac245 | ||
|
|
4e79196222 | ||
|
|
9b57078d31 | ||
|
|
cb80788734 | ||
|
|
845d171721 | ||
|
|
5f57f3c9ea | ||
|
|
96a0fffa00 | ||
|
|
6d71a60154 | ||
|
|
dd5a6b8273 | ||
|
|
a3452937c3 | ||
|
|
6826920184 | ||
|
|
0dc9feeb1e | ||
|
|
12b5161918 | ||
|
|
e72b602600 | ||
|
|
a271fe9267 | ||
|
|
75758f8130 | ||
|
|
d48161d203 | ||
|
|
1682ce0474 | ||
|
|
bc7970690c | ||
|
|
d9c5299436 | ||
|
|
bc829d6bd4 | ||
|
|
ec71d2ad0d | ||
|
|
b18393bcab | ||
|
|
2eb26617bc | ||
|
|
95988755ed | ||
|
|
87ad97ce2b | ||
|
|
a58685229f | ||
|
|
2ad5b68eab | ||
|
|
8ac511aba3 | ||
|
|
12d93c7532 | ||
|
|
e53f1b21bc | ||
|
|
b0a4bed5e3 | ||
|
|
a4a672645c | ||
|
|
88f9b4b5e5 | ||
|
|
b62dce39dd | ||
|
|
2b9b79ca2d | ||
|
|
aba8492fc0 | ||
|
|
34f5b08101 | ||
|
|
322fc6d0a9 | ||
|
|
34ee44e0bf | ||
|
|
4517a675a5 | ||
|
|
6ce248f694 | ||
|
|
e68eb6d118 | ||
|
|
ab81073003 | ||
|
|
78410abe82 | ||
|
|
7d76f646d0 | ||
|
|
e97e964099 | ||
|
|
fa358cd328 | ||
|
|
5360a839d8 | ||
|
|
a7493258eb | ||
|
|
909f3b3392 | ||
|
|
688b05df78 | ||
|
|
ec301dc22e | ||
|
|
df5271c1f0 | ||
|
|
bad2e3758e | ||
|
|
23b36b83f8 | ||
|
|
94d9daf215 | ||
|
|
aae261c589 | ||
|
|
4e3261d776 | ||
|
|
74083f704c | ||
|
|
68e1abf727 | ||
|
|
1d3ca10164 | ||
|
|
0c1cd9cc3f | ||
|
|
e186f86389 | ||
|
|
6954f448ad | ||
|
|
656414369c | ||
|
|
760ab549c8 | ||
|
|
de37d606a3 | ||
|
|
e58dbf3486 | ||
|
|
6bb5b04199 | ||
|
|
85c2aa280d | ||
|
|
f8138f0d4d | ||
|
|
894bd7f06d | ||
|
|
ba7c8c5785 | ||
|
|
05431ec7b5 | ||
|
|
005904e863 | ||
|
|
c330651f35 | ||
|
|
8af274817d | ||
|
|
f06fd070ef | ||
|
|
b9d3f439e8 | ||
|
|
e49a6c29d1 | ||
|
|
0b5ca09c7f | ||
|
|
c6f480705d | ||
|
|
7c3adc460b | ||
|
|
e8da527325 | ||
|
|
39d7f092ec | ||
|
|
6732fa374b | ||
|
|
cceae6efa2 | ||
|
|
016af57a07 | ||
|
|
a9ea76e43c | ||
|
|
394d7df094 | ||
|
|
78007fc967 | ||
|
|
c1ac5b027e | ||
|
|
b6b898c9b3 | ||
|
|
f96bcb80d0 | ||
|
|
1194ffde82 | ||
|
|
24082d53e0 | ||
|
|
61bbc43497 | ||
|
|
3dcf2bffe2 | ||
|
|
653b93b089 | ||
|
|
1b663c0e8b | ||
|
|
5747642055 | ||
|
|
53b2e273b4 | ||
|
|
3ed4844d87 | ||
|
|
b6b37f4661 | ||
|
|
236415222d | ||
|
|
bd76ff6049 | ||
|
|
a51757c174 | ||
|
|
9d8f63df3d | ||
|
|
4890a7cf5b | ||
|
|
8089ec7d15 | ||
|
|
f6fd1d0285 | ||
|
|
f655410167 | ||
|
|
8f20b0729a | ||
|
|
f37e6e8ebc | ||
|
|
5a934c3211 | ||
|
|
b079c6e944 | ||
|
|
2f0ca0f549 | ||
|
|
555ea5bac7 | ||
|
|
75b63ed087 | ||
|
|
ded2e2a546 | ||
|
|
f70d8d9a15 | ||
|
|
4d54e89621 | ||
|
|
69b91223d7 | ||
|
|
8c5061ead5 | ||
|
|
19ffe20330 | ||
|
|
9819543f47 | ||
|
|
b42eec1b66 | ||
|
|
0ef8d16da5 | ||
|
|
37e1ff3427 | ||
|
|
63b0682f06 | ||
|
|
735d2f628b | ||
|
|
d2a3f1a0b1 | ||
|
|
b54cbadebe | ||
|
|
1126b139a9 | ||
|
|
db44295738 | ||
|
|
539f457207 | ||
|
|
383e856e33 | ||
|
|
7d05d4e4c4 | ||
|
|
1555d09a45 | ||
|
|
05bf388d8d | ||
|
|
41ad93b327 | ||
|
|
56b18e40df | ||
|
|
e2acb1a6a1 | ||
|
|
680c44180c | ||
|
|
158b6684a7 | ||
|
|
2a4a209de9 | ||
|
|
4f862f04b0 | ||
|
|
7eb7943b55 | ||
|
|
bf009c2e3e | ||
|
|
94ff1da4de | ||
|
|
a6f4ca0068 | ||
|
|
533e0cbaae | ||
|
|
25df2c0c29 | ||
|
|
c4d759304d | ||
|
|
a856304062 | ||
|
|
be7d0ee723 | ||
|
|
91aebf44bc | ||
|
|
6d45d52f7a | ||
|
|
c451a5d55e | ||
|
|
646e8ae0f6 | ||
|
|
28ded0c1b1 | ||
|
|
8c9de5a1de | ||
|
|
a3c468b779 | ||
|
|
6907e92d14 | ||
|
|
418173849b | ||
|
|
087230e800 | ||
|
|
1ccfc7e5ba | ||
|
|
cdbe9e3cef | ||
|
|
a254b70139 | ||
|
|
c3c0e55597 | ||
|
|
26f8e97b25 | ||
|
|
353e4efea6 | ||
|
|
ae934bc6b7 | ||
|
|
8ebb383764 | ||
|
|
8f7b064cee | ||
|
|
73fa209e9f | ||
|
|
3372307112 | ||
|
|
c1e771d8a7 | ||
|
|
0dc2891387 | ||
|
|
819929e2f6 | ||
|
|
e76f23dc3d | ||
|
|
33c6d8e5e1 | ||
|
|
f927aa05ee | ||
|
|
2efbe7bd51 | ||
|
|
c0e88cb509 | ||
|
|
cb8c268a14 | ||
|
|
6df771c86e | ||
|
|
dbef6f9389 | ||
|
|
f4eaebb52a | ||
|
|
4df2dfd5fc | ||
|
|
f20de5ad53 | ||
|
|
005edc089a | ||
|
|
ddab8d063d | ||
|
|
308744681b | ||
|
|
291b988c05 | ||
|
|
7fdd97bb7d | ||
|
|
02af10133c | ||
|
|
de01a52886 | ||
|
|
cbcbeefd40 | ||
|
|
7083da0297 | ||
|
|
83b08f00eb | ||
|
|
3e7bf85750 | ||
|
|
e9d626d495 | ||
|
|
bc9983a31e | ||
|
|
c798d39c4b | ||
|
|
0394ced3ff | ||
|
|
fc40c9dc23 | ||
|
|
728ef2d2a6 | ||
|
|
bc725db4ec | ||
|
|
6cd3bb32a7 | ||
|
|
14faf68a0b | ||
|
|
e7bdafc2c5 | ||
|
|
241a62e465 | ||
|
|
5118be3ddd | ||
|
|
eebe8bcdfb | ||
|
|
e4983c6f2e | ||
|
|
3d3bb20365 | ||
|
|
77f9e60a9c | ||
|
|
66111f74bf | ||
|
|
c2bb7728b2 | ||
|
|
d5c2fa15e0 | ||
|
|
f93208e75f | ||
|
|
660c7fbb5e | ||
|
|
bcb88c72ea | ||
|
|
b81a3dd314 | ||
|
|
51bdaa118a | ||
|
|
59cf61a26a | ||
|
|
876eb8327f | ||
|
|
018e462ce8 | ||
|
|
74cde8a871 | ||
|
|
bc9f0e9d85 | ||
|
|
683c251693 | ||
|
|
1d02efb21d | ||
|
|
f35da93d6d | ||
|
|
cd2e29b286 | ||
|
|
82e4f7fd95 | ||
|
|
99e64fa2e0 | ||
|
|
9b0dd2ccaa | ||
|
|
0235395e54 | ||
|
|
67ba766a87 | ||
|
|
85216555fc | ||
|
|
25ebbc919b | ||
|
|
312dc3a1a9 | ||
|
|
f31d2a9770 | ||
|
|
0199605d07 | ||
|
|
092775a3f2 | ||
|
|
aea6c88b1e | ||
|
|
846194a5b1 | ||
|
|
814ed2d59a | ||
|
|
41106588ff | ||
|
|
6f50d77756 | ||
|
|
99bf898cdc | ||
|
|
4ef7d81018 | ||
|
|
37ce99a37a | ||
|
|
2f3be902e4 | ||
|
|
968f1d0cb9 | ||
|
|
0b103bccca | ||
|
|
406b643f94 | ||
|
|
33c7bec41f | ||
|
|
3d827c8506 | ||
|
|
b04c6d7469 | ||
|
|
242e2eadee | ||
|
|
08303d337b | ||
|
|
be861c7919 | ||
|
|
092045e807 | ||
|
|
1e0f17c93a | ||
|
|
310079b3bc | ||
|
|
a81252aeb4 | ||
|
|
cc58c72d94 | ||
|
|
2162e1c6b4 | ||
|
|
d46d860338 | ||
|
|
c65360de09 | ||
|
|
9873fc22de | ||
|
|
e6954fbca8 | ||
|
|
048ba5f5cb | ||
|
|
bd1063a965 | ||
|
|
ae6b22f4f0 | ||
|
|
f45d2d5ee3 | ||
|
|
7bc8a249b4 | ||
|
|
742ff71a97 | ||
|
|
9291ae3b38 | ||
|
|
5a5656078b | ||
|
|
ea815e83fa | ||
|
|
869c00c950 | ||
|
|
a670693e9d | ||
|
|
5de91ad57b | ||
|
|
31899458de | ||
|
|
dce462ba53 | ||
|
|
5ebed4b05d | ||
|
|
13353793af | ||
|
|
3583a59aa8 | ||
|
|
1c5aee7c67 | ||
|
|
c87883dbe8 | ||
|
|
7d84561690 | ||
|
|
3f43ffd4fe | ||
|
|
1ac2092129 | ||
|
|
579807ff20 | ||
|
|
566e8be3c3 | ||
|
|
1b9eb77964 | ||
|
|
9f24ba10b2 | ||
|
|
f5237c31c4 | ||
|
|
3ddb3870b9 | ||
|
|
763252a5ed | ||
|
|
6a37f32322 | ||
|
|
48e27be1a7 | ||
|
|
90df4e8409 | ||
|
|
3b46d96ec8 | ||
|
|
13c5a9b802 | ||
|
|
e6ddebadf5 | ||
|
|
1bb4cf33ff | ||
|
|
36dfbe42f5 | ||
|
|
6c06f0b62c | ||
|
|
a83536be21 | ||
|
|
07f9e26827 | ||
|
|
c5e6221ebb | ||
|
|
2b0fe98cf5 | ||
|
|
58386ffe2c | ||
|
|
23fc27027a | ||
|
|
e4c00094e5 | ||
|
|
a357fd0445 | ||
|
|
c43967984b | ||
|
|
e47d947dc0 | ||
|
|
ffb54ec064 | ||
|
|
08abc33494 | ||
|
|
34722f952c | ||
|
|
4003b8ac01 | ||
|
|
160b5fc062 | ||
|
|
b722c39f79 | ||
|
|
8da7bd7cd4 | ||
|
|
712cb172eb | ||
|
|
d623d8cc33 | ||
|
|
5ca38ca6d8 | ||
|
|
db0128c971 | ||
|
|
856aa53843 | ||
|
|
f9c3f82b27 | ||
|
|
97d5f396de | ||
|
|
760fba77ab | ||
|
|
f83ce0bd72 | ||
|
|
7425952616 | ||
|
|
7fac0134b3 | ||
|
|
df46939fe3 | ||
|
|
9e88b11496 | ||
|
|
c427ae23d4 | ||
|
|
cb849c5ba9 | ||
|
|
6e777dd72a | ||
|
|
80ab4876f2 | ||
|
|
cd0bf1f970 | ||
|
|
5f5a5c8ef7 | ||
|
|
9a1e0d0de7 | ||
|
|
fb44203510 | ||
|
|
010057b34c | ||
|
|
575e862ae3 | ||
|
|
aed6b8875a | ||
|
|
10b6f859fd | ||
|
|
9ac856c0ee | ||
|
|
1ff7f36482 | ||
|
|
df150da37e | ||
|
|
eb7409c0b3 | ||
|
|
1411a1d366 | ||
|
|
ec95cb40de | ||
|
|
b502d7ea1e | ||
|
|
74bf2a9e4c | ||
|
|
1ee637c367 | ||
|
|
c08771b57e | ||
|
|
8a468e4f79 | ||
|
|
23a33f1c3d | ||
|
|
b3767861a2 | ||
|
|
65acfff0ff | ||
|
|
ab3fc26409 | ||
|
|
7b7039e0e3 | ||
|
|
e93f24452c | ||
|
|
63c9375331 | ||
|
|
adfb486004 | ||
|
|
5828736369 | ||
|
|
40a93ee62d | ||
|
|
8fa82c7661 | ||
|
|
141003df89 | ||
|
|
6ef7b8a2de | ||
|
|
6ade33b849 | ||
|
|
02d3aa8259 | ||
|
|
1ae2bb3ee3 | ||
|
|
d84c73d2bb | ||
|
|
89b55971f1 | ||
|
|
fd1856bc7b | ||
|
|
e6dbbababb | ||
|
|
3e2b5ddc8e | ||
|
|
1b974a0371 | ||
|
|
8bf9667a15 | ||
|
|
94bd4c166c | ||
|
|
4ad9f9bdc9 | ||
|
|
13e1526ef5 | ||
|
|
ab3021a371 | ||
|
|
3e506c1dce | ||
|
|
85356ca8e8 | ||
|
|
530ce851e4 | ||
|
|
d953ef795a | ||
|
|
7fcb0945a2 | ||
|
|
ab4670c21b | ||
|
|
1ae7bf77b2 | ||
|
|
63e3fe1ccb | ||
|
|
280a2b5c4f | ||
|
|
bf2734d907 | ||
|
|
07341c14d3 | ||
|
|
e150b67cf4 | ||
|
|
1a1722168c | ||
|
|
7f266b0c98 | ||
|
|
6e92a2dfde | ||
|
|
66cd1ec650 | ||
|
|
7c4916324e | ||
|
|
d747e34853 | ||
|
|
3afab440c8 | ||
|
|
e576e6332c | ||
|
|
60d0c9d0bf | ||
|
|
8f800d388b | ||
|
|
0fb3163ed5 | ||
|
|
a64cf7d62a | ||
|
|
ac78d9a6e1 | ||
|
|
46035af2b3 | ||
|
|
3ee531f221 | ||
|
|
294be124f2 | ||
|
|
b1b8b5f15e | ||
|
|
4f1d80f970 | ||
|
|
622016f4b7 | ||
|
|
ceb0262540 | ||
|
|
8e9f2d097e | ||
|
|
155dfaa4da | ||
|
|
ec0273f09d | ||
|
|
7a80f2be2f | ||
|
|
0821c019bd | ||
|
|
f6cc8d152e | ||
|
|
2921cb5a85 | ||
|
|
017c1ff813 | ||
|
|
eabe54c8ec | ||
|
|
ba14973ad3 | ||
|
|
e29786ba24 | ||
|
|
71e3e48f69 | ||
|
|
c0ef559eae | ||
|
|
60a338b730 | ||
|
|
b4039ff9af | ||
|
|
f51a0de5dc | ||
|
|
5a006fa89f | ||
|
|
1ecaa5ea76 | ||
|
|
5d0eb4e44e | ||
|
|
1756d57a39 | ||
|
|
2cf1b63895 | ||
|
|
b0c2a58a83 | ||
|
|
188b8d8ae8 | ||
|
|
fbd5f2815f | ||
|
|
898f39bf8e | ||
|
|
91f5056b9d | ||
|
|
a00a529387 | ||
|
|
b5d564fab8 | ||
|
|
be19dbccf8 | ||
|
|
b21df113a8 | ||
|
|
4e0abb6ca6 | ||
|
|
9cbec551f8 | ||
|
|
7531cf66f7 | ||
|
|
cf0d77e010 | ||
|
|
713f20d494 | ||
|
|
19293c1efb | ||
|
|
2466a6b98c | ||
|
|
de8e0a6808 | ||
|
|
8b9252c697 | ||
|
|
12c0f72243 | ||
|
|
5e43599338 | ||
|
|
1bf5bf0492 | ||
|
|
ff9f70daa8 | ||
|
|
89676e4ba8 | ||
|
|
679efef951 | ||
|
|
bcd9e8dbc9 | ||
|
|
8b0fa91233 | ||
|
|
e55e90c345 | ||
|
|
4e81c97b2b | ||
|
|
16b414bd3d | ||
|
|
8798c9fb3d | ||
|
|
9f91767246 | ||
|
|
a6878b35d0 | ||
|
|
39c3b27038 | ||
|
|
5cc54ab981 | ||
|
|
91034216a5 | ||
|
|
2c78ead4cd | ||
|
|
a0cf98bc47 | ||
|
|
6898ead0a5 | ||
|
|
c7843e8077 | ||
|
|
370486291a | ||
|
|
e26e04c603 | ||
|
|
1fa7d17587 | ||
|
|
27bc2e49c4 | ||
|
|
18beb6fb1d | ||
|
|
5f68bda686 | ||
|
|
81ec5c8528 | ||
|
|
0d0c808cc3 | ||
|
|
097a2196f3 | ||
|
|
c592b04c18 | ||
|
|
b0d61dc4f1 | ||
|
|
f5d7ab9005 | ||
|
|
26cc39322a | ||
|
|
9441dfc1f7 | ||
|
|
4231c9c97f | ||
|
|
9a72648ff8 | ||
|
|
2059009c1b | ||
|
|
44e32f0206 | ||
|
|
924d431c0d | ||
|
|
3ee9dd8bea | ||
|
|
fc6817520e | ||
|
|
b53ad3ea4e | ||
|
|
a999408fed | ||
|
|
984a10abef | ||
|
|
1a7274e5a6 | ||
|
|
3e9810e91a | ||
|
|
6aa2c5d471 | ||
|
|
3433411a01 | ||
|
|
1177e5c9f6 | ||
|
|
ca5b91c340 | ||
|
|
ba0aeae3e5 | ||
|
|
24162830ae | ||
|
|
d0015e3f88 | ||
|
|
6b2788923e | ||
|
|
3b5aa9006d | ||
|
|
8637ed63ba | ||
|
|
f78781c63b | ||
|
|
dc6ffed270 | ||
|
|
2686e1c2ea | ||
|
|
59c7fb9b01 | ||
|
|
b6b877e851 | ||
|
|
90a7624489 | ||
|
|
ade63c53fd | ||
|
|
08e6cd3d2e | ||
|
|
6839b55d1d | ||
|
|
528a7ec18b | ||
|
|
9a3f1a1241 | ||
|
|
10c1e0cfea | ||
|
|
68162b406c | ||
|
|
4cc80b8529 | ||
|
|
0ae3bd4afa | ||
|
|
2207889c6d | ||
|
|
338b390a78 | ||
|
|
c173b9d09d | ||
|
|
e1120a0449 | ||
|
|
b4551323c8 | ||
|
|
50a276f582 | ||
|
|
63a078be27 | ||
|
|
16ab5675e2 | ||
|
|
7509aca08d | ||
|
|
5320bc5211 | ||
|
|
d32a39993c | ||
|
|
5795eefaab | ||
|
|
28e6a0032a | ||
|
|
96fea67112 | ||
|
|
9005c58349 | ||
|
|
dea9200f9d | ||
|
|
b6e72cc5f5 | ||
|
|
5165793a4e | ||
|
|
1fe2653b4f | ||
|
|
740b57798f | ||
|
|
07cf6d6e26 | ||
|
|
21896944f9 | ||
|
|
62a5408e6e | ||
|
|
e1e82cb630 | ||
|
|
81c13df956 | ||
|
|
65acdc971c | ||
|
|
eb1c88ee1a | ||
|
|
d0fdb7b1f3 | ||
|
|
724c6921da | ||
|
|
31ecd58857 | ||
|
|
aee33f0354 | ||
|
|
baf3fb9b4f | ||
|
|
437ce18170 | ||
|
|
167abce895 | ||
|
|
ed23307267 | ||
|
|
436bd4e306 | ||
|
|
8ee9df51fd | ||
|
|
a421212e63 | ||
|
|
10c3b248e9 | ||
|
|
b5da9c2822 | ||
|
|
509110b1ea | ||
|
|
852fdc6016 | ||
|
|
69449ca6b9 | ||
|
|
35ff20bc78 | ||
|
|
000cf2d4bf | ||
|
|
20f1d99e7c | ||
|
|
960e225f0c | ||
|
|
d8449d4458 | ||
|
|
50e20b812c | ||
|
|
fa8616f8a3 | ||
|
|
362352379d | ||
|
|
580e2724ff | ||
|
|
19c3f13de9 | ||
|
|
53f3fbf7c7 | ||
|
|
7426fc5794 | ||
|
|
089ca908ed | ||
|
|
820c678533 | ||
|
|
abd0bbd7cf | ||
|
|
af68b9aa0a | ||
|
|
4ce2161ec7 | ||
|
|
2d9338a613 | ||
|
|
9e39c4229b | ||
|
|
ea58cc336b | ||
|
|
323bb28cc3 | ||
|
|
7f232be90f | ||
|
|
60d48c6f83 | ||
|
|
0753dae8ca | ||
|
|
d70cf53f16 | ||
|
|
c4589bd9fe | ||
|
|
33b888e1ea | ||
|
|
fa8ab60870 | ||
|
|
7f8609bbea | ||
|
|
927c3ac6a5 | ||
|
|
2e7f191a31 | ||
|
|
2da0508964 | ||
|
|
02d0d06b84 | ||
|
|
4eb00cfdc6 | ||
|
|
389921d0da | ||
|
|
09ab9cc9fc | ||
|
|
c0efe8c411 | ||
|
|
26626ced9f | ||
|
|
d7d733c5a4 | ||
|
|
82fd604be4 | ||
|
|
342782fe12 | ||
|
|
a45235ef58 | ||
|
|
440ed3d81a | ||
|
|
fc9da2ace1 | ||
|
|
9a6fc37b7d | ||
|
|
3ac4303dad | ||
|
|
93f4768535 | ||
|
|
a24b78e654 | ||
|
|
843dbc2c66 | ||
|
|
9e75a75049 | ||
|
|
df3349e403 | ||
|
|
396613aee3 | ||
|
|
06859d6bdb | ||
|
|
d4cb65277b | ||
|
|
8b2a50d288 | ||
|
|
10146f6723 | ||
|
|
5294166ea7 | ||
|
|
fbfc2c86e0 | ||
|
|
1cee5ead28 | ||
|
|
eba7be8626 | ||
|
|
8d699f5a6b | ||
|
|
35b5f12395 | ||
|
|
2d8fb4b135 | ||
|
|
9bb5730793 | ||
|
|
8bc416d8ae | ||
|
|
48250d04ac | ||
|
|
8ecdc3253d | ||
|
|
58900eb9eb | ||
|
|
9c04a9d5e4 | ||
|
|
c6f47729b4 | ||
|
|
7a1b4b40d1 | ||
|
|
0f70020312 | ||
|
|
24ef56ca18 | ||
|
|
3d85f365db | ||
|
|
eaa1a88318 | ||
|
|
11e82686d9 | ||
|
|
25f96d0ca6 | ||
|
|
4c5bb19766 | ||
|
|
57a798af34 | ||
|
|
b3814fc7c0 | ||
|
|
09fc2e10d6 | ||
|
|
a3fcc3d7b3 | ||
|
|
8ae42c5d97 | ||
|
|
832820c97c | ||
|
|
5ec2f8ca5c | ||
|
|
581e2fa807 | ||
|
|
b8e2e10288 | ||
|
|
15c7b76b51 | ||
|
|
86c7281c46 | ||
|
|
57fb819152 | ||
|
|
acdcdbafb1 | ||
|
|
dd2d759d35 | ||
|
|
3494e07d67 | ||
|
|
32a3a3f97a | ||
|
|
4386d2cb80 | ||
|
|
f82e905c8e | ||
|
|
775d62702f | ||
|
|
f1ec0f8c46 | ||
|
|
247bb8227e | ||
|
|
339ebcab7a | ||
|
|
fe384f546b | ||
|
|
278fa74e1b | ||
|
|
8587e4618b | ||
|
|
62fb3978b2 | ||
|
|
f0573ac033 | ||
|
|
799f7b2916 | ||
|
|
1b2c5a35ef | ||
|
|
ce49a2da51 | ||
|
|
3d4a15efd7 | ||
|
|
9eb85f61ed | ||
|
|
f0775cbbb9 | ||
|
|
25e9b2645e | ||
|
|
2ace6c728b | ||
|
|
da7f542122 | ||
|
|
8d0da90367 | ||
|
|
24f55ab085 | ||
|
|
ea63de0d21 | ||
|
|
2230842648 | ||
|
|
ad77e2cdd9 | ||
|
|
439d12694b | ||
|
|
6a951c9d3c | ||
|
|
f185138712 | ||
|
|
bf13f61046 | ||
|
|
c3d4882151 | ||
|
|
56f4e6efec | ||
|
|
a175fd2f26 | ||
|
|
7975373551 | ||
|
|
73502e3a9a | ||
|
|
b04a76a6dc | ||
|
|
c05b43a4dd | ||
|
|
e99462b8ff | ||
|
|
201ea1eb25 | ||
|
|
9c9c42309a | ||
|
|
baf4907912 | ||
|
|
bc1e695ca8 | ||
|
|
3f6b9ef1ad | ||
|
|
a5410053e1 | ||
|
|
798eb7a394 | ||
|
|
cda8313cfd | ||
|
|
462e22a745 | ||
|
|
3215b32b32 | ||
|
|
c64256ca26 | ||
|
|
6459d24bce | ||
|
|
15d00bc8de | ||
|
|
643e6a1701 | ||
|
|
09932642f9 | ||
|
|
b3a5ab20d4 | ||
|
|
9c387e11be | ||
|
|
96013f4a9c | ||
|
|
a5a8f4268a | ||
|
|
f398efa76c | ||
|
|
b34c0d77b9 | ||
|
|
922cba40f3 | ||
|
|
451dcb7e45 | ||
|
|
3b316448a7 | ||
|
|
280755c83a | ||
|
|
b94a55f9ce | ||
|
|
3ab8951764 | ||
|
|
83f01434c4 | ||
|
|
e7dcf1c898 | ||
|
|
533f97c1c8 | ||
|
|
d9fb0dde48 | ||
|
|
077644ec76 | ||
|
|
21060b410b | ||
|
|
f53a560b84 | ||
|
|
7bdc524de4 | ||
|
|
36ffb7c93a | ||
|
|
ce9d2aa8dc | ||
|
|
05dad27f35 | ||
|
|
1700343f11 | ||
|
|
2a0ab59f0f | ||
|
|
c057bfd531 | ||
|
|
855363b2c7 | ||
|
|
c8a5304d3c | ||
|
|
3aa919e825 | ||
|
|
4181efb75c | ||
|
|
7b7b3383ed | ||
|
|
e1b1fcb6b5 | ||
|
|
d3b9fce0bd | ||
|
|
5d0531d35a | ||
|
|
0f6ea26560 | ||
|
|
5c434e3b3e | ||
|
|
b5f0cb2ae7 | ||
|
|
02219a22e7 | ||
|
|
c80f28b588 | ||
|
|
4daa44442f | ||
|
|
0bd1b6fefc | ||
|
|
0f279082c4 | ||
|
|
cde418ffae | ||
|
|
9e681c6363 | ||
|
|
2b4aa6fb2e | ||
|
|
c11ffe82b0 | ||
|
|
0296f3c5c3 | ||
|
|
279557459d | ||
|
|
3b64d994f7 | ||
|
|
50e7a8f1fe | ||
|
|
f3ad5fde7c | ||
|
|
129fd02ce1 | ||
|
|
b622847184 | ||
|
|
c743e82451 | ||
|
|
ee91ef2972 | ||
|
|
a7195f44b3 | ||
|
|
b1c8cf6e00 | ||
|
|
227f5da7ba | ||
|
|
dc9c9756ae | ||
|
|
9aebf5129d | ||
|
|
c23aba87b6 | ||
|
|
fcd572a5e4 | ||
|
|
a8e352ce17 | ||
|
|
e69a7b6166 | ||
|
|
b66b655154 | ||
|
|
b900095313 | ||
|
|
4f7fc78e59 | ||
|
|
eaf1e0c340 | ||
|
|
8786b2034b | ||
|
|
7ec2a22b64 | ||
|
|
1429b510bd | ||
|
|
b16d6b8e5a | ||
|
|
2c5bc8dbf4 | ||
|
|
25904c8d55 | ||
|
|
eb7f668d52 | ||
|
|
f223f84f02 | ||
|
|
46185f8832 | ||
|
|
e543e8a892 | ||
|
|
a7ae0fbc0c | ||
|
|
3317ab74c2 | ||
|
|
3f265c8f5b | ||
|
|
0e647ddfac | ||
|
|
1c064e56b2 | ||
|
|
e63fe53dc6 | ||
|
|
6858fe4101 | ||
|
|
f5b7b25611 | ||
|
|
b2407ebdea | ||
|
|
caefccfda1 | ||
|
|
4f5c09f32c | ||
|
|
1c0d7e1d5b | ||
|
|
ed27152c51 | ||
|
|
95bbbfc4e7 | ||
|
|
c17e161f80 | ||
|
|
7456d62171 | ||
|
|
9eb5bb4584 | ||
|
|
17d9895070 | ||
|
|
b7bc20574a | ||
|
|
5dbe2af6ed | ||
|
|
a1e41101cc | ||
|
|
3e04881e96 | ||
|
|
ee6e492185 | ||
|
|
ec613d4549 | ||
|
|
717548af26 | ||
|
|
287b72726e | ||
|
|
d76fc7494c | ||
|
|
33e28877e9 | ||
|
|
114ae77bec | ||
|
|
4cf74b19b5 | ||
|
|
96b12eabe3 | ||
|
|
d51b92bbec | ||
|
|
5e4227181b | ||
|
|
7761f63534 | ||
|
|
f1881993dd | ||
|
|
7cfc586667 | ||
|
|
19e4620c23 | ||
|
|
3a407b7c08 | ||
|
|
6eb07b688f | ||
|
|
c147048e6d | ||
|
|
750cba1b19 | ||
|
|
ffaba32b12 | ||
|
|
dedad9e55a | ||
|
|
ea91de84c0 | ||
|
|
944ec4576b | ||
|
|
96b8f69464 | ||
|
|
17a43b8001 | ||
|
|
cbce0c7ac1 | ||
|
|
0117198864 | ||
|
|
150d320e7f | ||
|
|
290702e7be | ||
|
|
75a7feabf9 | ||
|
|
603e5cbaf2 | ||
|
|
3c52246478 | ||
|
|
3acf590e1c | ||
|
|
060cfac2f8 | ||
|
|
3e5d2f4c6f | ||
|
|
623c4b37fc | ||
|
|
6e85653f82 | ||
|
|
8e5e2b3cbd | ||
|
|
094df2a7bb | ||
|
|
47155fbec3 | ||
|
|
96ef3fe6b7 | ||
|
|
6cd9d9496c | ||
|
|
223ab891db | ||
|
|
25c5e05ea3 | ||
|
|
aad5ab772f | ||
|
|
4651ae021a | ||
|
|
8abcc8e78c | ||
|
|
44532424a9 | ||
|
|
bea6fdc0ec | ||
|
|
b6b110fd10 | ||
|
|
3339b131e1 | ||
|
|
58d1bcf2f9 | ||
|
|
c5008e263a | ||
|
|
ef451feb6a | ||
|
|
2bf589d4f8 | ||
|
|
fd8a29f9cd | ||
|
|
f0517e072d | ||
|
|
6cd3abe6ea | ||
|
|
2d8e33151f | ||
|
|
307f373b42 | ||
|
|
45cbd02370 | ||
|
|
cddba4ef7a | ||
|
|
4988dd7a00 | ||
|
|
ea61978d08 | ||
|
|
a11c1d4191 | ||
|
|
4ac78002ff | ||
|
|
10bb5873cf | ||
|
|
ac30c9f39b | ||
|
|
a5d3f9a493 |
1558
.all-contributorsrc
1558
.all-contributorsrc
File diff suppressed because it is too large
Load Diff
70
.eslintrc.js
70
.eslintrc.js
@@ -1,14 +1,70 @@
|
||||
module.exports = {
|
||||
extends: ['react-app', 'prettier/@typescript-eslint', 'plugin:prettier/recommended'],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 6,
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
project: `./tsconfig.json`,
|
||||
},
|
||||
plugins: ["@typescript-eslint", "import", "unicorn", "simple-import-sort"],
|
||||
extends: ["react-app"],
|
||||
rules: {
|
||||
'import/first': 0,
|
||||
'import/no-default-export': ['error'],
|
||||
'unicorn/filename-case': [
|
||||
'error',
|
||||
"react/react-in-jsx-scope": "off", // React is always in scope with Blitz
|
||||
"jsx-a11y/anchor-is-valid": "off", //Doesn't play well with Blitz/Next <Link> usage
|
||||
"import/first": "off",
|
||||
"import/no-default-export": "error",
|
||||
"require-await": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"unicorn/filename-case": [
|
||||
"error",
|
||||
{
|
||||
case: 'kebabCase',
|
||||
case: "kebabCase",
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
// note you must disable the base rule as it can report incorrect errors
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-use-before-define": ["error"],
|
||||
// note you must disable the base rule as it can report incorrect errors
|
||||
"no-redeclare": "off",
|
||||
"@typescript-eslint/no-redeclare": ["error"],
|
||||
"simple-import-sort/imports": [
|
||||
"warn",
|
||||
{
|
||||
groups: [
|
||||
[
|
||||
// Side effect imports.
|
||||
"^\\u0000",
|
||||
// Packages.
|
||||
// Things that start with a letter (or digit or underscore), or `@` followed by a letter.
|
||||
"^@?\\w",
|
||||
// Absolute imports and other imports such as Vue-style `@/foo`.
|
||||
// Anything that does not start with a dot.
|
||||
"^[^.]",
|
||||
// Relative imports.
|
||||
// Anything that starts with a dot.
|
||||
"^\\.",
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: ['unicorn'],
|
||||
ignorePatterns: [
|
||||
"packages/cli/",
|
||||
"packages/generator/templates",
|
||||
".eslintrc.js",
|
||||
"recipes/*/templates",
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
files: ["examples/**", "packages/gui/**", "recipes/**"],
|
||||
rules: {
|
||||
"import/no-default-export": "off",
|
||||
"unicorn/filename-case": "off",
|
||||
"@typescript-eslint/no-floating-promises": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
8
.github/CODEOWNERS
vendored
8
.github/CODEOWNERS
vendored
@@ -1,8 +1,8 @@
|
||||
# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners
|
||||
|
||||
* @flybayer
|
||||
*.md @merelinguist
|
||||
|
||||
packages/server/**/* @ryardley
|
||||
packages/cli/**/* @aem
|
||||
packages/generator/**/* @aem
|
||||
packages/cli/**/* @aem, @flybayer
|
||||
packages/generator/**/* @aem @flybayer
|
||||
packages/generator/templates**/* @flybayer
|
||||
packages/installer/**/* @aem @flybayer
|
||||
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,4 +1,4 @@
|
||||
github: blitz-js
|
||||
custom: ['https://paypal.me/thebayers']
|
||||
custom: ["https://paypal.me/thebayers"]
|
||||
open_collective: blitzjs
|
||||
patreon: flybayer
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -8,16 +8,16 @@ assignees: ''
|
||||
|
||||
### What is the problem?
|
||||
|
||||
### Steps to Reproduce: <!-- if you can, link to a repo with the failing code -->
|
||||
### Steps to Reproduce
|
||||
|
||||
1. 2.
|
||||
1.
|
||||
|
||||
### Versions:
|
||||
### Versions
|
||||
|
||||
```
|
||||
[Add the output of `blitz --version --verbose` here]
|
||||
output of `blitz --version --verbose`
|
||||
```
|
||||
|
||||
### Supporting Documentation
|
||||
### Other
|
||||
|
||||
Please include applicable logs and screenshots that show your problem.
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Prisma issue?
|
||||
url: https://github.com/prisma/prisma/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc
|
||||
about: All Prisma issues should be opened in the Prisma Github
|
||||
- name: Question, Discussion, Idea?
|
||||
url: https://github.com/blitz-js/blitz/discussions/new
|
||||
about: Ask questions and discuss with other community members
|
||||
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: Feature/change request
|
||||
about: Something new or better!
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
title: ""
|
||||
labels: ""
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
### What do you want and why?
|
||||
|
||||
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,22 +1,10 @@
|
||||
### Type: ?? <!-- feature, bug fix, refactor, tests, etc -->
|
||||
|
||||
Closes: ??
|
||||
|
||||
### What are the changes and their implications? :gear:
|
||||
|
||||
??
|
||||
### What are the changes and their implications?
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Tests added for changes
|
||||
- [ ] Any added terminal logging uses `packages/server/src/log.ts`
|
||||
|
||||
### Breaking change: ?? <!-- yes or no -->
|
||||
|
||||
<!-- If yes, describe the impact and migration path for existing apps-->
|
||||
|
||||
### Other information
|
||||
|
||||
<!-- Before/after screenshots, etc. -->
|
||||
- [ ] Changes covered by tests (tests added if needed)
|
||||
- [ ] PR submitted to [blitzjs.com](https://github.com/blitz-js/blitzjs.com) for any user facing changes
|
||||
|
||||
<!-- IMPORTANT: Make sure to check the "Allow edits from maintainers" box below this window -->
|
||||
|
||||
18
.github/checkInstallTime.js
vendored
Executable file
18
.github/checkInstallTime.js
vendored
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("fs")
|
||||
const yarnOut = fs.readFileSync(0, {encoding: "utf8"})
|
||||
|
||||
const [installTimeString] = /(?<=^Done in )\d+\.\d+(?=s\.$)/m.exec(yarnOut)
|
||||
const installTime = Number(installTimeString)
|
||||
|
||||
console.log(`Install time: ${installTime}s`)
|
||||
|
||||
if (installTime < 30) {
|
||||
console.log("We're below 30 secs. That's awesome!")
|
||||
} else if (installTime < 50) {
|
||||
console.log("We're below 50 secs. That's fine!")
|
||||
} else {
|
||||
console.log("We're above 50 secs. That's not great!")
|
||||
process.exit(1)
|
||||
}
|
||||
26
.github/workflows/compressed.yml
vendored
Normal file
26
.github/workflows/compressed.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master, canary]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Compressed Size
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node and Yarn
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn && yarn build
|
||||
env:
|
||||
CI: true
|
||||
- name: Count size
|
||||
uses: preactjs/compressed-size-action@v2
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
113
.github/workflows/main.yml
vendored
113
.github/workflows/main.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Continuous Integration
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -11,31 +11,120 @@ on:
|
||||
- canary
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Build & Test
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12.16.1'
|
||||
node-version: "14"
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Cache Node.js modules
|
||||
- name: Cache node_modules
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
**/node_modules
|
||||
/home/runner/.cache/Cypress
|
||||
C:\Users\runneradmin\AppData\Local\Cypress\Cache
|
||||
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v4-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
${{ runner.os }}-${{ runner.node_version}}-yarn-v4-
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile --silent
|
||||
env:
|
||||
CI: true
|
||||
- name: Test Blitz Packages
|
||||
run: yarn test
|
||||
- name: yarn lint
|
||||
run: yarn lint
|
||||
env:
|
||||
CI: true
|
||||
build_and_test_pkgs:
|
||||
name: Packages Tests
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
node_version: [12, 14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Cache node_modules
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
/home/runner/.cache/Cypress
|
||||
C:\Users\runneradmin\AppData\Local\Cypress\Cache
|
||||
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v4-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.node_version}}-yarn-v4-
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile --silent
|
||||
env:
|
||||
CI: true
|
||||
- name: Setup kernel to increase watchers
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
- name: Build packages
|
||||
run: yarn build
|
||||
env:
|
||||
CI: true
|
||||
- name: Test Blitz Packages
|
||||
run: yarn testonly:packages
|
||||
env:
|
||||
CI: true
|
||||
build_and_test_examples:
|
||||
timeout-minutes: 30
|
||||
name: Example Apps Tests
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
node_version: [12, 14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Cache node_modules
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
/home/runner/.cache/Cypress
|
||||
C:\Users\runneradmin\AppData\Local\Cypress\Cache
|
||||
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v4-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.node_version}}-yarn-v4-
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile --silent
|
||||
env:
|
||||
CI: true
|
||||
- name: Setup kernel to increase watchers
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
- name: Build packages
|
||||
run: yarn build
|
||||
env:
|
||||
CI: true
|
||||
- name: Test examples
|
||||
run: yarn testonly:examples
|
||||
env:
|
||||
CI: true
|
||||
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
.log
|
||||
.DS_Store
|
||||
.idea
|
||||
.jest-*
|
||||
lib
|
||||
node_modules
|
||||
@@ -15,10 +16,14 @@ tsconfig.tsbuildinfo
|
||||
dist
|
||||
.now
|
||||
# local env files
|
||||
**/.envrc
|
||||
**/.env
|
||||
**/.env.local
|
||||
**/.env.development.local
|
||||
**/.env.test.local
|
||||
**/.env.production.local
|
||||
**/.env.*.local
|
||||
**/.envrc
|
||||
.blitz-*
|
||||
.blitz-cli-cache
|
||||
.vscode
|
||||
.tsbuildinfo
|
||||
.nvmrc
|
||||
**/.test*
|
||||
examples/auth2
|
||||
.idea
|
||||
|
||||
5
.kodiak.toml
Normal file
5
.kodiak.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
# .kodiak.toml
|
||||
# Minimal config. version is the only required field.
|
||||
version = 1
|
||||
merge.automerge_label = "0 - <(^_^)> - merge it! ✌️"
|
||||
approve.auto_approve_usernames = ["flybayer", "depfu"]
|
||||
1
.node-version
Normal file
1
.node-version
Normal file
@@ -0,0 +1 @@
|
||||
12.20.0
|
||||
@@ -16,5 +16,6 @@ reports
|
||||
tsconfig.tsbuildinfo
|
||||
dist
|
||||
bin
|
||||
!packages/blitz/src/bin
|
||||
packages/generator/templates/**
|
||||
.github/ISSUE_TEMPLATE/bug_report.md
|
||||
|
||||
@@ -1,60 +1,3 @@
|
||||
# The Blitz Community Code of Conduct
|
||||
|
||||
The Blitz core members take this CoC very serious. All members, contributors and volunteers in this community are required to act according to the following Code of Conduct to keep Blitz a positive, growing project and community and help us provide and ensure a safe environment for everyone.
|
||||
|
||||
The Blitz community
|
||||
|
||||
## When Something Happens
|
||||
|
||||
If you see a Code of Conduct violation, follow these steps:
|
||||
|
||||
1. Let the person know that what they did is not appropriate and ask them to stop and/or edit their message(s).
|
||||
2. That person should immediately stop the behavior and correct the issue.
|
||||
3. If this doesn’t happen, or if you’re uncomfortable speaking up, contact Brandon Bayer ([Twitter](https://twitter.com/flybayer) | [Email](mailto:b@bayer.ws)).
|
||||
|
||||
When reporting, please include any relevant details, links, screenshots, context, or other information that may be used to better understand and resolve the situation.
|
||||
|
||||
The core members will prioritize the well-being and comfort of the recipients of the violation over the comfort of the violator.
|
||||
|
||||
## What We Believe and How We Act
|
||||
|
||||
- We are committed to providing a friendly, safe and welcoming environment for everyone, regardless of age, body size, culture, ethnicity, gender expression, gender identity, level of experience, nationality, personal ability or disability, physical appearance, physical or mental difference, race, religion, set of skills, sexual orientation, socio-economic status, and subculture. We welcome people regardless of these or other attributes.
|
||||
- We are better together. We are more alike than different.
|
||||
- Our community is based on mutual respect, tolerance, and encouragement.
|
||||
- We believe that a diverse community where people treat each other with respect is stronger, more vibrant and has more potential contributors and more sources for ideas. We aim for more diversity.
|
||||
- We are kind, welcoming and courteous to everyone.
|
||||
- We’re respectful of others, their positions, their skills, their commitments and their efforts.
|
||||
- We’re attentive in our communications, whether in person or online, and we’re tactful when approaching differing views.
|
||||
- We are aware that language shapes reality. Thus, we use inclusive, gender-neutral language in the documents we provide and when we talk to people. When referring to a group of people, we aim to use gender-neutral terms like “team”, “folks”, “everyone”. (For details, we recommend [this post](https://modelviewculture.com/pieces/gendered-language-feature-or-bug-in-software-documentation)).
|
||||
- We respect that people have differences of opinion and criticize constructively.
|
||||
- We value people over code.
|
||||
|
||||
## Don'ts
|
||||
|
||||
- Don’t discriminate against anyone.
|
||||
- Sexism and racism of any kind (including sexist and racist “jokes”), demeaning or insulting behaviour and harassment are seen as direct violations to this Code of Conduct. Harassment includes offensive verbal comments related to age, body size, culture, ethnicity, gender expression, gender identity, level of experience, nationality, personal ability or disability, physical appearance, physical or mental difference, race, religion, set of skills, sexual orientation, socio-economic status, and subculture. Harassment also includes sexual images in public spaces, deliberate intimidation, stalking, following, harassing photography or recording, inappropriate physical contact, and unwelcome sexual attention.
|
||||
- On Slack and other online or offline communications channels, don't use overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all.
|
||||
- Don’t be mean or rude.
|
||||
- Respect that some individuals and cultures consider the casual use of profanity offensive and off-putting.
|
||||
- Unwelcome / non-consensual sexual advances over Slack or any other channels related with this community are not okay.
|
||||
- Derailing, tone arguments and otherwise playing on people’s desires to be nice are not welcome, especially in discussions about violations to this Code of Conduct.
|
||||
- Please avoid unstructured critique.
|
||||
- Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
|
||||
- Sponsors of Blitz are also subject to this Code of Conduct. In particular, sponsors are required to not use sexualized images, activities, or other material which is not according to this Code of Conduct.
|
||||
|
||||
## Consequences for Violations to this Code of Conduct
|
||||
|
||||
If a participant engages in any behavior violating this Code of Conduct, the core members of this community will take any action they deem appropriate, starting with a gentle warning and then escalating as needed to expulsion from the community, exclusion from any interaction and loss of all rights in this community.
|
||||
|
||||
## Decisions About Consequences of Violations
|
||||
|
||||
Decisions about consequences of violations of this Code of Conduct are made by this community’s core members and may not be discussed with the person responsible for the violation.
|
||||
|
||||
## For Questions or Feedback
|
||||
|
||||
If you have any questions or feedback on this Code of Conduct, we’re happy to hear from you.
|
||||
|
||||
## Thanks for the Inspiration To
|
||||
|
||||
- [Hood.ie](http://hood.ie/code-of-conduct/)
|
||||
- [WeAllJS](https://wealljs.org/code-of-conduct)
|
||||
[Read the Code of Conduct at Blitzjs.com](https://blitzjs.com/docs/code-of-conduct)
|
||||
|
||||
155
CONTRIBUTING.md
155
CONTRIBUTING.md
@@ -1,154 +1,3 @@
|
||||

|
||||
# Contributing
|
||||
|
||||
<br>
|
||||
|
||||
We're so excited you're interested in helping with Blitz! We happy to help you get started, even if you don't have any previous open-source experience :)
|
||||
|
||||
<br>
|
||||
|
||||
### Blitz is a Community Project
|
||||
|
||||
Blitz is built by and for the community. There's no large company sponsoring development. So all community contributions are very appreciated!
|
||||
|
||||
<br>
|
||||
|
||||
### Our Codebase is a Garden
|
||||
|
||||
The Blitz codebase is like a community garden. There's a lot of beautiful plants and vegetables, but it won't take long until you find some weeds! When you find weeds, please remove them :) Minor refactoring is always encouraged. If you'd like to do some major refactoring, it's best to first either open an issue or check with us in Slack. Most likely we'll agree with you.
|
||||
|
||||
<br>
|
||||
|
||||
### First Things First
|
||||
|
||||
1. New to open source? take a look at [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)
|
||||
2. Familiarize yourself with the [Blitz Code of Conduct](https://github.com/blitz-js/blitz/blob/canary/CODE_OF_CONDUCT.md)
|
||||
3. Join the [Blitz Slack Community](https://slack.blitzjs.com)
|
||||
|
||||
<br>
|
||||
|
||||
### Weekly Contributors video call
|
||||
|
||||
Every week Blitz contributors meet in a video call to discuss progress and ideas.
|
||||
|
||||
The contributor video call is mainly for those contributing or want to start contributing. However anyone is welcome to join and listen!
|
||||
|
||||
#### The timezone
|
||||
|
||||
We are literally all over the globe, so the weekly contributors call alternates timezones every other week to accomodate different regions
|
||||
|
||||
- Even weeks of the year: **Tuesday at 1am UTC** (Note: this is Monday evening in the USA)
|
||||
- Odd weeks of the year: **Wednesday at 9am UTC**
|
||||
- Use this link to see [the current week of the year](https://whatweekisit.com)
|
||||
|
||||
#### How to join
|
||||
|
||||
Zoom link will be posted in slack `#-announcements` channel before the call
|
||||
|
||||
#### Recording
|
||||
|
||||
Recordings of previous calls can be found [here](https://www.youtube.com/playlist?list=PLvm6NqxNNnBLFxZux5OHraTAcIBJz2FvR)
|
||||
|
||||
<br>
|
||||
|
||||
### What to Work On?
|
||||
|
||||
Issues with the label [`status/ready-to-work-on`](https://github.com/blitz-js/blitz/labels/status%2Fready-to-work-on) are the best place to start. If you find one that looks interesting and no one else is already working on it, comment in the issue that you are going to work on it. Please ask as many questions as you need, either directly in the issue or in Slack. We're happy to help!
|
||||
|
||||
The Blitzjs.com website and documentation repo also has issues with [`ready to work on | help wanted`](https://github.com/blitz-js/blitzjs.com/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22ready+to+work+on+%7C+help+wanted%22).
|
||||
|
||||
#### Things that are ALWAYS welcome
|
||||
|
||||
- Adding tests
|
||||
- Improved documentation
|
||||
- Improved error messages
|
||||
- Improved logging (i.e. more clear, more beautiful)
|
||||
- Performance or security improvements
|
||||
- Educational content like blogs, videos, courses
|
||||
|
||||
If there's some other way you'd like to contribute, just ask us about it in slack!
|
||||
|
||||
After you contribute in any way, please add yourself as a contributor via the [@all-contributors bot](https://allcontributors.org/docs/en/bot/usage)!
|
||||
|
||||
<br>
|
||||
|
||||
## Development Setup
|
||||
|
||||
**1.** Fork this repo
|
||||
|
||||
**2.** Clone your fork repo
|
||||
|
||||
```sh
|
||||
# If you didn't fork the repo use blitz-js as the USERNAME
|
||||
git clone git@github.com:USERNAME/blitz.git
|
||||
cd blitz
|
||||
```
|
||||
|
||||
**3.** Install dependencies
|
||||
|
||||
```
|
||||
yarn
|
||||
```
|
||||
|
||||
**4.** Start the package server. This must be running for any package development or example development
|
||||
|
||||
```
|
||||
yarn dev
|
||||
```
|
||||
|
||||
**5.** Run tests
|
||||
|
||||
```
|
||||
yarn test
|
||||
```
|
||||
|
||||
#### Sync your fork
|
||||
|
||||
```sh
|
||||
./scripts/fetchRemote.sh
|
||||
git merge upstream/canary
|
||||
```
|
||||
|
||||
#### Link the Blitz CLI (Optional)
|
||||
|
||||
The following will link the development CLI as a local binary so you can use it anywhere for testing.
|
||||
|
||||
```
|
||||
yarn link-cli
|
||||
// `yarn unlink-cli` will unlink
|
||||
```
|
||||
|
||||
#### Develop a Blitz `package`
|
||||
|
||||
**1.** Change to a package directory
|
||||
|
||||
```
|
||||
cd packages/core
|
||||
```
|
||||
|
||||
**2.** Start the test runner
|
||||
|
||||
```
|
||||
yarn test:watch
|
||||
```
|
||||
|
||||
#### Develop a Blitz `example`
|
||||
|
||||
**1.** Change to an example directory
|
||||
|
||||
```
|
||||
cd examples/store
|
||||
```
|
||||
|
||||
**2.** Follow instructions in the example's README
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you run into issues that should be documented here, please submit a PR! ❤️
|
||||
|
||||
**#### Windows Subsystem for Linux**
|
||||
|
||||
`node-pty` error when running `yarn`. Fix by installing `node-pty dependencies`
|
||||
|
||||
```
|
||||
sudo apt install -y make python build-essential
|
||||
```
|
||||
[Read the Contributing Guide at Blitzjs.com](https://blitzjs.com/docs/contributing)
|
||||
|
||||
105
MAINTAINERS.md
105
MAINTAINERS.md
@@ -1,104 +1 @@
|
||||
# ❤️ Blitz Maintainers
|
||||
|
||||
Aside from the core team, there are two levels of maintainers, described below.
|
||||
|
||||
## Becoming a Maintainer
|
||||
|
||||
We always need more level 1 maintainers! The main requirement is that you can show empathy when communicating online. We'll train you as needed on the other specifics. **This is a great role if you have limited time, because you can spend just as much time as you have without any ongoing responsibilities (unlike level 2)**
|
||||
|
||||
Level 2 maintainers have a much higher responsibility, so usually you will spend time as a level 1 maintainer before moving to level 2.
|
||||
|
||||
Please DM a core team member (Brandon Bayer, Rudi Yardley, or Dylan Brookes) in Slack if you're interested in becoming an official maintainer!
|
||||
|
||||
## Level 1 Maintainers
|
||||
|
||||
Level 1 maintainers are critical for a healthy Blitz community and project. They take a lot of burden off the core team and level 2 maintainers so they can focus on higher level things with longer term impact.
|
||||
|
||||
The primary responsibilities of level 1 maintainers are:
|
||||
|
||||
- Being a friendly, welcoming voice for the Blitz community
|
||||
- Issue triage
|
||||
- Pull request triage
|
||||
- Monitor and answer the `#-help` slack channel
|
||||
- Community encouragement
|
||||
- Community moderation
|
||||
- Tracking and ensuring progress of key issues
|
||||
|
||||
## Level 2 Maintainers
|
||||
|
||||
Level 2 maintainers are the backbone of the project. They are watchdogs over the code, ensuring code quality, correctness, and security. They also facilitate a rapid pace of progress.
|
||||
|
||||
The primary responsibilities of level 2 maintainers are:
|
||||
|
||||
- Code ownership over specific parts of the project
|
||||
- Maintaining and improving the architecture of what they own
|
||||
- Final pull request reviews
|
||||
- Merging pull requests
|
||||
- Tracking and ensuring progress of open pull requests
|
||||
|
||||
## ⚠️ Fundamentals
|
||||
|
||||
Maintainers are the face of the project and the front-line touch point for the community. Therefore maintainers have the very important responsibility of making people feel welcome, valued, understood, and appreciated.
|
||||
|
||||
**Please take time to read and understand everything outlined in this [guide on building welcoming communities](https://opensource.guide/building-community)**
|
||||
|
||||
Some especially important points:
|
||||
|
||||
- **Gratitude:** immediately express gratitude when someone opens an issue or PR. This takes effort/time and we appreciate it
|
||||
- **Responsiveness:** during issue/PR triage, even if we can’t do a full review right away, leave a comment thanking them and saying we’ll review it soon
|
||||
- **Understanding:** it's critical to ensure you understand exactly what someone is saying before you respond. Ask plenty of questions if needed. It's very bad if someone has to reply to your response and say "actually I was asking about X"
|
||||
- In fact, at least one question is almost always required before you can respond appropriately — whether in Github or in Slack
|
||||
|
||||
## Slack
|
||||
|
||||
- All `#-*` channels are for Blitz users
|
||||
- All `#dev-*` channels are for Blitz internal development
|
||||
|
||||
If someone that's not a maintainer post in the wrong area, that's fine. Don't tell them they posted in the wrong place. But as a maintainer, you should for sure post in the right channel :)
|
||||
|
||||
## Issue Triage
|
||||
|
||||
#### If a bug report:
|
||||
|
||||
- Does it have enough information? Versions? Logs? Some way to reproduce?
|
||||
- Has this already been fixed in a previous release?
|
||||
- Is there already an existing issue for this?
|
||||
|
||||
### If a feature/change request:
|
||||
|
||||
- Is it clear what the request is and what the benefit will be?
|
||||
- Is this an obvious win for Blitz? Then accept it
|
||||
- If not obvious, then pull in a core team member or level 2 maintainer for more review
|
||||
|
||||
### Actions
|
||||
|
||||
1. Add tags:
|
||||
- Add a `kind/*` tag
|
||||
- Add a `scope/*` tag
|
||||
- Add a `status/*` tag
|
||||
- Add a good first/second issue tag if appropriate
|
||||
|
||||
## Pull Request Triage
|
||||
|
||||
- Are the changes covered by tests?
|
||||
- Do the changes look ok? Make sure there's no obvious issues
|
||||
|
||||
### Actions
|
||||
|
||||
1. Kindly request any changes if needed
|
||||
2. Else add a Github approval so that level 2 maintainers know it's already had an initial review
|
||||
|
||||
## Final PR Review & Merging (Level 2 maintainers)
|
||||
|
||||
As a level 2 maintainer, it is your responsibility to make sure broken code and regressions never reach the canary branch.
|
||||
|
||||
1. Ensure the PR'ed code fully works as intended and that there are no regressions in related code
|
||||
1. If not fully covered by automated tests, you need to pull down the code locally and manually verify everything (the Github CLI helps with this!)
|
||||
2. During squash & merge:
|
||||
1. Change the commit title to be public friendly - this exact text will go in the release notes
|
||||
2. Add the commit type in the description, in parenthesis like `(patch)`. Commit types:
|
||||
- `major` - major breaking change
|
||||
- `minor` - minor feature addition
|
||||
- `patch` - patches, bug fixes, perf improvements, etc
|
||||
- `example` - change to an example app
|
||||
- `meta` - internal meta change related to the Blitz repo/project
|
||||
This document has moved here: https://blitzjs.com/docs/maintainers
|
||||
|
||||
75
MANIFESTO.md
75
MANIFESTO.md
@@ -1,74 +1,3 @@
|
||||
# The Blitz.js Manifesto
|
||||
# Manifesto
|
||||
|
||||
## Background
|
||||
|
||||
Technology follows a repeating cycle of bundling and unbundling. Created in 2005, Ruby on Rails became a major bundling force. It made web application development easier and more accessible than ever before. This benefited everyone, from those learning programming to seniors building production systems.
|
||||
|
||||
A major unbundling happened in 2013 with the release of React because it is hyper focused on the rendering layer. As React grew in popularity, so did the choices for all the other parts, leaving developers with hundreds of decisions to make when starting a new app. While this has contributed to JavaScript Fatigue, it's been a powerful driving force for rapid frontend innovation.
|
||||
|
||||
Now, in 2020, is the perfect time for another major bundling. Developers are yearning for an easier, simpler way to build web applications. Beginners want a guiding hand for building a robust app. And seniors want a framework that removes mundane tasks and provides a highly scalable architecture.
|
||||
|
||||
Hence the creation of Blitz.
|
||||
|
||||
## What is Blitz For?
|
||||
|
||||
Blitz is for building tiny to large fullstack database-backed applications that have one or more graphical user interfaces like web or mobile apps.
|
||||
|
||||
## Foundational Principles
|
||||
|
||||
1. Fullstack & Monolithic
|
||||
2. API Not Required
|
||||
3. Convention over Configuration
|
||||
4. Loose Opinions
|
||||
5. Easy to Start, Easy to Scale
|
||||
6. Stability
|
||||
7. Community over Code
|
||||
|
||||
### 1. Fullstack & Monolithic
|
||||
|
||||
A fullstack, monolithic application is simpler than an application where frontend and backend are developed and deployed separately. Monolithic doesn't mean it will be slow or hard to scale to large teams. Monolithic doesn't mean there isn't separation of concerns. Monolithic means you can reason about your app as a single entity.
|
||||
|
||||
### 2. API Not Required
|
||||
|
||||
Until now, choosing React for your view layer required you to have a REST or GraphQL API even if it wasn't used by third-parties. This additional complexity is a significant drawback not shared by traditional server rendered apps like Ruby on Rails.
|
||||
|
||||
Blitz is a framework for the 99% of us at companies with <100 employees. The vast majority of these apps don't actually need an API, at least not until years down the road when you have the time and resources to build one.
|
||||
|
||||
### 3. Convention over Configuration
|
||||
|
||||
Starting a new fullstack React app is currently too hard. You have to spend days on things like configuring eslint, prettier, husky, jest, cypress, typescript, deciding on a file structure, setting up a database, adding authentication and authorization, setting up a router, defining routing conventions, and setting up your styling library.
|
||||
|
||||
Blitz makes as many decisions and does as much work for you as possible. This makes it lightning fast to start real development. It also greatly benefits the community. Common project structure and architectural patterns make it easy to move from Blitz app to Blitz app and immediately feel at home.
|
||||
|
||||
Convention over configuration doesn't mean no configuration. It means configuration is optional. Blitz will provide all the escape hatches you need for bespoke customization.
|
||||
|
||||
### 4. Loose Opinions
|
||||
|
||||
Blitz is opinionated. The out-of-the-box experience guides you on a path perfect for most applications. However, Blitz isn't arrogant. It understands there are very good reasons for deviating from convention, and it allows you to do so. For example, Blitz has a conventional file structure, but, with few exceptions, doesn't _enforce_ it.
|
||||
|
||||
And when there's not community consensus, `blitz new` prompts you to choose.
|
||||
|
||||
### 5. Easy to Start, Easy to Scale
|
||||
|
||||
A framework that's only easy for one end of an application lifecycle is not a good framework. Both starting and scaling must be easy.
|
||||
|
||||
Easy to start includes being easy for beginners and being easy to migrate existing Next.js apps to Blitz.
|
||||
|
||||
Scaling is important in all forms: lines of code, number of people working in the codebase, and code execution.
|
||||
|
||||
### 6. Stability
|
||||
|
||||
In the fast-paced world of Javascript, a stable, predictable release cycle is a breath of fresh air. A stable release cycle ensures minimal breaking changes, and it ensures you know exactly what and when a breaking change will occur. It also minimizes bugs in stable releases by ensuring features are in beta for a minimum amount of time. [Ember is the model citizen](https://emberjs.com/releases/) in this regard.
|
||||
|
||||
The exact details of the Blitz release cycle are to be determined, but we'll follow a pattern similar to Ember which strictly follows SemVer with stable releases every 6 weeks and LTS releases every 6 months.
|
||||
|
||||
Blitz will follow a public RFC (request for comments) process so all users and companies can participate in proposing and evaluating new features.
|
||||
|
||||
If a Blitz API needs to be removed, it will be deprecated in a minor release. Major releases will simply remove APIs already deprecated in a previous release.
|
||||
|
||||
### 7. Community over Code
|
||||
|
||||
The Blitz community is the most important aspect of the framework, by far.
|
||||
We have a comprehensive [Code of Conduct](https://github.com/blitz-js/blitz/blob/canary/CODE_OF_CONDUCT.md). LGBTQ+, women, and minorities are especially welcome.
|
||||
|
||||
We are all in this together, from the youngest to the oldest. We are all more similar than we are different. We can and should solve problems together. We should learn from other communities, not compete against them.
|
||||
[The Manifesto has been moved to Blitzjs.com](https://blitzjs.com/docs/manifesto)
|
||||
|
||||
124
MEETING_NOTES.md
Normal file
124
MEETING_NOTES.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 2020-08-17 Blitz Contributor Call
|
||||
|
||||
- Attending: Brandon Bayer, Adam Markon, Kellen Mace, Myron Davis, Dwight Watson
|
||||
- Brandon:
|
||||
- Auth out, set up by default in canary release
|
||||
- Need to work on a potential CSRF bug
|
||||
- Next major release will include auth by default and allow you to choose your form library
|
||||
- Next auth features are email confirmation
|
||||
- After that, logging and plugins are next
|
||||
- Adam:
|
||||
- Overhauled the recipe infrastructure. Now using jscodeshift instead of recast
|
||||
- Added support for conditional JSX in templates
|
||||
- Going to work on custom templates next
|
||||
- Dwight
|
||||
- Has been opening issues for problems
|
||||
- Made a few PRs for some issues
|
||||
|
||||
# 2020-07-07 Blitz Contributor Call
|
||||
|
||||
- Attending: Brandon Bayer, Robert Rosenburg, Jeremy Liberman
|
||||
- Brandon:
|
||||
- Finishing up session managment code
|
||||
- Waiting for review of session managment code from Rishabh
|
||||
- Will be working on actual auth code (vs session management) like password hashing, password reset, social login, etc.
|
||||
- Benefits of our session managment vs rails
|
||||
- Don’t have to redirect to login page
|
||||
- Using top level error component that catches authentication errors
|
||||
- You can login from anywhere, during sign up
|
||||
- Robert:
|
||||
- Waiting on Kirstina’s website designs. Desktop design is finished, but she's working on mobile design
|
||||
- Code snippet / sandbox of blitz code for the website
|
||||
|
||||
# 2020-06-23 Blitz Contributor Call
|
||||
|
||||
- Attending: Brandon Bayer, Robert Rosenberg, Justin Hall, Adam Markon
|
||||
- Brandon:
|
||||
- Server side session management code is mostly set up. Still need to integrate client side of RPC calls to expose session information
|
||||
- Identity verification/Oauth integrations still need to be firmed up
|
||||
- Once auth is wrapped up we should be ready to start on plugins
|
||||
- Adam:
|
||||
- Been experimenting with smart page generation based on the current schema model
|
||||
- MDX installer recipes
|
||||
- Robert:
|
||||
- Waiting on designs from Kristina
|
||||
- Prism component from theme ui customization? If not try and grab source code from docusaurus line highlighting component
|
||||
- Justin:
|
||||
- Updated the tutorial
|
||||
- Helped with various bug fixes
|
||||
|
||||
# 2020-06-17 Blitz Contributor Call
|
||||
|
||||
- Attending: Brandon Bayer, Fran Zekan, Sigurd Wahl
|
||||
- Brandon:
|
||||
- Spent the past week implementing HTTP middlware which is now released!
|
||||
- Now working on implementing session management!
|
||||
- Fran:
|
||||
- Finishing up the PR for adding `blitz db seed`
|
||||
- Sigurd:
|
||||
- New to the community, slowly learning the codebase
|
||||
- Exicited to get a first PR in here sometime soon :)
|
||||
|
||||
# 2020-06-09 Blitz Contributor Call
|
||||
|
||||
- Attending: Brandon Bayer, Rudi Yardley, Fran Zekan, Adam Markon, Robert Rosenberg, Kristina Matuska
|
||||
- Brandon:
|
||||
- blitzjs.com published, docs + marketing site v0.1 live
|
||||
- For now most of the docs copied from react-query and Next, we should eventually clean them up so they're stylistically similar to ours, but really easy to start
|
||||
- HTTP middleware is almost done, just fixing a few edge cases
|
||||
- Authentication is up next after that, translating pseudocode into actual code
|
||||
- Kristina:
|
||||
- Homepage design mostly wrapped up right now, have to finish up mobile and light mode and then ready to move on to the rest of the docs
|
||||
- Syntax highlighting, we can customize colors
|
||||
- Sidebar inspiration from tailwindcss
|
||||
- Robert:
|
||||
- Decided to wait to convert existing components to theme UI until the final design is done
|
||||
- More docs content:
|
||||
- More guides
|
||||
- Improve the tutorial to incorporate relationship generation
|
||||
- Add branches for canary and master
|
||||
- If you add a feature you can add your documentation to the canary branch
|
||||
- Adam:
|
||||
- blitz generate model finished!
|
||||
- Installer rewrite complete
|
||||
- At similar place to what Gatsby has for installing stuff
|
||||
- Next up:
|
||||
- Support gatsby MDX recipes
|
||||
- Make all code generators aware of actual model attributes
|
||||
- Fran:
|
||||
- Working on a package for getting the blitz config anywhere - getConfig()
|
||||
- Prevent app from "warming" the server when deployed as server rather than serverless
|
||||
- Testing examples - e2e with cypress and unit tests with Jest so we can link to a testing setup in the docs/getting started guide
|
||||
- Rudi:
|
||||
- Extracted out the @blitzjs/file-pipeline (previously synchronizer)
|
||||
- Extracted out the @blitzjs/display package
|
||||
- Working on various Next.js compatibility issues
|
||||
- Debugging a bug in blitz start where it gets stuck at \_manifest.json
|
||||
|
||||
# 2020-05-26 Blitz Contributor Call
|
||||
|
||||
- Attending: Brandon Bayer, Robert Rosenberg, Adam Markon, Simon Debbarma
|
||||
- Brandon:
|
||||
- Kitze livestream last week went great — recording on youtube
|
||||
- Codebase walkthrough yesterday went great — recording on youtube
|
||||
- Website overhaul, installed Theme UI
|
||||
- Adam:
|
||||
- Opened PR for Prisma model generation from the CLI
|
||||
- Working on Installer stuff and prepping for integration with Gatsby recipes
|
||||
- Simon
|
||||
- Working on custom illustrations for the web
|
||||
- Robert
|
||||
- Misc work on website
|
||||
- Website
|
||||
- Most website components are owned by us now instead of docusaurus, we'll need to be weary of api updates and any other important component updates
|
||||
- Make sidebar like tailwind docs sidebar
|
||||
- Dark theme needs to be fixed
|
||||
- Theme switcher inconsistent
|
||||
- Live code sandbox examples
|
||||
- Code comparison between blitz and rails
|
||||
- Auth
|
||||
- Rishabh continuing to work on pseudo code for the session management library
|
||||
- Brandon planning to build http middleware support this week
|
||||
- CLI
|
||||
- Adam working on new features, including generating prisma models and making existing templates aware of actual model attributes
|
||||
- Plugin ideas / discussion once recipes are farther along. Will post an RFC for plugins at some point
|
||||
458
README.md
458
README.md
@@ -2,11 +2,11 @@
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
<p align="center">
|
||||
<a aria-label="Join our Slack Community" href="https://slack.blitzjs.com">
|
||||
<a aria-label="Join our Discord Community" href="https://discord.blitzjs.com">
|
||||
<img alt="" src="https://img.shields.io/badge/Join%20our%20community-6700EB.svg?style=for-the-badge&labelColor=000000&logoWidth=20&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ9SURBVHgB7d3dVdtAEIbhcSpICUoH0IEogQqSVBBSAU4FSSpIOoAORAfQgSghHXzZ1U/YcMD4R9rZmf2ec3y448LyiNf27iLiGIAmPLrweC9Un3DhrzG6EarLNP09nlwJ1SOZ/lQr5N80/S/p2QMVCBf5N17XCfm1Y/rBHqjAG9PPHvBsz+mf9WAP+HLA9M/YA14cOP2payH7jpj+VCtk1wnTP+vj7xCy6cTpn7EHLMLp059iD1iD8eveJbVCNsSLheX1YA/YgOWnf8YeKB3Wmf7Ud6Fy4f/FHmtpxbl3YlC4MJ/Cj0bWdwPnPbARg+L0S54XQHS32WwuxClzd4CM0z9rPfeAuTtA5ulPXYQ7wZ04Y+oOoDD9KZc9YOoOoDj9s4dwFzgXR6w1wIPoOvPWA9buAHEJ173o3gWiy3AnuBUHLEbgmYwvAk1/wuM8vAgexThzbwPDkx7/DHwVXfFOxP2GmsKd4Ab6zPeAyU8CI7AHFmH2BRCBPXAyk18GzUrqAXCTiR4ssyj0VFw/oCU8+e+RZ33AWz6KMaYbIIWxB+JSLs1bsbkeMN0AqakHvoku9oA2sAfqBvbAQdw0QArsgb25aYBUQT3QgT2gB+yBuqGcHij2UCqXDZACe2Anlw2QYg/QAOyBuoE98CL3DZDCuK4/rh/Q7oGL6U+TOvcNkJoijN8X1C48+T+g75eQDrAH/qmqAVJgDwyqaoAUe4AGYA/UDZX3QLUNkEIZPRCd5+6BahsgVUgPROwBTSijB7jpVAvGHriHvmw9wAZ4BpX1ABvgmakHtPcbRuwBTWAPULgAV9D/jKDY9YRvwvgEaurD44uQHvAol7qBW7WKluVtIHiUS7GyvA0s6CiXDnxrpQfsgbqBS7GKk/2jYHCrVlGyfxTMrVo0ALdq1Q3sgSKofh0M9oA61a+D2QM0AHugbmAPqClmSRjK2apVVQ8UsySsoK1aHdgDesCtWnUDeyCrIpeFg1u3sylyWTi3btMA7IG6gT2wuuK3hoE9sKrit4YVslWLPaAN7IG6ocKt2zmY2h4O9sDiTG0PZw/QANy6XTewBxZj9ogYVHy025LMHhEz9cBn0We6B0yfERReBLfhx0/R1YQHPx/QBPbA0VwcEwf2wNFcHBPHHjiem3MC2QPHcXdSaJjA+KfgTPQ8hhfjBzHC40mhlzJ+Xq9lK4a4PCs43AVaGTed5mZq+iOXZwWHi3AnOj2wFWNcnxYe7gTxLtBKHuamP/J+Wnh8a5irB7ZC5Yk9gPX1QuXC+usHWqGyhYvUYR0a7zboUOFCNVhnk0krZAOW7wFOvzXhom2xnEbIHizTA1wEYhWW6YFGyC6c1gOcfg9wfA80Qj7g8B7g9HuCww+haIR8wf49wOn3Cvv9k8tGyC/s7gFOv3fY3QONkH+v9MBWqB7PeqDn9FcIT//kcitUn6kHOu/T/xfWzlQy3dEHhwAAAABJRU5ErkJggg==">
|
||||
</a>
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-45-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
|
||||
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-209-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
<a aria-label="License" href="https://github.com/blitz-js/blitz/blob/canary/LICENSE">
|
||||
<img alt="" src="https://img.shields.io/npm/l/blitz.svg?style=for-the-badge&labelColor=000000&color=blue">
|
||||
@@ -19,51 +19,85 @@
|
||||
|
||||
<br>
|
||||
|
||||
<h3 align="center">Blitz is a Rails-like framework for monolithic, full-stack React apps — built on Next.js</h3>
|
||||
<h1 align="center">The Fullstack React Framework</h1>
|
||||
|
||||
<h5 align="center">"Zero-API" Data Layer — Built on Next.js — Inspired by Ruby on Rails</h3>
|
||||
<h3 align="center">Makes you far more productive than you ever dreamed was possible 😉</h3>
|
||||
<h3 align="center"><a href="https://blitzjs.com" target="_blank">Read the Documentation</a></h3>
|
||||
<br>
|
||||
|
||||
“Zero-API” data layer **lets you import server code directly into your React components** instead of having to manually add API endpoints and do client-side fetching and caching.
|
||||
|
||||
Includes everything you need for production apps. **Everything end-to-end from the database to the frontend.**
|
||||
|
||||
Blitz brings back the **simplicity and conventions** of server-rendered frameworks like Ruby on Rails while preserving everything we love about React and client-side rendering!
|
||||
|
||||
Blitz is the framework for the 99% of us at companies with <100 employees. This means **we don't force you to use advanced technologies like GraphQL**. We let you add advanced technologies on your terms and at your pace.
|
||||
|
||||
Blitz **maximizes your productivity** both when starting an app and when scaling it to lots of code and users.
|
||||
|
||||
<br>
|
||||
|
||||
### :tada: Alpha Release Now Available :tada:
|
||||
### Quick Start
|
||||
|
||||
1. `npm i -g blitz`
|
||||
2. `blitz new myapp`
|
||||
3. [Read the Alpha User Guide](https://github.com/blitz-js/blitz/blob/canary/USER_GUIDE.md)
|
||||
You need Node.js 12 or newer
|
||||
|
||||
or
|
||||
#### Install Blitz
|
||||
|
||||
3. Start with the [Blitz Beginner Tutorial](https://github.com/blitz-js/blitz/blob/canary/TUTORIAL.md)
|
||||
Run `npm install -g blitz` or `yarn global add blitz`
|
||||
|
||||
<br>
|
||||
_You can alternatively use [`npx`](https://www.npmjs.com/package/npx)_
|
||||
|
||||
#### Create a New App
|
||||
|
||||
1. `blitz new myAppName`
|
||||
2. `cd myAppName`
|
||||
3. `blitz dev`
|
||||
4. View your baby app at http://localhost:3000
|
||||
|
||||
<br><br>
|
||||
|
||||
<a aria-label="Bytes Newsletter" href="https://ui.dev/bytes/?r=blitzjs">
|
||||
<img alt="Bytes Newsletter" src="https://files-8wtskjofb.vercel.app/smarter-16x1.jpg">
|
||||
</a>
|
||||
|
||||
<br><br>
|
||||
|
||||

|
||||
|
||||
<br><br>
|
||||
|
||||
**Features:**<br>
|
||||
⚡️ Built on Next.js<br>
|
||||
⚡️ Don't have to build an API for client-side rendering<br>
|
||||
⚡️ Client-side rendering, Server-side rendering, and fully static pages all in the same app<br>
|
||||
⚡️ Full Typescript support with static, end-to-end typing (no code generation step needed like with GraphQL)<br>
|
||||
⚡️ Full TypeScript support with static, end-to-end typing (no code generation step needed like with GraphQL)<br>
|
||||
⚡️ React Concurrent Mode enabled<br>
|
||||
⚡️ Database/ORM agnostic, but Prisma 2 is default<br>
|
||||
⚡️ CLI with code scaffolding, Rails-style console REPL, etc<br>
|
||||
⚡️ GraphQL Ready<br>
|
||||
⚡️ Deploy serverless or serverful<br>
|
||||
|
||||
**Other key features coming:**<br>
|
||||
⚡️ Highly secure authentication <br>
|
||||
⚡️ Authorization you can use on both server and client<br>
|
||||
⚡️ Recipes for easily adding libraries like Tailwind, CSS-in-JS, etc.<br>
|
||||
|
||||
**Other key features coming:**<br>
|
||||
⚡️ Model validation you can use on both server and client<br>
|
||||
⚡️ Plugins for easily adding libraries like Tailwind, CSS-in-JS, etc.<br>
|
||||
⚡️ React native support<br>
|
||||
⚡️ GUI so you don't have to use the CLI<br>
|
||||
|
||||
<br>
|
||||
|
||||
### The Foundational Principles
|
||||
|
||||
1. Fullstack & Monolithic
|
||||
2. API Not Required
|
||||
3. Convention over Configuration
|
||||
4. Loose Opinions
|
||||
5. Easy to Start, Easy to Scale
|
||||
6. Stability
|
||||
7. Community over Code
|
||||
|
||||
[The Blitz Manifesto](https://blitzjs.com/docs/manifesto) explains these principles in detail.
|
||||
|
||||
<br>
|
||||
|
||||
### What is Blitz Designed For?
|
||||
|
||||
Blitz is designed for tiny to large database-backed applications that have one or more graphical user interfaces.
|
||||
@@ -72,38 +106,58 @@ While we currently only support web, we are pursuing the dream of a single monol
|
||||
|
||||
<br>
|
||||
|
||||
### What are the Foundational Principles?
|
||||
|
||||
1. Fullstack & Monolithic
|
||||
2. API Not Required
|
||||
3. Convention over Configuration
|
||||
4. Loose Opinions
|
||||
5. Easy to Start, Easy to Scale
|
||||
6. Stability
|
||||
7. Community over Code
|
||||
|
||||
[The Blitz Manifesto](https://github.com/blitz-js/blitz/blob/canary/MANIFESTO.md) explains these principles in detail.
|
||||
|
||||
<br>
|
||||
|
||||
## Welcome to the Blitz Community 👋
|
||||
|
||||
The Blitz community is warm, safe, diverse, inclusive, and fun! LGBTQ+, women, and minorities are especially welcome. Please read our [Code of Conduct](https://github.com/blitz-js/blitz/blob/canary/CODE_OF_CONDUCT.md).
|
||||
The Blitz community is warm, safe, diverse, inclusive, and fun! LGBTQ+, women, and minorities are especially welcome. Please read our [Code of Conduct](https://blitzjs.com/docs/code-of-conduct).
|
||||
|
||||
[Join our Slack Community](https://slack.blitzjs.com) where we help each other build Blitz apps. It's also where we collaborate on building Blitz itself.
|
||||
[Join our Discord Community](https://discord.blitzjs.com) where we help each other build Blitz apps. It's also where we collaborate on building Blitz itself.
|
||||
|
||||
There's still a lot of work to do, so you are especially invited to join us in building Blitz! A good place to start is [The Contributing Guide](CONTRIBUTING.md).
|
||||
For questions and longer form discussions, [post in our forum](https://github.com/blitz-js/blitz/discussions).
|
||||
|
||||
There's still a lot of work to do, so you are especially invited to join us in building Blitz! A good place to start is [The Contributing Guide](https://blitzjs.com/docs/contributing).
|
||||
|
||||
<br>
|
||||
|
||||
## Sponsors and Donations
|
||||
## Financial Contributors
|
||||
|
||||
- Contribute via [GitHub Sponsors](https://github.com/sponsors/blitz-js)
|
||||
- Contribute via [PayPal](https://paypal.me/thebayers)
|
||||
- Contribute via [Open Collective](https://opencollective.com/blitzjs)
|
||||
- Contribute via [Patreon](https://patreon.com/flybayer)
|
||||
Your financial contributions help ensure Blitz continues to be developed and maintained! We have monthly sponsorship options starting at $5/month.
|
||||
|
||||
_Sponsor Blitz and display your logo and hiring status here. This is a great way to get in front of early adopters!_
|
||||
👉 View options and contribute at [GitHub Sponsors](https://github.com/sponsors/blitz-js), [PayPal](https://paypal.me/thebayers), or [Open Collective](https://opencollective.com/blitzjs)
|
||||
|
||||
|
||||
### 🌱 Seedling Sponsors
|
||||
|
||||
<a aria-label="React Bricks" href="https://reactbricks.com/?utm_source=blitzjs&utm_medium=sponsorship&utm_campaign=blitzjs_sponsorship">
|
||||
<img alt="" src="https://reactbricks.com/reactbricks_icon.svg" width="30px"/>
|
||||
</a>
|
||||
<a aria-label="Andreas Asprou" href="https://andreas.fyi">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/andreas.jpg" width="30px"/>
|
||||
</a>
|
||||
|
||||
|
||||
### 🥉 Bronze Sponsors
|
||||
|
||||
<a aria-label="Render.com" href="https://render.com?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2020">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/render-logo-color2.png" width="110px">
|
||||
</a>
|
||||
|
||||
### 🥈 Silver Sponsors
|
||||
|
||||
<a aria-label="Fauna" href="https://dashboard.fauna.com/accounts/register?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2020">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/Fauna_Logo_Blue.png" width="200px">
|
||||
</a>
|
||||
|
||||
### 🏆 Gold Sponsors
|
||||
|
||||
<a aria-label="Your Company" href="#">
|
||||
<img alt="" src="https://dummyimage.com/1000x330/efe8ff/000000.png&text=Your+Logo+Here" width="300px">
|
||||
</a>
|
||||
|
||||
### 💎 Diamond Sponsors
|
||||
|
||||
<a aria-label="Your Company" href="#">
|
||||
<img alt="" src="https://dummyimage.com/1000x330/efe8ff/000000.png&text=Your+Logo+Here" width="400px">
|
||||
</a>
|
||||
|
||||
<br>
|
||||
|
||||
@@ -113,8 +167,6 @@ _Sponsor Blitz and display your logo and hiring status here. This is a great way
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://twitter.com/flybayer"><img src="https://avatars3.githubusercontent.com/u/8813276?v=4" width="100px;" alt=""/><br /><sub><b>Brandon Bayer</b></sub></a><br />Creator</td>
|
||||
<td align="center"><a href="https://medium.com/@ryardley"><img src="https://avatars0.githubusercontent.com/u/1256409?v=4" width="100px;" alt=""/><br /><sub><b>Rudi Yardley</b></sub></a><br />Node.js Wizard</td>
|
||||
<td align="center"><a href="https://merelinguist.now.sh"><img src="https://avatars3.githubusercontent.com/u/24858006?v=4" width="100px;" alt=""/><br /><sub><b>Dylan Brookes</b></sub></a><br />Friendly Generalist</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- markdownlint-enable -->
|
||||
@@ -133,6 +185,7 @@ _Code ownership, pull request approvals and merging, etc_ (see [MAINTAINERS.md](
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/aem"><img src="https://avatars0.githubusercontent.com/u/1909883?v=4" width="100px;" alt=""/><br /><sub><b>Adam Markon</b></sub></a><br />CLI</td>
|
||||
<td align="center"><a href="http://robdrosenberg.com"><img src="https://avatars0.githubusercontent.com/u/20813991?v=4" width="100px;" alt=""/><br /><sub><b>Robert Rosenberg</b></sub></a><br />Website/Docs</td>
|
||||
<td align="center"><a href="http://simonknott.de"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4" width="100px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br />SuperJSON</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- markdownlint-enable -->
|
||||
@@ -144,20 +197,24 @@ _Code ownership, pull request approvals and merging, etc_ (see [MAINTAINERS.md](
|
||||
|
||||
_Issue triage, pull request triage, community encouragement and moderation, etc_ (see [MAINTAINERS.md](./MAINTAINERS.md))
|
||||
|
||||
We need more woman & nonbinary level 1 maintainers. See [MAINTAINERS.md](./MAINTAINERS.md) for what this entails
|
||||
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/LoriKarikari"><img src="https://avatars1.githubusercontent.com/u/7902980?v=4" width="100px;" alt=""/><br /><sub><b>Lori Karikari</b></sub></a></td>
|
||||
<td align="center"><a href="https://corey-brown.com"><img src="https://avatars1.githubusercontent.com/u/12791148?v=4" width="100px;" alt=""/><br /><sub><b>Corey Brown</b></sub></a></td>
|
||||
<td align="center"><a href="http://simonknott.de"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4" width="100px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a></td>
|
||||
<td align="center"><a href="https://twitter.com/GeggsElias"><img src="https://avatars3.githubusercontent.com/u/22719177?v=4" width="100px;" alt=""/><br /><sub><b>Elias Johansson</b></sub></a></td>
|
||||
<td align="center"><a href="http://jeremyliberman.com/"><img src="https://avatars3.githubusercontent.com/u/2754163?v=4" width="100px;" alt=""/><br /><sub><b>Jeremy Liberman</b></td>
|
||||
<td align="center"><a href="http://jagascript.com"><img src="https://avatars0.githubusercontent.com/u/4562878?v=4" width="100px;" alt=""/><br /><sub><b>Jaga Santagostino</b></sub></a></td>
|
||||
<td align="center"><a href="https://simonpeterdebbarma.com"><img src="https://avatars3.githubusercontent.com/u/31207418?v=4" width="100px;" alt=""/><br /><sub><b>Simon Debbarma</b></sub></a></td>
|
||||
<td align="center"><a href="https://twitter.com/nitaking_"><img src="https://avatars2.githubusercontent.com/u/10850034?v=4" width="100px;" alt=""/><br /><sub><b>Satoshi Nitawaki</b></sub></a></td>
|
||||
<td align="center"><a href="https://twitter.com/sandulat"><img src="https://avatars2.githubusercontent.com/u/7345874?v=4" width="100px;" alt=""/><br /><sub><b>Alexandru Stratulat</b></sub></a></td>
|
||||
<td align="center"><a href="https://github.com/engelkes-finstreet"><img src="https://avatars0.githubusercontent.com/u/36962022?s=460&u=34cfc4a3d6da0a87026f6068c371779c68daa3a2&v=4" width="100px;" alt=""/><br /><sub><b>Patrick Engelkes</b></sub></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://twitter.com/jdavenport97"><img src="https://avatars2.githubusercontent.com/u/1329874?v=4" width="100px;" alt=""/><br /><sub><b>Jamie Davenport</b></sub></a></td>
|
||||
<td align="center"><a href="https://twitter.com/myrondavis"><img src="https://avatars2.githubusercontent.com/u/1430136?v=4" width="100px;" alt=""/><br /><sub><b>Myron Davis</b></sub></a></td>
|
||||
<td align="center"><a href="https://flavioander.com/"><img src="https://avatars2.githubusercontent.com/u/14948074?s=460&u=31d7ea58b5c5cd9f724d684ed578f68896c4af71&v=4" width="100px;" alt=""/><br /><sub><b>Flavio Andrade</b></sub></a></td>
|
||||
<td align="center"><a href="https://twitter.com/NaReto1125_"><img src="https://avatars.githubusercontent.com/reo777" width="100px;" alt=""/><br /><sub><b>Reo Ishiyama</b></sub></a></td>
|
||||
<td align="center"><a href="https://github.com/malkomalko"><img src="https://avatars.githubusercontent.com/malkomalko" width="100px;" alt=""/><br /><sub><b>Robert Malko</b></sub></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- markdownlint-enable -->
|
||||
@@ -175,68 +232,279 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://twitter.com/flybayer"><img src="https://avatars3.githubusercontent.com/u/8813276?v=4" width="100px;" alt=""/><br /><sub><b>Brandon Bayer</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=flybayer" title="Code">💻</a> <a href="#content-flybayer" title="Content">🖋</a> <a href="#ideas-flybayer" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Aflybayer" title="Reviewed Pull Requests">👀</a></td>
|
||||
<td align="center"><a href="https://medium.com/@ryardley"><img src="https://avatars0.githubusercontent.com/u/1256409?v=4" width="100px;" alt=""/><br /><sub><b>Rudi Yardley</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ryardley" title="Code">💻</a> <a href="#ideas-ryardley" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Aryardley" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=ryardley" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://merelinguist.me"><img src="https://avatars3.githubusercontent.com/u/24858006?v=4" width="100px;" alt=""/><br /><sub><b>Dylan Brookes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=merelinguist" title="Code">💻</a> <a href="#ideas-merelinguist" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Amerelinguist" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=merelinguist" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=merelinguist" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/aem"><img src="https://avatars0.githubusercontent.com/u/1909883?v=4" width="100px;" alt=""/><br /><sub><b>Adam Markon</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aem" title="Code">💻</a> <a href="#ideas-aem" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Aaem" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=aem" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://corey-brown.com"><img src="https://avatars1.githubusercontent.com/u/12791148?v=4" width="100px;" alt=""/><br /><sub><b>Corey Brown</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=coreybrown89" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Acoreybrown89" title="Reviewed Pull Requests">👀</a></td>
|
||||
<td align="center"><a href="https://github.com/LoriKarikari"><img src="https://avatars1.githubusercontent.com/u/7902980?v=4" width="100px;" alt=""/><br /><sub><b>Lori Karikari</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=LoriKarikari" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3ALoriKarikari" title="Reviewed Pull Requests">👀</a></td>
|
||||
<td align="center"><a href="https://twitter.com/GeggsElias"><img src="https://avatars3.githubusercontent.com/u/22719177?v=4" width="100px;" alt=""/><br /><sub><b>Elias Johansson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=eliasjohansson" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Aeliasjohansson" title="Reviewed Pull Requests">👀</a></td>
|
||||
<td align="center"><a href="https://twitter.com/flybayer"><img src="https://avatars3.githubusercontent.com/u/8813276?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brandon Bayer</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=flybayer" title="Code">💻</a> <a href="#content-flybayer" title="Content">🖋</a> <a href="#ideas-flybayer" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Aflybayer" title="Reviewed Pull Requests">👀</a></td>
|
||||
<td align="center"><a href="https://medium.com/@ryardley"><img src="https://avatars0.githubusercontent.com/u/1256409?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rudi Yardley</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ryardley" title="Code">💻</a> <a href="#ideas-ryardley" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Aryardley" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=ryardley" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://merelinguist.me"><img src="https://avatars3.githubusercontent.com/u/24858006?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dylan Brookes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=merelinguist" title="Code">💻</a> <a href="#ideas-merelinguist" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Amerelinguist" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=merelinguist" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=merelinguist" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/aem"><img src="https://avatars0.githubusercontent.com/u/1909883?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Adam Markon</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aem" title="Code">💻</a> <a href="#ideas-aem" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Aaem" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=aem" title="Tests">⚠️</a> <a href="#maintenance-aem" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://corey-brown.com"><img src="https://avatars1.githubusercontent.com/u/12791148?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Corey Brown</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=coreybrown89" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Acoreybrown89" title="Reviewed Pull Requests">👀</a> <a href="#maintenance-coreybrown89" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://github.com/LoriKarikari"><img src="https://avatars1.githubusercontent.com/u/7902980?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lori Karikari</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=LoriKarikari" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3ALoriKarikari" title="Reviewed Pull Requests">👀</a> <a href="#maintenance-LoriKarikari" title="Maintenance">🚧</a> <a href="https://github.com/blitz-js/blitz/commits?author=LoriKarikari" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://twitter.com/GeggsElias"><img src="https://avatars3.githubusercontent.com/u/22719177?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Elias Johansson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=eliasjohansson" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Aeliasjohansson" title="Reviewed Pull Requests">👀</a> <a href="#maintenance-eliasjohansson" title="Maintenance">🚧</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://fabulas.io"><img src="https://avatars1.githubusercontent.com/u/14793389?v=4" width="100px;" alt=""/><br /><sub><b>Michael Edelman </b></sub></a><br /><a href="#infra-medelman17" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/blitz-js/blitz/commits?author=medelman17" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.geistinteractive.com"><img src="https://avatars2.githubusercontent.com/u/316792?v=4" width="100px;" alt=""/><br /><sub><b>Todd Geist</b></sub></a><br /><a href="#financial-toddgeist" title="Financial">💵</a></td>
|
||||
<td align="center"><a href="https://github.com/quirk0o"><img src="https://avatars3.githubusercontent.com/u/5123725?v=4" width="100px;" alt=""/><br /><sub><b>Beata Obrok</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=quirk0o" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/tsawan"><img src="https://avatars3.githubusercontent.com/u/3263082?v=4" width="100px;" alt=""/><br /><sub><b>Tahir Awan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tsawan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://raluce.com"><img src="https://avatars1.githubusercontent.com/u/2454632?v=4" width="100px;" alt=""/><br /><sub><b>Camilo Gonzalez</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=camilo86" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://da.nielkempner.com"><img src="https://avatars3.githubusercontent.com/u/2532112?v=4" width="100px;" alt=""/><br /><sub><b>Daniel Kempner</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dkempner" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://gielcobben.com"><img src="https://avatars0.githubusercontent.com/u/2663212?v=4" width="100px;" alt=""/><br /><sub><b>Giel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=gielcobben" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://fabulas.io"><img src="https://avatars1.githubusercontent.com/u/14793389?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Edelman </b></sub></a><br /><a href="#infra-medelman17" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/blitz-js/blitz/commits?author=medelman17" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.geistinteractive.com"><img src="https://avatars2.githubusercontent.com/u/316792?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Todd Geist</b></sub></a><br /><a href="#financial-toddgeist" title="Financial">💵</a> <a href="https://github.com/blitz-js/blitz/commits?author=toddgeist" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://robdrosenberg.com"><img src="https://avatars0.githubusercontent.com/u/20813991?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robert Rosenberg</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=robdrosenberg" title="Code">💻</a> <a href="#maintenance-robdrosenberg" title="Maintenance">🚧</a> <a href="https://github.com/blitz-js/blitz/commits?author=robdrosenberg" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/quirk0o"><img src="https://avatars3.githubusercontent.com/u/5123725?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Beata Obrok</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=quirk0o" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/tsawan"><img src="https://avatars3.githubusercontent.com/u/3263082?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tahir Awan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tsawan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://raluce.com"><img src="https://avatars1.githubusercontent.com/u/2454632?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Camilo Gonzalez</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=camilo86" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://da.nielkempner.com"><img src="https://avatars3.githubusercontent.com/u/2532112?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Kempner</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dkempner" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://jeremyliberman.com/"><img src="https://avatars3.githubusercontent.com/u/2754163?v=4" width="100px;" alt=""/><br /><sub><b>Jeremy Liberman</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=MrLeebo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://jimthedev.com"><img src="https://avatars0.githubusercontent.com/u/108938?v=4" width="100px;" alt=""/><br /><sub><b>Jim Cummins</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jimthedev" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://kristinamatuska.com/"><img src="https://media-exp1.licdn.com/dms/image/C5603AQHVPAjV21gw9g/profile-displayphoto-shrink_200_200/0?e=1591228800&v=beta&t=0MlbmiYhNvGv1xjLD_fOhOFjVDZ7ltNwfGNeJ4DHedQ" width="100px;" alt=""/><br /><sub><b>Kristina Matuška</b></sub></a><br /><a href="#design" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/jasonblalock"><img src="https://avatars0.githubusercontent.com/u/5899929?v=4" width="100px;" alt=""/><br /><sub><b>Jason Blalock</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jasonblalock" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/aej11a"><img src="https://avatars2.githubusercontent.com/u/10066422?v=4" width="100px;" alt=""/><br /><sub><b>aej11a</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aej11a" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/marcoseoane"><img src="https://avatars0.githubusercontent.com/u/28088807?v=4" width="100px;" alt=""/><br /><sub><b>marcoseoane</b></sub></a><br /><a href="#ideas-marcoseoane" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://github.com/rishabhpoddar"><img src="https://avatars2.githubusercontent.com/u/2976287?v=4" width="100px;" alt=""/><br /><sub><b>Rishabh Poddar</b></sub></a><br /><a href="#ideas-rishabhpoddar" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="http://gielcobben.com"><img src="https://avatars0.githubusercontent.com/u/2663212?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=gielcobben" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://jeremyliberman.com/"><img src="https://avatars3.githubusercontent.com/u/2754163?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jeremy Liberman</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=MrLeebo" title="Code">💻</a> <a href="#maintenance-MrLeebo" title="Maintenance">🚧</a> <a href="https://github.com/blitz-js/blitz/commits?author=MrLeebo" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://jimthedev.com"><img src="https://avatars0.githubusercontent.com/u/108938?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jim Cummins</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jimthedev" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://kristinamatuska.com/"><img src="https://media-exp1.licdn.com/dms/image/C5603AQHVPAjV21gw9g/profile-displayphoto-shrink_200_200/0?e=1591228800&v=beta&t=0MlbmiYhNvGv1xjLD_fOhOFjVDZ7ltNwfGNeJ4DHedQ?s=100" width="100px;" alt=""/><br /><sub><b>Kristina Matuška</b></sub></a><br /><a href="#design" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/jasonblalock"><img src="https://avatars0.githubusercontent.com/u/5899929?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jason Blalock</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jasonblalock" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/aej11a"><img src="https://avatars2.githubusercontent.com/u/10066422?v=4?s=100" width="100px;" alt=""/><br /><sub><b>aej11a</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aej11a" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/marcoseoane"><img src="https://avatars0.githubusercontent.com/u/28088807?v=4?s=100" width="100px;" alt=""/><br /><sub><b>marcoseoane</b></sub></a><br /><a href="#ideas-marcoseoane" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/lorenzorapetti"><img src="https://avatars1.githubusercontent.com/u/2632174?v=4" width="100px;" alt=""/><br /><sub><b>Lorenzo Rapetti</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lorenzorapetti" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/wKovacs64"><img src="https://avatars1.githubusercontent.com/u/1288694?v=4" width="100px;" alt=""/><br /><sub><b>Justin Hall</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=wKovacs64" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=wKovacs64" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sijad"><img src="https://avatars3.githubusercontent.com/u/7693001?v=4" width="100px;" alt=""/><br /><sub><b>Sajjad Hashemian</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sijad" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ETLopes"><img src="https://avatars3.githubusercontent.com/u/34959471?v=4" width="100px;" alt=""/><br /><sub><b>Eduardo Lopes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ETLopes" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mattleff"><img src="https://avatars0.githubusercontent.com/u/120155?v=4" width="100px;" alt=""/><br /><sub><b>Matthew Leffler</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mattleff" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://hew.tools"><img src="https://avatars0.githubusercontent.com/u/3103241?v=4" width="100px;" alt=""/><br /><sub><b>Matt</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hew" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sonnypgs"><img src="https://avatars3.githubusercontent.com/u/1431300?v=4" width="100px;" alt=""/><br /><sub><b>Sonny</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sonnypgs" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/rishabhpoddar"><img src="https://avatars2.githubusercontent.com/u/2976287?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rishabh Poddar</b></sub></a><br /><a href="#ideas-rishabhpoddar" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://github.com/lorenzorapetti"><img src="https://avatars1.githubusercontent.com/u/2632174?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lorenzo Rapetti</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lorenzorapetti" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/wKovacs64"><img src="https://avatars1.githubusercontent.com/u/1288694?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Justin Hall</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=wKovacs64" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=wKovacs64" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sijad"><img src="https://avatars3.githubusercontent.com/u/7693001?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sajjad Hashemian</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sijad" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ETLopes"><img src="https://avatars3.githubusercontent.com/u/34959471?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Eduardo Lopes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ETLopes" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mattleff"><img src="https://avatars0.githubusercontent.com/u/120155?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matthew Leffler</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mattleff" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://hew.tools"><img src="https://avatars0.githubusercontent.com/u/3103241?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matt</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hew" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/Zeko369"><img src="https://avatars3.githubusercontent.com/u/3064377?v=4" width="100px;" alt=""/><br /><sub><b>Fran Zekan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Zeko369" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Zeko369" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://twitter.com/JanBaykara"><img src="https://avatars2.githubusercontent.com/u/237556?v=4" width="100px;" alt=""/><br /><sub><b>Jan Baykara</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=janbaykara" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mikeattara.com"><img src="https://avatars1.githubusercontent.com/u/31483629?v=4" width="100px;" alt=""/><br /><sub><b>Mike Perry Y Attara</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mikeattara" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://devanthe.dev"><img src="https://avatars0.githubusercontent.com/u/354652?v=4" width="100px;" alt=""/><br /><sub><b>Devan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=DevanB" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jclancy93"><img src="https://avatars2.githubusercontent.com/u/7850202?v=4" width="100px;" alt=""/><br /><sub><b>Jack Clancy</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jclancy93" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ntgussoni"><img src="https://avatars0.githubusercontent.com/u/10161067?v=4" width="100px;" alt=""/><br /><sub><b>Nicolas Torres</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://jagascript.com"><img src="https://avatars0.githubusercontent.com/u/4562878?v=4" width="100px;" alt=""/><br /><sub><b>Jaga Santagostino</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kandros" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=kandros" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sonnypgs"><img src="https://avatars3.githubusercontent.com/u/1431300?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sonny</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sonnypgs" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Zeko369"><img src="https://avatars3.githubusercontent.com/u/3064377?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fran Zekan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Zeko369" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Zeko369" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://twitter.com/JanBaykara"><img src="https://avatars2.githubusercontent.com/u/237556?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jan Baykara</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=janbaykara" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mikeattara.com"><img src="https://avatars1.githubusercontent.com/u/31483629?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mike Perry Y Attara</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mikeattara" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://devanthe.dev"><img src="https://avatars0.githubusercontent.com/u/354652?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Devan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=DevanB" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jclancy93"><img src="https://avatars2.githubusercontent.com/u/7850202?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jack Clancy</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jclancy93" title="Code">💻</a> <a href="#maintenance-jclancy93" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://github.com/ntgussoni"><img src="https://avatars0.githubusercontent.com/u/10161067?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nicolas Torres</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Antgussoni" title="Reviewed Pull Requests">👀</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://www.joaoportela.com"><img src="https://avatars0.githubusercontent.com/u/1010018?v=4" width="100px;" alt=""/><br /><sub><b>João Portela</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jportela" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://dajin.dev"><img src="https://avatars0.githubusercontent.com/u/7122182?v=4" width="100px;" alt=""/><br /><sub><b>Da-Jin Chu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dajinchu" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://shinyaigeek.dev/"><img src="https://avatars1.githubusercontent.com/u/42742053?v=4" width="100px;" alt=""/><br /><sub><b>Shinobu Hayashi</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Shinyaigeek" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://karankiri.com"><img src="https://avatars2.githubusercontent.com/u/19989161?v=4" width="100px;" alt=""/><br /><sub><b>Karan Kiri</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=karankiri" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/fullmetalengineer"><img src="https://avatars2.githubusercontent.com/u/5294903?v=4" width="100px;" alt=""/><br /><sub><b>Alan Long</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=fullmetalengineer" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://codingsh.xyz"><img src="https://avatars2.githubusercontent.com/u/57037080?v=4" width="100px;" alt=""/><br /><sub><b>codingsh</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=developerfred" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://twitter.com/peaonunes"><img src="https://avatars0.githubusercontent.com/u/3356720?v=4" width="100px;" alt=""/><br /><sub><b>Rafael Nunes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Apeaonunes" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=peaonunes" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://simonknott.de"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Skn0tt" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Skn0tt" title="Tests">⚠️</a> <a href="#maintenance-Skn0tt" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="http://jagascript.com"><img src="https://avatars0.githubusercontent.com/u/4562878?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jaga Santagostino</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kandros" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=kandros" title="Documentation">📖</a> <a href="#maintenance-kandros" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="http://www.joaoportela.com"><img src="https://avatars0.githubusercontent.com/u/1010018?v=4?s=100" width="100px;" alt=""/><br /><sub><b>João Portela</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jportela" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://dajin.dev"><img src="https://avatars0.githubusercontent.com/u/7122182?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Da-Jin Chu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dajinchu" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://shinyaigeek.dev/"><img src="https://avatars1.githubusercontent.com/u/42742053?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shinobu Hayashi</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Shinyaigeek" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://karankiri.com"><img src="https://avatars2.githubusercontent.com/u/19989161?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Karan Kiri</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=karankiri" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/fullmetalengineer"><img src="https://avatars2.githubusercontent.com/u/5294903?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alan Long</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=fullmetalengineer" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://simonpeterdebbarma.com"><img src="https://avatars3.githubusercontent.com/u/31207418?v=4" width="100px;" alt=""/><br /><sub><b>Simon Debbarma</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Asimonpeterdebbarma" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=simonpeterdebbarma" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/0xflotus"><img src="https://avatars3.githubusercontent.com/u/26602940?v=4" width="100px;" alt=""/><br /><sub><b>0xflotus</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=0xflotus" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=0xflotus" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dev.to/tmns"><img src="https://avatars3.githubusercontent.com/u/35785003?v=4" width="100px;" alt=""/><br /><sub><b>tmns</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tmns" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=tmns" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://codingsh.xyz"><img src="https://avatars2.githubusercontent.com/u/57037080?v=4?s=100" width="100px;" alt=""/><br /><sub><b>codingsh</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=developerfred" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://twitter.com/peaonunes"><img src="https://avatars0.githubusercontent.com/u/3356720?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rafael Nunes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Apeaonunes" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=peaonunes" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://simonpeterdebbarma.com"><img src="https://avatars3.githubusercontent.com/u/31207418?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Simon Debbarma</b></sub></a><br /><a href="#design-0ww" title="Design">🎨</a> <a href="#maintenance-0ww" title="Maintenance">🚧</a> <a href="https://github.com/blitz-js/blitz/commits?author=0ww" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/0xflotus"><img src="https://avatars3.githubusercontent.com/u/26602940?v=4?s=100" width="100px;" alt=""/><br /><sub><b>0xflotus</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=0xflotus" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=0xflotus" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dev.to/tmns"><img src="https://avatars3.githubusercontent.com/u/35785003?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tmns</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tmns" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=tmns" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://jruharris.com"><img src="https://avatars1.githubusercontent.com/u/8636691?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jru Harris</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=harris1717" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://twitter.com/ivandevp"><img src="https://avatars3.githubusercontent.com/u/9284690?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ivan Medina</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ivandevp" title="Code">💻</a> <a href="#maintenance-ivandevp" title="Maintenance">🚧</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://www.dwightwatson.com"><img src="https://avatars3.githubusercontent.com/u/1100408?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dwight Watson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dwightwatson" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=dwightwatson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://is2ei.com/"><img src="https://avatars3.githubusercontent.com/u/3948353?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Horie Issei</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=is2ei" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://twitter.com/lednhatkhanh"><img src="https://avatars2.githubusercontent.com/u/9303093?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nhat Khanh</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lednhatkhanh" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://builtforfifty.com"><img src="https://avatars1.githubusercontent.com/u/19371989?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Abu Uzayr</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=abuuzayr" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/nabi009"><img src="https://avatars0.githubusercontent.com/u/3170831?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nabiullah elham</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nabi009" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://lachlanjc.com"><img src="https://avatars1.githubusercontent.com/u/5074763?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lachlan Campbell</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lachlanjc" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://enzoferey.com"><img src="https://avatars1.githubusercontent.com/u/10673347?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Enzo Ferey</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=enzoferey" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/pgrimaud"><img src="https://avatars1.githubusercontent.com/u/1866496?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pierre Grimaud</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=pgrimaud" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://pixelmord.github.io"><img src="https://avatars2.githubusercontent.com/u/224168?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andreas Adam</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=pixelmord" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://kevo.dev"><img src="https://avatars3.githubusercontent.com/u/15717067?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Tovar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kevotovar" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://anteprimorac.com.hr"><img src="https://avatars0.githubusercontent.com/u/972083?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ante Primorac</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=anteprimorac" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=anteprimorac" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://mykalmachon.dev"><img src="https://avatars1.githubusercontent.com/u/7844994?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mykal Machon</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=MykalMachon" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://jamiedavenport.dev"><img src="https://avatars2.githubusercontent.com/u/1329874?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jamie Davenport</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jamiedavenport" title="Code">💻</a> <a href="#maintenance-jamiedavenport" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://cloudnweb.dev/"><img src="https://avatars0.githubusercontent.com/u/17050715?v=4?s=100" width="100px;" alt=""/><br /><sub><b>GaneshMani</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ganeshmani" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://ramonmorcillo.com"><img src="https://avatars3.githubusercontent.com/u/31936665?v=4?s=100" width="100px;" alt=""/><br /><sub><b>reymon359</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=reymon359" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/gregory-vasquez-96413b184/"><img src="https://avatars1.githubusercontent.com/u/36422346?v=4?s=100" width="100px;" alt=""/><br /><sub><b>gvasquez11</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=gvasquez11" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/josemiguelo"><img src="https://avatars1.githubusercontent.com/u/15330034?v=4?s=100" width="100px;" alt=""/><br /><sub><b> José Miguel Ochoa</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=josemiguelo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/osirvent"><img src="https://avatars2.githubusercontent.com/u/5927133?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Oscar Sirvent</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=osirvent" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=osirvent" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/donni106"><img src="https://avatars0.githubusercontent.com/u/1942953?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Molnar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=donni106" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=donni106" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/exclipy"><img src="https://avatars1.githubusercontent.com/u/508799?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Wu Won</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=exclipy" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/tehnuge"><img src="https://avatars1.githubusercontent.com/u/1928236?v=4?s=100" width="100px;" alt=""/><br /><sub><b>John Duong</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tehnuge" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://noahfleischmann.com"><img src="https://avatars0.githubusercontent.com/u/23707137?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Noah Fleischmann</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=fnoah" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/toshi1127"><img src="https://avatars3.githubusercontent.com/u/32378535?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matsumoto Toshi</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=toshi1127" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=toshi1127" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/simonedelmann"><img src="https://avatars2.githubusercontent.com/u/2821076?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Simon Edelmann</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=simonedelmann" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://shaun.church"><img src="https://avatars3.githubusercontent.com/u/571764?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shaun Church</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=shaunchurch" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=shaunchurch" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://styfle.dev"><img src="https://avatars1.githubusercontent.com/u/229881?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=styfle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/SigurdMW"><img src="https://avatars3.githubusercontent.com/u/6359003?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sigurd Moland Wahl</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=SigurdMW" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://brianandrews.dev/"><img src="https://avatars1.githubusercontent.com/u/6384100?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brian Andrews</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sbardian" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://garrisonsnelling.com"><img src="https://avatars0.githubusercontent.com/u/5100597?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Garrison Snelling</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=garrisons" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/tylangesmith"><img src="https://avatars1.githubusercontent.com/u/22609577?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ty Lange-Smith</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tylangesmith" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://rubenmoya.dev"><img src="https://avatars3.githubusercontent.com/u/905225?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rubén Moya</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rubenmoya" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=rubenmoya" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/robertgrzonka"><img src="https://avatars0.githubusercontent.com/u/35585466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>robertgrzonka</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=robertgrzonka" title="Code">💻</a> <a href="#infra-robertgrzonka" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<td align="center"><a href="https://github.com/agoxlea"><img src="https://avatars3.githubusercontent.com/u/1240841?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alex Orr</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=agoxlea" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://christse.io"><img src="https://avatars1.githubusercontent.com/u/250450?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Chris Tse</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=chris-tse" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://twitter.com/nettofarah"><img src="https://avatars1.githubusercontent.com/u/270688?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Netto Farah</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nettofarah" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/rohanjulka19"><img src="https://avatars0.githubusercontent.com/u/19673968?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rohan Julka</b></sub></a><br /><a href="#infra-rohanjulka19" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<td align="center"><a href="https://www.ivansantos.me"><img src="https://avatars3.githubusercontent.com/u/301291?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ivan Santos</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=pragmaticivan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://able.bio"><img src="https://avatars0.githubusercontent.com/u/12991390?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Soumyajit Pathak</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=drenther" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.sebastiankurpiel.com"><img src="https://avatars2.githubusercontent.com/u/16307737?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sebastian Kurpiel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=SebastianKurp" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/scisteffan"><img src="https://avatars2.githubusercontent.com/u/2676185?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Steffan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=scisteffan" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=scisteffan" title="Documentation">📖</a> <a href="#financial-scisteffan" title="Financial">💵</a></td>
|
||||
<td align="center"><a href="https://github.com/kripod"><img src="https://avatars3.githubusercontent.com/u/14854048?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kristóf Poduszló</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kripod" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Weilbyte"><img src="https://avatars1.githubusercontent.com/u/43392677?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Weilbyte</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Weilbyte" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Weilbyte" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://ricardotrejos.tech"><img src="https://avatars1.githubusercontent.com/u/8602086?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ricardo Trejos</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cardotrejos" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=cardotrejos" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://gkaragkiaouris.tech/"><img src="https://avatars0.githubusercontent.com/u/8822835?v=4?s=100" width="100px;" alt=""/><br /><sub><b>George Karagkiaouris</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=karaggeorge" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=karaggeorge" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/brady-pascoe-3bba6b13a/"><img src="https://avatars0.githubusercontent.com/u/18705892?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brady Pascoe</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=bpas247" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.yeahcoach.com"><img src="https://avatars1.githubusercontent.com/u/761766?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jirka Svoboda</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=svobik7" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/alan2207"><img src="https://avatars3.githubusercontent.com/u/12713315?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alan Alickovic</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=alan2207" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=alan2207" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://yngve.hoiseth.net"><img src="https://avatars0.githubusercontent.com/u/8469540?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yngve Høiseth</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=yhoiseth" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://twitter.com/bruno_crosier"><img src="https://avatars1.githubusercontent.com/u/18399089?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bruno Crosier</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=brunocrosier" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/jschepmans"><img src="https://avatars2.githubusercontent.com/u/5782977?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Johan Schepmans</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jschepmans" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://twitter.com/dillonraphael"><img src="https://avatars0.githubusercontent.com/u/3496193?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dillon Raphael</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dillonraphael" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/clgeoio"><img src="https://avatars2.githubusercontent.com/u/37571416?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cody G</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=clgeoio" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=clgeoio" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/madflow"><img src="https://avatars0.githubusercontent.com/u/183248?v=4?s=100" width="100px;" alt=""/><br /><sub><b>madflow</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=madflow" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://twitter.com/nitaking_"><img src="https://avatars2.githubusercontent.com/u/10850034?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Satoshi Nitawaki</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nitaking" title="Code">💻</a> <a href="#maintenance-nitaking" title="Maintenance">🚧</a> <a href="#question-nitaking" title="Answering Questions">💬</a> <a href="https://github.com/blitz-js/blitz/commits?author=nitaking" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sirmyron"><img src="https://avatars2.githubusercontent.com/u/1430136?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sirmyron</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sirmyron" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=sirmyron" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/engelkes-finstreet"><img src="https://avatars1.githubusercontent.com/u/36962022?v=4?s=100" width="100px;" alt=""/><br /><sub><b>engelkes-finstreet</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=engelkes-finstreet" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=engelkes-finstreet" title="Code">💻</a> <a href="#maintenance-engelkes-finstreet" title="Maintenance">🚧</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://twitter.com/pixelscommander"><img src="https://avatars2.githubusercontent.com/u/810671?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Denis Radin</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3APixelsCommander" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=PixelsCommander" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=PixelsCommander" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/xiaoyu-tamu"><img src="https://avatars3.githubusercontent.com/u/33362998?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Li</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=xiaoyu-tamu" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/yuta0801"><img src="https://avatars2.githubusercontent.com/u/21266306?v=4?s=100" width="100px;" alt=""/><br /><sub><b>yuta0801</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=yuta0801" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Obii-bit"><img src="https://avatars2.githubusercontent.com/u/67339820?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Obadja Ris</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Obii-bit" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://jfelix.info"><img src="https://avatars2.githubusercontent.com/u/21092519?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jose Felix </b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=JoseRFelix" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/johncantrell97"><img src="https://avatars3.githubusercontent.com/u/41305919?v=4?s=100" width="100px;" alt=""/><br /><sub><b>John Cantrell</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=johncantrell97" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://kwuang.me"><img src="https://avatars1.githubusercontent.com/u/10319942?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kwuang Tang</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cktang88" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/johnletey"><img src="https://avatars1.githubusercontent.com/u/62398724?v=4?s=100" width="100px;" alt=""/><br /><sub><b>John Letey</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=johnletey" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ditorojuan"><img src="https://avatars0.githubusercontent.com/u/22530892?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Juan Di Toro</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ditorojuan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/taylorcjohnson"><img src="https://avatars0.githubusercontent.com/u/10552296?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Taylor Johnson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=taylorcjohnson" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=taylorcjohnson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://twitter.com/tsriram"><img src="https://avatars3.githubusercontent.com/u/450559?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sriram Thiagarajan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tsriram" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://sergiodxa.com"><img src="https://avatars2.githubusercontent.com/u/1312018?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sergio Xalambrí</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sergiodxa" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/doeixd"><img src="https://avatars3.githubusercontent.com/u/13461122?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Patrick G</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=doeixd" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://avinash.com.np"><img src="https://avatars3.githubusercontent.com/u/513457?v=4?s=100" width="100px;" alt=""/><br /><sub><b>अभिनाश (Avinash)</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hardfire" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://enricoschaaf.com"><img src="https://avatars1.githubusercontent.com/u/54645197?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Enrico Schaaf</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=enricoschaaf" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://kitze.io"><img src="https://avatars0.githubusercontent.com/u/1160594?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kitze</b></sub></a><br /><a href="#ideas-kitze" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://github.com/drmas"><img src="https://avatars3.githubusercontent.com/u/644440?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mohamed Shaban</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=drmas" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/jorisre"><img src="https://avatars1.githubusercontent.com/u/7545547?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Joris</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jorisre" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Kamshak"><img src="https://avatars3.githubusercontent.com/u/337968?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Valentin Funk</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Kamshak" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://lukebennett.com"><img src="https://avatars1.githubusercontent.com/u/135390?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Luke Bennett</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lukebennett" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://haseebmajid.dev"><img src="https://avatars0.githubusercontent.com/u/998807?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Haseeb Majid</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hmajid2301" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/phillippschmedt"><img src="https://avatars0.githubusercontent.com/u/16028406?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Phillipp Schmedt</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=phillippschmedt" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://haspar.us"><img src="https://avatars0.githubusercontent.com/u/15332326?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Piotr Monwid-Olechnowicz</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hasparus" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://mizchi.dev"><img src="https://avatars2.githubusercontent.com/u/73962?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kotaro Chikuba</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mizchi" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=mizchi" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/konradkalemba"><img src="https://avatars0.githubusercontent.com/u/8682104?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Konrad Kalemba</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=konradkalemba" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Alucard17"><img src="https://avatars1.githubusercontent.com/u/26205172?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alucard17</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Alucard17" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Dohxis"><img src="https://avatars2.githubusercontent.com/u/8768909?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Domantas Mauruča</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Dohxis" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=Dohxis" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://sandulat.com/"><img src="https://avatars0.githubusercontent.com/u/7345874?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stratulat Alexandru</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sandulat" title="Code">💻</a> <a href="#maintenance-sandulat" title="Maintenance">🚧</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/aericson"><img src="https://avatars3.githubusercontent.com/u/692542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>André Ericson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aericson" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=aericson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://Cajotafer.com"><img src="https://avatars2.githubusercontent.com/u/41461969?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Carlos Fernández</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cajotafer" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://oesterkilde.dk/"><img src="https://avatars1.githubusercontent.com/u/6379824?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Østerkilde</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Kosai106" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=Kosai106" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/aaronfulkerson"><img src="https://avatars0.githubusercontent.com/u/31112737?v=4?s=100" width="100px;" alt=""/><br /><sub><b>aaronfulkerson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aaronfulkerson" title="Code">💻</a> <a href="#question-aaronfulkerson" title="Answering Questions">💬</a></td>
|
||||
<td align="center"><a href="https://github.com/alexnaiman"><img src="https://avatars3.githubusercontent.com/u/25799714?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexandru Naiman</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=alexnaiman" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://davidlutta.github.io/portfolio/"><img src="https://avatars2.githubusercontent.com/u/14890315?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Ezekiel Lutta</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=davidlutta" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/wanjuntham"><img src="https://avatars1.githubusercontent.com/u/49380551?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wanjuntham</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=wanjuntham" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://www.nahuelchaves.xyz"><img src="https://avatars3.githubusercontent.com/u/96837?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Victor Nahuel Chaves</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nahue" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/peter50216"><img src="https://avatars3.githubusercontent.com/u/891109?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Peter Shih</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=peter50216" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://seweryn.kale.mba"><img src="https://avatars3.githubusercontent.com/u/37031328?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Seweryn Kalemba</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sewerynkalemba" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://nksaraf.github.io"><img src="https://avatars2.githubusercontent.com/u/11255148?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nikhil Saraf</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nksaraf" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=nksaraf" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://zane.sh"><img src="https://avatars0.githubusercontent.com/u/16865690?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Zane</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=zanedb" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/dulcehc"><img src="https://avatars1.githubusercontent.com/u/19391835?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dulce Hernández</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dulcehc" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://markhaehnel.de"><img src="https://avatars2.githubusercontent.com/u/1516205?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mark Hähnel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=markhaehnel" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://stackoverflow.com/users/872395/nemesv"><img src="https://avatars0.githubusercontent.com/u/251330?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Viktor Nemes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nemesv" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://gabeoleary.com"><img src="https://avatars1.githubusercontent.com/u/16123225?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gabe O'Leary</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=goleary" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/machadolucasvp"><img src="https://avatars0.githubusercontent.com/u/44952113?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lucas Machado</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=machadolucasvp" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/maciekgrzybek"><img src="https://avatars2.githubusercontent.com/u/16546428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>maciek_grzybek</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=maciekgrzybek" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mweibel"><img src="https://avatars1.githubusercontent.com/u/307427?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Weibel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mweibel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://isoppp.com"><img src="https://avatars0.githubusercontent.com/u/16318727?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Hiroki Isogai</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=isoppp" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/matamatanot"><img src="https://avatars2.githubusercontent.com/u/39780486?v=4?s=100" width="100px;" alt=""/><br /><sub><b>matamatanot</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=matamatanot" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/ericsakmar"><img src="https://avatars3.githubusercontent.com/u/5620709?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Eric Sakmar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ericsakmar" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/leggsimon"><img src="https://avatars2.githubusercontent.com/u/11544418?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Simon Legg</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=leggsimon" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://robsoriano.com"><img src="https://avatars3.githubusercontent.com/u/13049130?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robert Soriano</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=wobsoriano" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/benediktms"><img src="https://avatars2.githubusercontent.com/u/48836135?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Benedikt Schnatterbeck</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=benediktms" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://taloranderson.com"><img src="https://avatars2.githubusercontent.com/u/11509865?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Talor Anderson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Talor-A" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/akirabaruah"><img src="https://avatars2.githubusercontent.com/u/6751517?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Akira Baruah</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=akirabaruah" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://chriswray.dev/"><img src="https://avatars0.githubusercontent.com/u/53663762?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christopher Wray</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cwray-tech" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/piotrski"><img src="https://avatars0.githubusercontent.com/u/244174?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Piotrek Tomczewski</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=piotrski" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://raph.site"><img src="https://avatars3.githubusercontent.com/u/1575946?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Raphaël Huchet</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://kattcorp.com"><img src="https://avatars1.githubusercontent.com/u/459267?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alex Johansson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=KATT" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://davidmazza.com"><img src="https://avatars0.githubusercontent.com/u/120893?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Mazza</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dmzza" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/rayandrews"><img src="https://avatars1.githubusercontent.com/u/4437323?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ray Andrew</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rayandrews" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=rayandrews" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://Dal.Design"><img src="https://avatars3.githubusercontent.com/u/43112535?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Abdullah Mzaien</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Mzaien" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://kwao.io"><img src="https://avatars2.githubusercontent.com/u/8839514?v=4?s=100" width="100px;" alt=""/><br /><sub><b>William Kwao</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=williamkwao" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/sakulstra"><img src="https://avatars3.githubusercontent.com/u/4396533?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lukas Strassel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sakulstra" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://thibpat.com"><img src="https://avatars3.githubusercontent.com/u/494686?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Thibaut Patel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tpatel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://jonstuebe.com"><img src="https://avatars0.githubusercontent.com/u/156722?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jon Stuebe</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jonstuebe" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://ugogo.dev"><img src="https://avatars2.githubusercontent.com/u/5040476?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ugo Onali</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ugogo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://saintmalik.me"><img src="https://avatars1.githubusercontent.com/u/37118134?v=4?s=100" width="100px;" alt=""/><br /><sub><b>SaintMalik</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=saintmalik" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://khaledgarbaya.net"><img src="https://avatars1.githubusercontent.com/u/1156093?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Khaled Garbaya</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Khaledgarbaya" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://tundera.dev"><img src="https://avatars0.githubusercontent.com/u/61833561?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tundera</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tundera" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/markylaing"><img src="https://avatars2.githubusercontent.com/u/41469221?v=4?s=100" width="100px;" alt=""/><br /><sub><b>markylaing</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=markylaing" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=markylaing" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://akfm.dev/"><img src="https://avatars2.githubusercontent.com/u/25711332?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Akifumi Sato</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=AkifumiSato" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/beeplin"><img src="https://avatars3.githubusercontent.com/u/13058150?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Beep LIN</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=beeplin" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://mattwood.tech/"><img src="https://avatars1.githubusercontent.com/u/22530815?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matt Wood</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mattfwood" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://joaquin.axai.mx"><img src="https://avatars1.githubusercontent.com/u/15214?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Joaquin Bravo Contreras</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jackbravo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/arjundubey-cr"><img src="https://avatars0.githubusercontent.com/u/40758425?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Arjun Dubey</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=arjundubey-cr" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/chanand"><img src="https://avatars0.githubusercontent.com/u/1317789?v=4?s=100" width="100px;" alt=""/><br /><sub><b>chanand</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=chanand" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/phillipkregg"><img src="https://avatars0.githubusercontent.com/u/1066044?v=4?s=100" width="100px;" alt=""/><br /><sub><b>phillipkregg</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=phillipkregg" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://timothyreynolds.co.uk"><img src="https://avatars1.githubusercontent.com/u/168870?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tim Reynolds</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=timReynolds" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://linbudu.top/"><img src="https://avatars0.githubusercontent.com/u/48507806?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Linbudu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=linbudu599" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.superservice-international.com"><img src="https://avatars0.githubusercontent.com/u/6090492?v=4?s=100" width="100px;" alt=""/><br /><sub><b>C Reimers</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=creimers" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/kyken"><img src="https://avatars2.githubusercontent.com/u/20137120?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tsuyoshi Osawa</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kyken" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://rembrandtreyes.com/"><img src="https://avatars1.githubusercontent.com/u/15057964?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rembrandt Reyes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rembrandtreyes" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=rembrandtreyes" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=rembrandtreyes" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://doi-t.net"><img src="https://avatars2.githubusercontent.com/u/5877477?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Toshiya Doi</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=doi-t" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://www.koolii.net/"><img src="https://avatars1.githubusercontent.com/u/3866581?v=4?s=100" width="100px;" alt=""/><br /><sub><b>t.kuriyama</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=koolii" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/malkomalko"><img src="https://avatars3.githubusercontent.com/u/763?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robert Malko</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=malkomalko" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ranjan-purbey"><img src="https://avatars3.githubusercontent.com/u/6953187?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ranjan Purbey</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ranjan-purbey" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/tarunama"><img src="https://avatars3.githubusercontent.com/u/6047881?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tarunama</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tarunama" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.bacongravy.net/"><img src="https://avatars3.githubusercontent.com/u/16848768?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Kramer</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=bacongravy" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://mikeesto.com"><img src="https://avatars1.githubusercontent.com/u/21051488?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Esteban</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mikeesto" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/marina-ki"><img src="https://avatars0.githubusercontent.com/u/54174518?v=4?s=100" width="100px;" alt=""/><br /><sub><b>marina</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=marina-ki" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=marina-ki" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/jonasthiesen"><img src="https://avatars.githubusercontent.com/u/23408018?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jonas Thiesen</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jonasthiesen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://thakkaryash94.github.io/"><img src="https://avatars.githubusercontent.com/u/7349778?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yash Thakkar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=thakkaryash94" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/rince"><img src="https://avatars.githubusercontent.com/u/933895?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kazuma Suzuki</b></sub></a><br /><a href="#design-rince" title="Design">🎨</a> <a href="https://github.com/blitz-js/blitz/commits?author=rince" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://queq1890.info"><img src="https://avatars.githubusercontent.com/u/32263803?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yuji Matsumoto</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=queq1890" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Gim3l"><img src="https://avatars.githubusercontent.com/u/46765702?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gimel Dick</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Gim3l" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/akbo"><img src="https://avatars.githubusercontent.com/u/1926271?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andreas Bollig</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=akbo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://ajm.codes"><img src="https://avatars.githubusercontent.com/u/66390428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>AJ Markow</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ajmarkow" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=ajmarkow" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://wafuwafu13.hateblo.jp/"><img src="https://avatars.githubusercontent.com/u/50798936?v=4?s=100" width="100px;" alt=""/><br /><sub><b>TagawaHirotaka</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=wafuwafu13" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=wafuwafu13" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/merodiro"><img src="https://avatars.githubusercontent.com/u/17033502?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Amr A.Mohammed</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=merodiro" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.lucaswillems.com"><img src="https://avatars.githubusercontent.com/u/5437552?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lucas Willems</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lcswillems" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=lcswillems" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://alistair.cloud"><img src="https://avatars.githubusercontent.com/u/25351731?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alistair Smith</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=alii" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://rodrigoehlers.com"><img src="https://avatars.githubusercontent.com/u/19683042?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rodrigo Ehlers</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rodrigoehlers" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.builtopen.com/"><img src="https://avatars.githubusercontent.com/u/1734057?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Ford</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mtford90" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- markdownlint-restore -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
591
TUTORIAL.md
591
TUTORIAL.md
@@ -1,591 +0,0 @@
|
||||
# Blitz Tutorial
|
||||
|
||||
← [Back to Alpha Guide](https://github.com/blitz-js/blitz/blob/canary/USER_GUIDE.md)
|
||||
|
||||
Thanks for trying out Blitz! In this tutorial, we’ll walk you through the creation of a basic voting application.
|
||||
|
||||
We’ll assume that you have [Blitz installed](https://github.com/blitz-js/blitz/blob/canary/USER_GUIDE.md#blitz-app-development) already. You can tell if Blitz is installed, and which version you have by running the following command in your terminal:
|
||||
|
||||
```sh
|
||||
$ blitz -v
|
||||
```
|
||||
|
||||
If Blitz is installed, you should see the version of your installation. If it isn’t, you’ll get an error saying something like “command not found: blitz”.
|
||||
|
||||
## Creating a project
|
||||
|
||||
If this is your first time using Blitz, you’ll have to begin with some initial setup. We provide a command which takes care of all this for you, generating the configuration and code you need to get started.
|
||||
|
||||
From the command line, `cd` into the directory where you’d like to store your code, and then run the following command to create a new TypeScript blitz project:
|
||||
|
||||
```sh
|
||||
blitz new mysite
|
||||
```
|
||||
|
||||
*Note, you can create a JavaScript blitz project instead by running `blitz new mysite --js`; however, this tutorial assumes a TypeScript project.*
|
||||
|
||||
This should create a `mysite` directory in your current directory.
|
||||
|
||||
Let’s look at what `blitz new` created:
|
||||
|
||||
```
|
||||
mysite
|
||||
├── app
|
||||
│ ├── components
|
||||
│ │ └── ErrorBoundary.tsx
|
||||
│ ├── layouts
|
||||
│ └── pages
|
||||
│ ├── _app.tsx
|
||||
│ ├── _document.tsx
|
||||
│ └── index.tsx
|
||||
├── db
|
||||
│ ├── migrations
|
||||
│ ├── index.ts
|
||||
│ └── schema.prisma
|
||||
├── integrations
|
||||
├── jobs
|
||||
├── node_modules
|
||||
├── public
|
||||
│ ├── favicon.ico
|
||||
│ └── logo.png
|
||||
├── utils
|
||||
├── .babelrc.js
|
||||
├── .env
|
||||
├── .eslintrc.js
|
||||
├── .gitignore
|
||||
├── .npmrc
|
||||
├── .prettierignore
|
||||
├── README.md
|
||||
├── blitz.config.js
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── yarn.lock
|
||||
```
|
||||
|
||||
These files are:
|
||||
|
||||
- The `app/` directory is a container for most of your project. This is where you’ll put any pages or API routes.
|
||||
|
||||
- `db`/ is where your database configuration goes. If you’re writing models or checking migrations, this is where to go.
|
||||
|
||||
- `node_modules/` is where your “dependencies” are stored. This directory is updated by your package manager, so you don’t have to worry too much about it.
|
||||
|
||||
- `public/` is a directory where you will put any static assets. If you have images, files, or videos which you want to use in your app, this is where to put them.
|
||||
|
||||
- `utils/` is a good place to put any shared utility files which you might use across different sections of your app.
|
||||
|
||||
- `.babelrc.js`, `.env`, etc. ("dotfiles") are configuration files for various bits of JavaScript tooling.
|
||||
|
||||
- `blitz.config.js` is for advanced custom configuration of Blitz. It extends [`next.config.js`](https://nextjs.org/docs/api-reference/next.config.js/introduction).
|
||||
|
||||
- `package.json` contains information about your dependencies and devDependencies. If you’re using a tool like `npm` or `yarn`, you won’t have to worry about this much.
|
||||
|
||||
- `tsconfig.json` is our recommended setup for TypeScript.
|
||||
|
||||
## The development server
|
||||
|
||||
Let’s check that your Blitz project works. Make sure you are in the `mysite` directory, if you haven’t already, and run the following command:
|
||||
|
||||
```sh
|
||||
$ blitz start
|
||||
```
|
||||
|
||||
You’ll see the following output on the command line:
|
||||
|
||||
```sh
|
||||
✔ Prepped for launch
|
||||
[ wait ] starting the development server ...
|
||||
[ info ] waiting on http://localhost:3000 ...
|
||||
[ info ] bundled successfully, waiting for typecheck results...
|
||||
[ wait ] compiling ...
|
||||
[ info ] bundled successfully, waiting for typecheck results...
|
||||
[ ready ] compiled successfully - ready on http://localhost:3000
|
||||
```
|
||||
|
||||
Now that the server’s running, visit http://localhost:3000/ with your Web browser. You’ll see a welcome page, with the Blitz logo. It worked!
|
||||
|
||||
## Write your first page
|
||||
|
||||
Now that your development environment—a “project”—is set up, you’re ready to start building out the app. First, we’ll create your first page.
|
||||
|
||||
Open the file `app/pages/index.tsx` and put the following code in it:
|
||||
|
||||
```tsx
|
||||
export default () => (
|
||||
<div>
|
||||
<h1>Hello, world!</h1>
|
||||
</div>
|
||||
)
|
||||
```
|
||||
|
||||
This is the simplest page possible in Blitz. To look at it, go back to your browser and go to http://localhost:3000. You should see your text appear! Try editing the `index.tsx` file, and make it your own! When you’re ready, move on to the next section.
|
||||
|
||||
## Database setup
|
||||
|
||||
Now, we’ll setup the database and create your first model.
|
||||
|
||||
Open up `db/schema.prisma`. It’s a configuration file which our default database engine Prisma uses.
|
||||
|
||||
By default, the apps is created with SQLite. If you’re new to databases, or you’re just interested in trying Blitz, this is the easiest choice. Note that when starting your first project, you may want to use a more scalable database like PostgreSQL, to avoid the pains of switching your database down the road.
|
||||
|
||||
## Creating models
|
||||
|
||||
Now we’ll define your models — essentially your database layout — with additional metadata.
|
||||
|
||||
In `schema.prisma`, we’ll create two models: `Question`, and `Choice`. A `Question` has a question and a publication date. A `Choice` has two fields: the text of the choice and a vote count. Each has an id, and each `Choice` is associated with a `Question`.
|
||||
|
||||
Edit the `schema.prisma` file so it looks like this:
|
||||
|
||||
```
|
||||
// (datasource and generator)
|
||||
|
||||
...
|
||||
|
||||
model Question {
|
||||
id Int @default(autoincrement()) @id
|
||||
text String
|
||||
publishedAt DateTime
|
||||
choices Choice[]
|
||||
}
|
||||
|
||||
model Choice {
|
||||
id Int @default(autoincrement()) @id
|
||||
text String
|
||||
votes Int @default(0)
|
||||
question Question @relation(fields: [questionId], references: [id])
|
||||
questionId Int
|
||||
}
|
||||
```
|
||||
|
||||
Now, we need to migrate our database. This is a way of telling it that you have edited your schema in some way. Run the below command. When it asks you to enter a migration name you can enter anything you want, perhaps `
|
||||
"init db":
|
||||
|
||||
```sh
|
||||
$ blitz db migrate
|
||||
```
|
||||
|
||||
## Playing with the API
|
||||
|
||||
Now, let’s hop into the interactive Blitz shell and play around with the free API Blitz gives you. To invoke the Blitz console, use this command:
|
||||
|
||||
```sh
|
||||
$ blitz console
|
||||
```
|
||||
|
||||
Once you’re in the console, explore the Database API:
|
||||
|
||||
[//]: # 'Let’s move this to await when it’s available for all */'
|
||||
|
||||
```sh
|
||||
# No questions are in the system yet.
|
||||
⚡ > db.question.findMany().then(console.log)
|
||||
[]
|
||||
|
||||
# Create a new Question.
|
||||
⚡ > let q
|
||||
undefined
|
||||
|
||||
⚡ > db.question.create({data: {text: 'What’s new?', publishedAt: new Date()}}).then(res => q = res)
|
||||
Promise { <pending> }
|
||||
|
||||
# See the entire object
|
||||
⚡ > q
|
||||
{ id: 1, text: "What’s new?", publishedAt: 2020-04-24T22:08:17.307Z }
|
||||
|
||||
# Or access individual values on the object.
|
||||
⚡ > q.text
|
||||
"What’s new?"
|
||||
|
||||
⚡ > q.publishedAt
|
||||
2020-04-24T22:08:17.307Z
|
||||
|
||||
# Change values by using the update function
|
||||
⚡ > db.question.update({where: {id: 1}, data: {text: 'What’s up?'}}).then(res => q = res)
|
||||
Promise { <pending> }
|
||||
|
||||
# See the result
|
||||
⚡ > q
|
||||
{ id: 1, text: 'What’s up?', publishedAt: 2020-04-24T22:08:17.307Z }
|
||||
|
||||
# db.question.findMany() displays all the questions in the database.
|
||||
⚡ > db.question.findMany().then(console.log)
|
||||
[
|
||||
{ id: 1, text: 'What’s up?', publishedAt: 2020-04-24T22:08:17.307Z }
|
||||
]
|
||||
```
|
||||
|
||||
## Writing more pages
|
||||
|
||||
Let’s create some more pages. Blitz provides a handy utility for scaffolding out pages, called `generate`. Let’s run it now with our `Question` model:
|
||||
|
||||
```sh
|
||||
$ blitz generate all question
|
||||
```
|
||||
|
||||
Great! Before running the app again, we need to customise some of these pages which have just been generated. Open your text editor and look at `app/questions/pages/index.tsx`. Notice that a `QuestionsList` component has been generated for you:
|
||||
|
||||
```jsx
|
||||
export const QuestionsList = () => {
|
||||
const [questions] = useQuery(getQuestions)
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{questions.map((question) => (
|
||||
<li key={question.id}>
|
||||
<Link href="/questions/[id]" as={`/questions/${question.id}`}>
|
||||
<a>{question.name}</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
This won’t work though! Remember that the `Question` model we created above doesn’t have any `name` field. To fix this, replace `question.name` with `question.text`:
|
||||
|
||||
```jsx
|
||||
export const QuestionsList = () => {
|
||||
const [questions] = useQuery(getQuestions)
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{questions.map((question) => (
|
||||
<li key={question.id}>
|
||||
<Link href="/questions/[id]" as={`/questions/${question.id}`}>
|
||||
<a>{question.text}</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Next, let’s apply a similar fix to `app/questions/pages/questions/new.tsx`. In the form submission, replace
|
||||
|
||||
```jsx
|
||||
const question = await createQuestion({data: {name: 'MyName'}})
|
||||
```
|
||||
|
||||
with
|
||||
|
||||
```jsx
|
||||
const question = await createQuestion({
|
||||
data: {text: 'Do you love Blitz?', publishedAt: new Date()},
|
||||
})
|
||||
```
|
||||
|
||||
Finally, we just need to fix the edit page. Open `app/questions/pages/questions/[id]/edit.tsx` and replace
|
||||
|
||||
```jsx
|
||||
const updated = await updateQuestion({
|
||||
where: {id: question.id},
|
||||
data: {name: 'MyNewName'},
|
||||
})
|
||||
```
|
||||
|
||||
with
|
||||
|
||||
```jsx
|
||||
const updated = await updateQuestion({
|
||||
where: {id: question.id},
|
||||
data: {text: 'Do you really love Blitz?'},
|
||||
})
|
||||
```
|
||||
|
||||
Great! Now make sure your app is running. If it isn’t, just run `blitz start` in your terminal, and visit http://localhost:3000/questions. Play around with your new app a bit! Try creating questions, editing, and deleting them.
|
||||
|
||||
## Writing a minimal form
|
||||
|
||||
You’re doing great so far! The next thing we’ll do is give our form some real inputs. At the moment it’s giving every `Question` the same name! Have a look at `app/questions/pages/questions/new.tsx` in your editor.
|
||||
|
||||
Delete the div that says: `<div>Put your form fields here. But for now, just click submit</div>`, and replace it with some inputs:
|
||||
|
||||
```jsx
|
||||
<input placeholder="Name" />
|
||||
|
||||
<input placeholder="Choice 1" />
|
||||
<input placeholder="Choice 1" />
|
||||
<input placeholder="Choice 1" />
|
||||
```
|
||||
|
||||
Finally, we’re going to make sure all that data is submitted. In the end, your page should look something like this:
|
||||
|
||||
```jsx
|
||||
import {Head, Link, useRouter} from 'blitz'
|
||||
import createQuestion from 'app/questions/mutations/createQuestion'
|
||||
|
||||
const NewQuestionPage = () => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<Head>
|
||||
<title>New Question</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<main>
|
||||
<h1>Create New Question </h1>
|
||||
|
||||
<form
|
||||
onSubmit={async (event) => {
|
||||
event.preventDefault()
|
||||
|
||||
try {
|
||||
const question = await createQuestion({
|
||||
data: {
|
||||
text: event.target[0].value,
|
||||
publishedAt: new Date(),
|
||||
choices: {
|
||||
create: [
|
||||
{text: event.target[1].value},
|
||||
{text: event.target[2].value},
|
||||
{text: event.target[3].value},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
alert('Success!' + JSON.stringify(question))
|
||||
router.push('/questions/[id]', `/questions/${question.id}`)
|
||||
} catch (error) {
|
||||
alert('Error creating question ' + JSON.stringify(error, null, 2))
|
||||
}
|
||||
}}>
|
||||
<input placeholder="Name" />
|
||||
|
||||
<input placeholder="Choice 1" />
|
||||
<input placeholder="Choice 1" />
|
||||
<input placeholder="Choice 1" />
|
||||
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
|
||||
<p>
|
||||
<Link href="/questions">
|
||||
<a>Questions</a>
|
||||
</Link>
|
||||
</p>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NewQuestionPage
|
||||
```
|
||||
|
||||
## Listing choices
|
||||
|
||||
Time for a breather. Go back to `http://localhost:3000/questions` in your browser and look at all the questions you‘ve created. How about we list these questions’ choices here too? First, we need to customise the question queries. In Prisma, you need to manually let the client know that you want to query for nested relations. Change your `getQuestion.ts` and `getQuestions.ts` files to look like this:
|
||||
|
||||
```js
|
||||
// app/questions/queries/getQuestion.ts
|
||||
|
||||
import db, {FindOneQuestionArgs} from 'db'
|
||||
|
||||
export default async function getQuestion(args: FindOneQuestionArgs) {
|
||||
const question = await db.question.findOne({...args, include: {choices: true}})
|
||||
|
||||
return question
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// app/questions/queries/getQuestions.ts
|
||||
|
||||
import db, {FindManyQuestionArgs} from 'db'
|
||||
|
||||
export default async function getQuestions(args: FindManyQuestionArgs) {
|
||||
const questions = await db.question.findMany({...args, include: {choices: true}})
|
||||
|
||||
return questions
|
||||
}
|
||||
```
|
||||
|
||||
Now hop back to our main questions page in your editor, and we can list the choices of each question easily. Just add this code beneath the `Link` in our `QuestionsList`:
|
||||
|
||||
```jsx
|
||||
<ul>
|
||||
{question.choices.map((choice) => (
|
||||
<li key={choice.id}>
|
||||
{choice.text} - {choice.votes} votes
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
```
|
||||
|
||||
Magic! Let’s do one more thing–let people vote on these questions!
|
||||
|
||||
Open `app/questions/pages/questions/[id].tsx` in your editor. First, we’re going to improve this page somewhat.
|
||||
|
||||
1. Replace `<h1>Question {question.id}</h1>` with `<h1>{question.text}</h1>`.
|
||||
|
||||
2. Delete the `pre` element, and copy in our choices list which we wrote before:
|
||||
|
||||
```jsx
|
||||
<ul>
|
||||
{question.choices.map((choice) => (
|
||||
<li key={choice.id}>
|
||||
{choice.text} - {choice.votes} votes
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
```
|
||||
|
||||
If you go back to your browser, your page should now look something like this!
|
||||
|
||||
<img width="567" alt="Screenshot 2020-04-27 at 16 06 55" src="https://user-images.githubusercontent.com/24858006/80387990-3c3d8b80-88a1-11ea-956a-5be85f1e8f12.png">
|
||||
|
||||
Now that you’ve improved the question page, it’s time for the vote button.
|
||||
|
||||
First, we need a new mutation. Create a file at `app/questions/mutations/updateChoice.ts`, and paste in the following code:
|
||||
|
||||
```js
|
||||
import db, {ChoiceUpdateArgs} from 'db'
|
||||
|
||||
export default async function updateChoice(args: ChoiceUpdateArgs) {
|
||||
// Don't allow updating ID
|
||||
delete args.data.id
|
||||
|
||||
const choice = await db.choice.update(args)
|
||||
|
||||
return choice
|
||||
}
|
||||
```
|
||||
|
||||
Back in `app/questions/pages/questions/[id].tsx`, we can now add a vote button.
|
||||
|
||||
In our `li`, add a button like so:
|
||||
|
||||
```jsx
|
||||
<li key={choice.id}>
|
||||
{choice.text} - {choice.votes} votes
|
||||
<button>Vote</button>
|
||||
</li>
|
||||
```
|
||||
|
||||
Then, import our `updateChoice` mutation, and create a `handleVote` function in our page:
|
||||
|
||||
```jsx
|
||||
import updateChoice from "app/questions/mutations/updateChoice"
|
||||
|
||||
...
|
||||
|
||||
const handleVote = async (id, votes) => {
|
||||
try {
|
||||
const updated = await updateChoice({
|
||||
where: { id },
|
||||
data: { votes: votes + 1 },
|
||||
})
|
||||
alert("Success!" + JSON.stringify(updated))
|
||||
} catch (error) {
|
||||
alert("Error creating question " + JSON.stringify(error, null, 2))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Finally, we’ll just tell our `button` to call that function!
|
||||
|
||||
```jsx
|
||||
<button onClick={() => handleVote(choice.id, choice.votes)}>Vote</button>
|
||||
```
|
||||
|
||||
Just to be sure, this is what all that should look like:
|
||||
|
||||
```jsx
|
||||
import { Suspense } from "react"
|
||||
import { Head, Link, useRouter, useQuery } from "blitz"
|
||||
import getQuestion from "app/questions/queries/getQuestion"
|
||||
import deleteQuestion from "app/questions/mutations/deleteQuestion"
|
||||
import updateChoice from "app/questions/mutations/updateChoice"
|
||||
|
||||
export const Question = () => {
|
||||
const router = useRouter()
|
||||
const id = parseInt(router?.query.id as string)
|
||||
const [question] = useQuery(getQuestion, { where: { id } })
|
||||
|
||||
const handleVote = async (id, votes) => {
|
||||
try {
|
||||
const updated = await updateChoice({
|
||||
where: { id },
|
||||
data: { votes: votes + 1 },
|
||||
})
|
||||
alert("Success!" + JSON.stringify(updated))
|
||||
} catch (error) {
|
||||
alert("Error creating question " + JSON.stringify(error, null, 2))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{question.text}</h1>
|
||||
<ul>
|
||||
{question.choices.map((choice) => (
|
||||
<li key={choice.id}>
|
||||
{choice.text} - {choice.votes} votes
|
||||
<button onClick={() => handleVote(choice.id, choice.votes)}>Vote</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<Link href="/questions/[id]/edit" as={`/questions/${question.id}/edit`}>
|
||||
<a>Edit</a>
|
||||
</Link>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
if (confirm("This will be deleted")) {
|
||||
await deleteQuestion({ where: { id: question.id } })
|
||||
router.push("/questions")
|
||||
}
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ShowQuestionPage = () => {
|
||||
return (
|
||||
<div className="container">
|
||||
<Head>
|
||||
<title>Question</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<main>
|
||||
<p>
|
||||
<Link href="/questions">
|
||||
<a>Questions</a>
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Question />
|
||||
</Suspense>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShowQuestionPage
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
🥳 Congrats! You just created your very own Blitz app! Have fun playing around with it, or sharing it with your friends. Now that you’ve finished this tutorial, why not try making your voting app even better? You could try:
|
||||
|
||||
- Adding styling
|
||||
- Showing some more statistics about votes
|
||||
- Deploying live so you can send it around (we recommend [Vercel](https://vercel.com/))
|
||||
|
||||
If you want to share your project with the world wide Blitz community there is no better place to do that than on Slack.
|
||||
|
||||
Just visit https://slack.blitzjs.com. Then, post the link to the **#show-and-tell** channel to share it with everyone!
|
||||
384
USER_GUIDE.md
384
USER_GUIDE.md
@@ -1,384 +0,0 @@
|
||||

|
||||
|
||||
<br>
|
||||
|
||||
Before getting started, you should know **this is alpha software**. Blitz is incomplete. There are rough spots and bugs. APIs may change. But you can build an app and deploy it to production. We're excited to see what you build!
|
||||
|
||||
If you have any issues at all, please [open an issue](https://github.com/blitz-js/blitz/issues/new/choose) or join the [Blitz slack](https://slack.blitzjs.com) and talk to us in the **#help** channel. If you get stuck and frustrated, please don't blame yourself. This user guide, and Blitz in general, is not yet fine-tuned for those with less experience. But eventually, it will be because this is very important to us.
|
||||
|
||||
If you’re looking for a slower, more guided start to Blitz, read the **[Blitz Beginner Tutorial](https://github.com/blitz-js/blitz/blob/canary/TUTORIAL.md)**.
|
||||
|
||||
<br>
|
||||
|
||||
## Introduction
|
||||
|
||||
Blitz is a Rails-like framework for building monolithic, full-stack React apps. The idea is that Blitz makes you extremely productive by doing as much set up and grunt work for you.
|
||||
|
||||
**When building a Blitz app, you don’t have to think about “building an API” or “fetching data from your API”**. You only think about writing functions that get and change data. And to use those functions in your component, you simply import and call them like a regular function.
|
||||
|
||||
Blitz is built on Next.js, so if you are familiar with that, you will feel right at home.
|
||||
|
||||
<br>
|
||||
|
||||
## Blitz App Development
|
||||
|
||||
### Set Up Your Computer
|
||||
|
||||
- [ ] You need Node.js 12 or newer
|
||||
|
||||
<br>
|
||||
|
||||
### Create Your Blitz App
|
||||
|
||||
1. `npm install -g blitz` or `yarn global add blitz`
|
||||
2. Run `blitz new myAppName` to create a new TypeScript blitz app in the `myAppName` directory. Alternatively, run `blitz new myAppName --js` to create a JavaScript blitz app
|
||||
3. `cd myAppName`
|
||||
4. `blitz start`
|
||||
5. View your baby app at [http://localhost:3000](http://localhost:3000)
|
||||
|
||||
<br>
|
||||
|
||||
### Set Up Your Database
|
||||
|
||||
By default, Blitz uses Prisma 2 which is a strongly typed database client. **You probably want to read [the Prisma 2 documentation](https://www.prisma.io/docs/understand-prisma/introduction).** _Note, Prisma 2 is not required for Blitz. The only Prisma-Blitz integration is the `blitz db` cli command. You can use anything you want, such as Mongo, TypeORM, etc._
|
||||
|
||||
1. Open `db/schema.prisma` and add the following:
|
||||
|
||||
```prisma
|
||||
model Project {
|
||||
id Int @default(autoincrement()) @id
|
||||
name String
|
||||
tasks Task[]
|
||||
}
|
||||
|
||||
model Task {
|
||||
id Int @default(autoincrement()) @id
|
||||
name String
|
||||
project Project @relation(fields: [projectId], references: [id])
|
||||
projectId Int
|
||||
}
|
||||
```
|
||||
|
||||
2. Run `blitz db migrate`
|
||||
- If this fails, you need to change the `DATABASE_URL` value in `.env` to whatever is required by your Postgres installation.
|
||||
|
||||
<br>
|
||||
|
||||
### Scaffold out all the files your basic CRUD actions
|
||||
|
||||
_CRUD = create, read, update, delete_
|
||||
|
||||
1. Run `blitz generate all project` to generate fully working queries, mutations, and pages
|
||||
2. Open [http://localhost:3000/projects](http://localhost:3000/projects) to see the default project list page
|
||||
3. Explore the generated pages and view, create, update, and delete projects.
|
||||
|
||||
<br>
|
||||
|
||||
### Pages
|
||||
|
||||
Blitz.js pages are exactly the same as Next.js pages. If you need, read [the Next.js Page documentation](https://nextjs.org/docs/basic-features/pages)
|
||||
|
||||
- Unlike Next.js, you can have many `pages/` folders nested inside `app/`. This way pages can be organized neatly, especially for larger projects. Like this:
|
||||
- `app/pages/about.tsx`
|
||||
- `app/projects/pages/projects/index.tsx`
|
||||
- `app/tasks/pages/projects/[projectId]/tasks/[id].tsx`
|
||||
- All React components inside a `pages/` folder are accessible at a URL corresponding to its path inside `pages/`. So `pages/about.tsx` will be at `localhost:3000/about`.
|
||||
|
||||
The Next.js router APIs are all exported from the `blitz` package: `useRouter()`, `withRouter()`, and `Router`. If you need, read [the Next.js Router documentation](https://nextjs.org/docs/api-reference/next/router).
|
||||
|
||||
<br>
|
||||
|
||||
### Writing Queries & Mutations
|
||||
|
||||
Blitz queries and mutations are plain, asynchronous Javascript functions that always run on the server.
|
||||
|
||||
We automatically alias the root of your project, so `import db from 'db'` is importing `<project_root>/db/index.ts`
|
||||
|
||||
**Example Query:**
|
||||
|
||||
```ts
|
||||
// app/products/queries/getProduct.tsx
|
||||
import db, {FindOneProductArgs} from 'db'
|
||||
|
||||
export default async function getProduct(args: FindOneProductArgs) {
|
||||
// Can do any pre-processing or event triggers here
|
||||
const product = await db.product.findOne(args)
|
||||
// Can do any post-processing or event triggers here
|
||||
|
||||
return product
|
||||
}
|
||||
```
|
||||
|
||||
**Example Mutation:**
|
||||
|
||||
```ts
|
||||
// app/products/mutations/createProduct.tsx
|
||||
import db, {ProductCreateArgs} from 'db'
|
||||
|
||||
export default async function createProduct(args: ProductCreateArgs) {
|
||||
// Can do any pre-processing or event triggers here
|
||||
const product = await db.product.create(args)
|
||||
// Can do any post-processing or event triggers here
|
||||
|
||||
return product
|
||||
}
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
### Using Queries
|
||||
|
||||
#### In a React Component
|
||||
|
||||
Blitz provides a `useQuery` hook, which is built on [`react-query`](https://github.com/tannerlinsley/react-query). The first argument is a query function. The second argument is the input to the query function. The third argument is any valid react-query configuration item.
|
||||
|
||||
At build time, the direct function import is swapped out for a function that executes a network call to run the query serverside.
|
||||
|
||||
**React Concurrent Mode is enabled by default for Blitz apps.** So the `<Suspense>` component is used for loading states and `<ErrorBoundary>` is used to display errors. If you need, you can read the [React Concurrent Mode Docs](https://reactjs.org/docs/concurrent-mode-intro.html).
|
||||
|
||||
```tsx
|
||||
import {Suspense} from 'react'
|
||||
import {useRouter, useQuery} from 'blitz'
|
||||
import getProduct from 'app/products/queries/getProduct'
|
||||
import ErrorBoundary from 'app/components/ErrorBoundary'
|
||||
|
||||
function Product() {
|
||||
const router = useRouter()
|
||||
const id = parseInt(router.query.id as string)
|
||||
const [product] = useQuery(getProduct, {where: {id}})
|
||||
|
||||
return <div>{product.name}</div>
|
||||
}
|
||||
|
||||
function WrappedProduct() {
|
||||
return (
|
||||
<div>
|
||||
<ErrorBoundary fallback={(error) => <div>Error: {JSON.stringify(error)}</div>}>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Product />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default WrappedProduct
|
||||
```
|
||||
|
||||
#### On the Server
|
||||
|
||||
In `getStaticProps`, a query function can be called directly without `useQuery`
|
||||
|
||||
```tsx
|
||||
import getProduct from '/app/products/queries/getProduct'
|
||||
|
||||
export const getStaticProps = async (context) => {
|
||||
const product = await getProduct({where: {id: context.params?.id}})
|
||||
return {props: {product}}
|
||||
}
|
||||
|
||||
function ProductPage({product}) {
|
||||
return <div>{product.name}</div>
|
||||
}
|
||||
|
||||
export default ProductPage
|
||||
```
|
||||
|
||||
In `getServerSideProps`, pass a query function to `ssrQuery` which will ensure appropriate middleware is run before/after your query function.
|
||||
|
||||
```tsx
|
||||
import {ssrQuery} from 'blitz'
|
||||
import getProduct from '/app/products/queries/getProduct'
|
||||
|
||||
export const getServerSideProps = async ({params, req, res}) => {
|
||||
const product = await ssrQuery(getProduct, {where: {id: params.id}}, {req, res}))
|
||||
return {props: {product}}
|
||||
}
|
||||
|
||||
function ProductPage ({product}) {
|
||||
return <div>{product.name}</div>
|
||||
}
|
||||
|
||||
export default ProductPage
|
||||
```
|
||||
|
||||
For more details, read the comprehensive [Query & Mutation Usage Issue](https://github.com/blitz-js/blitz/issues/89)
|
||||
|
||||
<br>
|
||||
|
||||
### Using Mutations
|
||||
|
||||
Mutations are called directly, like a regular asynchronous function.
|
||||
|
||||
At build time, the direct function import is swapped out for a function that executes a network call to run the mutation server-side.
|
||||
|
||||
```tsx
|
||||
import {useQuery} from 'blitz'
|
||||
import getProduct from '/app/products/queries/getProduct'
|
||||
import updateProduct from '/app/products/mutations/updateProduct'
|
||||
|
||||
function (props) {
|
||||
const [product] = useQuery(getProduct, {where: {id: props.id}})
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={product}
|
||||
onSubmit={async values => {
|
||||
try {
|
||||
const product = await updateProduct(values)
|
||||
} catch (error) {
|
||||
alert('Error saving product')
|
||||
}
|
||||
}}>
|
||||
{/* ... */}
|
||||
</Formik>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
For more details, read the comprehensive [Query & Mutation Usage Issue](https://github.com/blitz-js/blitz/issues/89)
|
||||
|
||||
<br>
|
||||
|
||||
### Custom API Routes
|
||||
|
||||
Blitz.js custom API routes are exactly the same as Next.js custom API routes. If you need, read [the Next.js API route documentation](https://nextjs.org/docs/api-routes/introduction)
|
||||
|
||||
- Unlike Next.js, your `api/` folder must be a sibling of `pages/` instead of being nested inside.
|
||||
- All React components inside an `api/` folder are accessible at a URL corresponding to it's path inside `api/`. So `app/projects/api/webhook.tsx` will be at `localhost:3000/api/webhook`.
|
||||
|
||||
<br>
|
||||
|
||||
### Customize the Webpack Config
|
||||
|
||||
Blitz uses the `blitz.config.js` config file at the root of your project. This is exactly the same as `next.config.js`. Read [the Next.js docs on customizing webpack](https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config).
|
||||
|
||||
<br>
|
||||
|
||||
### Deploy to Production
|
||||
|
||||
1. You need a production Postgres database. It's easy to set this up on [Digital Ocean](https://www.digitalocean.com/products/managed-databases-postgresql/?refcode=466ad3d3063d).
|
||||
2. For deploying serverless, you also need a connection pool. This is also relatively easy to set up on Digital Ocean.
|
||||
1. [Read the Digital Ocean docs on setting up your connection pool](https://www.digitalocean.com/docs/databases/postgresql/how-to/manage-connection-pools/#creating-a-connection-pool?refcode=466ad3d3063d)
|
||||
2. Ensure you set your "Pool Mode" to be "Session" instead of "Transaction" (because of a bug in Prisma)
|
||||
3. You need your entire database connection string. If you need, [read the Prisma docs on this](https://www.prisma.io/docs/reference/database-connectors/postgresql#connection-details).
|
||||
1. If deploying to serverless with a connection pool, make sure you get the connection string to your connection pool, not directly to the DB.
|
||||
4. You need to change the defined datasource in `db/schema.prisma` from SQLite to Postgres
|
||||
|
||||
#### Serverless
|
||||
|
||||
Assuming you already have a Vercel account and the `now` cli installed, you can do the following:
|
||||
|
||||
1. Add your DB url as a secret environment variable by running `now secrets add @database-url "DATABASE_CONNECTION_STRING"`
|
||||
2. Add a `now.json` at your project root with
|
||||
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"DATABASE_URL": "@database-url"
|
||||
},
|
||||
"build": {
|
||||
"env": {
|
||||
"DATABASE_URL": "@database-url"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Run `now`
|
||||
|
||||
Once working and deployed to production, your app should be very stable because it’s running Next.js which is already battle-tested.
|
||||
|
||||
#### Traditional, Long-Running Server
|
||||
|
||||
You can deploy a Blitz app like a regular Node or Express project.
|
||||
|
||||
`blitz start --production` will start your app in production mode. Make sure you provide the `DATABASE_URL` environment variable for your production database.
|
||||
|
||||
<br>
|
||||
|
||||
## Blitz CLI Commands
|
||||
|
||||
#### `blitz new NAME`
|
||||
|
||||
Generate a new TypeScript blitz project at `<current_folder>./NAME`
|
||||
|
||||
#### `blitz new NAME --js`
|
||||
|
||||
Generate a new JavaScript blitz project at `<current_folder>./NAME`
|
||||
|
||||
#### `blitz start`
|
||||
|
||||
Start your app in development mode
|
||||
|
||||
#### `blitz start --production`
|
||||
|
||||
Start your app in production mode
|
||||
|
||||
#### `blitz db migrate`
|
||||
|
||||
Run any needed migrations via Prisma 2 and generate Prisma Client
|
||||
|
||||
#### `blitz db introspect`
|
||||
|
||||
Will introspect the database defined in `db/schema.prisma` and automatically generate a complete `schema.prisma` file for you. Lastly, it'll generate Prisma Client.
|
||||
|
||||
#### `blitz db studio`
|
||||
|
||||
Open the Prisma Studio UI at [http://localhost:5555](http://localhost:5555) so you can easily see and change data in your database.
|
||||
|
||||
#### `blitz generate -h`
|
||||
|
||||
Generate different types of files for a model. Your model input can be singular or plural, but the generated files will be the same in both cases.
|
||||
|
||||
#### `blitz console`
|
||||
|
||||
Start a Node.js REPL that's preloaded with your `db` object and all your queries and mutations. This is awesome for quickly trying your code without running the app!
|
||||
|
||||
<br>
|
||||
|
||||
## More Information
|
||||
|
||||
- Read the [Architecture RFC](https://github.com/blitz-js/blitz/pull/73) for more details on the architecture, our decision making, and how queries/mutations work under the hood
|
||||
- Read the [File Structure & Routing RFC](https://github.com/blitz-js/blitz/pull/74) for more details about the file structure and routing conventions.
|
||||
- View an example Blitz app at [`examples/store`](https://github.com/blitz-js/blitz/tree/canary/examples/store)
|
||||
|
||||
<br>
|
||||
|
||||
## What's Next for Blitz.js?
|
||||
|
||||
Here's the list of big things that are currently missing from Blitz but are a top priority for us:
|
||||
|
||||
- A real Blitzjs.com website and documentation
|
||||
- Translated documentation. If you're interested in helping, [comment in this issue](https://github.com/blitz-js/blitzjs.com/issues/20).
|
||||
- Authentication
|
||||
- Authorization (use auth rules both on server and client)
|
||||
- Model validation (use model validation both on server and client)
|
||||
- React-Native support
|
||||
- GUI for folks who prefer that over CLIs
|
||||
- ... and tons more 🙂
|
||||
|
||||
<br>
|
||||
|
||||
## FAQ
|
||||
|
||||
- **Will you support other ESLint configs for the `blitz new` app?** Yes, there's [an issue for this](https://github.com/blitz-js/blitz/issues/161)
|
||||
|
||||
<br>
|
||||
|
||||
## You are invited to help — let’s build the future of web dev together! 🤝
|
||||
|
||||
Blitz is just getting started, and it's going to take an entire community to bring it to fruition!
|
||||
|
||||
How you can help:
|
||||
|
||||
1. Tell others about Blitz
|
||||
2. Report bugs by opening an issue here on GitHub
|
||||
3. Send us feedback in the [Blitz slack](https://slack.blitzjs.com).
|
||||
4. Contribute code. We have a lot of issues that are ready to work on! Start by reading [The Contributing Guide](https://github.com/blitz-js/blitz/blob/canary/CONTRIBUTING.md). Let us know if you need help.
|
||||
5. Any way you want! We totally appreciate any type of contribution, such as documentation, videos, blog posts, etc. If you have a crazy idea, feel free to run it past us in Slack! :)
|
||||
6. [Sponsorships & donations](https://github.com/blitz-js/blitz#sponsors-and-donations)
|
||||
|
||||
<br>
|
||||
|
||||
That's all for now. We hope to see you in the [Blitz slack community](https://slack.blitzjs.com)!
|
||||
@@ -1,3 +1,3 @@
|
||||
const {fs} = require('memfs')
|
||||
const {fs} = require("memfs")
|
||||
|
||||
module.exports = fs
|
||||
|
||||
BIN
assets/Fauna_Logo_Blue.png
Normal file
BIN
assets/Fauna_Logo_Blue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/andreas.jpg
Normal file
BIN
assets/andreas.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
assets/render-logo-color2.png
Normal file
BIN
assets/render-logo-color2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
3
examples/auth/.eslintrc.js
Normal file
3
examples/auth/.eslintrc.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ["blitz"],
|
||||
}
|
||||
57
examples/auth/.gitignore
vendored
Normal file
57
examples/auth/.gitignore
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.pnp.*
|
||||
.npm
|
||||
web_modules/
|
||||
|
||||
# blitz
|
||||
/.blitz/
|
||||
/.next/
|
||||
*.sqlite
|
||||
.now
|
||||
.blitz-console-history
|
||||
blitz-log.log
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.envrc
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
*.lcov
|
||||
.nyc_output
|
||||
lib-cov
|
||||
|
||||
# Caches
|
||||
*.tsbuildinfo
|
||||
.eslintcache
|
||||
.node_repl_history
|
||||
.yarn-integrity
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
.vercel
|
||||
5
examples/auth/.prettierignore
Normal file
5
examples/auth/.prettierignore
Normal file
@@ -0,0 +1,5 @@
|
||||
.gitkeep
|
||||
.env*
|
||||
*.ico
|
||||
*.lock
|
||||
db/migrations
|
||||
2
examples/auth/.vercelignore
Normal file
2
examples/auth/.vercelignore
Normal file
@@ -0,0 +1,2 @@
|
||||
.blitz
|
||||
*.sqlite
|
||||
26
examples/auth/README.md
Normal file
26
examples/auth/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# auth
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Add this code to db/schema.prisma:
|
||||
|
||||
```
|
||||
model Project {
|
||||
id Int @default(autoincrement()) @id
|
||||
name String
|
||||
}
|
||||
```
|
||||
|
||||
2. DB migrate
|
||||
|
||||
```
|
||||
blitz prisma migrate dev --preview-feature
|
||||
```
|
||||
|
||||
3. Start the dev server
|
||||
|
||||
```
|
||||
blitz dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
143
examples/auth/app/api/auth/[...auth].ts
Normal file
143
examples/auth/app/api/auth/[...auth].ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import {passportAuth, PublicData} from "blitz"
|
||||
import db from "db"
|
||||
import {Strategy as TwitterStrategy} from "passport-twitter"
|
||||
import {Strategy as GitHubStrategy} from "passport-github2"
|
||||
import {Strategy as Auth0Strategy} from "passport-auth0"
|
||||
import {Role} from "types"
|
||||
|
||||
function assert(condition: any, message: string): asserts condition {
|
||||
if (!condition) throw new Error(message)
|
||||
}
|
||||
|
||||
// These aren't required, but this is a good practice to ensure you have the env vars you need
|
||||
assert(process.env.TWITTER_CONSUMER_KEY, "You must provide the TWITTER_CONSUMER_KEY env variable")
|
||||
assert(
|
||||
process.env.TWITTER_CONSUMER_SECRET,
|
||||
"You must provide the TWITTER_CONSUMER_SECRET env variable",
|
||||
)
|
||||
assert(process.env.GITHUB_CLIENT_ID, "You must provide the GITHUB_CLIENT_ID env variable")
|
||||
assert(process.env.GITHUB_CLIENT_SECRET, "You must provide the GITHUB_CLIENT_SECRET env variable")
|
||||
|
||||
assert(process.env.AUTH0_DOMAIN, "You must provide the AUTH0_DOMAIN env variable")
|
||||
assert(process.env.AUTH0_CLIENT_ID, "You must provide the AUTH0_CLIENT_ID env variable")
|
||||
assert(process.env.AUTH0_CLIENT_SECRET, "You must provide the AUTH0_CLIENT_SECRET env variable")
|
||||
|
||||
export default passportAuth({
|
||||
successRedirectUrl: "/",
|
||||
strategies: [
|
||||
{
|
||||
strategy: new TwitterStrategy(
|
||||
{
|
||||
consumerKey: process.env.TWITTER_CONSUMER_KEY,
|
||||
consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
|
||||
callbackURL:
|
||||
process.env.NODE_ENV === "production"
|
||||
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/twitter/callback"
|
||||
: "http://localhost:3000/api/auth/twitter/callback",
|
||||
includeEmail: true,
|
||||
},
|
||||
async function (_token, _tokenSecret, profile, done) {
|
||||
const email = profile.emails && profile.emails[0]?.value
|
||||
|
||||
if (!email) {
|
||||
// This can happen if you haven't enabled email access in your twitter app permissions
|
||||
return done(new Error("Twitter OAuth response doesn't have email."))
|
||||
}
|
||||
|
||||
const user = await db.user.upsert({
|
||||
where: {email},
|
||||
create: {
|
||||
email,
|
||||
name: profile.displayName,
|
||||
},
|
||||
update: {email},
|
||||
})
|
||||
|
||||
const publicData = {userId: user.id, roles: [user.role], source: "twitter"}
|
||||
done(null, {publicData})
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
strategy: new GitHubStrategy(
|
||||
{
|
||||
clientID: process.env.GITHUB_CLIENT_ID,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
||||
callbackURL:
|
||||
process.env.NODE_ENV === "production"
|
||||
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/github/callback"
|
||||
: "http://localhost:3000/api/auth/github/callback",
|
||||
},
|
||||
async function (
|
||||
_token: string,
|
||||
_tokenSecret: string,
|
||||
profile: any,
|
||||
done: (err: Error | null, data?: {publicData: PublicData}) => void,
|
||||
) {
|
||||
const email = profile.emails && profile.emails[0]?.value
|
||||
|
||||
if (!email) {
|
||||
// This can happen if you haven't enabled email access in your twitter app permissions
|
||||
return done(new Error("Twitter OAuth response doesn't have email."))
|
||||
}
|
||||
|
||||
const user = await db.user.upsert({
|
||||
where: {email},
|
||||
create: {
|
||||
email,
|
||||
name: profile.displayName,
|
||||
},
|
||||
update: {email},
|
||||
})
|
||||
|
||||
const publicData = {
|
||||
userId: user.id,
|
||||
roles: [user.role as Role],
|
||||
source: "github",
|
||||
githubUsername: profile.username,
|
||||
}
|
||||
done(null, {publicData})
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
authenticateOptions: {scope: "openid email profile"},
|
||||
strategy: new Auth0Strategy(
|
||||
{
|
||||
domain: process.env.AUTH0_DOMAIN,
|
||||
clientID: process.env.AUTH0_CLIENT_ID,
|
||||
clientSecret: process.env.AUTH0_CLIENT_SECRET,
|
||||
callbackURL:
|
||||
process.env.NODE_ENV === "production"
|
||||
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/auth0/callback"
|
||||
: "http://localhost:3000/api/auth/auth0/callback",
|
||||
},
|
||||
async function (_token, _tokenSecret, _extraParams, profile, done) {
|
||||
const email = profile.emails && profile.emails[0]?.value
|
||||
|
||||
if (!email) {
|
||||
// This can happen if you haven't enabled email access in your twitter app permissions
|
||||
return done(new Error("Auth0 response doesn't have email."))
|
||||
}
|
||||
|
||||
const user = await db.user.upsert({
|
||||
where: {email},
|
||||
create: {
|
||||
email,
|
||||
name: profile.displayName,
|
||||
},
|
||||
update: {email},
|
||||
})
|
||||
|
||||
const publicData = {
|
||||
userId: user.id,
|
||||
roles: [user.role],
|
||||
source: "auth0",
|
||||
githubUsername: profile.username,
|
||||
}
|
||||
done(undefined, {publicData})
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
})
|
||||
54
examples/auth/app/auth/components/LoginForm.tsx
Normal file
54
examples/auth/app/auth/components/LoginForm.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import {AuthenticationError, Link, useMutation} from "blitz"
|
||||
import {LabeledTextField} from "app/core/components/LabeledTextField"
|
||||
import {Form, FORM_ERROR} from "app/core/components/Form"
|
||||
import login from "app/auth/mutations/login"
|
||||
import {Login} from "app/auth/validations"
|
||||
|
||||
type LoginFormProps = {
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
export const LoginForm = (props: LoginFormProps) => {
|
||||
const [loginMutation] = useMutation(login)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Login</h1>
|
||||
|
||||
<Form
|
||||
submitText="Login"
|
||||
schema={Login}
|
||||
initialValues={{email: "", password: ""}}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await loginMutation(values)
|
||||
props.onSuccess?.()
|
||||
} catch (error) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
return {[FORM_ERROR]: "Sorry, those credentials are invalid"}
|
||||
} else {
|
||||
return {
|
||||
[FORM_ERROR]:
|
||||
"Sorry, we had an unexpected error. Please try again. - " + error.toString(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LabeledTextField name="email" label="Email" placeholder="Email" />
|
||||
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
|
||||
<div>
|
||||
<Link href="/forgot-password">
|
||||
<a>Forgot your password?</a>
|
||||
</Link>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<div style={{marginTop: "1rem"}}>
|
||||
Or <Link href="/signup">Sign Up</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginForm
|
||||
44
examples/auth/app/auth/components/SignupForm.tsx
Normal file
44
examples/auth/app/auth/components/SignupForm.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from "react"
|
||||
import {useMutation} from "blitz"
|
||||
import {LabeledTextField} from "app/core/components/LabeledTextField"
|
||||
import {Form, FORM_ERROR} from "app/core/components/Form"
|
||||
import signup from "app/auth/mutations/signup"
|
||||
import {Signup} from "app/auth/validations"
|
||||
|
||||
type SignupFormProps = {
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
export const SignupForm = (props: SignupFormProps) => {
|
||||
const [signupMutation] = useMutation(signup)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Create an Account</h1>
|
||||
|
||||
<Form
|
||||
submitText="Create Account"
|
||||
schema={Signup}
|
||||
initialValues={{email: "", password: ""}}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await signupMutation(values)
|
||||
props.onSuccess?.()
|
||||
} catch (error) {
|
||||
if (error.code === "P2002" && error.meta?.target?.includes("email")) {
|
||||
// This error comes from Prisma
|
||||
return {email: "This email is already being used"}
|
||||
} else {
|
||||
return {[FORM_ERROR]: error.toString()}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LabeledTextField name="email" label="Email" placeholder="Email" />
|
||||
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SignupForm
|
||||
23
examples/auth/app/auth/mutations/changePassword.ts
Normal file
23
examples/auth/app/auth/mutations/changePassword.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import {NotFoundError, SecurePassword, resolver} from "blitz"
|
||||
import db from "db"
|
||||
import {authenticateUser} from "./login"
|
||||
import {ChangePassword} from "../validations"
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(ChangePassword),
|
||||
resolver.authorize(),
|
||||
async ({currentPassword, newPassword}, ctx) => {
|
||||
const user = await db.user.findFirst({where: {id: ctx.session.userId!}})
|
||||
if (!user) throw new NotFoundError()
|
||||
|
||||
await authenticateUser(user.email, currentPassword)
|
||||
|
||||
const hashedPassword = await SecurePassword.hash(newPassword)
|
||||
await db.user.update({
|
||||
where: {id: user.id},
|
||||
data: {hashedPassword},
|
||||
})
|
||||
|
||||
return true
|
||||
},
|
||||
)
|
||||
56
examples/auth/app/auth/mutations/forgotPassword.test.ts
Normal file
56
examples/auth/app/auth/mutations/forgotPassword.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import {hash256, Ctx} from "blitz"
|
||||
import forgotPassword from "./forgotPassword"
|
||||
import db from "db"
|
||||
import previewEmail from "preview-email"
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.$reset()
|
||||
})
|
||||
|
||||
const generatedToken = "plain-token"
|
||||
jest.mock("blitz", () => ({
|
||||
...jest.requireActual("blitz")!,
|
||||
generateToken: () => generatedToken,
|
||||
}))
|
||||
jest.mock("preview-email", () => jest.fn())
|
||||
|
||||
describe("forgotPassword mutation", () => {
|
||||
it("does not throw error if user doesn't exist", async () => {
|
||||
await expect(forgotPassword({email: "no-user@email.com"}, {} as Ctx)).resolves.not.toThrow()
|
||||
})
|
||||
|
||||
it("works correctly", async () => {
|
||||
// Create test user
|
||||
const user = await db.user.create({
|
||||
data: {
|
||||
email: "user@example.com",
|
||||
tokens: {
|
||||
// Create old token to ensure it's deleted
|
||||
create: {
|
||||
type: "RESET_PASSWORD",
|
||||
hashedToken: "token",
|
||||
expiresAt: new Date(),
|
||||
sentTo: "user@example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {tokens: true},
|
||||
})
|
||||
|
||||
// Invoke the mutation
|
||||
await forgotPassword({email: user.email}, {} as Ctx)
|
||||
|
||||
const tokens = await db.token.findMany({where: {userId: user.id}})
|
||||
const token = tokens[0]
|
||||
|
||||
// delete's existing tokens
|
||||
expect(tokens.length).toBe(1)
|
||||
|
||||
expect(token.id).not.toBe(user.tokens[0].id)
|
||||
expect(token.type).toBe("RESET_PASSWORD")
|
||||
expect(token.sentTo).toBe(user.email)
|
||||
expect(token.hashedToken).toBe(hash256(generatedToken))
|
||||
expect(token.expiresAt > new Date()).toBe(true)
|
||||
expect(previewEmail).toBeCalled()
|
||||
})
|
||||
})
|
||||
41
examples/auth/app/auth/mutations/forgotPassword.ts
Normal file
41
examples/auth/app/auth/mutations/forgotPassword.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {resolver, generateToken, hash256} from "blitz"
|
||||
import db from "db"
|
||||
import {forgotPasswordMailer} from "mailers/forgotPasswordMailer"
|
||||
import {ForgotPassword} from "../validations"
|
||||
|
||||
const RESET_PASSWORD_TOKEN_EXPIRATION_IN_HOURS = 4
|
||||
|
||||
export default resolver.pipe(resolver.zod(ForgotPassword), async ({email}) => {
|
||||
// 1. Get the user
|
||||
const user = await db.user.findFirst({where: {email: email.toLowerCase()}})
|
||||
|
||||
// 2. Generate the token and expiration date.
|
||||
const token = generateToken()
|
||||
const hashedToken = hash256(token)
|
||||
const expiresAt = new Date()
|
||||
expiresAt.setHours(expiresAt.getHours() + RESET_PASSWORD_TOKEN_EXPIRATION_IN_HOURS)
|
||||
|
||||
// 3. If user with this email was found
|
||||
if (user) {
|
||||
// 4. Delete any existing password reset tokens
|
||||
await db.token.deleteMany({where: {type: "RESET_PASSWORD", userId: user.id}})
|
||||
// 5. Save this new token in the database.
|
||||
await db.token.create({
|
||||
data: {
|
||||
user: {connect: {id: user.id}},
|
||||
type: "RESET_PASSWORD",
|
||||
expiresAt,
|
||||
hashedToken,
|
||||
sentTo: user.email,
|
||||
},
|
||||
})
|
||||
// 6. Send the email
|
||||
await forgotPasswordMailer({to: user.email, token}).send()
|
||||
} else {
|
||||
// 7. If no user found wait the same time so attackers can't tell the difference
|
||||
await new Promise((resolve) => setTimeout(resolve, 750))
|
||||
}
|
||||
|
||||
// 8. Return the same result whether a password reset email was sent or not
|
||||
return
|
||||
})
|
||||
29
examples/auth/app/auth/mutations/login.ts
Normal file
29
examples/auth/app/auth/mutations/login.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import {resolver, SecurePassword, AuthenticationError} from "blitz"
|
||||
import db from "db"
|
||||
import {Login} from "../validations"
|
||||
import {Role} from "types"
|
||||
|
||||
export const authenticateUser = async (email: string, password: string) => {
|
||||
const user = await db.user.findFirst({where: {email}})
|
||||
if (!user) throw new AuthenticationError()
|
||||
|
||||
const result = await SecurePassword.verify(user.hashedPassword, password)
|
||||
|
||||
if (result === SecurePassword.VALID_NEEDS_REHASH) {
|
||||
// Upgrade hashed password with a more secure hash
|
||||
const improvedHash = await SecurePassword.hash(password)
|
||||
await db.user.update({where: {id: user.id}, data: {hashedPassword: improvedHash}})
|
||||
}
|
||||
|
||||
const {hashedPassword, ...rest} = user
|
||||
return rest
|
||||
}
|
||||
|
||||
export default resolver.pipe(resolver.zod(Login), async ({email, password}, ctx) => {
|
||||
// This throws an error if credentials are invalid
|
||||
const user = await authenticateUser(email, password)
|
||||
|
||||
await ctx.session.$create({userId: user.id, roles: [user.role as Role]})
|
||||
|
||||
return user
|
||||
})
|
||||
5
examples/auth/app/auth/mutations/logout.ts
Normal file
5
examples/auth/app/auth/mutations/logout.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import {Ctx} from "blitz"
|
||||
|
||||
export default async function logout(_: any, ctx: Ctx) {
|
||||
return await ctx.session.$revoke()
|
||||
}
|
||||
82
examples/auth/app/auth/mutations/resetPassword.test.ts
Normal file
82
examples/auth/app/auth/mutations/resetPassword.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import resetPassword from "./resetPassword"
|
||||
import db from "db"
|
||||
import {hash256, SecurePassword} from "blitz"
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.$reset()
|
||||
})
|
||||
|
||||
const mockCtx: any = {
|
||||
session: {
|
||||
$create: jest.fn,
|
||||
},
|
||||
}
|
||||
|
||||
describe("resetPassword mutation", () => {
|
||||
it("works correctly", async () => {
|
||||
expect(true).toBe(true)
|
||||
|
||||
// Create test user
|
||||
const goodToken = "randomPasswordResetToken"
|
||||
const expiredToken = "expiredRandomPasswordResetToken"
|
||||
const future = new Date()
|
||||
future.setHours(future.getHours() + 4)
|
||||
const past = new Date()
|
||||
past.setHours(past.getHours() - 4)
|
||||
|
||||
const user = await db.user.create({
|
||||
data: {
|
||||
email: "user@example.com",
|
||||
tokens: {
|
||||
// Create old token to ensure it's deleted
|
||||
create: [
|
||||
{
|
||||
type: "RESET_PASSWORD",
|
||||
hashedToken: hash256(expiredToken),
|
||||
expiresAt: past,
|
||||
sentTo: "user@example.com",
|
||||
},
|
||||
{
|
||||
type: "RESET_PASSWORD",
|
||||
hashedToken: hash256(goodToken),
|
||||
expiresAt: future,
|
||||
sentTo: "user@example.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
include: {tokens: true},
|
||||
})
|
||||
|
||||
const newPassword = "newPassword"
|
||||
|
||||
// Non-existent token
|
||||
await expect(
|
||||
resetPassword({token: "no-token", password: "", passwordConfirmation: ""}, mockCtx),
|
||||
).rejects.toThrowError()
|
||||
|
||||
// Expired token
|
||||
await expect(
|
||||
resetPassword(
|
||||
{token: expiredToken, password: newPassword, passwordConfirmation: newPassword},
|
||||
mockCtx,
|
||||
),
|
||||
).rejects.toThrowError()
|
||||
|
||||
// Good token
|
||||
await resetPassword(
|
||||
{token: goodToken, password: newPassword, passwordConfirmation: newPassword},
|
||||
mockCtx,
|
||||
)
|
||||
|
||||
// Delete's the token
|
||||
const numberOfTokens = await db.token.count({where: {userId: user.id}})
|
||||
expect(numberOfTokens).toBe(0)
|
||||
|
||||
// Updates user's password
|
||||
const updatedUser = await db.user.findFirst({where: {id: user.id}})
|
||||
expect(await SecurePassword.verify(updatedUser!.hashedPassword, newPassword)).toBe(
|
||||
SecurePassword.VALID,
|
||||
)
|
||||
})
|
||||
})
|
||||
47
examples/auth/app/auth/mutations/resetPassword.ts
Normal file
47
examples/auth/app/auth/mutations/resetPassword.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {resolver, SecurePassword, hash256} from "blitz"
|
||||
import db from "db"
|
||||
import {ResetPassword} from "../validations"
|
||||
import login from "./login"
|
||||
|
||||
export class ResetPasswordError extends Error {
|
||||
name = "ResetPasswordError"
|
||||
message = "Reset password link is invalid or it has expired."
|
||||
}
|
||||
|
||||
export default resolver.pipe(resolver.zod(ResetPassword), async ({password, token}, ctx) => {
|
||||
// 1. Try to find this token in the database
|
||||
const hashedToken = hash256(token)
|
||||
const possibleToken = await db.token.findFirst({
|
||||
where: {hashedToken, type: "RESET_PASSWORD"},
|
||||
include: {user: true},
|
||||
})
|
||||
|
||||
// 2. If token not found, error
|
||||
if (!possibleToken) {
|
||||
throw new ResetPasswordError()
|
||||
}
|
||||
const savedToken = possibleToken
|
||||
|
||||
// 3. Delete token so it can't be used again
|
||||
await db.token.delete({where: {id: savedToken.id}})
|
||||
|
||||
// 4. If token has expired, error
|
||||
if (savedToken.expiresAt < new Date()) {
|
||||
throw new ResetPasswordError()
|
||||
}
|
||||
|
||||
// 5. Since token is valid, now we can update the user's password
|
||||
const hashedPassword = await SecurePassword.hash(password)
|
||||
const user = await db.user.update({
|
||||
where: {id: savedToken.userId},
|
||||
data: {hashedPassword},
|
||||
})
|
||||
|
||||
// 6. Revoke all existing login sessions for this user
|
||||
await db.session.deleteMany({where: {userId: user.id}})
|
||||
|
||||
// 7. Now log the user in with the new credentials
|
||||
await login({email: user.email, password}, ctx)
|
||||
|
||||
return true
|
||||
})
|
||||
15
examples/auth/app/auth/mutations/signup.ts
Normal file
15
examples/auth/app/auth/mutations/signup.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {resolver, SecurePassword} from "blitz"
|
||||
import db from "db"
|
||||
import {Signup} from "app/auth/validations"
|
||||
import {Role} from "types"
|
||||
|
||||
export default resolver.pipe(resolver.zod(Signup), async ({email, password}, ctx) => {
|
||||
const hashedPassword = await SecurePassword.hash(password)
|
||||
const user = await db.user.create({
|
||||
data: {email: email.toLowerCase(), hashedPassword, role: "user"},
|
||||
select: {id: true, name: true, email: true, role: true},
|
||||
})
|
||||
|
||||
await ctx.session.$create({userId: user.id, roles: [user.role as Role]})
|
||||
return user
|
||||
})
|
||||
47
examples/auth/app/auth/pages/forgot-password.tsx
Normal file
47
examples/auth/app/auth/pages/forgot-password.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import {BlitzPage, useMutation} from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import {LabeledTextField} from "app/core/components/LabeledTextField"
|
||||
import {Form, FORM_ERROR} from "app/core/components/Form"
|
||||
import {ForgotPassword} from "app/auth/validations"
|
||||
import forgotPassword from "app/auth/mutations/forgotPassword"
|
||||
|
||||
const ForgotPasswordPage: BlitzPage = () => {
|
||||
const [forgotPasswordMutation, {isSuccess}] = useMutation(forgotPassword)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Forgot your password?</h1>
|
||||
|
||||
{isSuccess ? (
|
||||
<div>
|
||||
<h2>Request Submitted</h2>
|
||||
<p>
|
||||
If your email is in our system, you will receive instructions to reset your password
|
||||
shortly.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<Form
|
||||
submitText="Send Reset Password Instructions"
|
||||
schema={ForgotPassword}
|
||||
initialValues={{email: ""}}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await forgotPasswordMutation(values)
|
||||
} catch (error) {
|
||||
return {
|
||||
[FORM_ERROR]: "Sorry, we had an unexpected error. Please try again.",
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LabeledTextField name="email" label="Email" placeholder="Email" />
|
||||
</Form>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ForgotPasswordPage.getLayout = (page) => <Layout title="Forgot Your Password?">{page}</Layout>
|
||||
|
||||
export default ForgotPasswordPage
|
||||
18
examples/auth/app/auth/pages/login.tsx
Normal file
18
examples/auth/app/auth/pages/login.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from "react"
|
||||
import {useRouter, BlitzPage} from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import {LoginForm} from "app/auth/components/LoginForm"
|
||||
|
||||
const LoginPage: BlitzPage = () => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LoginForm onSuccess={() => router.push("/")} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
LoginPage.getLayout = (page) => <Layout title="Log In">{page}</Layout>
|
||||
|
||||
export default LoginPage
|
||||
58
examples/auth/app/auth/pages/reset-password.tsx
Normal file
58
examples/auth/app/auth/pages/reset-password.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import {BlitzPage, useRouterQuery, Link, useMutation} from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import {LabeledTextField} from "app/core/components/LabeledTextField"
|
||||
import {Form, FORM_ERROR} from "app/core/components/Form"
|
||||
import {ResetPassword} from "app/auth/validations"
|
||||
import resetPassword from "app/auth/mutations/resetPassword"
|
||||
|
||||
const ResetPasswordPage: BlitzPage = () => {
|
||||
const query = useRouterQuery()
|
||||
const [resetPasswordMutation, {isSuccess}] = useMutation(resetPassword)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Set a New Password</h1>
|
||||
|
||||
{isSuccess ? (
|
||||
<div>
|
||||
<h2>Password Reset Successfully</h2>
|
||||
<p>
|
||||
Go to the <Link href="/">homepage</Link>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<Form
|
||||
submitText="Reset Password"
|
||||
schema={ResetPassword}
|
||||
initialValues={{password: "", passwordConfirmation: "", token: query.token as string}}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await resetPasswordMutation(values)
|
||||
} catch (error) {
|
||||
if (error.name === "ResetPasswordError") {
|
||||
return {
|
||||
[FORM_ERROR]: error.message,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
[FORM_ERROR]: "Sorry, we had an unexpected error. Please try again.",
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LabeledTextField name="password" label="New Password" type="password" />
|
||||
<LabeledTextField
|
||||
name="passwordConfirmation"
|
||||
label="Confirm New Password"
|
||||
type="password"
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ResetPasswordPage.getLayout = (page) => <Layout title="Reset Your Password">{page}</Layout>
|
||||
|
||||
export default ResetPasswordPage
|
||||
19
examples/auth/app/auth/pages/signup.tsx
Normal file
19
examples/auth/app/auth/pages/signup.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import {Head, useRouter, BlitzPage, useMutation} from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import {Form, FORM_ERROR} from "app/core/components/Form"
|
||||
import {LabeledTextField} from "app/core/components/LabeledTextField"
|
||||
import {SignupForm} from "app/auth/components/SignupForm"
|
||||
|
||||
const SignupPage: BlitzPage = () => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SignupForm onSuccess={() => router.push("/")} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
SignupPage.getLayout = (page) => <Layout title="Sign Up">{page}</Layout>
|
||||
|
||||
export default SignupPage
|
||||
33
examples/auth/app/auth/validations.ts
Normal file
33
examples/auth/app/auth/validations.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import * as z from "zod"
|
||||
|
||||
const password = z.string().min(10).max(100)
|
||||
|
||||
export const Signup = z.object({
|
||||
email: z.string().email(),
|
||||
password,
|
||||
})
|
||||
|
||||
export const Login = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string(),
|
||||
})
|
||||
|
||||
export const ForgotPassword = z.object({
|
||||
email: z.string().email(),
|
||||
})
|
||||
|
||||
export const ResetPassword = z
|
||||
.object({
|
||||
password: password,
|
||||
passwordConfirmation: password,
|
||||
token: z.string(),
|
||||
})
|
||||
.refine((data) => data.password === data.passwordConfirmation, {
|
||||
message: "Passwords don't match",
|
||||
path: ["passwordConfirmation"], // set the path of the error
|
||||
})
|
||||
|
||||
export const ChangePassword = z.object({
|
||||
currentPassword: z.string(),
|
||||
newPassword: password,
|
||||
})
|
||||
65
examples/auth/app/core/components/Form.tsx
Normal file
65
examples/auth/app/core/components/Form.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import React, {ReactNode, PropsWithoutRef} from "react"
|
||||
import {Form as FinalForm, FormProps as FinalFormProps} from "react-final-form"
|
||||
import * as z from "zod"
|
||||
export {FORM_ERROR} from "final-form"
|
||||
|
||||
export interface FormProps<S extends z.ZodType<any, any>>
|
||||
extends Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit"> {
|
||||
/** All your form fields */
|
||||
children?: ReactNode
|
||||
/** Text to display in the submit button */
|
||||
submitText?: string
|
||||
schema?: S
|
||||
onSubmit: FinalFormProps<z.infer<S>>["onSubmit"]
|
||||
initialValues?: FinalFormProps<z.infer<S>>["initialValues"]
|
||||
}
|
||||
|
||||
export function Form<S extends z.ZodType<any, any>>({
|
||||
children,
|
||||
submitText,
|
||||
schema,
|
||||
initialValues,
|
||||
onSubmit,
|
||||
...props
|
||||
}: FormProps<S>) {
|
||||
return (
|
||||
<FinalForm
|
||||
initialValues={initialValues}
|
||||
validate={(values) => {
|
||||
if (!schema) return
|
||||
try {
|
||||
schema.parse(values)
|
||||
} catch (error) {
|
||||
return error.formErrors.fieldErrors
|
||||
}
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
render={({handleSubmit, submitting, submitError}) => (
|
||||
<form onSubmit={handleSubmit} className="form" {...props}>
|
||||
{/* Form fields supplied as children are rendered here */}
|
||||
{children}
|
||||
|
||||
{submitError && (
|
||||
<div role="alert" style={{color: "red"}}>
|
||||
{submitError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{submitText && (
|
||||
<button type="submit" disabled={submitting}>
|
||||
{submitText}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<style global jsx>{`
|
||||
.form > * + * {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
`}</style>
|
||||
</form>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Form
|
||||
59
examples/auth/app/core/components/LabeledTextField.tsx
Normal file
59
examples/auth/app/core/components/LabeledTextField.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import React, {PropsWithoutRef} from "react"
|
||||
import {useField} from "react-final-form"
|
||||
|
||||
export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["input"]> {
|
||||
/** Field name. */
|
||||
name: string
|
||||
/** Field label. */
|
||||
label: string
|
||||
/** Field type. Doesn't include radio buttons and checkboxes */
|
||||
type?: "text" | "password" | "email" | "number"
|
||||
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
|
||||
}
|
||||
|
||||
export const LabeledTextField = React.forwardRef<HTMLInputElement, LabeledTextFieldProps>(
|
||||
({name, label, outerProps, ...props}, ref) => {
|
||||
const {
|
||||
input,
|
||||
meta: {touched, error, submitError, submitting},
|
||||
} = useField(name, {
|
||||
parse: props.type === "number" ? Number : undefined,
|
||||
})
|
||||
|
||||
const normalizedError = Array.isArray(error) ? error.join(", ") : error || submitError
|
||||
|
||||
return (
|
||||
<div {...outerProps}>
|
||||
<label>
|
||||
{label}
|
||||
<input {...input} disabled={submitting} {...props} ref={ref} />
|
||||
</label>
|
||||
|
||||
{touched && normalizedError && (
|
||||
<div role="alert" style={{color: "red"}}>
|
||||
{normalizedError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<style jsx>{`
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
font-size: 1rem;
|
||||
}
|
||||
input {
|
||||
font-size: 1rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
border: 1px solid purple;
|
||||
appearance: none;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export default LabeledTextField
|
||||
7
examples/auth/app/core/hooks/useCurrentUser.ts
Normal file
7
examples/auth/app/core/hooks/useCurrentUser.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import {useQuery} from "blitz"
|
||||
import getCurrentUser from "app/users/queries/getCurrentUser"
|
||||
|
||||
export const useCurrentUser = () => {
|
||||
const [user] = useQuery(getCurrentUser, null)
|
||||
return user
|
||||
}
|
||||
30
examples/auth/app/core/layouts/Layout.tsx
Normal file
30
examples/auth/app/core/layouts/Layout.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import {useSession, useRouter, useMutation, Head} from "blitz"
|
||||
import logout from "app/auth/mutations/logout"
|
||||
|
||||
export default function Layout({title, children}: {title?: string; children: React.ReactNode}) {
|
||||
const session = useSession({suspense: false})
|
||||
const router = useRouter()
|
||||
const [logoutMutation] = useMutation(logout)
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{title || "__name__"}</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<div>
|
||||
{session.userId && (
|
||||
<button
|
||||
onClick={async () => {
|
||||
router.push("/")
|
||||
await logoutMutation()
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
)}
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
13
examples/auth/app/mutations/makeCoffee.ts
Normal file
13
examples/auth/app/mutations/makeCoffee.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import {resolver} from "blitz"
|
||||
import db from "db"
|
||||
import * as z from "zod"
|
||||
|
||||
const __Name__ = z
|
||||
.object({
|
||||
id: z.number(),
|
||||
})
|
||||
.nonstrict()
|
||||
|
||||
export default resolver.pipe(resolver.zod(__Name__), resolver.authorize(), async (input) => {
|
||||
// Do your stuff :)
|
||||
})
|
||||
19
examples/auth/app/pages/404.tsx
Normal file
19
examples/auth/app/pages/404.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import {Head, ErrorComponent} from "blitz"
|
||||
|
||||
// ------------------------------------------------------
|
||||
// This page is rendered if a route match is not found
|
||||
// ------------------------------------------------------
|
||||
export default function Page404() {
|
||||
const statusCode = 404
|
||||
const title = "This page could not be found"
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>
|
||||
{statusCode}: {title}
|
||||
</title>
|
||||
</Head>
|
||||
<ErrorComponent statusCode={statusCode} title={title} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
48
examples/auth/app/pages/_app.tsx
Normal file
48
examples/auth/app/pages/_app.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
withBlitzAppRoot,
|
||||
AppProps,
|
||||
ErrorComponent,
|
||||
useRouter,
|
||||
AuthenticationError,
|
||||
AuthorizationError,
|
||||
ErrorFallbackProps,
|
||||
} from "blitz"
|
||||
import {ErrorBoundary} from "react-error-boundary"
|
||||
import {queryCache} from "react-query"
|
||||
import LoginForm from "app/auth/components/LoginForm"
|
||||
|
||||
export default function App({Component, pageProps}: AppProps) {
|
||||
const getLayout = Component.getLayout || ((page) => page)
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
FallbackComponent={RootErrorFallback}
|
||||
resetKeys={[router.asPath]}
|
||||
onReset={() => {
|
||||
// This ensures the Blitz useQuery hooks will automatically refetch
|
||||
// data any time you reset the error boundary
|
||||
queryCache.resetErrorBoundaries()
|
||||
}}
|
||||
>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
function RootErrorFallback({error, resetErrorBoundary}: ErrorFallbackProps) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
return <LoginForm onSuccess={resetErrorBoundary} />
|
||||
} else if (error instanceof AuthorizationError) {
|
||||
return (
|
||||
<ErrorComponent
|
||||
statusCode={error.statusCode}
|
||||
title="Sorry, you are not authorized to access this"
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ErrorComponent statusCode={error.statusCode || 400} title={error.message || error.name} />
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Document, Html, DocumentHead, Main, NextScript /*DocumentContext*/ } from "@blitzjs/core"
|
||||
import {Document, Html, DocumentHead, Main, BlitzScript /*DocumentContext*/} from "blitz"
|
||||
|
||||
class MyDocument extends Document {
|
||||
// Only uncomment if you need to customize this behaviour
|
||||
@@ -13,7 +13,7 @@ class MyDocument extends Document {
|
||||
<DocumentHead />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
<BlitzScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
27
examples/auth/app/pages/index.test.tsx
Normal file
27
examples/auth/app/pages/index.test.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import {render} from "test/utils"
|
||||
import Home from "./index"
|
||||
|
||||
jest.mock("blitz", () => ({
|
||||
...jest.requireActual("blitz")!,
|
||||
useQuery: () => [
|
||||
{
|
||||
id: 1,
|
||||
name: "User",
|
||||
email: "user@email.com",
|
||||
role: "user",
|
||||
},
|
||||
],
|
||||
}))
|
||||
|
||||
test("renders blitz documentation link", () => {
|
||||
// This is an example of how to ensure a specific item is in the document
|
||||
// But it's disabled by default (by test.skip) so the test doesn't fail
|
||||
// when you remove the the default content from the page
|
||||
|
||||
// This is an example on how to mock api hooks when testing
|
||||
|
||||
const {getByText} = render(<Home />)
|
||||
const element = getByText(/powered by blitz/i)
|
||||
// @ts-ignore
|
||||
expect(element).toBeInTheDocument()
|
||||
})
|
||||
229
examples/auth/app/pages/index.tsx
Normal file
229
examples/auth/app/pages/index.tsx
Normal file
@@ -0,0 +1,229 @@
|
||||
import {Suspense} from "react"
|
||||
import {Head, Link, useSession, useRouterQuery, useMutation, invoke, useQuery} from "blitz"
|
||||
import getUser from "app/users/queries/getUser"
|
||||
import trackView from "app/users/mutations/trackView"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
|
||||
const CurrentUserInfo = () => {
|
||||
const session = useSession()
|
||||
const [currentUser] = useQuery(getUser, {where: {id: session.userId!}})
|
||||
|
||||
return <pre>{JSON.stringify(currentUser, null, 2)}</pre>
|
||||
}
|
||||
|
||||
const UserStuff = () => {
|
||||
const session = useSession()
|
||||
const query = useRouterQuery()
|
||||
const [trackViewMutation] = useMutation(trackView)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!session.userId && (
|
||||
<>
|
||||
<div style={{marginTop: "1rem"}}>
|
||||
<Link href="/signup">Sign Up</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Link href="/login">Login</Link>
|
||||
</div>
|
||||
<a href="/api/auth/twitter" style={{display: "block"}}>
|
||||
Login with Twitter
|
||||
</a>
|
||||
<a href="/api/auth/github" style={{display: "block"}}>
|
||||
Login with Github
|
||||
</a>
|
||||
{query.authError && <div style={{color: "red"}}>{query.authError}</div>}
|
||||
</>
|
||||
)}
|
||||
<pre>{JSON.stringify(session, null, 2)}</pre>
|
||||
<Suspense fallback="Loading...">
|
||||
<CurrentUserInfo />
|
||||
</Suspense>
|
||||
<button
|
||||
onClick={async () => {
|
||||
try {
|
||||
const user = await invoke(getUser, {where: {id: session.userId as number}})
|
||||
alert(JSON.stringify(user))
|
||||
} catch (error) {
|
||||
alert("error: " + JSON.stringify(error))
|
||||
}
|
||||
}}
|
||||
>
|
||||
Get user
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
try {
|
||||
await trackViewMutation()
|
||||
} catch (error) {
|
||||
alert("error: " + error)
|
||||
console.log(error)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Track view
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Home = () => (
|
||||
<Layout>
|
||||
<div className="container">
|
||||
<Head>
|
||||
<title>auth</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<main>
|
||||
<div className="logo">
|
||||
<img src="/logo.png" alt="blitz.js" />
|
||||
</div>
|
||||
|
||||
<Suspense fallback={"Loading..."}>
|
||||
<UserStuff />
|
||||
</Suspense>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<a
|
||||
href="https://blitzjs.com?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Powered by Blitz.js
|
||||
</a>
|
||||
</footer>
|
||||
|
||||
<style jsx>{`
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 5rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main p {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
border-top: 1px solid #eaeaea;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #45009d;
|
||||
}
|
||||
|
||||
footer a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: #f4f4f4;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-gap: 0.5rem;
|
||||
margin-top: 6rem;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background-color: #6700eb;
|
||||
padding: 1rem 2rem;
|
||||
color: #f4f4f4;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a.button:hover {
|
||||
background-color: #45009d;
|
||||
}
|
||||
|
||||
a.button-outline {
|
||||
border: 2px solid #6700eb;
|
||||
padding: 1rem 2rem;
|
||||
color: #6700eb;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a.button-outline:hover {
|
||||
border-color: #45009d;
|
||||
color: #45009d;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #fafafa;
|
||||
border-radius: 5px;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
code {
|
||||
font-size: 0.9rem;
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
max-width: 800px;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.grid {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<style jsx global>{`
|
||||
@import url("https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@300;700&display=swap");
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Libre Franklin", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
export default Home
|
||||
64
examples/auth/app/pages/projects/[projectId].tsx
Normal file
64
examples/auth/app/pages/projects/[projectId].tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import {Suspense} from "react"
|
||||
import {Head, Link, useRouter, useQuery, useParam, BlitzPage, useMutation} from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import getProject from "app/projects/queries/getProject"
|
||||
import deleteProject from "app/projects/mutations/deleteProject"
|
||||
|
||||
export const Project = () => {
|
||||
const router = useRouter()
|
||||
const projectId = useParam("projectId", "number")
|
||||
const [deleteProjectMutation, {isSuccess}] = useMutation(deleteProject)
|
||||
const [project] = useQuery(getProject, {id: projectId}, {enabled: !isSuccess})
|
||||
|
||||
if (!project) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Project {project.id}</title>
|
||||
</Head>
|
||||
|
||||
<div>
|
||||
<h1>Project {project.id}</h1>
|
||||
<pre>{JSON.stringify(project, null, 2)}</pre>
|
||||
|
||||
<Link href={`/projects/${project.id}/edit`}>
|
||||
<a>Edit</a>
|
||||
</Link>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
if (window.confirm("This will be deleted")) {
|
||||
await deleteProjectMutation({id: project.id})
|
||||
router.push("/projects")
|
||||
}
|
||||
}}
|
||||
style={{marginLeft: "0.5rem"}}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const ShowProjectPage: BlitzPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
<Link href="/projects">
|
||||
<a>Projects</a>
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Project />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ShowProjectPage.getLayout = (page) => <Layout>{page}</Layout>
|
||||
|
||||
export default ShowProjectPage
|
||||
70
examples/auth/app/pages/projects/[projectId]/edit.tsx
Normal file
70
examples/auth/app/pages/projects/[projectId]/edit.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import {Suspense} from "react"
|
||||
import {Head, Link, useRouter, useQuery, useMutation, useParam, BlitzPage} from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import getProject from "app/projects/queries/getProject"
|
||||
import updateProject from "app/projects/mutations/updateProject"
|
||||
import {ProjectForm, FORM_ERROR} from "app/projects/components/ProjectForm"
|
||||
|
||||
export const EditProject = () => {
|
||||
const router = useRouter()
|
||||
const projectId = useParam("projectId", "number")
|
||||
const [project, {setQueryData}] = useQuery(getProject, {id: projectId})
|
||||
const [updateProjectMutation] = useMutation(updateProject)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Edit Project {project.id}</title>
|
||||
</Head>
|
||||
|
||||
<div>
|
||||
<h1>Edit Project {project.id}</h1>
|
||||
<pre>{JSON.stringify(project)}</pre>
|
||||
|
||||
<ProjectForm
|
||||
submitText="Update Project"
|
||||
// TODO use a zod schema for form validation
|
||||
// - Tip: extract mutation's schema into a shared `validations.ts` file and
|
||||
// then import and use it here
|
||||
// schema={UpdateProject}
|
||||
initialValues={project}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
const updated = await updateProjectMutation({
|
||||
id: project.id,
|
||||
...values,
|
||||
})
|
||||
await setQueryData(updated)
|
||||
router.push(`/projects/${updated.id}`)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return {
|
||||
[FORM_ERROR]: error.toString(),
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const EditProjectPage: BlitzPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<EditProject />
|
||||
</Suspense>
|
||||
|
||||
<p>
|
||||
<Link href="/projects">
|
||||
<a>Projects</a>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
EditProjectPage.getLayout = (page) => <Layout>{page}</Layout>
|
||||
|
||||
export default EditProjectPage
|
||||
66
examples/auth/app/pages/projects/index.tsx
Normal file
66
examples/auth/app/pages/projects/index.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import {Suspense} from "react"
|
||||
import {Head, Link, usePaginatedQuery, useRouter, BlitzPage} from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import getProjects from "app/projects/queries/getProjects"
|
||||
|
||||
const ITEMS_PER_PAGE = 100
|
||||
|
||||
export const ProjectsList = () => {
|
||||
const router = useRouter()
|
||||
const page = Number(router.query.page) || 0
|
||||
const [{projects, hasMore}] = usePaginatedQuery(getProjects, {
|
||||
orderBy: {id: "asc"},
|
||||
skip: ITEMS_PER_PAGE * page,
|
||||
take: ITEMS_PER_PAGE,
|
||||
})
|
||||
|
||||
const goToPreviousPage = () => router.push({query: {page: page - 1}})
|
||||
const goToNextPage = () => router.push({query: {page: page + 1}})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ul>
|
||||
{projects.map((project) => (
|
||||
<li key={project.id}>
|
||||
<Link href={`/projects/${project.id}`}>
|
||||
<a>{project.name}</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<button disabled={page === 0} onClick={goToPreviousPage}>
|
||||
Previous
|
||||
</button>
|
||||
<button disabled={!hasMore} onClick={goToNextPage}>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ProjectsPage: BlitzPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Projects</title>
|
||||
</Head>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
<Link href="/projects/new">
|
||||
<a>Create Project</a>
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<ProjectsList />
|
||||
</Suspense>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
ProjectsPage.getLayout = (page) => <Layout>{page}</Layout>
|
||||
|
||||
export default ProjectsPage
|
||||
45
examples/auth/app/pages/projects/new.tsx
Normal file
45
examples/auth/app/pages/projects/new.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import {Link, useRouter, useMutation, BlitzPage} from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import createProject from "app/projects/mutations/createProject"
|
||||
import {ProjectForm, FORM_ERROR} from "app/projects/components/ProjectForm"
|
||||
|
||||
const NewProjectPage: BlitzPage = () => {
|
||||
const router = useRouter()
|
||||
const [createProjectMutation] = useMutation(createProject)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Create New Project</h1>
|
||||
|
||||
<ProjectForm
|
||||
submitText="Create Project"
|
||||
// TODO use a zod schema for form validation
|
||||
// - Tip: extract mutation's schema into a shared `validations.ts` file and
|
||||
// then import and use it here
|
||||
// schema={CreateProject}
|
||||
// initialValues={{}}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
const project = await createProjectMutation(values)
|
||||
router.push(`/projects/${project.id}`)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return {
|
||||
[FORM_ERROR]: error.toString(),
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<p>
|
||||
<Link href="/projects">
|
||||
<a>Projects</a>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
NewProjectPage.getLayout = (page) => <Layout title={"Create New Project"}>{page}</Layout>
|
||||
|
||||
export default NewProjectPage
|
||||
88
examples/auth/app/pages/ssr.tsx
Normal file
88
examples/auth/app/pages/ssr.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import {FC} from "react"
|
||||
import {getSessionContext} from "@blitzjs/server"
|
||||
import {
|
||||
invokeWithMiddleware,
|
||||
useRouter,
|
||||
GetServerSideProps,
|
||||
PromiseReturnType,
|
||||
ErrorComponent as ErrorPage,
|
||||
useMutation,
|
||||
AuthenticationError,
|
||||
AuthorizationError,
|
||||
} from "blitz"
|
||||
import getUser from "app/users/queries/getUser"
|
||||
import logout from "app/auth/mutations/logout"
|
||||
import path from "path"
|
||||
|
||||
type PageProps = {
|
||||
user?: PromiseReturnType<typeof getUser>
|
||||
error?: {
|
||||
statusCode: number
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<PageProps> = async ({req, res}) => {
|
||||
// Ensure these files are not eliminated by trace-based tree-shaking (like Vercel)
|
||||
// https://github.com/blitz-js/blitz/issues/794
|
||||
path.resolve("next.config.js")
|
||||
path.resolve("blitz.config.js")
|
||||
path.resolve(".next/blitz/db.js")
|
||||
// End anti-tree-shaking
|
||||
|
||||
const session = await getSessionContext(req, res)
|
||||
console.log("Session id:", session.userId)
|
||||
try {
|
||||
const user = await invokeWithMiddleware(
|
||||
getUser,
|
||||
{where: {id: Number(session.userId)}},
|
||||
{res, req},
|
||||
)
|
||||
return {props: {user}}
|
||||
} catch (error) {
|
||||
if (error.name === "NotFoundError") {
|
||||
res.statusCode = 404
|
||||
res.end()
|
||||
return {props: {}}
|
||||
} else if (error instanceof AuthenticationError) {
|
||||
res.writeHead(302, {location: "/login"}).end()
|
||||
return {props: {}}
|
||||
} else if (error instanceof AuthorizationError) {
|
||||
return {
|
||||
props: {
|
||||
error: {
|
||||
statusCode: error.statusCode,
|
||||
message: "Sorry, you are not authorized to access this",
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return {props: {error: {statusCode: error.statusCode || 500, message: error.message}}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Test: FC<PageProps> = ({user, error}: PageProps) => {
|
||||
const router = useRouter()
|
||||
const [logoutMutation] = useMutation(logout)
|
||||
|
||||
if (error) {
|
||||
return <ErrorPage statusCode={error.statusCode} title={error.message} />
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>Logged in user id: {user?.id}</div>
|
||||
<button
|
||||
onClick={async () => {
|
||||
await logoutMutation()
|
||||
router.push("/")
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Test
|
||||
12
examples/auth/app/projects/components/ProjectForm.tsx
Normal file
12
examples/auth/app/projects/components/ProjectForm.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import {Form, FormProps} from "app/core/components/Form"
|
||||
import {LabeledTextField} from "app/core/components/LabeledTextField"
|
||||
import * as z from "zod"
|
||||
export {FORM_ERROR} from "app/core/components/Form"
|
||||
|
||||
export function ProjectForm<S extends z.ZodType<any, any>>(props: FormProps<S>) {
|
||||
return (
|
||||
<Form<S> {...props}>
|
||||
<LabeledTextField name="name" label="Name" placeholder="Name" />
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
16
examples/auth/app/projects/mutations/createProject.ts
Normal file
16
examples/auth/app/projects/mutations/createProject.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {resolver} from "blitz"
|
||||
import db from "db"
|
||||
import * as z from "zod"
|
||||
|
||||
const CreateProject = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
})
|
||||
.nonstrict()
|
||||
|
||||
export default resolver.pipe(resolver.zod(CreateProject), resolver.authorize(), async (input) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const project = await db.project.create({data: input})
|
||||
|
||||
return project
|
||||
})
|
||||
16
examples/auth/app/projects/mutations/deleteProject.ts
Normal file
16
examples/auth/app/projects/mutations/deleteProject.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {resolver} from "blitz"
|
||||
import db from "db"
|
||||
import * as z from "zod"
|
||||
|
||||
const DeleteProject = z
|
||||
.object({
|
||||
id: z.number(),
|
||||
})
|
||||
.nonstrict()
|
||||
|
||||
export default resolver.pipe(resolver.zod(DeleteProject), resolver.authorize(), async ({id}) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const project = await db.project.delete({where: {id}})
|
||||
|
||||
return project
|
||||
})
|
||||
21
examples/auth/app/projects/mutations/updateProject.ts
Normal file
21
examples/auth/app/projects/mutations/updateProject.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import {resolver} from "blitz"
|
||||
import db from "db"
|
||||
import * as z from "zod"
|
||||
|
||||
const UpdateProject = z
|
||||
.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
})
|
||||
.nonstrict()
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(UpdateProject),
|
||||
resolver.authorize(),
|
||||
async ({id, ...data}) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const project = await db.project.update({where: {id}, data})
|
||||
|
||||
return project
|
||||
},
|
||||
)
|
||||
17
examples/auth/app/projects/queries/getProject.ts
Normal file
17
examples/auth/app/projects/queries/getProject.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import {resolver, NotFoundError} from "blitz"
|
||||
import db from "db"
|
||||
import * as z from "zod"
|
||||
|
||||
const GetProject = z.object({
|
||||
// This accepts type of undefined, but is required at runtime
|
||||
id: z.number().optional().refine(Boolean, "Required"),
|
||||
})
|
||||
|
||||
export default resolver.pipe(resolver.zod(GetProject), resolver.authorize(), async ({id}) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const project = await db.project.findFirst({where: {id}})
|
||||
|
||||
if (!project) throw new NotFoundError()
|
||||
|
||||
return project
|
||||
})
|
||||
25
examples/auth/app/projects/queries/getProjects.ts
Normal file
25
examples/auth/app/projects/queries/getProjects.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {paginate, resolver} from "blitz"
|
||||
import db, {Prisma} from "db"
|
||||
|
||||
interface GetProjectsInput
|
||||
extends Pick<Prisma.ProjectFindManyArgs, "where" | "orderBy" | "skip" | "take"> {}
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.authorize(),
|
||||
async ({where, orderBy, skip = 0, take = 100}: GetProjectsInput) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const {items: projects, hasMore, nextPage, count} = await paginate({
|
||||
skip,
|
||||
take,
|
||||
count: () => db.project.count({where}),
|
||||
query: (paginateArgs) => db.project.findMany({...paginateArgs, where, orderBy}),
|
||||
})
|
||||
|
||||
return {
|
||||
projects,
|
||||
nextPage,
|
||||
hasMore,
|
||||
count,
|
||||
}
|
||||
},
|
||||
)
|
||||
9
examples/auth/app/users/mutations/trackView.ts
Normal file
9
examples/auth/app/users/mutations/trackView.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import {Ctx} from "blitz"
|
||||
|
||||
export default async function trackView(_: any, {session}: Ctx) {
|
||||
const currentViews = session.views || 0
|
||||
await session.$setPublicData({views: currentViews + 1})
|
||||
await session.$setPrivateData({views: currentViews + 1})
|
||||
|
||||
return
|
||||
}
|
||||
13
examples/auth/app/users/queries/getCurrentUser.ts
Normal file
13
examples/auth/app/users/queries/getCurrentUser.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import {Ctx} from "blitz"
|
||||
import db from "db"
|
||||
|
||||
export default async function getCurrentUser(_ = null, {session}: Ctx) {
|
||||
if (!session.userId) return null
|
||||
|
||||
const user = await db.user.findFirst({
|
||||
where: {id: session.userId},
|
||||
select: {id: true, name: true, email: true, role: true},
|
||||
})
|
||||
|
||||
return user
|
||||
}
|
||||
18
examples/auth/app/users/queries/getUser.ts
Normal file
18
examples/auth/app/users/queries/getUser.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {Ctx, NotFoundError} from "blitz"
|
||||
import db, {Prisma} from "db"
|
||||
|
||||
type GetUserInput = {
|
||||
where: Prisma.UserFindFirstArgs["where"]
|
||||
}
|
||||
|
||||
export default async function getUser({where}: GetUserInput, ctx: Ctx) {
|
||||
if (!ctx.session.userId) return null
|
||||
|
||||
const user = await db.user.findFirst({where})
|
||||
|
||||
if (!user) throw new NotFoundError(`User with id ${where?.id} does not exist`)
|
||||
|
||||
const {hashedPassword, ...rest} = user
|
||||
|
||||
return rest
|
||||
}
|
||||
26
examples/auth/app/users/queries/getUsers.ts
Normal file
26
examples/auth/app/users/queries/getUsers.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {Ctx} from "blitz"
|
||||
import db, {Prisma} from "db"
|
||||
|
||||
type GetUsersInput = Pick<Prisma.UserFindManyArgs, "where" | "orderBy" | "skip" | "take">
|
||||
|
||||
export default async function getUsers({where, orderBy, skip = 0, take}: GetUsersInput, ctx: Ctx) {
|
||||
ctx.session.$authorize()
|
||||
|
||||
const users = await db.user.findMany({
|
||||
where,
|
||||
orderBy,
|
||||
take,
|
||||
skip,
|
||||
})
|
||||
|
||||
const count = await db.user.count()
|
||||
const hasMore = typeof take === "number" ? skip + take < count : false
|
||||
const nextPage = hasMore ? {take, skip: skip + take!} : null
|
||||
|
||||
return {
|
||||
users,
|
||||
nextPage,
|
||||
hasMore,
|
||||
count,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
presets: ['next/babel'],
|
||||
presets: ["blitz/babel"],
|
||||
plugins: [],
|
||||
}
|
||||
32
examples/auth/blitz.config.js
Normal file
32
examples/auth/blitz.config.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const {sessionMiddleware, simpleRolesIsAuthorized} = require("@blitzjs/server")
|
||||
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
||||
enabled: process.env.ANALYZE === "true",
|
||||
})
|
||||
|
||||
module.exports = withBundleAnalyzer({
|
||||
middleware: [
|
||||
sessionMiddleware({
|
||||
isAuthorized: simpleRolesIsAuthorized,
|
||||
// sessionExpiryMinutes: 4,
|
||||
}),
|
||||
],
|
||||
log: {
|
||||
// level: "trace",
|
||||
},
|
||||
experimental: {
|
||||
isomorphicResolverImports: false,
|
||||
},
|
||||
/*
|
||||
webpack: (config, {buildId, dev, isServer, defaultLoaders, webpack}) => {
|
||||
// Note: we provide webpack above so you should not `require` it
|
||||
// Perform customizations to webpack config
|
||||
// Important: return the modified config
|
||||
return config
|
||||
},
|
||||
webpackDevMiddleware: (config) => {
|
||||
// Perform customizations to webpack dev middleware config
|
||||
// Important: return the modified config
|
||||
return config
|
||||
},
|
||||
*/
|
||||
})
|
||||
6
examples/auth/cypress.json
Normal file
6
examples/auth/cypress.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3099",
|
||||
"defaultCommandTimeout": 10000,
|
||||
"video": false,
|
||||
"chromeWebSecurity": false
|
||||
}
|
||||
5
examples/auth/cypress/fixtures/example.json
Normal file
5
examples/auth/cypress/fixtures/example.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
8
examples/auth/cypress/index.d.ts
vendored
Normal file
8
examples/auth/cypress/index.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/// <reference types="Cypress" />
|
||||
/// <reference types="@cypress/skip-test" />
|
||||
|
||||
declare namespace Cypress {
|
||||
interface Chainable {
|
||||
signup(user: {email: string; password: string}): void
|
||||
}
|
||||
}
|
||||
71
examples/auth/cypress/integration/index.test.ts
Normal file
71
examples/auth/cypress/integration/index.test.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import {createRandomUser} from "../support/helpers"
|
||||
|
||||
describe("index page", () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/")
|
||||
})
|
||||
|
||||
it("goes to the signup page", () => {
|
||||
cy.contains("a", "Sign Up").click()
|
||||
cy.location("pathname").should("equal", "/signup")
|
||||
})
|
||||
|
||||
it("goes to the login page", () => {
|
||||
cy.contains("a", /login/i).click()
|
||||
cy.location("pathname").should("equal", "/login")
|
||||
})
|
||||
|
||||
it("allows the user to signup", () => {
|
||||
const user = createRandomUser()
|
||||
|
||||
cy.signup(user)
|
||||
|
||||
cy.location("pathname").should("equal", "/")
|
||||
cy.contains("button", "Logout")
|
||||
})
|
||||
|
||||
it("allows the user to log in", () => {
|
||||
const user = createRandomUser()
|
||||
|
||||
cy.signup(user)
|
||||
|
||||
cy.wait(500)
|
||||
cy.contains("button", "Logout").click()
|
||||
cy.contains("a", /login/i).click()
|
||||
|
||||
cy.contains("Email").find("input").type(user.email)
|
||||
cy.contains("Password").find("input").type(user.password)
|
||||
cy.contains("button", /login/i).click()
|
||||
|
||||
cy.location("pathname").should("equal", "/")
|
||||
cy.wait(500)
|
||||
cy.contains("button", "Logout")
|
||||
})
|
||||
|
||||
it("allows the user to logout", () => {
|
||||
const user = createRandomUser()
|
||||
|
||||
cy.signup(user)
|
||||
|
||||
cy.contains("button", "Logout").click()
|
||||
|
||||
cy.location("pathname").should("equal", "/")
|
||||
cy.contains("a", /login/i)
|
||||
})
|
||||
|
||||
it("tracks anonymous sessions", () => {
|
||||
// TODO - why does this fail on windows??
|
||||
cy.skipOn("windows")
|
||||
const user = createRandomUser()
|
||||
|
||||
cy.contains("button", "Track view").click()
|
||||
cy.contains("button", "Track view").click()
|
||||
cy.contains('"views": 2')
|
||||
|
||||
cy.signup(user)
|
||||
|
||||
cy.contains('"views": 2')
|
||||
})
|
||||
})
|
||||
|
||||
export {}
|
||||
22
examples/auth/cypress/plugins/index.ts
Normal file
22
examples/auth/cypress/plugins/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
//@ts-ignore
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
||||
19
examples/auth/cypress/support/commands.ts
Normal file
19
examples/auth/cypress/support/commands.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
|
||||
Cypress.Commands.add("signup", ({email, password}) => {
|
||||
cy.contains("a", "Sign Up").click()
|
||||
|
||||
cy.contains("Email").find("input").type(email)
|
||||
cy.contains("Password").find("input").type(password)
|
||||
cy.contains("button", "Create Account").click()
|
||||
})
|
||||
7
examples/auth/cypress/support/helpers.ts
Normal file
7
examples/auth/cypress/support/helpers.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const createRandomUser = () => {
|
||||
const random = Math.round(Math.random() * 100000).toString()
|
||||
const email = `test_${random}@example.com`
|
||||
const password = `password_${random}`
|
||||
|
||||
return {email, password}
|
||||
}
|
||||
26
examples/auth/cypress/support/index.ts
Normal file
26
examples/auth/cypress/support/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import "./commands"
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
Cypress.Screenshot.defaults({
|
||||
screenshotOnRunFailure: false,
|
||||
})
|
||||
|
||||
require("@cypress/skip-test/support")
|
||||
7
examples/auth/cypress/tsconfig.json
Normal file
7
examples/auth/cypress/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "es5"],
|
||||
"types": ["cypress"]
|
||||
}
|
||||
}
|
||||
7
examples/auth/db/index.ts
Normal file
7
examples/auth/db/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import {enhancePrisma} from "blitz"
|
||||
import {PrismaClient} from "@prisma/client"
|
||||
|
||||
const EnhancedPrisma = enhancePrisma(PrismaClient)
|
||||
|
||||
export * from "@prisma/client"
|
||||
export default new EnhancedPrisma()
|
||||
@@ -0,0 +1,32 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"name" TEXT,
|
||||
"email" TEXT NOT NULL,
|
||||
"hashedPassword" TEXT,
|
||||
"role" TEXT NOT NULL DEFAULT 'user'
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Session" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"expiresAt" DATETIME,
|
||||
"handle" TEXT NOT NULL,
|
||||
"userId" INTEGER,
|
||||
"hashedSessionToken" TEXT,
|
||||
"antiCSRFToken" TEXT,
|
||||
"publicData" TEXT,
|
||||
"privateData" TEXT,
|
||||
|
||||
FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User.email_unique" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Session.handle_unique" ON "Session"("handle");
|
||||
@@ -0,0 +1,7 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Project" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"name" TEXT NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Project" ADD COLUMN "dueDate" DATETIME;
|
||||
@@ -0,0 +1,15 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Token" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"hashedToken" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"expiresAt" DATETIME NOT NULL,
|
||||
"sentTo" TEXT NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Token.hashedToken_type_unique" ON "Token"("hashedToken", "type");
|
||||
2
examples/auth/db/migrations/migration_lock.toml
Normal file
2
examples/auth/db/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
# Please do not edit this file manually
|
||||
provider = "sqlite"
|
||||
70
examples/auth/db/schema.prisma
Normal file
70
examples/auth/db/schema.prisma
Normal file
@@ -0,0 +1,70 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
datasource sqlite {
|
||||
provider = "sqlite"
|
||||
url = "file:./db.sqlite"
|
||||
}
|
||||
|
||||
// SQLite is easy to start with, but if you use Postgres in production
|
||||
// you should also use it in development with the following:
|
||||
//datasource postgresql {
|
||||
// provider = "postgresql"
|
||||
// url = env("DATABASE_URL")
|
||||
//}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
name String?
|
||||
email String @unique
|
||||
hashedPassword String?
|
||||
role String @default("user")
|
||||
|
||||
sessions Session[]
|
||||
tokens Token[]
|
||||
}
|
||||
|
||||
model Session {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
expiresAt DateTime?
|
||||
handle String @unique
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId Int?
|
||||
hashedSessionToken String?
|
||||
antiCSRFToken String?
|
||||
publicData String?
|
||||
privateData String?
|
||||
}
|
||||
|
||||
model Token {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
hashedToken String
|
||||
type String
|
||||
expiresAt DateTime
|
||||
sentTo String
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
|
||||
@@unique([hashedToken, type])
|
||||
}
|
||||
|
||||
model Project {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
name String
|
||||
dueDate DateTime?
|
||||
}
|
||||
6
examples/auth/db/seeds.ts
Normal file
6
examples/auth/db/seeds.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import db from "./index"
|
||||
const seed = async () => {
|
||||
const user = await db.user.create({data: {name: "FooBar", email: "hey@" + new Date().getTime()}})
|
||||
console.log("Created user", user)
|
||||
}
|
||||
export default seed
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user