In our previous article we answered the question why does Hyperlambda exist, and in the article before that we created our first Hyperlambda CRUD API. The first article where we generated a CRUD API without writing code ourselves was arguably the famous "Hello World tutorial" that every programming language book starts out with. However, we didn't write the hello world program ourselves, the machine did it for us instead. And where a typical Hello World tutorial only displays "Hello World" on the screen, we created a complete web API with thousands of lines of code wrapping our database. In this article we will analyse the code Magic produced for us in our generate a CRUD API article to understand how it is being assembled by Magic.
Template based code generation
If you use Hyper IDE to look at the files Magic generated for you, you will see something resembling the following.
Above you will find 2 HTTP GET
files, one DELETE
, one PUT
, and one POST
file for most of your database tables. You can see the HTTP verb of your files as the extension your files are using, such as for instance "album.delete.hl" where "album" is the table name, "delete" is the verb, and "hl" simply implies Hyperlambda. The files ending with "-count.get.hl" is for counting items in your tables. Assuming you have a table named foo, the complete list of files would become as follows.
- foo-count.get.hl
- foo.get.hl
- foo.post.hl
- foo.put.hl
- foo.delete.hl
This structure is repeated once for all your tables, assuming Magic can create functions for all CRUD operations towards your database table. If you open up two files such as for instance "album-count.get.hl" and "artist-count.get.hl" and you compare their content, you will see that these two files are quite similar, and the only differences being which arguments the files can handle and which database table it selects from. The rest of the files are fairly similar in structure and both files are invoking the same functions ("slots"), and contains the same logic.
This is because in Magic there exists only 4 different template files for creating your CRUD API, and the CRUD Generator will use one of these template files as it generates your CRUD API depending upon which CRUD operation it is currently wrapping, and dynamically substitute the parts that is unique for your database tables as it generates the resulting code. This process is why we needed a meta programming language to create Magic in the first place.
The advantage Hyperlambda has over other more traditional programming languages here, is that Hyperlambda can structurally understand Hyperlambda, and doesn't have to resort to regular expressions or template fields to do its job. The CRUD Generator for instance has a checkbox allowing you to turn on web socket messaging upon invocations to POST, PUT and DELETE endpoints. From your point of view this is just a simple checkbox, allowing you to turn it on and off in a second. However, from the CRUD generator's point of view, this is a parameter it is given that informs it that it needs to inject web socket messaging upon invocations to specific endpoints. Below is the actual code executing that checks to see if socket messaging is specified by the caller or not.
The checkbox itself looks like the following from your point of view as you generate your CRUD API.
If you turn on socket messaging the end result for your POST, PUT and DELETE endpoints will resemble the following.
Lines 33 to 38 in the above screenshot was dynamically injected into the code semantically. Doing something such as the above with a traditional programming language wouldn't even be possible in theory I suspect. In Hyperlambda it's easy to achieve.
The file system
Open up any of the generated Hyperlambda files and click the 3 small dots in the top right corner to open up the context menu, and choose "Invoke" inside of "Active file" as we illustrate below.
If you click the invoke button, you will be shown the integrated endpoint invocation form, allowing you to immediately invoke your endpoint, passing in arguments as required by the endpoint. For the above file the form will resemble the following.
You can do this with any Hyperlambda file you have, assuming the file is an HTTP endpoint type of Hyperlambda file. However, the thing I want you to notice here, is the relationship between the filename and its invocation URL. The relative URL in the top left corner of your invocation form and the physical filename on disc obviously have some sort of relationship with each other. For instance the invocation URL is as follows for the above file.
- magic/modules/chinook/album
While the physical filename on disc is as follows.
- modules/chinook/album.delete.hl
If you remove everything from the first .
in your filename, and prepend "modules" with "magic/" the above two different paths becomes identical. Then as you see the extension of the Hyperlambda file being "delete" while its HTTP verb is DELETE
, you probably understand already now how Magic dynamically resolves URLs in your backend to invoke Hyperlambda files dynamically given an HTTP request.
You will see a similar relationship between your physical files and your HTTP endpoints regardless of which Hyperlambda file you open. This creates a "super structure" for your execution environment where Magic's core can use conventions to determine what a potential client wants to do on the server, implying which files the client wants to invoke, and how it wants to invoke the file. Hence, Hyperlambda becomes "dynamic web functions" you might argue.
Arguments
At this point the only remaining question is; "How does the Hyperlambda files handle arguments?"
If you look at the above screenshot of the invoke endpoint dialog, you'll see it takes one argument. If you close it and look at the top of the Hyperlambda file, you will see something resembling the following.
.arguments
AlbumId:long
The [.arguments] node declares which arguments the endpoint can legally handle, and the type of argument it expects. If you add another argument to the above such as illustrated below.
.arguments
AlbumId:long
foo:string
And you save the file using the context menu for "Active file", and you try to open the "Invoke" modal window again, you'll see the following.
All arguments are optional, but no arguments not explicitly declared in your [.arguments] collection can be specified during invocations to your endpoints, unless you explicitly turn off sanity checking of your arguments by providing a type declaration of *
, signalling to Magic's core to basically "accept whatever".
And that's it, literally. Of course there are Hyperlambda functions ("slots") for opening database connections, reading, updating, creating, and deleting database records etc, but these will be the subject for another article.
Top comments (4)
I'm a fronted developer and am following the series of your Hyperlambda articles. I've found it quote impressive, since it seems easy to understand. While playing around with the code as described here, I was wondering if the file name's extensions play any significant role. Is it necessary to follow the rule of e.g. ".get.hl" while creating a new file with the purpose of "get" ?
BTW thank you for the articles.
Thx Julia, and the answer is yes, the file names are "semantic" in nature. The CRUD generator will for instance create 5 files for you with the following structure if you wrap a table named "table1".
The last parts, the ".hl" part just implies "Hyperlambda", but the parts before that is actually the HTTP verb required to invoke the endpoint, implying if the filename is "foo.post.hl" then it is wrapping the HTTP POST verb. Magic supports the following verbs out of the box.
But the CRUD generator only uses 4 of the above, and not the patch keyword. However, manually you can create PATCH endpoints by making sure your file ends with ".patch.hl"
Interesting! Thank you for the details :)
NP, let me know if you or anybody else have additional questions ... :)