{"version":"https://jsonfeed.org/version/1","title":"Ayodeji's Blog","home_page_url":"https://trulyao.dev/","feed_url":"https://trulyao.dev/feed.json","author":{"name":"Ayodeji Osasona","url":"https://trulyao.dev"},"items":[{"id":"a-gleamy-exploration.mdx","title":"A gleamy exploration","url":"https://trulyao.dev/posts/a-gleamy-exploration","tags":["gleam","beam","programming language"],"summary":"I found this new programming language with the soul of Erlang's fault-tolerant concurrent runtime and the body of the type-safe, fast & highly beloved Rust, you know I just had to try it out.","content_text":"\n![Gleam 'n' Vim](/images/gleam.png)\n\n# Why try a new language?\n\n> **EDIT (11 Jan, 2023)**: I have been using Gleam for about 4 months now and I have written a couple of packages and other stuff, I have come to realise that a lot of the examples I gave here could have been done much cleaner, don't let it discourage you from checking out the language.\n\nI have been exploring a lot of languages recently, and I have stumbled upon the world of functional programming while doing that. Elixir was obviously my first go-to in that world but... I just couldn't get it and I have no one to blame for that but myself (skill issue hehe). Let's talk about why I am even exploring these languages to begin with (although there will be another article talking about my explorations and the languages themselves later). I primarily work in PHP, Go and Typescript, and I don't think any of these languages are the best, but I enjoy using them, I know where and how they suck, I know where and how they shine, they get the job done and one of them even pays my tuition.\n\nI started exploring other languages months back to see what I was missing out on, how my current languages could be better, find new ways to think about the same problem in these sometimes wildly different languages, just to have fun and to potentially add one more language to my stack for realtime applications (now you know how I landed on Elixir in the first place - Go and TS could already do real-time stuff to some extent, I mean almost every language can spin up a websocket, but it's more than that, I am talking about a language/runtime designed and built to operate in that space and that's Erlang/Erlang's runtime). I'd love to go on and explain more on why I am exploring these languages but that's not the point of this article, so let's get to it.\n\n# What the heck is Gleam?\n\nFirst time I ran into Gleam a couple of months back, I had the same question on my mind except I didn't bother to try to get an answer at the time because it looked new and there was no way I was going to go on to build any of my real projects in that, plus Elixir was way more popular so of course it made more sense to learn that instead. Fast forwards to two weeks ago, I ran into Gleam again and this time I decided to at least read the docs and a few minutes in, I was in love with this language, I did not care that it was new, I knew I wanted to be in that ecosystem, so I created a new project and started building the same thing I build in any language I intend to use for web stuff; a notes API with auth (sort of, sign in and sign up). Okay, I actually haven't given you a useful answer to the question yet, so let's get to it. From their homepage:\n\n> Gleam is a friendly language for building type-safe systems that scale!\n> The power of a type system, the expressiveness of functional programming, and the reliability of the highly concurrent, fault-tolerant Erlang runtime, with a familiar and modern syntax.\n\nThat is probably a lot to take in at once and you could say a lot of those things about Typescript too so, let's talk about what makes Gleam, well, Gleam. For starters, it runs on the Erlang virtual machine ([BEAM](https://elixirforum.com/t/how-is-otp-distinct-from-the-beam/49354)) which is known to be very fault-tolerant (also the same reason Elixir is so reliable, these languages are designed to handle crashing the best way possible; without affecting anything else running at the time), it also has a very familiar syntax if you have used Rust before, in fact, the [compiler](https://github.com/gleam-lang/gleam) is built in Rust, and it borrows a lot of Rust's syntax and constructs even down to the Option and Result types. Gleam is also a functional language, so you get all the goodies that come with that, like pattern matching, immutability, etc and unlike Elixir (Elixir is researching a type system at the time of writing this article), it is statically typed which means you can catch a lot of errors before and during compilation and just like the Rust compiler, the Gleam compiler guides you a lot in fixing these errors. I think that's enough to get you started, let's get to the fun part.\n\n## A note on immutability\n\nWhile you can't mutate variables or actual data in Gleam, you can rebind them, so you can do something like this:\n\n```rust\nlet x = 1\nlet x = x + 1\n\n// and of course, there are also constants\nconst y = 1\nconst y = y + 1 // this will not compile\n```\n\nI should also mention I am on the fence about immutability, it creates more memory and in turn more garbage, I don't know if Gleam does something to keep that memory usage down, but it also makes it easier to reason about your code and makes it impossible to run into bugs that could be caused by mutation, so I don't hate it but I don't love it either.\n\n# The weird parts\n\nGleam being a functional programming language already makes it feel weird enough (although in my opinion, less weird than Elixir or Erlang itself, thanks to its Rust-like syntax) if you're coming from other non-functional languages, but there are some other things that are just weird in Gleam, and I am going to talk about them here.\n\n## If statements, returns and loops\n\nFirst thing to discuss here are if statements, return statements and for/while loops... or the lack thereof. Gleam does not have any of these constructs, instead, it leans heavily into pattern matching and recursion (although unlike Elixir, you cannot overload functions which makes it a bit trickier too) and I have mixed feelings about this decision, at first you wonder how you'll do certain things but in reality, it doesn't take away your ability to do the things you would normally have to do with these constructs, you just have to do them a bit differently and I appreciate the new thought process that comes with that but it also makes it extremely easy to nest match statements and that can get out of hand really quickly. As an example, say I need to validate some fields only when they are present in the request body, I would have to do something like this:\n\n```rust\n // decode the request body into a struct (sort of)\n  use body <- api.to_json( ctx,\n    dynamic.decode3(\n      UpdateNoteBody,\n      dynamic.field(\"title\", dynamic.optional(dynamic.string)),\n      dynamic.field(\"body\", dynamic.optional(dynamic.string)),\n      dynamic.field(\"folder_id\", dynamic.optional(dynamic.int)),\n    ),\n  )\n\n  let validations =\n    []\n    |> fn(v) {\n      case body.title {\n        Some(title) ->\n          list.append(v, [\n            validator.Field(name: \"title\", value: title, rules: [\n              validator.Required, validator.MinLength(1), validator.MaxLength(255)\n            ])\n          ])\n        None -> v\n      }\n    }\n    |> fn(v) {\n      case body.body {\n        Some(body) ->\n          list.append(v, [\n              validator.Field(name: \"body\", value: body, rules: [validator.MaxLength(65_535)]),\n          ])\n        None -> v\n      }\n    }\n\n  use <- api.validate_body(validations)\n```\n\nIn this [piece of code](https://github.com/aosasona/noht.gleam/blob/b06ff2b6b1d1f23d757e94f5732d014249d58495/src/handlers/notes.gleam#L66), we're taking the raw JSON string into a Gleam type that we can use in our code, the `use` keyword in Gleam is a way to avoid nesting in some cases, it is also a bit weird at first but you can have a look at [this](https://gleam.run/book/tour/use.html) to understand what it does. Unfortunately, in a lot of cases like this, you would need to use the `use` keyword and put things into new functions to avoid nesting and to do early returns, for example, in this example, the `api.to_json` method can only return an error response early if the request body doesn't match what we need by either using a `use` statement like we have or going down the callback-like path which would end up being deeply nested. Although, I also think that the `use` keyword is a clever solution to the problem of early returns and nesting in Gleam (I mean, they did create the problem to begin with but it's still clever).\n\n## The Gleam LSP\n\nThis one doesn't really bother me much and it was sort of expected but I thought I should mention it still, the LSP is buggy sometimes, I don't get definitions sometimes and other times I do, it doesn't show the available methods in a module when I hit the dot (.) key, it goofs up with the types sometimes (shows the return type instead of the actual variable type) but all that being said, it is not a big deal. You're probably thinking \"What?? what the hell do you mean it's not a big deal?\" and I get you but really, it's not and I'll tell you why.\n\n![LSP failing for type defs](/gifs/gleam-lsp-issue.gif)\n\n- When you screw up, the LSP and compiler will tell you (that one never failed for me), missing parameters and wrong types are also reported correctly - I am just saying all the things that are important to me are working fine.\n- All packages, just like in Elixir, are automatically documented on Hex docs, even the standard library, so you can always go there to check what a function does and how to use it (although I wish the LSP would consistently show me the docs for a function when I hover over it, that would be nice).\n- The language is stable enough considering how new it is, I can trust the compiler and seeing that only a few people work on it at the moment, I understand not having the best LSP in the world **yet**, I mean, it's not like I am paying for it or anything, if I had to choose between a stable LSP and a stable compiler, I'd choose the compiler any day of the week.\n- The cost of not having these things is negligible (to me of course), so I don't really care that much.\n\n## Functions and modules\n\nRecursion and function overloading is a very big part of Elixir, but in Gleam, you can't have optional arguments that are just... not there, you can't have different signatures for the same function (function overloading) in the same module, and don't get me wrong, I am used to not having these things, it's not a problem for me, it still has Option types for optional values and I personally don't use any other language with support for function overloading either, but I am mentioning this because it's a bit weird to see a language that is so similar to Elixir not have these things you'd expect it to have, it's not a bad thing, it's just weird. Also, Gleam uses files as modules which means everything related to a module has to live in the same file, Rust does similar thing to some extent, it's up to you to decide if that's a good thing or not, I personally don't mind it.\n\nSo far, those are the only things I have found weird about Gleam, I am sure there are more but I haven't run into them yet, I'll update this article if I do.\n\n> **EDIT (11 Jan, 2024)**: Due to the lack of any meta-programming features, Gleam requires you to write `decoders` ([see this example](https://github.com/gleam-lang/json)) for transforming data into type-safe Gleam structures. Writing decoders also became frustrating because the stdlib only comes with a `decode9` function as the maximum (which means you can only use the standard library's convenient functions to write decoders for up to 9 fields, anything more and you have to painfully hand-roll it), but there are also talks in the community on how to solve this in the language itself in the future.\n\n> **EDIT (11 Jan, 2024)**: I picked up Gleam to get the Erlang/BEAM benefits in a familiar body but since Gleam has to care about Javascript and eventually WASM, most of the native constructs that allow you to easily use those things (`receive` mainly) or even write Erlang directly in a language like Elixir cannot simply be added to the language (the way Javascript works is very different to how the BEAM does stuff, especially when doing async work/the concurrency models as I understand it).\n\n# The good parts\n\nNow that we have talked about the weird parts, let's talk about the good parts, the parts that make me want to use Gleam everyday, and boy, there are a lot of them. I am not going to talk about the BEAM, OTP and all that stuff, I am going to focus on the language itself and I will try to keep it short.\n\n## The compiler and the guard rails\n\nThis is probably the best thing about Gleam, just like Rust, the compiler uses the information it has of the code it is running/building to help the programmer (I wish Go would take hints). The compiler is also pretty fast but to be honest, blazing fast speed isn't why anyone uses any of these languages and that's fine, although Gleam is still plenty fast. Like Go, and unlike Rust, Gleam does have `Nil` but unlike Go, it protects you from shooting yourself with it, in fact, every bug I have run into so far while building this tiny project has been my fault for not paying attention, really! There has been no case of the language letting me do things I shouldn't be doing, like attempting to use an empty value as a non-empty one, thanks to the presence of the Option type, it forces you to do a check before you can use the value (although it has a `let assert Ok(_) = ...` sort of expression to blow things up if they don't match what you want, I avoid that). Gleam also handles errors really well and I am not talking about the OTP this time, Gleam has a `Result` type that you can use to handle errors and it is very similar to Rust's `Result` type, it makes sure you know what functions can fail and ~~cause an error~~ return an error, you can read more about it [here](https://gleam.run/book/tour/result.html).\n\n## The standard library, ecosystem and interop\n\nGleam has a small and rich ecosystem at the same time, rich in the sense that the standard library (which in itself is a package you can choose not to install) has a very large percent of what you'll need to do what you need to and small in the sense that there are very few 3rd-party packages compared to more matured languages (expected and obvious) and that includes packages like `json` managed by the Gleam team which is somehow not in the stdlib (?) and you will have to reach into another language for those things.\n\nI know, I know, you are thinking this language is so poor you have to reach into other languages to find packages you need to get work done (\"What the...? Why would I want to do that??\"), Gleam is a new language and I don't expect it to have a butt-load of third-party packages available already (Zig doesn't even have a package manager yet but it's been used for large programs!), but this ability to reach into other languages is actually a strength of Gleam. Using the `@external` ~~macro~~ attribute (You can't create custom ~~macros~~ attributes yet AFAIK), you can not only reach into Elixir, Erlang and LFE but you can also reach into Javascript. Yes, you read that right, Gleam can run with a Javascript runtime as the target and as if that wasn't good enough, almost every Elixir and Erlang package is compatible with Gleam, so you can use them in your Gleam project, I mean, how cool is that? I am not going to talk about the interop with Javascript because I haven't tried it yet but I am sure it's just as good as the Elixir/Erlang interop (I used \"almost\" here because I have been told Gleam cannot handle Elixir macros for certain good reasons). Here is an example of using the Elixir `argon2_elixir` package in gleam for password hashing:\n\n```bash\n# install the package\ngleam add argon2_elixir\n```\n\nAnd here is how you use it in your Gleam code:\n\n```rust\n// lib/argon2.gleam\n@external(erlang, \"Elixir.Argon2\", \"hash_pwd_salt\")\npub fn hash_password(password: String) -> String\n\n@external(erlang, \"Elixir.Argon2\", \"verify_pass\")\npub fn compare_password(password: String, hash: String) -> Bool\n```\n\nUsing the package in your code is as simple as importing the module and calling the functions:\n\n```rust\n// handlers/auth.gleam\nimport lib/argon2\n\n...\nlet hashed_password = argon2.hash_password(password)\n...\n```\n\nThis is extremely powerful and I am sure it will only get better with time, it also means you don't just have the Gleam ecosystem, you have the Elixir/Erlang ecosystem too, and that's a lot of packages that have been battle-tested for years, and you can even, in theory, use Javascript packages by using them in your custom Javascript code and then using that in your Gleam code, I haven't tried it yet but it's most likely possible.\n\n> **EDIT (11 Jan, 2024)**: Gleam has no macros, `@external`, `@target` etc. are all attributes, this is by design; although there have been talks about some meta-programming capability in the future.\n\n## Pattern matching\n\nThis part isn't Gleam-specific but until you try pattern matching, you have no idea how awesome it is! Pattern matching allows you to do a lot of cool stuff like using the language itself as your router like this:\n\n```rust\npub fn router(ctx: Context) -> Response(ResponseData) {\n  case ctx.path {\n    [\"ping\"] -> handle_ping(ctx)\n    [\"@me\"] | [\"me\"] -> auth.me(ctx)\n    [\"auth\", ..path] ->\n      case path {\n        [\"sign-up\"] -> auth.sign_up(ctx)\n        [\"sign-in\"] -> auth.sign_in(ctx)\n      }\n    [\"notes\"] -> notes.handle_root(ctx)\n    [\"notes\", id] -> notes.handle_id(ctx, id)\n    _ -> respond.with_err(err: error.NotFound, errors: [])\n  }\n}\n\n// handlers/notes.gleam\npub fn handle_root(ctx: Context) -> Response(ResponseData) {\n  case ctx.method {\n    Get -> get_all(ctx)\n    Post -> create(ctx)\n    _ ->\n      respond.with_err(\n        err: error.MethodNotAllowed(method: ctx.method, path: ctx.path),\n        errors: [],\n      )\n  }\n}\n\npub fn handle_id(ctx: Context, note_id: String) -> Response(ResponseData) {\n  let id = int.parse(note_id)\n\n  case id {\n    Ok(id) ->\n      case ctx.method {\n        Get -> get_one(ctx, id)\n        Patch -> update(ctx, id)\n        Delete -> delete(ctx, id)\n        _ ->\n          respond.with_err(\n            err: error.MethodNotAllowed(method: ctx.method, path: ctx.path),\n            errors: [],\n          )\n      }\n    Error(_) ->\n      respond.with_err(\n        err: error.ClientError(\"Invalid note id, must be an integer\"),\n        errors: [],\n      )\n  }\n}\n```\n\nPattern matching in Gleam (and a lot of other languages) makes sure you perform all the checks you need to perform and it also makes sure you don't forget to handle all the cases you need to handle, it is also very easy to understand and use, I love it.\n\n## The Gleam community\n\nThis is the first time I have been in a Discord/Slack for a specific language so, these things are most definitely not specific to Gleam, but it has an awesome community of people constantly building things and willing to help you out, I have asked a few questions and I have gotten answers to all of them pretty fast, I have also seen a lot of people ask questions and get answers, it's a very friendly community and I am glad to be a part of it, albeit a very small part.\n\n# Conclusion\n\nI know I have really compressed the good parts but when you weigh both the good parts and the bad parts and/or you use Gleam for a while, you'll realise that the good parts are really good and the bad parts are not that bad, in fact, they're not even bad, they're just weird and they will either get better as the language matures and/or you will get used to them. I am not saying Gleam is perfect, it is not, but it is a very good language and I am sure it will only get better with time, I am definitely going to be using it for a while and I am sure I'll be writing more about it in the future.\n\nIf you want to learn more about Gleam, you can check out the [official docs](https://gleam.run/) and the [language tour](https://gleam.run/book/), they're both very good and will get you started in no time. If you are interested in the project I have been building with Gleam, you can check it out [here](https://github.com/aosasona/noht.gleam)\n\nI hope you enjoyed reading this article, I'll see you in the next one, you can always give me a shout on [Twitter](https://twitter.com/trulyao) if you have any questions, suggestions, corrections or just want to say hi :)\n\n> I am not a Gleam expert by any means, I have only been using it for about two weeks now so, many of the code examples here might not be the best way to do things, I am still learning and I am sure I'll get better with time, so please, if you see anything that can be improved, let me know, I'll be happy to learn from you.\n","image":"https://og.trulyao.dev/api/v1/images/trulyao/preview?variant=blog&style=blog&size=medium&vars=title%3AA+gleamy+exploration%2Cdate%3ASep+25+2023","date_published":"2023-09-25T00:00:00.000Z"},{"id":"arguments.mdx","title":"Arguments","url":"https://trulyao.dev/posts/arguments","tags":["rant","programming"],"summary":"An argument about arguments...","content_text":"\nFunctions; they're everywhere, and arguments; they're everywhere; especially Twitter, sorry, not that type of argument,\nwe're talking about function arguments. If you have no idea what I am talking about, check out [this\nlink](https://www.geeksforgeeks.org/function-arguments-in-golang/).\n\nAt some point in your life, like me, you have written or still write functions with this type of signature:\n\n```go\nfunc SaveUser(firstName string, lastName string, email string) error {\n...\n}\n```\n\nIn this case, `SaveUser` is most likely some sort of wrapper around a database operation which made sense to you since you would only have to change the code in there once if you switched your database or something, no pressure. You went on with this, \"it works\", and few weeks later, you realized you wanted to save ages too, so you created the database migration, opened up your `xx/x.go` file and added one more argument because why not? It seemed like the easy thing to do, you pushed the code, it went live, no pressure again.\n\nSix weeks later, you wanted to save phone numbers, flags for account verification (`is_verified`, or something else), `last_active` and probably usernames because haha, just like YouTube, you didn't think of that. Your app has been gaining users faster than you thought it would and you have to push out changes FASSSSTTTTTTTTT , so you did the same thing again and now your function looks like this:\n\n```go\nfunc SaveUser(firstName string, lastName string, email string, age int, phoneNumber int, isVerified bool, lastActive\nstring) error {\n...\n}\n```\n\nYour function call now looks even worse and oh dear, you know where this is heading. Naive you, it worked so you just left it that way not knowing it'll come back to bite you in the ass. Four months later, you now have over 100 files and thousands of lines of code and tens or hundreds of other functions with a similar signature, you have also now hired an assistant who has no choice but to use some of those functions you wrote and both of you now have to look at a bunch of files just to know what exactly a function is taking in and even you are\nfrustrated.\n\nI have done this, you have done this and in most cases, we have not been able to go back to fix this. Why am I talking about this? Well, I did write a function months ago that did this sort of thing and it didn't seem so bad at the time since it was a typed language (Typescript) and your IDE would help out (gosh, WTF was I on?), I did not realise I had committed this gruesome error until I had to work with a particular cURL wrapper function in a 10-year old codebase and when I felt the frustration of looking at a piece of function call that looked like this:\n\n```php\n$result = XYZ::cURL(null, \"url.com\", true, null, null, \"POST\", null, CURL_...);\n```\n\nI felt sorry for whoever was working with that piece of horrible code I wrote then, they must have PTSD now, I absolutely felt like $hit but I can't go back to fix it now, I left the project already and all I could do was consciously make sure I didn't write anything like this ever. This piece of code was probably written at a time when they needed to do things fast but sadly, as in any fast-paced development environment, no one went back to fix that particular function and there are a lot of other things wrong with this function call, let's talk about the obvious prominent ones.\n\n> Note: the snippet above was not the actual code, this is just to give you an idea of what the call looked like.\n\nPassing NULL or booleans (without or even with any sort of context) into any function is a very BAD idea, it probably suggests you are acting on that piece of data and doing different things in your function's body based on that value and at that point, your function is probably doing two different things and has broken the [Single Responsibility Principle](https://stackify.com/solid-design-principles/), tsk, not great. If you ever run into this piece of code in a codebase and you have never heard of 'curl', would you even have an idea what it does? You're probably thinking _\"Oh it's because it's PHP\"_, zip it! Bad code like this can be written in any language. Even worse, do you think you would be able to use this function safely or confidently without having to toggle between files where it's been used, where you are trying to use it and the actual function declaration.\n\nA lot of languages have built-in ways to avoid this kind of code blasphemy. If you are thinking _\"Yeah yeah, Python has named arguments and kwargs\"_, please bury that thought, you would still have about 10 arguments going in, naming them doesn't make it better, **Clean Code by Robert C. Martin** suggests a developer should try to limit function arguments to just two or at most three. While you don't NEED to follow everything the book says, we can all agree that function calls with 10 arguments would become quite frustrating to read or use; named or not. The way I have decided to go around this is using associative arrays in PHP, objects in JS/TS & structs in Golang, there are probably better ways to do this but this still makes your code a bit more readable (if you have other ways you handle this, let me know; @ me on Twitter or leave a comment under any post where I shared this article). Now you can have something like this that would be much easier to use and understand.\n\n```go\ntype User struct {\n\tFirstName \tstring\n\tLastName\tstring\n\tEmail\t\tstring\n\tAge\t\t\tint\n\tPhoneNumber\tint\n\tIsVerified\tbool\n\tLastActive\tstring\n}\n\nfunc SaveUser(user User) error {\n...\n}\n```\n\nWhilst this is probably not EXACTLY how you would write it, I think we can agree this is easier to read and actually reuse since we now have a dedicated struct that can be used anywhere in your codebase to define a user. At some point every developer's done something as seemingly obvious or stupid like this but that's part of the job; learning on the job, it won't be the last mistake you make either, finding better ways to do things is important.\n\n> NOTE: I am in no way recommending a certain way to do things or code, this is only my opinion and I am happy to hear what you think too.\n\nThat's all I have to rant about today, have a great weekend or week (whenever you're reading this) :)\n","image":"https://og.trulyao.dev/api/v1/images/trulyao/preview?variant=blog&style=blog&size=medium&vars=title%3AArguments%2Cdate%3AOct+22+2022","date_published":"2022-10-22T00:00:00.000Z"},{"id":"computers-are-fast.mdx","title":"Did you forget? Computers are fast.","url":"https://trulyao.dev/posts/computers-are-fast","tags":["rant","software development"],"summary":"It seems people have forgotten just how fast and powerful computers are and how much they keep evolving, let's remind ourselves.","content_text":"\n# What triggered this article\nI logged on to ~~Twitter~~ X two days ago and I saw a thread with a discussion that reminded me of something I have been thinking about a lot lately, it was [this tweet](https://x.com/mattpocockuk/status/1814247057286189102). Hang on, hang on, let me explain before you burn me at the stake for disagreeing that it is neglible. Actually, I do agree that it is neglible in the CI case (which is what they are referring to here), I am not here to argue against that. The amount of time it would take Github Actions to start and get to your actual job would eat up any gains anyway; 200ms is indeed neglible here for **most** people.\n\n![Matt Pocock's Tweet](/images/matt-p-node-tweet.jpg)\n\nI did not grow up with *fast* software or in \"the early days\" of computers by any means, I grew up in the early iPhone and Windows XP era which wasn't so long ago, but the more I interact with the tech community and use newer software (even the ones I had written myself and perhaps even will still write in the next couple of months), the more I feel like we have forgotten how powerful computers are and how fast they really can be!\n\n# My history with computers\n\n*(Warning: you may want to skip to the next section, this could be its own article)*\n\nLet me give you a bit more context regarding my history with computers. I grew up around computers and I was very interested in them because my Mom worked on them a lot *(fun fact: she even had one of those electric typewriters from back in the day)*, my first Desktop encounter was with **Windows Vista** I believe, and then **Windows XP** when she setup our home computer, after I broke that computer somehow during one of my explorations, it had to be repaired and the computer guy put the latest and greatest at the time on it; **Windows 7**... and I hated it, I can't tell you why but I missed XP, the computer just seemed better BEFORE the repair.\n\nI remember Windows being less bloated and annoying than I find it now but still it was slow, and to be fair, the hardware was really bad too; Pentium 3/4 CPU I think, no GPU, about 512MB of RAM I think (at most 2GB; I can't remember but I do remember it was really really bad). There was no Wi-Fi card and we didn't have Ethernet; I had to use wired tethering on a very cheap data plan I could buy with my lunch money just to quickly run whatever search I had typed into IE beforehand and at least view some things before it ran out, you probably get the picture. It was around this time I saw something called \"HTML\" in Microsoft Word's \"Save As\" menu, double-clicking this file would open it in the browser, it was strange and I was curious.\n\nThis was my first encounter with \"programming\"; I didn't play Minecraft, hell I did not even know what it was, I sort of knew what YouTube was but come on, I was trying to get a few clicks out of my 50MB data plan, I wasn't about to watch a video; I don't think I would have known what to search for either. I got my first feature phone with a QWERTY keyboard around this time from my Dad (his old device); a Nokia Asha 200 (yes, you're getting old too), I became a tinkerer, I did things to that device, hell, I even got WhatsApp on it back then, it was very difficult, although I do recall that the newer model; the 205, supported it out of the box, it was fun digging into these things until I broke them.\n\n> At this stage, I only really knew about using Notepad and uh... Microsoft Word to build HTML pages, I played with MS Access to connect these things together and save data the way websites I had used must have done it but never really got it; due to the nature of my Mom's work, we had all the MS suite apps installed - Publisher, Excel, Word etc and they ironically had a thing to do with my interests in graphics and coding as I grew up.\n\nRe-telling the full history will be difficult and lengthy but before and few years around this period, I used a few hand-me-down devices before they \"gave up\": the Nokia C1-01 from my brother who had just graduted high school, a broken HTC Desire from my sister (my first android device... which could only stay on for about an hour before heating itself to death), a Blackberry from one of her friends (also broken, but I didn't mind, I got to explore these things), a Samsung Galaxy S3 from my Uncle (also broken, but also didn't care), an iPod Touch 6G from another Uncle (this one was only about 6 years ago, I still have it but the screen is shattered; the only device I had physically broken myself) and a Samsung Galaxy A3 (2015); the timeline is all messy but frankly, I can't remember myself, I probably skipped one or two. Now, that last one is what got me to spend more time on XDA Forums, I was installing custom ROMs, and rooting devices and whatever stupid kid me could think of (I did break a cheap smartwatch I had and stayed up all night finding a compatible ROM; the best I could find permanently killed the camera).\n\nI also finally learned I couldn't just make pages in MS Word/Notepad (not Notepad++) and have the HTML put the data in the database as I wanted to (what a stupid kid), I discovered PHP, and frankly, I wasn't sure I was cut out for it at first. Our school had started using Computer-Based Tests and the URL caught my attention, specifically the end of it; \".php\". Then I went on to download PDFs since again, I couldn't stay online at a stretch, I purchased midnight plans to download WAMP and then Notepad++ which I could copy from my phone (which uses less data) to the PC via a cable, are you still following? Good, we're almsost done. Okay, back to computers, I don't remember what I won or who gave me the money but I badgered my Mom to help me get one of those small HP student laptops with it, it was my first laptop and I could write code on it, it wasn't fast by any means, it also had Windows 7 but this time I had a whooping 4GB of RAM!.\n\n> Oh, I still hated Windows 7 by the way, it just was significantly slower than the Vista and XP I grew up with.\n\nI got into mods; login screen mods, skin and transformation packs, game mods (I could sort of run GTA: Vice City again), tinkering with the registry and other things, I was working with PHP more, all I could think of was code. Then our house got burgled, that laptop was the sole thing that was stolen and it crushed my soul, I went back to using the painfully slow home PC, I did a bunch of my programming there and that hard disk still holds my earliest projects from long before I discovered GitHub. My Dad got his own laptop a couple of months later, and he let me use it under certain conditions and... uh I broke it and learned what a ransomware was the hard way, I installed a ransomware while trying to find something and it locked me out of all files and apps before asking me to pay this $900 to get my files back, they stole my browser data (this I am sure about because my accounts were accessed), and a lot of things I will never know about; it was a shit show and I lost all files.\n\nFast forward to my final year in high school, I realised I could make some money doing this thing I was already spending all my free time doing, so I got into freelancing, I did get taken advantage of but I did learn a lot and made enough to buy my first laptop by myself! It was shitty but it was also my own money. I had a year after high school, I leaned more into programming, got my first Mac with some help. It was the perfect computer; at least I thought it was, it was used of course but it was fast, it was thinner than anything else I had used up till that point. It finally died a few years ago in 2021/22 due to some hardware issues that caused it to overheat and it wasn't worth repairing, I gave almost what I had at the time to get the base model 14\" M1 Pro, it was worth it to me since it seemed like a machine I would use for years and I was right; I am writing this article on it right now!\n\n\n# A huge misconception\n\nI gave you all that history to give you more context; I did not grow up with well-speced computers or mobile devices so I am not \"spoilt\" by having decently fast hardware or money to buy the best software, but I still was able to learn how to code, figure out how to use GIMP for graphics editing and edit videos with VSDC which was the only editor I could run at a usable performance. Now those things were painfully slow, the apps would take forever to start, they would freeze, windows would crash yada yada yada and I had to learn to wait. So, this article isn't coming from a place of being \"spoilt\" or unreasonable.\n\nBut it is 2024, my Apple Watch has more memory than the PC I used for most of my life, my iPad has nearly the same chip as my laptop that never seems to slow down regardless of what I throw at it, yet... I stil have to wait for a lot of things, a lot.\n\nFor a long while, a large part of my life really, I went on thinking this was normal, that computers just weren't powerful enough, that apps had to take a while to start because they were doing a lot, web apps **had** to be slow of course, they have to query data and do this and that; there was simply no getting around it! I didn't care much about the performance of code even I wrote because \"these things happen!\"... oh what a fool I had been.\n\n# It is not normal\n\nA few years ago, I heard [Jonathan Blow](https://www.youtube.com/watch?v=ZSRHeXYDLko) and someone else before that talk about performance and as I listened, it began to occur to me; no, these things aren't normal! I learned more about how things actually worked and I realised even more that these things are most definitely not normal. While kid-me attributed the insane slowdown that came from going from Windows Vista up to Windows 7 to it just being \"normal\" since we had an older piece of hardware and the OS was newer (which he was partially right about), older me was starting to realise how bad the slowdown really was and that it also definitely wasn't normal!\n\nThinking about how much work even the Raspberry Pi can do per second and that Postman/Hoppscotch still takes about 15 seconds to startup, or that Jetbrains IDEs can't seemingly cope with my own not-so-large projects, it pisses me off, I know that it is not normal, these things could be faster, my hardware is fast enough to render videos in Final Cut Pro in almost no time yet nothing seems to be able to take less than double-digit seconds to open (even if they are fully local). I have a 5G network with decent upload and download speeds and I still cannot navigate Github in a sane amount of time and when it does work, 20% of the time, the new UI is broken somehow. THIS IS NOT SUPPOSED TO BE NORMAL.\n\nI cannot stand waiting around for things anymore, this is mainly why I have moved to doing a lot of my work in the terminal these days; both at work and at home, it is just significantly more responsive to me.\n\n\n# Four seconds is a lot\n\nSomeone in that thread mentioned 4 seconds not being a lot of time and in that context, again, I agree, this is not a dig at them or even directly related to that conversation<sup>[1]</sup> but 4 seconds is a lot for a computer these days. For scale, 4 seconds is 4,000 milliseconds, and for those games you'd love to maintain 120FPS on your expensive device that probably can't even run Discord and VSCode at the same time, the game developers have had to write code that repaints and redraws in less than 8 (EIGHT) milliseconds! In 4 seconds, your screen and that game can literally redraw its elements (sometimes the whole screen) hundreds of times while your fancy little app is still bouncing in my MacOS dock.\n\n> [1] I rarely even write things that run on Node.js or JS in general these days, so, I don't particularly care about that discussion.\n\nI don't care about what you are going to say about the network boundary or whatever, yes, it does exist, but we both know that is not why our code is painfully slow, we both know most games are multiplayer these days and are not breaking the laws of physics to stay (or at least appear to be) performant even across the network; so it is possible to be faster in a lot of places and sometimes with a little bit of care.\n\nAs programmers, especially web programmers, it feels like we have really forgotten how much work computers can do in very little time, we are so busy putting layers upon layers of abstraction and straying further from the foundation while convincing ourselves it's a worthy trade-off for a better DX or \"developer velocity\"; no, good software takes time and effort, just ask those game devs. I am not just ignoring the business needs, I get it, code has to be shot out fast to please the company and your boss, still, a little bit of care won't hurt (>_-)\n\nThat horrible Windows Explorer can be [this](https://x.com/vkrajacic/status/1779178723465441705) [fast](https://x.com/vkrajacic/status/1729944069437042874), no, it is not doing any more work that should make it slow by default. Visual Studio doesn't have to take forever to startup or launch your debugger (I rage-quit everytime I launch Visual Studio), it can be [this](https://x.com/ryanjfleury/status/1801685216001724548?s=46&t=oZkX14dN7BKTYc0mRw0ryg) fast, you can find a lot more examples following these people and more people like [Casey](https://x.com/cmuratori), [Dax](https://x.com/thdxr), [Tsoding](https://x.com/tsoding) and a lot of other people I can't recall right now.\n\n# \"Ego check\"\n\nLook, the whole point of this article isn't to blame you, not alone at least, I blame me too. I am not asking you to do \"premature optimisation\" (which is usually people being too lazy to just do the darn thing), I just want you to NOT be okay with things being slow.\n\nI want you to keep it at the back of your mind that your bank's app does not need 25 seconds to start up regardless of whatever excuses you have told yourself about security and whatnot; there is nothing you can do about it personally but I want you to remember this should **NOT** be the norm.\n\nI want you to remember that three thousand lines of text is smaller than a megabyte and should not cause your editor to crawl to a halt, that isn't normal, it's how you end up with [this sluggish nonsense](https://github.com/aosasona/jsorm) that I am ashamed of even if it is a demo or whatever. I am pointing to myself and you to show you I am not in denial about this, neither should you be. Next time you think it is okay for something to take long or eat up a bunch of memory, pinch yourself and ask if that's actually true, and more often than not, it isn't.\n\nI want you to take a step back, slow down, be more intentional about every line of code you put out into the world and learn how and why things works, I have been on years-long mission to do this too, it will greatly help you understand and deliver better software, you can do better. Yes, it is hard, but you really should care, don't contribute to the landfill of bad software.\n\n# More things to look at\n- [Performance Excuses Debunked](https://youtu.be/x2EOOJg8FkA?si=JD4SI6ZjUdp6JEMm)\n- [Clean Code, Horrible Performance](https://youtu.be/tD5NrevFtbU?si=64kBJIX9LD-wI_lv)\n- [People expect technology to suck because it actually sucks](https://tonsky.me/blog/tech-sucks/)\n- [The art of living with broken things](https://youtu.be/dyfC37iTDcQ?si=-667_OT3y_MT0DK4)\n- [#FAIL](https://www.youtube.com/watch?v=6xrGo1IIB3w)\n- [It is fast or it is wrong](https://tonsky.me/blog/slow-wrong/)\n- [Software disenchantment](https://tonsky.me/blog/disenchantment/)\n- [Why modern software is slow](https://randomascii.wordpress.com/2022/09/29/why-modern-software-is-slow-windows-voice-recorder/)\n- [Leveraging Rust and the GPU to render user interfaces at 120 FPS](https://zed.dev/blog/videogame)\n- [(PODCAST) Maybe Programmers Are Just Bad ft. Casey Muratori](https://www.youtube.com/watch?v=qqUgl6pFx8Q)\n","image":"https://og.trulyao.dev/api/v1/images/trulyao/preview?variant=blog&style=blog&size=medium&vars=title%3ADid+you+forget%3F+Computers+are+fast.%2Cdate%3AJul+25+2024","date_published":"2024-07-25T00:00:00.000Z"},{"id":"good-enough.mdx","title":"Good Enough","url":"https://trulyao.dev/posts/good-enough","tags":["rant","life update"],"summary":"You probably know a lot about the impostor syndrome, but this article isn't really about it. Sorry to steer you in the wrong direction. This is about not being good enough, but also not what you expect.","content_text":"\nHey there again, buddy! The initial intention for this article, which I started writing about a month or two ago, was for me to rant about how I feel I am not good enough. But instead of doing that, let's talk about growth and positivity, why? IDK, do you want to be sad?\n\nMany people I know or who frequently come into contact with think I am an excellent programmer and know a lot based on my GitHub or the _crap_ I post on Twitter or LinkedIn, but I don't feel that way, I know I am not, and I frequently tell people so. And during the past few months, I've felt a strong urge to improve significantly in my field — not so I can live up to the hype, but rather for MYSELF. I recently started to realize that being a developer involves more than just writing code, and I made the decision to **NEVER SETTLE** with being \"good enough\".\n\nDespite appearances, I have received a large number of rejections this year since beginning to apply for 'office'/full-time positions (over a hundred at this point, I lost count), primarily due to my visa cap, but that is not the point. I received a number of interviews whilst I was re-building my CV, I passed several, and even signed contracts only to have them revoked because my visa only allowed me to work 20 hours per week (even contract roles, sucks right?) And when I finally decided to just state that on my CV, it became even more difficult to get any interviews; and I was slowly burning through my savings, so at this point I was tired, and all I had left was to just build myself even more and resort to menial jobs because getting freelance gigs is harder nowadays, if you know what I mean, and not quite reliable for me when I have thousands of £ to pay in tuition fees and other things, only a handful of my friends; [@\\_frokes](https://twitter.com/_frokes) and [@ipariola](https://twitter.com/ipariola) knew about this frustration, often supported me and thanks a lot to them too. **Spoiler alert**: there is no \"I started a role at Amazon\" or \"I 100x-ed my income\" at the end of this article.\n\nIn between all of this, I've taken a few freelance jobs through referrals, but the feeling of not being 'good' enough lingered in my mind no matter how much I seemed to learn or practice. It wasn't so much about learning more frameworks for me as it was about becoming more of an engineer and less of a 'developer'; which entails having much more technical details and understanding (again, this isn't just about the title), so I continued pushing. I bought a handful of Udemy courses, completed some of them, but they didn't feel in-depth enough, I didn't feel like I understood significantly more, and I was hungry for knowledge. Asking for help was harder(er) for me months ago because I wasn't used to it; probably just like you, and I was even more terrified of being ignored. If you read my Twitter thread about never settling, you'd know I was a \"lone wolf\" for a very long time and I didn't meet or know any other developer for over 3 years until last year when I got on Twitter (for context; the first time I heard about Node.js was about 8 months ago, shocking isn't it?), so I'll admit I was living in a bubble for the last 3 years before that with what I thought I knew, it felt like I wasted a lot of time because I did but it wasn't too late either.\n\nYou undoubtedly understand my desire to create a community for all 'Techies' at [Frikax](https://www.frikax.net); a community, or at least the people I've met, has truly helped me grow, and I believe it might benefit a lot of other people as well. I finally understood the importance of being 'social' at the time, so I joined LinkedIn, reconnected to Facebook, became more involved on Twitter, and met a lot of wonderful individuals, of whom I am fairly convinced you are one. In April/May, I began to overcome my fear of being ignored and simply reached out to a few of extremely senior engineers whose work I admired and respected, and a few of them did respond and offer to help out, which they did, but understandably, most of them were very busy with other things and I wasn't offended either. I would contact several of them when I needed to learn something or know how they did things, and they would respond, explain, and point me to useful links, videos, or concepts. You might be thinking, \\\"Why not just Google those things yourself?\\\" Sure, I could, but I've discovered that I learn best with and from other people, thus you'll find me 'stalking' a lot of repos and reading code there. Huge thanks to [@pipedev](https://twitter.com/pipe_dev) and [@lanreadelowo](https://twitter.com/lanreadelowo); I probably ask them more than I should, but they are extremely helpful still.\n\nThe other crucial aspect was applying the new skills and \"advanced\" concepts I had learned, but since I wasn't going to get a company job or any major job that could really allow me to gain the experience, I did what any truly knowledge-hungry developer would do: I dove into old projects and rebuilt them, built even more new ones, and did as much coding as I did reading and watching videos in the unhealthiest way possible, and it started to tell on me so I also did learn to take short breaks (this is VERY essential, not just to prevent a burn-out but your health is also very important). Concurrently, I was working to overcome my urge to have everything turn out flawlessly (but obviously as a programmer or anyone reading this, you know things aren't flawless) and avoid criticism while I was building these things. And at some point, I just stopped caring and started posting everything I'd worked on, fully anticipating the \"You shouldn't have done this?\" and \"Why do it like this?\" comments no matter how 'attacking' they seemed (some people are naturally just trolls, I mostly not reply to those) - BLII.\n\nI've spent the last few months learning not just about writing code but also about deeper concepts, tools, and operating systems, and I finally feel like I'm starting to understand how to be a good developer and what I'm doing. Eventually, I'll understand how to approach things like an engineer, but in the meantime, I'll keep building, learning, iterating, and improving, and most likely writing articles at [aerdeets.com](https://www.aerdeets.com) to share what I've learned and to help beginners; explaining as best I can. I currently work full-time/part-time, so I don't have much time to write articles on a regular basis, but I enjoy working there too; the staff is fantastic!. I also do get a bunch of recruiter messages on LinkedIn and Twitter, but at some point, you have to acknowledge you don't have to take on every single job at **ONCE**to 'gain experience' or 'make money' (you'll most likely end up sucking at a lot of them even if you don't have deadlines). Invest in your learning as well, and understand your health is just as important.\n\nThat's all I've got for now; you can grow on your own, but it's easier, better and faster to 'scale' when you're with other people; don't forget that. Enjoy your week or weekend whenever you're reading this, remember to not just 'write code' or 'just design' or whatever you do, you could get a good life not knowing anything really, but you'll agree it feels good to be actually pretty good at what you do, and if you also did fall off the wagon, it's not too late to get back on it either. Ciao :)\n","image":"https://og.trulyao.dev/api/v1/images/trulyao/preview?variant=blog&style=blog&size=medium&vars=title%3AGood+Enough%2Cdate%3AJul+15+2022","date_published":"2022-07-15T00:00:00.000Z"},{"id":"introducing-robin.mdx","title":"Introducing robin","url":"https://trulyao.dev/posts/introducing-robin","tags":["go","typescript","web development"],"summary":"Or as an alternative title: Introducing Robin - a new way to build type-safe full-stack applications in Go!","content_text":"\n# Why I use Go\n\n> Bear with me, I know this article is supposed to be introducing a new library but you see a lot of Go and general programming language stuff, just hang on, I promise it will make sense!\n\nFor the past few months; on and off, I have been working on something that is mostly how **I** want to build web services in Go. I have certain... reservations about Go and I think anyone who's ever mentioned Go in a conversation with me at this point knows that but why do I still bother with it? Why have I chosen to write most of my web apps and a [lot](https://github.com/aosasona/vanity) of [other](https://github.com/aosasona/lito) things [in](https://github.com/aosasona/bore) and [for](https://github.com/aosasona/mirror) it?\n\nWell, it's simple. I spent a good chunk of last year evaluating a lot of languages from different \"genres\" like [Rust](https://https://www.rust-lang.org), [Gleam](https://gleam.run), [Crystal](https://crystal-lang.org/), [Inko](https://inko-lang.org/), [Elixir](https://elixir-lang.org), [Erlang](https://erlang.org), [Zig](https://ziglang.org) and a few others I don't even remember. There was one I kept coming back to because while it was annoying, it had most of what I needed - which is what really matters - and that was Go. That's not to say the other ones are bad by any means, I had a ton of fun writing most of the ones I did; especially Gleam and Rust! I even maintain a few packages in the Gleam ecosystem even though I no longer write Gleam (momentarily; I haven't needed it - see what I was saying?).\n\nFor most things I write, I _could_ use Rust, it is the language that will come up in nearly every comparison to Go, but after writing [a bit of Rust](https://github.com/aosasona/chimney) and sometimes failing to (I have a few projects I ended up rewriting _FROM_ Rust), I have come to realize it was just a tad too much for me; the compile times, the cryptic error messages when things fail catastrophically (although a lot of things in Rust have nicer error messages than anything I have ever used, until you break a Trait or similar), the \"rituals\" blah blah blah. I get it, I truly get what Rust offers me, I talk about it a lot, I love Rust... but I don't want to write a web backend in Rust, sorry. I have a few requirements for most of the things I work on personally:\n\n- Producing a \"lean\" static binary (this rules out a lot of languages already; PHP, JS, Python etc)\n- **Concurrency (via green threads, coroutines, goroutines, or similar)**: I like to keep applications as self-contained as possible which means I will most likely eventually need to run periodic, concurrent and/or background jobs\n- Low resource usage (CPU and memory) by default\n- **Real** compile-time (and runtime) type safety (i.e not JavaScript with an expensive linter)\n- **Fast compilation**: This is essential for quick iteration!\n- Good performance out of the box\n- Automatic memory management; obviously!\n- Portability\n\nThere are probably a bit more I have missed but as you might have already noticed; it's 2024 and most langauges can already do these things one way or another (you can even make desktop apps with Laravel now - **please, for the love of everything, don't**) but the major picture here is not those things _seperately_ but as a _single_ \"package\" - a combination of all - and this is where Go shines. Sure, I can find a lot of things to complain about but in the end, it ticks all these boxes mostly (I would not consider Go fully type-safe without nil safety but that is just me :/), it falls short at a lot with its design that has seemingly chosen to ignore every advancement in language design to be able to claim \"simplicity\" but, and this is the important bit, it works for most things!\n\n# Attempt #1, or \"The origin\"\n\nAround the time I was working on [a side project](https://github.com/aosasona/sidekyk) a while back, I worked on a thing called [`gots`](https://github.com/aosasona/gots) (now known as [`mirror`](https://github.com/aosasona/mirror)) which could take your Go types and use that to spit out fairly decent Typescript type definitions. `gots` was used to generate request and response schema types from the same Go types used in the HTTP handlers (for JSON unmarshalling, and related schema things) to provide some form of type-safety across the server &amp; client boundary and additionally, some near-instant feedback; if I changed an exported type on the server, the corresponding Typescript definition would change and I would get a compile error, nice!\n\nThis worked fairly okay but if you know anything about writing web applications; especially the frontend, getting the types is a very small (but still rather consequential) slice when it comes to writing proper code to communicate with the backend, I still had to recall the various HTTP methods, paths, write some more code to handle and filter the different errors properly, have duplicate/similar calls everywhere even though I had made a common abstraction to make it easier etc, you can find most of that code [here](https://github.com/aosasona/sidekyk/tree/master/mobile/src/lib/requests). It did not stop with that project, I would start new things and have to go through the same setup over and over again regardless of the boilerplate I already had; you just cannot cover all cases.\n\n> And to be clear, I am also aware Robin will NOT cover all cases either! Also, yes I know you can generate OpenAPI schemas and then use codegen tools and yada yada yada but there's still that friction there for me.\n\nIt was overly repetitive, more human-error prone, required knowing which types to put in which places, what endpoints took what payload and what the valid endpoints even were (yes, again, I know you can just document your stuff with Swagger or OpenAPI or \\<insert thing here\\>; they still require lots of manual effort; leave me alone, stop reading now), it kind of got... tiring. I like to find things that are just unnecessarily complex and attempt to \"automate\" them away for myself at least even if it doesn't work for anyone else (that's one of the fun parts of this job!) and in this case, I just want to work on my dumb little side projects with less friction and not care about those things. And, I think we both know I wasn't going to switch to writing TypeScript on the server for everything just to use tRPC; come on, there was only one _sane_ thing to do; write my own!\n\n> Am I being sarcastic about the sane bit? You'll never know.\n>\n> Also, yes, sometimes, I end up realising these automations or \"abstractions\" were wrong and I am not afraid of being wrong there; at least I had fun working on these things.\n\n# But, first...\n\nWhether I could or should do it was not the question anymore, I already knew I could generate valid and fairly accurate Typescript types, I was already doing it, but first, I had to rewrite something; as usual. The original version of `gots` was clearly thrown together in a weekend or so, a few months ago, I moved it to `mirror` where it now currently lives, I have rewritten it to be more customisable, more accurate, handle more edge cases (like [embedded structs](https://github.com/aosasona/gots/issues/3)) gracefully, to support more languages (although, I have only implemented Typescript support) and better code overall.\n\nSo, technically, robin can support the same languages mirror can under the hood and the rewrite was a very big part of that, it also introduced hooks which gave more control to the user; allowing you to modify and even add fields or whole types that never existed before on the go (robin makes heavy use of this)! The parts were also more decoupled for users (A.K.A me) to build on independently if needed, this was a crucial part of the rewrite as it enabled you to now bring in your own parser, plugin in your own custom langauge support etc.\n\n# Fine, I will do it myself\n\nWhile working on mirror and slowly working on \"designing\" robin in the \"get it to work\" phase, I came across [another project](https://github.com/blue-rpc/bluerpc) with similar goals and thought I wouldn't have to do it anymore but unfortunately, it still wasn't a good fit for me. I wanted something that was mainly \"just Go\"; you write normal Go functions, you return normal Go types, you use normal Go routing libraries or the built-in HTTP server etc. but this was clearly designed to take over everything which I didn't like, I knew that whatever I was going to make obviously wasn't going to be the answer to all the prayers but I needed it to stil give the user a lot of freedom while still being somewhat strict where required.\n\nI despise being forced into a certain way of doing something by a \"library\", it is why most things I work on end up with some customisation options where possible (and as much as time permits), I knew I still had to do it myself. So, I started working on it slowly, it got to a \"working point\" months ago when I initially [shared it](https://x.com/trulyao/status/1774227849374839064?s=46&t=oZkX14dN7BKTYc0mRw0ryg), unfortunately, I had to pause to finish the Mirror rewrite and work on a few things in between.\n\nI planned to use it to build my dissertation project (as the ultimate field test, let's hope I don't regret this - but so far, it has been a decent experience), so I pushed towards an initial release and we're there now! There is still a lot to figure out, there is still a very long way to go but right now, you can use it in your side projects if you are willing to deal with breaking changes every few weeks and nothing but a living documentation through me and bad examples. The best part is, the more I use it, the more I find things that could be improved or added and that is the whole point of this initial release.\n\nOf course, there are a few drawbacks with the biggest being a probable performance dip (although, not much I hope) due to how it has to work internally, but there's also missing documentation since I haven't had the time to do that, most of the _big_ features are still in the planned phase, it currently only supports Typescript only (no JSDoc) etc. but robin is currently usable for smaller and even bigger (personal) projects, it has built-in client generation so you don't have to install another NPM package for the client, it allows you to handle errors based on whatever criteria you want, and in my opinion, allows you to move fast(-er)!\n\n# What does it look like?\n\nBuilding with robin currently requires using Typescript and you can find some basic examples [here](https://github.com/aosasona/robin/tree/master/examples) and a more app-like demo [here](https://github.com/aosasona/robin-todo), but of course, I still need to show you some code here, don't I?\n\n## The server\n\nThe server code is relatively straight-forward, you need to create a robin instance, add your procedures and attach it to a single route (don't worry, it won't force you to return `200 OK` for all responses like another thing we shall not name, we actually allow proper status codes!) or you can also use the built-in `Serve` method as shown in this example:\n\n```go\npackage main\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"time\"\n\n\t\"go.trulyao.dev/robin\"\n)\n\ntype Todo struct {\n\tTitle     string    `json:\"title\"`\n\tCompleted bool      `json:\"completed\"`\n\tCreatedAt time.Time `json:\"created_at,omitempty\"`\n}\n\nfunc main() {\n\tr, err := robin.New(robin.Options{\n\t  // You get to decide some bits here, you an choose to just generate the schema and not the bindings if you want too\n\t\tCodegenOptions: robin.CodegenOptions{\n\t\t\tPath:             \".\",\n\t\t\tGenerateBindings: true,\n\t\t\tThrowOnError:     true,\n\t\t\tUseUnionResult:   true,\n\t\t},\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create a new Robin instance: %s\", err)\n\t}\n\n\ti, err := r.\n\t\tAdd(robin.Query(\"ping\", ping)).\n\t\tAdd(robin.Query(\"fail\", fail)).\n\t\tAdd(robin.Query(\"todos.list\", listTodos)).\n\t\tAdd(robin.Mutation(\"todos.create\", createTodo)).\n\t\tBuild()\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to build Robin instance: %s\", err)\n\t}\n\n\tif err := i.Export(); err != nil {\n\t\tlog.Fatalf(\"Failed to export client: %s\", err)\n\t}\n\n\tif err := i.Serve(robin.ServeOptions{Port: 8060, Route: \"/\"}); err != nil {\n\t\tlog.Fatalf(\"Failed to serve Robin instance: %s\", err)\n\t\treturn\n\t}\n}\n\nfunc ping(ctx *robin.Context, _ robin.Void) (string, error) {\n\treturn \"pong\", nil\n}\n\nfunc listTodos(ctx *robin.Context, _ robin.Void) ([]Todo, error) {\n\treturn []Todo{\n\t\t{\"Hello world!\", false, time.Now()},\n\t\t{\"Hello world again!\", true, time.Now()},\n\t}, nil\n}\n\nfunc createTodo(ctx *robin.Context, todo Todo) (Todo, error) {\n\ttodo.CreatedAt = time.Now()\n\treturn todo, nil\n}\n\n// Yes, you can just return normal errors!\nfunc fail(ctx *robin.Context, _ robin.Void) (robin.Void, error) {\n\treturn robin.Void{}, errors.New(\"This is a procedure error!\")\n}\n```\n\nAs you can see, your procedure functions are mostly just normal functions that take in a robin context (contains the original request and response structs and a few convenient functions), your automatically un-marshalled payload and returns whatever response you want as a basic Go type or an error, that's it. Robin also allows providing your own error handler function to filter out what to send or not send to the user like this:\n\n```go\n// Your custom error type\ntype Error struct {\n\tMessage string\n\tCode    int\n}\n\nfunc (e Error) MarshalJSON() ([]byte, error) {\n\treturn []byte(e.Message), nil\n}\n\nfunc (e Error) Error() string {\n\treturn e.Message\n}\n\nfunc NewError(message string, code int) *Error {\n\treturn &Error{Message: message, Code: code}\n}\n\ntype SerializableCustomError struct {\n\tMessage string\n\tCode    int\n}\n\nfunc (s *SerializableCustomError) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(map[string]interface{}{\n\t\t\"message\": s.Message,\n\t\t\"code\":    s.Code,\n\t})\n}\n\nfunc errorHandler(err error) (robin.Serializable, int) {\n\tmessage := err.Error()\n\tcode := 500\n\n\tif e, ok := err.(Error); ok {\n\t\tcode = e.Code\n\t\tmessage = e.Message\n\t} else if e, ok := err.(robin.Error); ok {\n\t\tcode = e.Code\n\t\tmessage = \"Something went wrong\"\n\t\t// You can proceed to log internal errors here or similar\n\t}\n\n\treturn &SerializableCustomError{Message: message, Code: code}, code\n}\n\nfunc main() {\n\tr, err := robin.New(robin.Options{\n\t  // ...\n\t\tErrorHandler:    errorHandler, // Register the error handler\n\t\t// ...\n\t})\n\n\t// ...\n}\n```\n\nThis mirrors the same pattern I have used in previous Go applications where I only allow custom error types through as they are because I don't want any of the other ones getting out there to the user, I have seen horrible error handling practices out there, robin tries to help you avoid that easily; it usually took me a lot more code in past projects to implement this for each one, [here](https://github.com/aosasona/go-echo-boilerplate/blob/master/pkg/response/response.go) is a basic example from my now-deprecated boilerplate.\n\n> You are probably thinking \"this is too much magic\" and you'd probably be right, that's fine, you don't have to use robin, I am aware it won't work for everyone but I implore you to be a tad more open-minded, probably give it a shot and come back to me with your list of complaints that I will be happy to consider.\n>\n> Oh, yeah, there is [middleware support](https://github.com/aosasona/robin-todo/blob/974a022c2d46653abdf472e5165884989cc421c6/main.go#L92-L106) too.\n\n## The client\n\nCalling your procedures on the client side is fairly straight-forward and you can choose to use other libraries like [Tanstack Query](https://tanstack.com/query/latest) but the generated client comes with a fairly decent amount of error handling (with options you can choose from; including using result types or just throwing!), here's an example:\n\n```typescript\nimport Client, { RequestOpts } from \"./bindings\";\n\n// By default, the credentials mode is not set, so you have to bring in your own client function if you want to send cookies or similar - this might change in the future depending on demand\nexport function httpClient(url: string, opts?: RequestOpts): Promise<Response> {\n  return fetch(url, {\n    method: opts?.method || \"GET\",\n    headers: opts?.headers || {},\n    body: opts?.body || undefined,\n    credentials: \"include\",\n  });\n}\n\nconst client = Client.new({\n  endpoint: \"http://localhost:8081/_robin\",\n  clientFn: httpClient,\n});\n\nawait client.queries.ping();\n\nconst todos = await client.queries.todosList();\nconst newTodo = await client.mutations.todosCreate({\n  title: \"Buy milk\",\n  completed: false,\n});\n\nconsole.log(\"todos -> \", todos);\nconsole.log(\"newTodo -> \", newTodo);\n\n// This should throw since the generated client is set to throw on errors\nawait client.queries.fail();\n```\n\nThe client is fully typed and will provide appropriate types for the payload and return types. Turning off `ThrowOnError` and enabling the `UseUnionResult` option will force you to have an `ok` check before accessing any of either the `error` or `data` field, like this:\n\n```typescript\n// This will not throw but it wil now require a guarded access\nconst result = await client.queries.fail();\nif (result.ok) {\n  // The `result.data` field is now available\n}\n\nif (!result.ok) {\n  // The `result.error` field is now available too\n}\n```\n\nI understand this can be annoying to do in every single place so I have provided alternatives, you can simply choose to let it throw and have a wrapper function (or let something like Tanstack Query handle it) or turn off the `UseUnionResult` option and you'll get back a type like this which will still require optional chaining to access the fields but will no longer force you to do a the `ok` check:\n\n```typescript\ntype Result = {\n  ok: boolean;\n  data?: ReturnOf</* ... */>;\n  error?: unknown;\n};\n```\n\n# What next?\n\nAs I mentioned in the previous section, this version, the code you see [here](https://github.com/aosasona/robin) right now is mostly experimental to figure out how people will use it, how they expect to use it, what the optimal APIs look like and other things like that which means that eventually and most likely at the point of a **V1** release, there will be a full rewrite focusing on significant performance gains, saner code and other things. These are a few things I intend to work on over the next few months:\n\n- At least some documentation\n- Live/Real-time procedures\n- Pure Javascript client generation (using JSDoc)\n- REST endpoints generation (yes, you should in fact be able to generate REST endpoints for external API usage)\n- Perhaps a way to version payloads?\n- Automatic documentation generation for your APIs (including probably OpenAPI, JSON Schema etc)\n- Batched procedures (a way to send multiple requests to call multiple procedures at once without actually making them multiple requests)\n\nOf course, there will be more discovered as usage increases and you can follow the Github issues to know what's planned or to simply make suggestions.\n\n# FAQs that no one has actually asked\n\n## Is it a library or a framework?\n\nPersonally, I think it is small and unobstructive enough to just be a library you can compose as you want but I don't really care, call it what you want.\n\n## Will it ever support [my other language]?\n\nPerhaps in the future, while support for that language can be added in mirror, this version of robin itself assumes Typescript as the default and will until or even past a 1.0 release.\n\n## Can I contribute?\n\nI cannot promise to swiftly review PRs due to time constraints at the moment but you are free to send in feedback or even code, I'd love to hear from you if you do use it!\n\n# Relevant links\n\n- Robin: [github.com/aosasona/robin](https://github.com/aosasona/robin)\n\n  - GoDocs: [godocs.io/go.trulyao.dev/robin](https://godocs.io/go.trulyao.dev/robin)\n  - pkg.go.dev docs: [pkg.go.dev/go.trulyao.dev/robin](https://pkg.go.dev/go.trulyao.dev/robin)\n  - Tasks application example: [github.com/aosasona/robin-todo](https://github.com/aosasona/robin-todo)\n\n- Mirror: [github.com/aosasona/mirror](https://github.com/aosasona/mirror)\n","image":"https://og.trulyao.dev/api/v1/images/trulyao/preview?variant=blog&style=blog&size=medium&vars=title%3AIntroducing+robin%2Cdate%3AOct+11+2024","date_published":"2024-10-11T00:00:00.000Z"},{"id":"my-stack-2024.mdx","title":"My Stack.","url":"https://trulyao.dev/posts/my-stack-2024","tags":["stack","tools","programming language"],"summary":"Things I use, will use and probably will continue to use.","content_text":"\nI have talked about the languages, frameworks, databases etc. I use and why I use them from time to time on Twitter but never in a structured way, this article will be a somewhat complete answer to questions around my 'stack', and we will be talking about everything; language, framework, ORM, monitors, headphones etc. You do not have to agree with my reasons for picking these things, that is fine, it's subjective. I spent a larger part of 2023 exploring different areas to figure out what else was out there, what I liked, what I didn't like, what works for me, what doesn't etc.\n\n![My Stack](/images/my-stack.png)\n\n> When I started writing this article, I was still away from social media trying to survive my school assessments and by the end, I may still be, which means this article might be up for weeks before we get to discuss about it but I would still love to have a discussion around it if you want.\n\n> Yes, I am still behind on that planned website redesign too\n\n> I have mostly been interested in developer tooling, databases and real-time systems lately so a lot of my choices and preferences may lean towards that.\n\n> I may get some things wrong here from my experience with these things but I will try as much as possible to provide accurate information, objective corrections are welcome!\n\n> I haven't really been able to take the time to review this thoroughly and I did not want to keep it in my drafts for too long so, I will be updating this article as I go along - mainly typos and stuff - I will try to keep the updates to a minimum though.\n\nBefore we get into all that though, I think it's only fair I give you a bit more context around what I need, what I think software should be like and all that. The more I explored, the more I ran into BROKEN software, not one or two bugs here and there but broken; whether it be bugs that made them unusable or a UX that did not seem like it was made for actual humans (this discussion is for another article), I started to see a pattern, even in Apple's software that used to feel very polished, it felt like no one cared about software anymore, everyone wants to 'ship' as they call it, no one seems to be willing to put it the work to make their software an experience they'd want to use or they have a really low bar for quality? I don't know but from most discussions I have had with most ~of these _shippers_~ people on Twitter, it seems to be both.\n\nYes, I am judging you, I am judging us, I wish we would adopt the work ethics of game developers; IMO, game dev is so hard and requires a lot of discipline, no one is going to buy a game with \"This production version works but you know, x just doesn't work yet, don't press y because it may delete your save data\", \"it isn't so bad, you just have to do x and y, and x works most times...\" or any of such narratives around it. I am guilty of this too, I have written bad software that I am ashamed of, I have written horrible code I would never want to see the light of day so, before I get to write an article on the state of software and how everyone should do better, I need to do better too. I know most of you reading this might have been born in the era of Microsoft Windows where everything was already slow by default (I am not blaming Microsoft, I am saying most of us got \"used\" to software being slow because the thing we used to run software was MS Windows which was and still is notoriously slow when doing things like just using the file explorer, without using search that is, it is even worse with search), but I have seen fast software, I have used fast software, I know it is possible to make fast software and we should make fast software.\n\nOh dear, that was... uh, quite the rant, but I promise we are actually getting into this section's purpose now. To do better, I have decided that for both software meant to be delivered into the hands of users and ones that would never be seen by the user:\n\n- I need to write software with no bugs that could and should have been caught before it ever made it out\n- No taking shortcuts around handling failures/errors/exceptions\n- Performance should never be an afterthought, it should be woven into every line of code\n- Re the point above, not trying to fit a square in a circle; possible but at what cost? A.K.A. please stop writing abominations (Laravel for mobile apps? what???)\n- Be able to verify the state of a piece of software with certainty (tests, readable enough to know what the hell is going on etc)\n\nThese are just summaries, there are a lot more to do and I know they may not make sense to you now, you will understand as we go deeper but a lot of the choices I have made are geared more towards these goals.\n\n# Languages\n\nLet's start with languages; the ones I intend to write often over the next few years. Most people say the choice of language doesn't matter, same people probably write backends in Javascript so... cough cough... That's all I will say about that, I think it does for some people and some cases so, I will explain why I have chosen these languages and where I intend to use them.\n\n## PHP\n\n> \"Heavens no, why would anyone write PHP? It's the worst thing in the universe\"\n>\n> \"Okay, maybe it's Laravel, I think it's a nice framework that makes PHP usable, I will let it go\"\n\n**NO**, I do not write Laravel, I do not like Laravel, I will never (re-)learn Laravel (or Django, or Rails, or any other meta framework; good for you if you love, live and breathe them), the \"why\" could be its own article, but not today.\n\n**NO**, I am also not in denial, PHP has its quirks, a lot of them in fact that calling them just _\"quirks\"_ is an understatement. I started with PHP 5.2 so believe me, I know, they are only just trying to fix most of them and improve the language now. It was a language born with no design in mind, a loud echo of its C origin, a great example of what happens when you make thinking (as in design considerations) and performance afterthoughts; although thankfully, there are people willing to work full-time to improve it - Thank you!.\n\nAs I mentioned, PHP was the first language (proper language, not markup language; I did pick up HTML first but you know we don't talk about that) I ever picked up, I remember looking at PHP code back in Junior secondary school and thinking I was never going to know that thing, it looked cursed, it looked complex, I wasn't sure I was smart enough for it. I had to learn a lot through trial and error; I had 25MB plans few times to look up how to do the basics and just ran from there, 00webhost for hosting (by uploading files from an iPod touch I had been gifted, use internet on my computer? are you crazy? - there was so much going on with Microsoft XP - or was it Vista? I cannot remember - and the $hitty network that I was out of mobile data - via USB tethering - before the page even loaded; if the desktop didn't just freeze that is) and dot.tk for domains, Twitter and Youtube were not in the picture either. I grew to like the language, it allowed me build things I thought of, it earned me my first income (eventually, I could use FileZilla like the rest of you) and still does (Yes, I still write PHP, I still deal with XML from cursed integrations at work that we have to maintain, sigh).\n\nEnough with the sentimental crap, PHP is still a part of my stack because it is still useful to me, I mean, it is not the poster child for performance or consistency but I know it, dare I say, inside-out and it only keeps getting better. I wish the deployment story was a bit smoother in this Docker age but it isn't and to be honest, it does make me want to write it less for my personal projects since I use Docker a lot. If I needed to build a web app quickly and did not care about over-engineering things as I usually would with my personal projects, I would still go with PHP anyway.\nAlthough, I eventually might do something like write a bloody ORM while telling myself \"I am not over-engineering, I am just writing utility functions in a class clearly named `BaseModel`\", you've been there, don't judge me, and that is majorly why I like and use PHP (not only because I am paid to write it), I barely ever need to reach outside the language to get things done in the way I want.\n\nWe are able to work on a codebase half as old as I am without a single `composer install` or even a `composer.json` file (yes, I am aware composer adds a certain DX, I am telling you it is possible to do without it and we do), the only build system we have is for... you guessed it, the Javascript[1] and CSS (minification and other things) and tailwind is the only reason we have had to even deal with Node recently.\n\n\"Well, that only works because you are building toy products, I can do that with Next.js and Prisma, and the thousand other dependencies that come with it too\" - Uhm, no, we manage about 8 different codebases that have to integrate with other systems and with our multiple mobile apps, it hasn't been without its issues like every other legacy system either. And, thank you, our performance is fine :)\n\n> [1] because we mainly use jQuery; a remnant of the past which I believe we are slowly migrating from since browsers and modern Javascript are good enough now\n\n## Typescript\n\n> \"Hypocrite, you just bashed Node.js and Javascript\"\n\nCalm down, I can explain.\n\nSee, Javascript also has a plethora of [birth](https://hackernoon.com/how-javascript-was-created-and-why-the-history-behind-it-is-important-fwh3tco) issues like PHP, but unlike PHP, some of these \"behaviors\" can no longer be fixed because of the way Javascript (the actual thing in the browser, not the... questionable ways to kill your server and maybe a few brain cells) is distributed. Javascript isn't something you can really install, your browser engine \"maker\" decides what you get, and when you get it, I can't go into details on how Javascript/ECMAScript works in this article but think of it this way; it is like your phone's chip, you are stuck with whatever the manufacturer put in your phone, you do not have a choice (unless you've spent too much time on XDA forum and were willing to do things that would make people keep their devices away from you, is it even still around?) and developer also do not have a choice so in some cases they have to ship this thing called a [polyfill](https://developer.mozilla.org/en-US/docs/Glossary/Polyfill) just to deal with the insanity. This, in turn means, when ECMAScript gets a new release, you have to often consider those new features untouchable for at least the next 5 years unless you:\n\n- are willing to ship polyfill for all the browsers (we have a lot of them these days)\n- are delusional enough to believe all, or even most, users actually update their browsers\n- don't care about a large percent of your users\n- don't even have users to begin with\n\nThis also means, making changes to behaviors that developers have either had to work around or depend on for years now would break a large percentage of websites and web apps on the internet today and we will see more screens like this often without Next.js feeding it to us for no reason, beautiful, isn't it?\n\n![Uhm, vercel?](/images/vercel-next-error.png)\n\nWhat was I talking about? Oh yeah, the facade.. sorry, language; Typescript. These days, I tend to keep to using Javascript and by extension; Typescript, where it was meant to live - in the browser since it is the only sane (I know, I feel it too, \"sane\" and \"Javascript\" in one sentence, oof) way to write interactive front-ends these days and since I like ~~type safety~~ types (better believe we will come back here), choosing Typescript is a no-brainer, it serves as some sort of contract (there are a lot of ifs here but we will skip it).\n\nOkay, right back to the type-safety part, you see, Typescript is just syntactical sugar over Javascript, it simply helps you know what a contract _may_ be, the types do not **really** have an effect during runtime, you are not getting any special performance benefit from smaller allocations by having more distinct integer types and whatnot. In fact, you could choose to lie to Typescript and in turn blow things up yourself with that lie, so Typescript strongly depends on both sides keeping the contract, but to be fair, not even Rust/Go is immune to something like an API response schema changing right under your feet, it is only worse with TS because you end up exposing that glorious `undefined` to the user (although some may consider it better than Go's decision to use defaults and whatever Rust does, I don't). I am sticking with Typescript because it is better than nothing, that's it, I want my IDE to help me write less hit-or-miss Javascript where possible; trusting Typescript entirely would be like believing the weather forecast in England and failing to prepare for the opposite, you are most certainly going to regret it.\n\n## Go\n\nYou probably saw this one coming anyway. I don't want to spend much time here, you probably already know how I feel about Go too; I hate it and I love it too. Go was the first compiled language I learned and stuck with. Go is just... fine. I have an article coming on why I will be writing less Go and more Rust so, I will try to be concise here.\n\nI have my issues with Go; mainly the fact that the compiler is stupid, tries to make up for it in speed and ends up giving you a program that is most likely to blow up AKA Go is _NOT_ safe and doesn't try to help you be safe either, but if I ever had to ship a performant API fast, do some networking experiment, make something that had to be self-contained and didn't need the extra performance Rust _might_ give or work on something for someone, I would use Go. I like a lot about Go and hate a lot about Go but again, not for this article, I care deeply about the things most people don't care about like image size, and Go's ability to make self-contained, fast programs is a big plus here (you could argue other languages offer same) especially if it is something I expect people to run themselves, I can shove it in an alpine image or a distroless image depending on the context and have it all be less than 25MB and require no external dependencies.\n\nGo is simple enough to let you pick it up and move fast, you _may_ pay for that later (as you will in any other language if you make poor choices) but it is generally, honestly, a good choice if you can put up with some of its weirdness and be okay with the fact that the makers will/may not give you those convenient things you want or fix any of that weirdness because it doesn't need fixing to them and that is fine, but I have used other things, I have seen things can be better and I am not fine with it.\n\nMost Go fans will tell you the complains are all due to _skill issues_, so will fans of any other language, so la la la la la la la la, not listening, fix your $hit, you deserve better, the compiler can and should tell you \"oh hey, this might go wrong\" because it will always know better than you, your type system shouldn't force you to reinvent things, you shouldn't need to use a pointer and a silly nil check to figure out if your user actually sent `false` or not because your language gave you no choice with the defaults... I will stop here before I step on more toes.\n\n> I also do **really** like Go, it's allowed me to build things that are fast in a fairly short amount of time and I did not have to learn \"too much\", the concurrency model is so good too! I am learning Rust at the moment and if I had to do most of the things I have done in Go in Rust, I would probably find it a tad more difficult to be honest.\n\n> Also, my next job may require me to use Go since that is popular for backends these days, and thanks to school, I know I want nothing to do with Java, so yeah, I will write Go from time to time, I still have projects in Go and will continue to write projects in Go when it fits. I do not intend to leave my current part-time/full-time (depending on when you are reading this) job anytime soon, apart from the fact that they took a bet on me (it is very difficult to even get interviews as a student here for dev jobs), I work with genuinely nice people, it is a perhaps more than average pay for the UK, but overall, I still have so much to learn from my boss, a bit more beard hair (and grey ones) and he may fit into that `graybeard` stereotype, I learn SO much from a 20-minute discussion about everything ranging from servers to networking to LINUX to you-name-it than I would sitting in a uni class for 4 hours every week. And apart from all that, it has had a great impact on my social life going in to an office to work with people, I have been pulled to multiple outings and parties in my short time here and out of my natural habitat; a computer workspace, and honestly it has been a great experience, I have spent and still spend a lot of my time indoors working; I spend way more time talking in my head and on Twitter than in the real world.\n\n## Gleam\n\n[Gleam](https://gleam.run) is a language that runs on the BEAM VM (yes, same one used by Erlang, it does actually compile down to Erlang, it is sort of like the Typescript of BEAM world but better), it can also run in a Javascript runtime, but I don't really care about that so I will be pretending that part doesn't exist. You can read more on why I ran with Gleam instead of Erlang itself or Elixir [here](https://trulyao.dev/blog/a-gleamy-exploration), but to keep this short, I like Gleam, I like the community, functional programming has been awesome to explore and I like it too, I will be using Gleam for real-time backends and APIs for my personal projects (I doubt I will get to use it anywhere else for a long while), that is what it shines at (although Elixir getting real types might make me have a second look at it).\n\nYes, I could also use Go for realtime stuff, in fact, you could use almost anything for a realtime backend, I mean, see [this](https://openswoole.com/), people will do anything given the chance to and don't get me wrong, it is good that people explore other possibilities. But, Erlang and its VM were made for this very purpose, they were made to be fault tolerant and handle tons of concurrent connections and tasks, of course, they are not suitable for memory or performance-intensive tasks but they can easily be augmented, and Elixir has clearly served Discord well, a few issues here and there but thanks to the fact that the VM play nice with other lower level languages via NIFs, they could be [fixed](https://discord.com/blog/using-rust-to-scale-elixir-for-11-million-concurrent-users) without even thinking about a rewrite.\n\nGleam is still in its early stages but since it stands on the shoulders of giants and plays nice with those giants, I can always reach into Erlang for missing functionalities and I do in fact enjoy it. I currently maintain a few libraries in the Gleam ecosystem and intend to spend a long time writing Gleam for years to come even if it is scoped to my own personal explorations and projects, I also like a lot of the decisions they have made around the syntax, type system and error handling; the similarity to Rust is a big plus for some people like myself.\n\n![Gleam code sample](/images/facquest-code-sample.png)\n\nYou can have an HTTP server right next to your CRON job runner and Websocket server and be confident that one of them crashing will not bring the others down with it, in fact, you do not need to care about that crash (unless when you need to), the processes will be restarted as necessary, the whole VM is designed to chug along, one process dying will not cause your entire system to crash (in a language like Go, a panic in what's essentially the equivalence of a BEAM process; a goroutine, would take down the whole service if `recover()` isn't used for that specific goroutine and even that is nuanced). A process in this context is not a real OS process, making it even cheaper to spawn and kill processes as needed! Need to send messages from one process to another? You've got it! Want to spawn a task and \"await\" the result in the calling process? Easy. Dare I say Go's concurrency model was inspired by the BEAM, they are similar and both a joy to use.\n\n> \"But you can just crash the whole thing and let Docker restart them for you\" - sigh, please no, and you clearly do not see the value here.\n\n> I may leave Gleam behind in the nearest future since Elixir appears to be getting types too and it isn't held back by its desire to compile to WASM or Javascript which in turn means it can do more things native to the BEAM VM out of the box (like you can literally type Erlang in Elixir fine), but for now, I am sticking with Gleam.\n\n## Rust\n\nRust is definitely hard to learn right but I also understand a lot of the problems it is trying to solve (no thanks to the several nil dereference panics in Go). Rust is not perfect either and I will talk about some of its issues in the article where I explain why I will be writing less Go and more Rust. For starters, Rust is less flexible in a way, so it is out of the question for most web backends I would work on; Gleam, Go or PHP would do fine there depending on the context. I am picking up and sticking with Rust for more lower level things that I intend to explore and do more; majorly dev tools and database explorations. Rust's Tauri is also really good and a good option for me to write desktop apps when I need to.\n\nI have some C++ experience from uni but since I probably write [worse C++](https://github.com/aosasona/ls.cpp/tree/master) than your Javascript and can't (and don't) want to use CMake, Rust is a perfect choice for me ([V language](https://v-lang.io) in theory was but they keep dropping the ball hard, the syntax is almost unrecognizable the last time I checked, there are now multiple versions of if and other things like $if, the syntax for macros or whatever they are is just `[stuff, \"another stuff\"]` and they are too busy building things into and around the language to sell it instead of focusing on stability etc.), I like the ergonomics, I know most people find it cumbersome, and I may in the future but for now, I am pleased with it and its helpful compiler (Go, feel free to pick some inspiration here) that would most certainly produce a binary that doesn't crash because of something that could have been fixed before it ever made it in past compilation. Rust being without a garbage collector makes it suitable for more things that Go isn't, like making high-performance databases, other languages etc, the type system behaves as a real part of the language, the meta programming features are really nice too, it allows you to do cool and convenient things like [Axum's route extractor](https://docs.rs/axum/latest/axum/extract/index.html), [rspc](https://www.rspc.dev) (the developer experience you get here makes me want to use Rust for APIs even, haha), or [Serde](https://serde.rs/) etc. significantly better than having to use [struct tags and weird reflection stuff](https://github.com/aosasona/gots) in Go.\n\nRust is lacking some things in its standard library unlike Go and you often have to choose between multiple options even when it comes to things like what async runtime to use but I am willing to cope with these, the benefits outweigh the nuances for me. Pattern matching is also a joy to use in any language, it feels like it should be in every language but sadly it is not, I have tasted it in Rust, Gleam and Elixir and I cannot go back now (Go, take more notes here).\n\n# Frameworks and libraries\n\nThere isn't much to talk about here, the language matters more for me but here you go.\n\n## Solid.js (Typescript)\n\n> \"Wait, are you nuts? not React?\"\n\nNope, not react. React has a very rich ecosystem that it is tempting to just stay there but I strive for simplicity and performance, React is neither. Hell, there is a [YC-backed startup](https://million.dev/) dedicated to helping you fix React issues (how long till we get one for Go too? they belong in the same basket, haha). React got me my start with modern frontend stuff but lately I have been okay with just using Astro for static stuff with a bit of interactivity; like this website and Solid.js for other things. Apart from the frequent drama in the React, Next.js and Vercel \"ecosystem\" that is enough to put anyone off, Solid genuinely does have enough appeal on its own for me.\n\nIt's done away with the virtual DOM, not that I care much about that, what I do care about is the effect this has on the framework itself; Solid.js, as I understand it, is able to be more performant since it doesn't need to keep a version of the current DOM in memory to diff on state changes, that combined with its choice to use signals (and their obvious dedication to performance-first) has made Solid.js pretty fast by default!\n\n> To be fair, React opened the door for a lot of newer frameworks and I am thankful for that, JSX is quite nice to work with, I like it, happy for you or sorry it happened if you don't.\n\nSolid.js also has a lot of first-party libraries and components that are guaranteed to retain that performance and also reduce the package choice fatigue, it is also similar to React enough that it is not that hard for any React user like myself to pick up.\n\n> Remember I am not trying to convince you to use Solid.js, I know people get defensive about their frameworks, I am also aware there are a lot of frameworks out here, explore and make your own decisions, this is mine.\n\n## Axum (Rust)\n\nI don't have much to say now, it looks nice, it is made by the same folks that made the most popular Rust async runtime, performance should not be a problem. Extractors are really nice too, but I haven't used it enough to say a lot about it, I also don't intend to do a lot of APIs in Rust anyway.\n\n## Wisp (Gleam)\n\nThis is less of a framework and more of a collection of convenient functions, nice to have, it is also maintained by the [creator](https://lpil.uk/) of Gleam.\n\n## Chi router (Go)\n\nNice APIs, good performance, no opinions.\n\n## Echo framework (Go)\n\nAlso, nice APIs, good performance, great for real projects where I don't want to roll a lot of my own stuff.\n\nThere you go, I don't have much opinions about libraries or frameworks, I try to do my own stuff when I can anyway.\n\n# ORMs/Database access stuff\n\nJust like the previous section, I don't have a lot of opinions here too (unless you mention Prisma, I have plenty of opinions like: USE SOMETHING ELSE)\n\n- Gleam: SQL, decoders & one of the drivers like [pgo](https://github.com/lpil/pgo) or [sqlight](https://github.com/lpil/sqlight)\n- PHP: again, just write SQL bro + PDO (and often \"accidentally\" rolling my own query builder/ORM)\n- Go: nothing really safe or sane enough in this ecosystem that won't burn you but I like [Bun by Uptrace](https://bun.uptrace.dev/), and [sqlc](https://sqlc.dev/) + I mainly just write SQL anyway\n- Rust: SQLx\n\n# Databases\n\nNow, this part is also pretty generic, but it also depends on what the application needs.\n\n## SQLite\n\nSQLite is just a good fit for smaller side projects and things like desktop and mobile apps that I have had to do recently, and thanks to [LiteFS](https://fly.io/docs/litefs/) and [Litestream](https://litestream.io/), accessing it outside one instance and backups are not _really_ an issue anymore.\n\n## MySQL & PostgreSQL\n\nI have very limited experience with Postgres but I have worked with MySQL for years now but to be honest, I have only really dug into both more recently. MySQL has gotten a lot of updates in recent years and performance boost but Postgres objectively can do more since it is more of a hybrid too (object-relational like Oracle12c) which is really nice but I am not picking one over the other because again, it depends on whatever the application needs (more reads? more writes? etc.). Although, it appears most people may not have to make that choice anyway since most managed services are for Postgres these days.\n\n> I think that is it for the code part, ping me if there is anything I should have included\n\n# Editor\n\nYou have probably noticed by now, I use Neovim mainly. I say mainly because, from time to time, I have to shell out to VS Code when I am testing an unsupported language (most languages try to support VS Code first) like V and, initially, Gleam, and at work, I use PHPStorm, not because everyone else uses PHPStorm but because Windows is so cursed that setting up my Neovim setup on it was a hassle and I only have Vim on there instead, this would have been okay and I could just use some FTP stuff later but it is so slow (the Windows terminal) that I don't bother to use it and I found no way to get Wezterm to use bash on Windows instead of whatever the hell it was using. I still use it from time to time when PHPStorm decides to take the whole day (obviously an hyperbole, but truly a long time) just to index stuff because I simply switched branches and prevents me from working.\n\nMy preferred terminal is [Wezterm](https://wezfurlong.org/wezterm/) since it supports everything I use right now, I like that it is customizable and the key bindings + panes are a blessing (nice that it uses lua too). I am still waiting to try out Ghostty, I will see if I will switch them but this setup has served me nicely for over a year now. I did try Alacritty, it had some weirdness going on and I did not bother with it, I used iTerm2 for a while but again, it also had some weirdness going on that I just didn't like, they both broke my fonts and looked odd in full screen.\n\n> EDIT: I got access to Ghostty a couple of days ago and I may switch, I still need to set it up to match my current Wezterm setup but I haven't had the time to do that yet since I have to dig through the source code to figure out how to do some things in the config file (which is plain text, not Lua).\n\n# Monitor(s), Keyboard and other stuff\n\n![My Setup](/images/setup.jpeg)\n\n> There are links to some of the things I mention here\n\nI use a dual [24\" Huawei monitor](https://amzn.eu/d/6YVQrjL) setup and for over 2 years, it's been fine, I have looked at other monitors and even considered an ultrawide but this feels just right to me. I prefer a dual monitor setup because I prefer having two distinct \"desktops\", I know you can get close to this with something like Rectangle but I like full screen as you can see, hitting `Option + B` (via [Zap](https://usezap.sh)) to go to my browser affects nothing else, my terminal doesn't suddenly move or get minimized, other things like Discord, Arc, Spotify etc that are not for writing code live in the left monitor.\n\nI use a 2021 14\" MacBook Pro (M1 Pro) and that's also been... fine, except for storage (my next laptop, perhaps in 2025?, would certainly not be 512GB). I have to run a Windows emulator sometimes for things like the Braid game because it won't run on my Mac natively (via Steam) or for accessing the Oracle Database server from home (I also don't know why this is broken on my Mac at home only but it works in the VM so...)\n\nOh, I own an Apple HomePod mini that works like 30% of the time - great job Apple, I thought you could do better than the Google Nest, at least that one had Bluetooth\n\nI have a pretty [unpopular dock](https://amzn.eu/d/ajHVJE7) from Anker that supports DisplayLink because that is the only way to run both monitors with one cable on newer MacBooks - again, great job Apple, now I can't use Amazon Prime on my laptop because it thinks I am screen-sharing.\n\nI have a Raspberry Pi 4 (8GB RAM) with a [case from DeskPi](https://deskpi.com/collections/frontpage/products/deskpi-pro-for-raspberry-pi-4) and a 512GB SSD from SanDisk that serves as a tiny home server to test things on and also media storage.\n\nMy keyboard of choice is the [Keychron K2 V2](https://www.keychron.com/products/keychron-k2-wireless-mechanical-keyboard?variant=40290116730969); aluminum build, full RGB and hot-swappable with Brown switches, I did not even know I got the highest spec until it arrived but whatever, it is a nice keyboard! I pair that with a Logitech MX Master 3 (I believe).\n\nThe [reMarkable 2](https://remarkable.com/store/configure/vertical/GB) is my recent attempt at getting into more reading and writing, quite pricey and has lesser features and storage than the Amazon Kindle Scribe so to be honest, this was purely based on design, it just looks and feels so good, not sure I would recommend it to other people over the Scribe though, the Kindle store integration alone is better than having to buy books on [ebooks.com](https://ebooks.com).\n\nI also use the AirPods Max as my preferred headphones at work and AirPods Pro (2nd generation) when I am not at work; so, not often. Most people say it sounds like crap compared to Sony's headphones but I don't know, I tried about 2-3 Sony headphones and I couldn't quite stick with them (last was the XM3 I believe), they didn't sound bad but these also do not sound bad to me.\n\n## Apps\n\n![Dock](/images/dock.png)\n\nI use certain apps often on my computer and phone, here's a short list:\n\n- ~~[Zap](https://usezap.sh) - Window manager for MacOS~~\n- [Octal](https://apps.apple.com/app/id1308885491) - Hackernews client for mobile\n- [Stats.fm](https://stats.fm) - Viewing Spotify stats\n- ~~[Linkvite](https://linkvite.io) (Beta) - Saving and organizing bookmarks~~\n- [Obsidian](https://obsidian.md) - Note taking, todo lists etc\n- [Arc](https://arc.net) - Internet browser, love the vertical tabs\n- [TablePlus](https://tableplus.com) - Database stuff\n- [OrbStack](https://orbstack.dev) - A drop-in replacement for Docker Desktop\n- ~~[Spacedrive](https://spacedrive.com) (Beta) - File manager, waiting for node sync stuff - I do not use it a lot for now~~\n- [Infuse 8](https://firecore.com/infuse) - Media player (supports Jellyfin)\n- ~~[Mammoth](https://getmammoth.app) - Beautiful & sane Mastodon client~~\n- ~~[Hoppscotch](https://hoppscotch.com) - API testing and documentation (I haven't used this much yet, I recently switched from Postman)~~\n\n> I will probably update this article with edits if I change my mind on any of these things\n","image":"https://og.trulyao.dev/api/v1/images/trulyao/preview?variant=blog&style=blog&size=medium&vars=title%3AMy+Stack.%2Cdate%3ADec+23+2023","date_published":"2023-12-23T00:00:00.000Z"},{"id":"no-newline-at-end-of-file-vim.mdx","title":"No newline at the end of file","url":"https://trulyao.dev/posts/no-newline-at-end-of-file-vim","tags":["neovim","linux","git","windows"],"summary":"You ran `git status` and got this weird diff at the end that read \"No newline at the end of file\" and now the reviewer is telling you to fix it, let's take a closer look.","content_text":"\n# Some background\n\nMy work-issued laptop is a Windows machine and for the last two years or so, I have had to use PHPStorm which is sort of integrated with our development server(s) and it is fine for the most part. I have tuned it over time to feel more like my preferred text editor; Neovim,  with the use of [IdeaVim](https://github.com/JetBrains/ideavim) and a couple of keyboard mappings to make in-editor and in-code navigation easier and significantly closer to what I am used to. There was only so much I could do in there to make it any closer to my Neovim + Wezterm setup, but it was still always jarring when I got home, went back to working in Neovim and resumed the next day to go back into PHPStorm.\n\nAlso due to how our dev environment is setup, we would often have files out of sync with the Git source and it is not fun to track that down when it happens (yes, your FTP-sniffing senses are correct). This essentially meant that our dev environment did not closely mirror production and always made bug hunting more frustrating since we had to also confirm it wasn't some file that changed under us in the environment or an actual bug in our new code... but the dev servers themselves are closely configured to match production, and this whole situation just makes that a wasted effort!\n\n## What I tried\n\nThere had to be a way to make this workflow better. There had been discussions in passing about moving development to the server(s) directly with our laptops becoming thin clients, this sounded good to me as I have had similar workflow in the past and still do with my iPad Air (I will write about that setup later), it was time to explore that.\n\n### Fleet\nI decided to move to just working directly on the server to remove the upload latency and the files going out of sync, and it seemed like a great opportunity to finally try out Jetbrain's new editor ([Fleet](https://www.jetbrains.com/fleet/)) that has been advertised a lot as useful for remote development, it wasn't the best experience but _**this was (and still is) beta software and they ship lots of updates every week**_, I did not expect it to be feature complete and on par with the older PHPStorm but the Vim mode emulation was completely unusable for me since they had to remake one from scratch and it was missing a lot - as I understand it, it uses an entirely new plugin system and IdeaVim is not supported?\n\n> If you are wondering why I did not try PHPStorm itself first for remote development, I completely forgot it had that feature even though I had seen it earlier, I haven't seen the \"splash screen\" in a long while now, but the feature was boldly highlighted on Fleet's homepage that I had been on a couple of times recently.\n\n### PHPStorm (Remote Development)\nAfter that whole thing, I realised PHPStorm did support remote development and could install its own binary on the target server, so I proceeded to use that, most of my settings got messed up and I had to reconfigure them and that worked okay once again, I did not entirely love it but it was usable.. pfft.\n\n### Neovim (via SSH/Mosh)\nI had to edit a commit message and my default editor was a weird version of Vi (I cannot remember what it was called but the server only had that, and Nano), my boss noticed it did different things to what I expected and remembered I was a Neovim user and offered to install Neovim on the server for me instead (I do not have install permissions), I accepted instantly, this was it... I could finally use Neovim directly in my preferred terminal emulator (that I finally now got to use Bash too); Wezterm. I couldn't import my [personal config](https://github.com/aosasona/nvim) since it has a lot of dependency on a few things like the Go compiler, Rust, a C compiler, etc to support all the different things I do in there, so I [wrote one from scratch](https://github.com/aosasona/work-nvim-config) loosely based on [kickstart.nvim](https://github.com/nvim-lua/kickstart.nvim) and that's been pretty... fine. I also added [Mosh](https://mosh.org/) and [Zellij](https://zellij.dev/) to the mix about two weeks ago and it has been a nice environment to work in!\n\n## More context for the switch\n\nMy reason for wanting to switch to Neovim at work wasn't because I am a \"Neovim best, other editors bad\" kind of person (although I do love Neovim and I think you should try it out), I like my terminal workflow which I have really tuned to be quite personal (jumping between my editor and the terminal without much thought or movement, having my sessions stored as they were regardless of what branch I was on or what directory I was in and ready to go instantly! etc.) but I knew I had to put up with PHPStorm since that is what everyone else was using and to be fair, I kind of like it and what it has to offer out of the box, but it was annoyingly slow(-er); like showing errors for seconds after I had fixed them, taking a while to become responsive every time I switched branches and it had to update its indexes (which I did a lot because I haven't bothered to learn git worktrees yet), and other smaller issues like just moving between files (I am really used to `telescope` and `ripgrep`!). I do understand why it has to do some of these things because I have seen, used and like the features it offers, and I did try some suggested fixes on the internet (feeding it more RAM, clearing the cache so it can rebuild it for some reason and other things) but they did not make any real difference.\n\n> I asked some other people in the office and apparently no one else thought it was slow, some folks on Twitter also said they were fine with the performance - perhaps I am the problem and it's not... slow?\n\nOur repos are fairly large and we share code a lot, which means I usually have multiple projects open, switching branches often and stuff like that, my Neovim setup handles that really well, I would say \"but to be fair it is also on a more powerful machine, compared to my work machine\" except my work machine is rather decently spec-ed and I have had similar experience with Goland, Webstorm and PHPStorm on my personal daily-driver; the 2021 14\" M1 Pro Macbook Pro (same RAM as my work machine; 16GB) with just about two or three windows/workspaces open.\n\n> And oh, Jetbrains had 20GB of cache on my personal machine which I just cleared today after getting rid of the last Jetbrains IDE I had left (Android Studio). This is not a campaign against Jetbrains, I really like their products and I have been using them for a long time, they just did not work for me anymore and I am perhaps too poor to buy the ideal machine they will run extremely well on, if they can ever.\n\n<br />\n\n# The problem\n\nAs I mentioned earlier, my work machine runs Windows, but the dev servers obviously run an enterprise distribution of Linux I shall not name. The problem first surfaced with a commit I made for a task, I was inspecting the diff again on the pull request page and saw something strange like this:\n\n```diff\n-} // This is the last line of the file\n+} // This is the last line of the file\n\\ No newline at end of file\n```\n\nThis made no sense to my brain at first, I completely forgot about the OS differences because PHPStorm managed tabs, new lines etc., for me transparently since the client was technically still on Windows (even though the IDE server was on a Linux machine), so I shrugged it off as a UI bug in the version control platform we use, created the pull request anyway, and carried on.\n\n> I know you are yelling at your screen right now; \"Just tell me the goddamn problem and get to the solution\", we're getting there, I promise!\n\nYes, that came back to bite me in the behind, because I had dismissed it earlier, I forgot about it for a short period... that was until I got a comment from my boss asking me to fix it and be careful about whatever my editor was doing, apparently, it's been in other commits and I simply hadn't noticed.\n\n## What was going on exactly?\nI had a proper look with `git show` in the terminal and oh... there it was, the sneaky `^M`. Ah! It was the DOS file format and CR all along! Hang on, let me explain that. You see, Windows represents new lines with `\\r\\n` instead of UNIX's regular `\\n`, and different applications account for that in different ways.\n\n> `\\r` is the _\"carriage return\"_ (CR) character essentially telling the computer \"this is the end of this line, move the cursor back to the beginning\", sort of like how typewriters worked back in the day (or so I've heard, I never paid much attention to my Mom's typewriter when she had it) and `\\n` is the _\"line feed\"_ (LF) - also commonly known as the \"new line\" - character, that combination Windows uses is also called `CRLF` (Carriage Return/Line Feed)\n\n~Vim (and by extension; Neovim) here added the `^M` character to the end of the file to indicate the carriage return (\\r) on that last line (meaning \"this is the end of this line, move the cursor back to the beginning\"), but not the new line one (LF or \\n) because there was indeed no newline there, it correctly detected the file as `dos` and just did what `dos` did.~\n\n> This was partially right but I had the wrong diagnosis here, sort of. Scroll down to read the latest update which contains more accurate information.\n\n## What I tried\nI initially thought, _\"Okay, I will just set file format to unix and that'll be it\"_ forgetting that it will try to convert the whole thing. Running `:set fileformat=unix` and `:w` was definitely not a good idea, I ran a hard git reset to try again. I added `dos` and `unix` to the file formats list thinking it will figure out the difference and fix it (looking back now, that was also silly; that is not what that does), ran `:update` and... of course, that did not do it. I tried replacing the invisible PITA character many ways but that also either did not work or broke the whole file somehow... I thought about using Vim to just convert from DOS to Unix format but you can already see the problems:\n\n- It will convert the whole file, not just the last line\n- It will cause a lot of unnecessary changes in the diffs\n- It will be reverted the next time one of my colleagues edits the file on their Windows machine\n\nThis was made worse by the fact that I couldn't just set the `eol` config globally as it also caused a lot of other problems in other files, I had to find a way to fix this on a per-file basis as I worked on them. During my search for a solution, I found answers that suggested using `:set binary noeol` and other things, that was a horrible idea, it turned it all red after rewriting the whole line to strip it all out, don't do it!\n\n> You can see my severe skill issues at play here, while I did sort of know what the problem was, I was having a bit of a trouble figuring out how to fix it without causing more problems.\n>\n> Ours being a fairly old codebase, it had a mix of a lot of conventions when it came to tabs, expanding tabs etc and things like that but PHPStorm handled most of that nicely for me, I got [sleuth](https://github.com/tpope/vim-sleuth) to handle most of that for me and it isn't an issue anymore it seems. Perhaps I should have endured PHPStorm, how I work in the terminal is just quite burned into my brain now and this is the first time I have had to worry about what OS the other person was using.\n\n\n# The (hacky) solution I (initially) settled on\n\nI ended up on doing the following to remove the EOL (end of line) trailing character from the last line of the file (but it is a temporary solution):\n\n```vim\n:set noeol nofixeol\n\" :update You may need to do this for some odd reason\n:w\n```\n\nI would go as far as making this an [autocmd](https://Neovim.io/doc/user/autocmd.html) or just a default setting, and I believe I have tried that but something else that I can't remember went wrong and I had to revert it, I will try again and update this post if I can get it to work.\n\n> Sorry, I have a pretty flaky memory, I should have written this sooner, but I hope this helps someone else out there somehow!\n\n# Update - 2024/06/07\n\nToday, I finally decided to add the autocmd with some restrictions that I'll explain below, here's what that [looked like](https://github.com/aosasona/work-nvim-config/blob/bb7bc6cbd23cd6b996599caa1d430eddd5ed8a16/lua/utils.lua#L30-L50):\n\n```lua\n-- file: lua/utils.lua\nfunction M.handle_eol()\n  local eol = vim.api.nvim_buf_get_option(0, \"eol\")\n  local fixeol = vim.api.nvim_buf_get_option(0, \"fixeol\")\n  local current_filetype = vim.bo.filetype\n  local current_file_format = vim.bo.fileformat\n\n  if eol or fixeol and (current_file_format ~= \"unix\" or current_filetype == \"php\") then\n    M.set_eol({})\n    vim.notify(\"Handled EOL for this buffer\", vim.log.levels.INFO)\n  end\nend\n\nfunction M.set_eol(opts)\n  vim.api.nvim_buf_set_option(0, \"eol\", false)\n  vim.api.nvim_buf_set_option(0, \"fixeol\", false)\n  vim.cmd([[ update ]])\n\n  if opts[\"save\"] ~= nil and opts[\"save\"] == true then\n    vim.cmd([[ w ]])\n  end\nend\n\n-- file: init.lua\nvim.api.nvim_create_autocmd({ \"BufReadPost\", \"BufNewFile\" }, {\n  desc = \"Handle EOL automatically depending on previous setting\",\n  callback = utils.handle_eol,\n})\n```\n\nThe weird restrictions exist instead of just letting it do it for all files by default because I have tried that and it caused issues with certain files that I had to  manually set `eol` for (they somehow had different settings and opening them caused them to create a diff I did not want). I also restricted the autocmd to PHP files because that is what I edit often, for other files, I added a keymap like this for doing that manually:\n\n```lua\n-- file: init.lua\nlocal function set_eol()\n  utils.set_eol({ save = true })\nend\n\nvim.keymap.set(\"n\", \"<leader>eo\", set_eol, { desc = \"Disable and fix DOS EOL manually\" })\n````\n\nThese things aren't really permanent solutions, I am open to whatever suggestions you might have (help!!) and I would love to hear from you!\n\n\n# Update - 2024/06/11\nAfter having to deal with it again, it hit me that perhaps I was looking at the problem from the wrong perspective, maybe I had the wrong solution because I thought I had the right problem but didn't. I finally decided stop being a lazy twat and took the time to investigate because I just knew the solutions I had tried weren't the best and they certainly were not right. So, I launched PHPStorm to have a look at the current configuration, it was set to `CLRF` which is correct, I looked at a couple of other settings and then proceeded to do the one thing I should have taken 5 minutes to actually do; I read the f*cking manual (for Neovim).\n\nI had read up on some of the EOL handling bits earlier but I didn't really go far into the other bits because I just needed to have it fixed and get back to work. I finally read the Neovim docs for `eol` and `fixeol` with more attention and oh... It was right there, it finally clicked. I knew Vim's behaviour and I knew how the line endings worked, I just didn't put it together until now.\n\nSee, PHPStorm knew that the line had actually ended and did not need to add the carriage return or a new line but Vim saw that as unexpected (because of some \"weird\" EOL and EOF handling, [this](https://unix.stackexchange.com/a/263919) may be a better explanation of Vi's - and in turn Vim's - behaviour), so, it did what it knew how to, it *\"fixed\"* it by adding the carriage return on every line that did not have it and that was the last time there. Now that I finally knew the problem, the solution was rather simple, set `nofixeol` to false globally so it doesn't fix it anymore, essentially telling Neovim; \"Don't fix my broken line endings, leave it as it is\".\n\n```lua\nvim.opt.fixeol = false\n````\n\nOr directly via Neovim's command mode (or .vimrc):\n\n```vim\n:set nofixeol\n```\n\nSo far, I have not had any issues with this and it has been working as expected, I will update this post if I run into any issues with this solution; which I don't expect to happen.\n\n> [This article](https://thoughtbot.com/blog/no-newline-at-end-of-file) is about a similar problem but the author WANTED a new line at the end of the file instead of removing the carriage return character.\n\n<br />\n\n# TLDR;\n- Windows uses `CRLF` for new lines, Linux uses `LF`\n- If you see `No newline at end of file` in your diffs, it is likely a `CRLF` issue\n- You can often temporarily fix it in Vim for that session by running `:set noeol nofixeol` and saving the file.\n- [RTFM](https://en.wikipedia.org/wiki/RTFM)\n","image":"https://og.trulyao.dev/api/v1/images/trulyao/preview?variant=blog&style=blog&size=medium&vars=title%3ANo+newline+at+the+end+of+file%2Cdate%3AJun+7+2024","date_published":"2024-06-07T00:00:00.000Z"},{"id":"not-ready.mdx","title":"!Ready","url":"https://trulyao.dev/posts/not-ready","tags":["product update"],"summary":"What went wrong?","content_text":"\nA little over eight months ago, two guys came up with the idea to create a platform, service, application, or whatever you want to call it to bring together people in the tech industry (Techies). They were overjoyed to put their plans into action, as a larger number of people than they had anticipated were enthusiastic about the concept. They were so excited to have their first two volunteers within the first two weeks of publicizing the idea. The \"planning\" and design process apparently took a month, and at this point everything still looked good (LMAO). As they would have definitely and desperately needed it a couple of months ago, they were thrilled by the idea and just went with the flow _(tsk, bad)_.\n\nThey started building but weren't moving fast enough, so one of them tweeted and the other talked to the interested volunteers, and they were overjoyed that everything was going so well. But then it didn't, and it made no sense at the time; things were slowing down as the months passed. The building continued, but the fact that the project was poorly planned and thus poorly executed could not be ignored because it was interfering with the project's development process, which they were completely ignorant of at the time. They were aware of what an MVP was, but they passively ignored it as well. At that point, people had already labelled the project as a startup, and the team had already done so, so they went along, even though they had no intention of it becoming a startup in the first place; it was always intended to be a project that delivered value. But now they had to build something more feature-rich (so they thought, stupid guys) as well as a monetization plan, because who would pitch a product with no revenue-generation plans? And this was difficult for the niche they were building in because they didn't want to rely on ads like other social media _(another sarcastic \"lmao\")_. It just sort of happened\" is not something you want to say about how a startup started in this day and age, which was the case here, big mistake again.\n\nThe product began to lose its original purpose as they attempted to do too much with it at this point, and it only got worse from there; it had been months and this \"MVP\" was nowhere near ready, and they were freaking out, so they did the same stupid thing again, bringing in more people who were interested _(at this point, they should have just open-sourced the project because wtf?)_. Sighs. The team members were also growing tired of wondering when the product would be released; everyone was confused at this point, which was obviously a bad sign; the project had been mismanaged; a lot of unnecessary features had been built out to near completion and had to be scrapped to focus on the essential ones; and while they had a 'working' product, it was of poor quality; too much was missing, and the great UI wouldn't compensate for that with the users that had been waiting for months. In summary, it was a disaster at this point _(yes, I keep saying \"point\", I know)_.\n\nThe amount of time they had already wasted on this project made it difficult to do what they should have done sooner; instead, they kept pushing because they didn't want to disappoint anyone; not the people working on the project, and certainly not the users (again, these asshats had already promised a slew of features, argh!). They had a **'working'** beta but it lacked a lot of core features, it was so bad they didn't want to release it and they knew it was entirely their fault. One guy was more of a builder, and the other was a great project manager who didn't realise it until it was too late and he had left the builder in charge for too long, and now they were thinking very hard about what to do, they already felt guilty enough for both team members and users who were waiting. Come on, they knew this was a poorly planned and poorly managed project that they couldn't pitch to anyone at this point because it had lost its identity, there was no \"good\" product to proudly show off, and it was all a complete mess.\n\nAt this point, they had two options: continue the cycle and waste more time trying to build a good product from scratch (even the actual files and code were messy at this point, continuing would be a disaster waiting to happen) or accept they messed up, own up to their shit, and stop wasting people's time with a mail promising a release they thought \"would be ready\" because of how things appeared, so they chose the latter, which is heartbreaking for them. You probably get the gist by now: I'm the builder, and [@\\_frokes](https://twitter.com/_frokes \"Frokes\") was the real \"product\" guy, and we screwed up. This should have been a relatively smooth build for us and the team, but we (I and him) fell for the hype and made a shitload of bad decisions as a result. It should have always remained a project and never tried to do too much; in fact, there are many \"it never should have\" moments. This project will be dead to you and everyone else by the time you read this, but not to us; we still love the idea even if we went about it the wrong way and everyone would be off the team and development would halt. We still have actual thoughts in mind for the project and we want to get things done right this time but not any time soon to be honest. You will receive an email after this article has been published with a link to unsubscribe from our wait-list; however, if you choose to remain indefinitely, we will continue to provide you updates as necessary.\n\n> **EDIT**: I actually decided to delete the whole data & drop the database, so the mails will not be going out.\n\nIt's been an incredible journey with everyone, and we want to thank everyone who has rooted for us, waited for us, and even built with us. If you're going to start a project or start-up, make sure you're not doing the same BS these guys did and fucked everything up. This is hopefully not the last you'll hear about the idea (not the Frikax product), and thank you once more! Ciao :(\n","image":"https://og.trulyao.dev/api/v1/images/trulyao/preview?variant=blog&style=blog&size=medium&vars=title%3A%21Ready%2Cdate%3AAug+20+2022","date_published":"2022-08-20T00:00:00.000Z"},{"id":"pilot.mdx","title":"Pilot","url":"https://trulyao.dev/posts/pilot","tags":["rant","product update"],"summary":"Even if I have no idea what exactly i'm talking about, I hope you like the read! just keep in mind that the natural order is disorder. yes, that was an avatar quote.","content_text":"\nI am still awake at 7 a.m. Why? We kept trying to make sure the emails we were going to send out this morning were perfect, and we also did some debugging for another project. This has been the pattern lately: not sleeping till it's around 4 a.m., which is obviously unhealthy, but we try to balance it out (as if balance actually exists). Oh, and the \"we\" I keep mentioning includes [@\\_frokes](https://twitter.com/_frokes) and me.\n\nI had built the basic bulk email delivery system the day before, which we would use to send emails to everyone on the wait-list for the next few weeks until... I can't give you a specific launch date because that would be a huge spoiler. But, so far, it's working perfectly and sending emails at breakneck speed, just as we planned (yeah, I lied, we didn't plan the speed, but it's on localhost, so why not?). If your local server is significantly slow, you should examine your code more closely.\n\nToday also feels like the most important day of the week; we had to make a key team-related choice that wasn't easy, but I can't reveal the details, sheesh! I can't tell you lot of things right now, but let's talk about my week.\n\nReturning to the previous paragraph, I was working on something on the staging version of Frikax, and the average 3-ish second load time for the posts wasn't good enough for me, so I basically tore down the infrastructure and rewrote it to achieve a speed of 110ms on average, which is still quite acceptable. I'm not exactly a \\\"sucker\\\" for perfection, especially since it's an MVP, but the authentication still didn't seem secure enough, so I rewrote it from the ground up on both sides. You're probably sighing at this point, so let's move on to today.\n\nWhat do I have planned for today? A lot. Despite my apparent constant presence on Twitter and WhatsApp, I have a lot more work to accomplish than Frikax. So today I'll be working on the new website for the temporary job I'm doing - I started it almost two weeks ago, although I did mess up my schedule, but who hasn't?\n\nThat'll probably do it for this piece; what's the point of it all, you might wonder? This is a public compilation of my numerous ideas, thoughts, and so on, as I stated on the blog home page.\n\nOkay, I'll stop here, but tell me, what are your plans for today? I'll get it if you mention/tag me in a tweet. Have a wonderful day, human!\n","image":"https://og.trulyao.dev/api/v1/images/trulyao/preview?variant=blog&style=blog&size=medium&vars=title%3APilot%2Cdate%3AMay+20+2022","date_published":"2022-05-20T00:00:00.000Z"},{"id":"thoughts-on-llms-2025.mdx","title":"My thoughts on LLMs (2025)","url":"https://trulyao.dev/posts/thoughts-on-llms-2025","tags":["rant","llm","programming"],"summary":"My usage, experience, and thoughts on Large Language Models as they are in 2025.","content_text":"\nI will tell you before you go any further: this article does not have any specific goal or point. I felt like writing about this, so I did.\n\n> The events described here are not in any particular chronological order because my memory sucks, but I will try my best to recount things as I remember them. I apologise in advance.\n\n# First Encounter\n\nMy first experience with something that claimed to be \"intelligent\" (or LLM-related at least) was with [Tabnine](https://tabnine.com), which I found via, wait for it, a YouTube ad. Yes, someone actually found something useful via a YouTube ad... unless I am remembering incorrectly.\n\nI was a Visual Studio Code user at the time, but I used it sparingly as the \"tab autocomplete\" solution it was because, well, I did most of my coding offline. You know, being from a not-so-first-world country with not-so-great internet at the time, I kinda just forgot about it for a long time until...\n\n# The LLM hype\n\nSee, [one _small_ company](https://openai.com) **I** did not know about released this thing that had people in the tech world excited; it was GPT-2. I actually did not, and still have not, gotten to try GPT-2. I just didn't care much, which even I still find strange. I love trying new tech, but this was way out of my field. The technical details or achievement meant nothing to me, so I didn’t bother. I had a lot of other things going on, and I didn’t really care to know what it actually did either.\n\nThen GPT-3 came along and I could not escape it. It was everywhere I looked. Even my favourite YouTubers were making videos on this revolutionary technology that would later be presented as something you might know; [ChatGPT](https://chat.openai.com). There was an actual webpage to try it out, the barrier to entry was significantly lower, so I gave it a shot.\n\nHonestly, I wasn't particularly impressed. It was behind on current events, it didn’t really do what I wanted most of the time, and it wasn’t trustworthy either. It just wasn’t very useful to me. I didn’t really understand the hype all over my Twitter feed and YouTube homepage. I mean, compared to the joke that is Siri, it could actually give me plain responses to things I asked. It was a nice alternative to Google, but nothing I was eager to jump on and use day-to-day.\n\n> I know someone is out there screaming \"It's not comparable to Siri, you moron\", I just had to take that dig.\n\nPeople who knew me as a \"tech person\" asked me all the time if I had tried ChatGPT and what I thought about it, and my response was usually along the lines of, \"Eh, yeah, but I don't use it a lot.\" And that was the truth. But at this point, transformers were definitely the hottest thing... everywhere, not just in the tech world.\n\n> I was very ignorant at this time. But during my dissertation, when I did get to do some research on the technology, I understood a lot more, and I finally got why these progressions have been hyped by people in the know.\n\n# GitHub Copilot\n\nThis thing called [GitHub Copilot](https://github.com/copilot) (the OG one, not whatever they call Copilot on the website and app these days) was announced and released by GitHub. At this point in time, I was in my JetBrains editors phase, and if I recall correctly, I had to switch to VS Code for a while to test out Copilot. I also had a student plan, so I never paid for anything, and I still don’t know if I need to.\n\nIt felt magical but also familiar. Then I finally remembered Tabnine, which I had tried a few years earlier, but **this** felt really good to use. Unfortunately, it didn’t take long for a few things to become obvious to me:\n\n- I couldn’t trust it, obviously.\n- It was better as a fancy autocomplete, helping complete a function call or a line of code at a time based on what's come before or is in another part of the file, rather than several new lines or even whole functions.\n\nWhile these might seem like negatives, I actually see them as positives. Based on my limited experience with ChatGPT at the time, where I constantly had to do manual fact-checking, I wasn’t too surprised to see it get things wrong often. After all, this was \"new\" tech.\n\nAs a fancy autocomplete, though, it was good enough to leave enabled. I installed it in my JetBrains IDE when it eventually became available, and later in Neovim when [copilot.lua](https://github.com/zbirenbaum/copilot.lua) came around. The fact that it could and would often reference other parts of my active files, or even other files entirely, gave me enough confidence to use it without feeling like it was completely making things up.\n\n# LLMs as programming assists\n\nI firmly believe in letting computers augment the programmer where possible, as long as the right balance is maintained. I don’t think you need to be a master of C and remember all its quirks to be considered a \"chad\" programmer. I don’t think you need to use Neovim and live in the desert either to be the greatest programmer ever. Working on Windows has been painful from time to time, but I don’t think using Arch Linux makes you better than everyone else. I don’t really care. I promise letting the compiler type-check your code doesn’t mean you’re weak. No one’s looking.\n\nAs long as you care about your craft, understand your tools, and they help you produce quality software, why not? Use whatever you want. Just be objective about it, or honestly, don’t be. Have fun. Build whatever crazy ideas come to mind with whatever you want. That’s the best part of this job, and for some, like myself, a hobby too.\n\nI’m not ashamed to say I **prefer** a compiler that saves me from myself. I’m not interested in fighting avoidable issues just to prove I’m the better programmer. The computer knows more about my code on many levels. If it can point out inevitable human mistakes or optimise parts that could be better, great. But if I didn’t have that, as I often don’t with the languages I mainly write (PHP, Go, JavaScript/TypeScript), would I completely crumble? No, I do kind of know what I’m doing. I’ve been doing this before LLMs and (_*cough cough*_) Rust, so it is what it is.\n\nThat’s exactly how I view LLMs for coding too. I only really have experience using them as fancy autocomplete or boilerplate producers. I haven’t used any of the agentic stuff, so I can’t speak on that. I don’t trust LLMs that far yet, and I don’t want to spend more time trying to get them to do what I want.\n\nThe natural fear I have is overdependence; where your brain instinctively pauses after every line and waits for the next suggestion, with near zero knowledge being retained. I’m not immune to this. Thankfully, I’m often annoyed enough and reminded of their limitations when I have to temporarily disable Copilot because it keeps interrupting me or is just plain wrong over and over.\n\nWhen I [moved to working on the server at work](/posts/no-newline-at-end-of-file-vim#some-background), I didn’t install it, and I didn’t really notice or miss it. I barely even had syntax highlighting and didn’t care much either. I started programming in [a very odd way](/posts/computers-are-fast#my-history-with-computers), from Microsoft Word, to Notepad, to Notepad++, without any of these assists (LLMs, LSPs, etc) because I didn’t even know they existed. Their absence doesn’t make me useless, but they certainly make my life a lot easier when I have them.\n\nI’m more worried when new people treat these tools as the sole way to get the job done, rather than as a way to learn, which they’re genuinely good for. That approach misses what, in my opinion, is the best part of this job; figuring things out by understanding why and how they work. That said, these things aren’t mutually exclusive, so I try to be less critical. I lean on them too, and I also learn with and from them.\n\n> It's like driving a GT car with all the usual driver assists. Sure, you can crank ABS and TC to the maximum, drive in a very suboptimal manner, and still keep it on the road. You’ll just be miles off the pace and probably chewing through tyres.\n>\n> To be truly fast, you don’t need to learn to drive without them, no one’s really asking for that. You need to understand they’re assists, tools in your toolbox. It’s still up to you to go fast, understand how they work, and know when you don’t want them kicking in, rather than keeping your right foot pinned everywhere.\n\n# Trust\n\nMy biggest issue with LLMs as they are now, even though they’ve been rapidly improving, is that I simply can’t trust them.\n\nI started paying for ChatGPT Plus while researching my dissertation, and it came in very handy for learning new things and understanding them. It saves me a lot of busywork and massively lowers the barrier to entry for new topics, like sim racing and motorsports, which I’ve recently gotten into. I didn’t need to bother anyone with questions like why smaller GT cars let prototypes through in endurance racing the first time I watched one. It’s a silly example, but I hope the point lands. They’re pretty reliable for that sort of widespread, common knowledge... as long as it exists before their knowledge cutoff which is usually around a year or so.\n\nI get frustrated when something like ChatGPT 5.2 tries to gaslight me into thinking the NVIDIA 5000 series or Radeon 9060 XT don’t exist, even though they were released well before. And when cutoff isn’t the issue, they often lose context and start making things up. It can feel like talking to a child, though even my toddler nephew seems to retain more context sometimes. Or maybe I’m just \"holding it wrong\"?\n\nMoments like that remind me how these things actually work, they don’t possess real _intelligence_, even if they (attempt to) mimic it well. They don’t self-learn in the way they want you to think, and obviously, a lot of what they know still depends on training data that’s often outdated.\n\nI’m not delusional. I know I’m not talking to a real person or some AI from the year 2075, but, I’m underwhelmed. Every GPT release gets massive hype, and all I see is slower responses because they’re \"thinking\", but they still have many of the same issues.\n\nMaybe Gemini or Claude are better. I don’t really know, and I haven’t bothered to find out. I don’t expect them to be not have similar issues, and I don’t want to pay for more LLM subscriptions.\n\n# The future\n\nNonetheless, I’m still excited about the future of AI in general. I’ve seen genuinely clever uses of this technology that I enjoy, and I even use it for things like planning my finances. I’m sure people far smarter than me will keep pushing towards **\"AGI\"**, or at least something close enough that the marketing department can advertise as such.\n\nThere have been interesting ideas and hardware products, like the Limitless pendant, which I never tried but appealed to someone with a terrible memory like mine. The tech just isn’t there yet. I’m glad there have been improvements in training methods and hardware efficiency, but it’s not where the hype suggests it should be, at least not for me.\n\nI’m underwhelmed. I don’t care if you augment yourself with AI, or more accurately LLMs, as long as it’s responsible and disclosed. And even so, I’m still looking forward to what comes next.\n","image":"https://og.trulyao.dev/api/v1/images/trulyao/preview?variant=blog&style=blog&size=medium&vars=title%3AMy+thoughts+on+LLMs+%282025%29%2Cdate%3ADec+17+2025","date_published":"2025-12-17T00:00:00.000Z"},{"id":"update-001-robin.mdx","title":"What's new in Robin 0.5.0","url":"https://trulyao.dev/posts/update-001-robin","tags":["go","typescript","web development","update"],"summary":"Robin got a major(-ish) update, let's talk about what's changed since my last article about it.","content_text":"\n# Why write this article?\n\nThis post is a follow-up to my [last article](/posts/introducing-robin) and the first in a series of updates I intend to start posting for the things I work on. While you can indeed read the changelogs (which I try to keep informative as possible but still fail at in my opinion), it requires you to be actively following me or the project on GitHub, but there is a good chance you had a look at the project when I first talked about it, realised it didn't have what you wanted, clicked off, and forgot about it; perhaps one of these updates will includes the things you want.\n\n# Middleware functions\n\nMiddleware support was added very early into the library but was not really talked about; here is the [commit](https://github.com/aosasona/robin/commit/7b678ec72f1348141d8f1ed727d7da0baeef664c) that added that feature. It does what it says on the tin, middleware functions are functions that are executed before the actual procedure function, you can chain as many as you need and they are guaranteed to be executed in that order.\n\nMiddleware functions have the following signature:\n\n```go\ntype Middleware func(*Context) error\n```\n\nUnlike normal procedures, you can only return errors (or `nil` if it should continue down the chain) and this is on purpose, middleware functions are designed to only be, well, middleware functions; they are not the final destination, they are not supposed to behave like normal procedures and leave you wondering where the `Ok` response was actually sent; that will ALWAYS be in the procedure function.\n\nMiddleware functions, as you might have noticed, also do not take the deserialized input but rather just the raw context for a very good reason; **re-usability**. Forcing middleware functions to conform to a specific input type makes it difficult to reuse them across procedures or even across projects (i.e. as separate packages if necessary).\n\nThis comes with the downside that you'd have to deserialize the body twice (once explicitly, and the other implicitly by Robin), I have refrained from adding an option to tell robin \"Don't worry, I have deserialized the input already, here it is, skip the process and just use this\" because, while I do want to provide some level of control, I won't pretend this library is not opinionated and it has to be. It is designed to keep things less confusing, keep the gnarly details out of your way until you need them and prevent silly human errors, you WILL forget to deserialize at some point, tell Robin you have and cause a panic.\n\n> It is easy to feel this is a library for idiots who need to be protected from themselves; like myself, but that is not the case. I prefer to let the computer do work it is good at and not get in my own way, we don't need to bang rocks together all the time to show just how intelligent and infallible we are.\n>\n> To be fair, that is a tough balance to achieve - what should and should not be hidden away? Robin is an experiment for now, and some things may be removed in the future.\n\n> In the future, I may add this option with a variety of checks - that is already possible - but I don't see a need for it now.\n\n## Example\n\n### Definition\n\n```go\npackage foo\n\nimport (\n\t\"encoding/base64\"\n\t\"log/slog\"\n\t\"net/http\"\n\n\t\"todo/repository\"\n\n\tapperrors \"todo/pkg/errors\"\n\n\t\"go.trulyao.dev/robin\"\n)\n\nfunc RequireAuth(c *robin.Context) error {\n\tauthCookie, found := c.Cookie(\"auth\")\n\tif !found {\n\t\treturn apperrors.New(http.StatusUnauthorized, \"Unauthorized\")\n\t}\n\n\tusername, err := base64.StdEncoding.DecodeString(authCookie.Value)\n\tif err != nil {\n\t\tslog.Error(\"Failed to decode auth cookie\", slog.String(\"cookie\", authCookie.Value))\n\t\treturn apperrors.New(http.StatusUnauthorized, \"Unauthorized\")\n\t}\n\n\tuser, err := repository.UserRepo().FindByUsername(string(username))\n\tif err != nil {\n\t\tslog.Error(\"Failed to find user\", slog.String(\"username\", string(username)))\n\t\treturn apperrors.New(http.StatusUnauthorized, \"Unauthorized\")\n\t}\n\n\tc.Set(\"user\", user)\n\treturn nil\n}\n\nfunc Log(c *robin.Context) error {\n\t// do something here\n\treturn nil\n}\n```\n\n> As you might have noticed, Robin has an undocumented in-memory `State` container where you can store data and access it later down the chain for convenience.\n\n### Usage\n\n```go\nfunc main() {\n\t// There are other ways to set the middleware functions like the `QueryWithMiddleware` constructor\n\t...\n\tinstance, err := r.Add(robin.Query(\"list-todos\", ListTodos).WithMiddleware(Log, RequireAuth))\n\t...\n\t// Execution order: `Log` -> `RequireAuth` -[if nil]-> `ListTodos`\n}\n```\n\n> This was extracted from an earlier version of the [Todo list demo app](https://github.com/aosasona/robin-todo).\n\n# Global middleware functions\n\nYou might have noticed a problem already, passing your middleware functions one-by-one to tens or may even hundreds of procedures can get exhausting really fast and would somewhat defeat the effeciency/iteration-speed reasons for going with this library in the first place.\n\nFor this reason, [version 0.4](https://github.com/aosasona/robin/releases/tag/v0.4.0) added support for (named) global middleware which still provide nearly the same guarantees as before and can be added to the instance itself with one key change: they are now _opt-out_ instead of _opt-in_. You can find the rationale for the current design of global middleware functions [here](https://github.com/aosasona/robin/issues/32).\n\n> In the future, I may introduce some sort of procedure grouping functionality as `robin.Group` to make it easier to apply middleware functions to only a certain group of procedures instead of having to mass-opt-out (and in turn recreating the original \"repitition\" problem) - tracked as [#40](https://github.com/aosasona/robin/issues/40)\n\n## Example\n\n### Usage\n\n```go\nfunc main() {\n\t...\n\tr.Use(\"log\", Log)\n\tr.Use(\"require-auth\", RequireAuth)\n\n\tinstance, err := r.\n\t\t// **None** of the global middleware functions will be executed for this procedure\n\t\tAdd(robin.Query(\"whoami\", WhoAmI).ExcludeMiddleware(\"*\")).\n\t\t// **All** of the global middleware functions will be executed for this procedure\n\t\tAdd(robin.Query(\"list-todos\", ListTodos)).\n\t\t// The `require-auth` middleware function will not be executed for the following procedures\n\t\tAdd(robin.Mutation(\"sign-in\", h.SignIn).ExcludeMiddleware(\"require-auth\")).\n\t\tAdd(robin.Mutation(\"sign-up\", h.SignUp).ExcludeMiddleware(\"require-auth\")).\n\t...\n}\n```\n\n> There is a known issue where arbitrary names can be passed to `ExcludeMiddleware` are not validated, this is tracked as [#35](https://github.com/aosasona/robin/issues/35)\n\n# REST-ful endpoints\n\nWhile Robin generates a type-safe **TypeScript** client for you, there are cases where you probably want to expose a REST-ful API for other developers to build on, or you simply are not working in TypeScript and cannot use the client. It is fairly easy to reverse-engineer the client to get the URLs but you will soon find out they are not REST-ful and are (in my opinion) ugly and hard to remember, which makes sense, they were not designed for you to look at or use outside the generated client(s). [This PR](https://github.com/aosasona/robin/pull/38) laid the foundation for future work like generating Open API/Swagger specs and web documentation amongst other things, which means you can expose REST-ful endpoints today!\n\nI will admit it is still clearly under-developed (because it is), and is quite limited when it comes to customisation, but there is now a new method `WithAlias` to set a different endpoint for the REST-ful layer.\n\n## Example\n\n### Definition\n\nYou don't need to rewrite your existing code to make use of this feature, you just need to pass in a new option to the `Serve` options (if you are using that) to enable REST endpoints, or attach the handlers manually using the new `BuildRestEndpoints` and `BuildProcedureHttpHandler` methods on the instance.\n\n> See documentation for [BuildProcedureHttpHandler](https://pkg.go.dev/go.trulyao.dev/robin#Instance.BuildProcedureHttpHandler) and [BuildRestEndpoints](https://pkg.go.dev/go.trulyao.dev/robin#Instance.BuildRestEndpoints)\n\n```go\nfunc main() {\n\tr, err := robin.New(robin.Options{/* ... */})\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create a new Robin instance: %s\", err)\n\t}\n\n\ti, err := r.\n\t\tAdd(robin.Query(\"ping\", ping)).\n\t\tAdd(robin.Query(\"list-todos\", listTodos)). // You can name your procedures like this or use an alias as shown below\n\t\tAdd(robin.Mutation(\"create.todo\", createTodo).WithAlias(\"/todo/new\")). // You can also add aliases to mutations! (not like this though, bad path)\n\t\tBuild()\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to build Robin instance: %s\", err)\n\t}\n\n\tif err := i.Serve(\n\t\trobin.ServeOptions{\n\t\t\t/* ... */\n\t\t\tRestApiOptions: &robin.RestApiOptions{Enable: true},\n\t\t}); err != nil {\n\t\tlog.Fatalf(\"Failed to serve Robin instance: %s\", err)\n\t\treturn\n\t}\n}\n\nfunc ping(ctx *robin.Context, _ robin.Void) (string, error) { /*...*/ }\n\nfunc listTodos(ctx *robin.Context, _ robin.Void) ([]Todo, error) { /*...*/ }\n\nfunc createTodo(ctx *robin.Context, todo Todo) (Todo, error) { /*...*/ }\n```\n\nBy default, the library will strip out certain prefixes based on the procedure type to make the endpoints cleaner and drop the leading verbs which only make sense in the generated client but not for REST-ful endpoints. For example, `list-todos` will become a `GET /<path>/todos` request instead of `GET /<path>/list-todos`, you can override this by using the `WithAlias` method.\n\n### Usage\n\nYou can proceed to call the REST endpoints using any tool you prefer in any language, here is an HTTP file matching the Robin instance defined above (along with matching CURL commands):\n\n```http\n@url=http://localhost:8060/api\n\nGET {{url}}/ping\nContent-Type: application/json\nAccept: application/json\n\n###\n\n# List todos\nGET {{url}}/todos\nContent-Type: application/json\nAccept: application/json\n\n###\n\n# Create todo\nPOST {{url}}/todo/new\nContent-Type: application/json\nAccept: application/json\n\n{\n\t\"d\": {\n\t\t\"title\": \"Test todo\",\n\t\t\"completed\": true\n\t}\n}\n```\n\n```shell\n# ping\ncurl -X GET 'http://localhost:8060/api/ping'\n```\n\n```shell\n# list-todos\ncurl -X GET 'http://localhost:8060/api/todos'\n```\n\n```shell\n# create.todo\ncurl -X POST 'http://localhost:8060/api/todo' \\\n  --data-raw $'{\n  \"d\": {\n    \"title\": \"Todo\\'s title\",\n    \"completed\": true\n  }\n}'\n```\n\n> The default REST endpoint path is `/api`, you can also customize this in the `RestApiOptions`\n\n# Minor changes\n\n## Extra options in client\n\n<small>Tracked as [#31](https://github.com/aosasona/robin/issues/31)</small>{\" \"}\n\nUsers can now supply extra fetch options like `credentials` to the built-in HTTP client used by the generated client, this previously required providing a custom implementation.\n\n### Example\n\n```typescript\nimport Client from \"./bindings\";\n\nconst client = Client.new({\n  endpoint: import.meta.env.DEV ? \"http://localhost:8081/_robin\" : \"/_robin\",\n  fetchOpts: {\n    credentials: \"include\",\n  },\n});\n\nexport default client;\n```\n\n## Exposed pre-flight and CORS handlers\n\n<small>Tracked as [#30](https://github.com/aosasona/robin/issues/30)</small>\n\nThe default (customisable) CORS handlers used by the built-in `Serve` method on the robin instance are now available to users who do not want to use the built-in `Serve` method.\n\n## Major Bug fixes\n\n- Prevent overriding procedures with the same name but different types[(#33)](https://github.com/aosasona/robin/issues/33)\n\n# Conclusion\n\nYou can view the live version of the demo todo application [here](https://robin-todo.fly.dev) with the source code available at [github.com/aosasona/robin-todo](https://github.com/aosasona/robin-todo).\n\nI have also started work on the documentation site, which is not going to be available for a long while as I have other commitments at the moment but you can follow the progress (if any) at [https://robin.trulyao.dev](https://robin.trulyao.dev).\n\nThat's it for now, you can track planned & on-going work and known issues [here](https://github.com/aosasona/robin/issues), you can also open an issue to tell me what you currently dislike or would like to see in the future!\n","image":"https://og.trulyao.dev/api/v1/images/trulyao/preview?variant=blog&style=blog&size=medium&vars=title%3AWhat%27s+new+in+Robin+0.5.0%2Cdate%3ANov+24+2024","date_published":"2024-11-24T00:00:00.000Z"},{"id":"what-about-reda.mdx","title":"What about Reda?","url":"https://trulyao.dev/posts/what-about-reda","tags":["product update"],"summary":"So, let's talk about Reda...","content_text":"\nA couple of months back, I wanted to do more reading; as in actual books, not just blog articles but I did not find any PDF reader I liked enough or felt would give me a good experience. I thought to myself; \"How hard could it be? I already write a bit of react, I could easily learn a bit of react native over the weekend and just build something out\" (spoiler; it took about two weekends), and that what I did, I wanted it to do a limited number of things:\n\n- Make it easy to pick up where I stopped\n- Make it easy to find things in the library (search, obviously)\n- Have a UI that actually felt like it was a library and not a glorified file explorer\n- Contain details about the book (preferably fetched from the internet)\n\nJust before I was about to kick back, I showed it off to a few people and they liked it and wanted it, and I began to think \"What if I made it a bit better and opened it up to more people?\". At this time, unlike other things I had/have worked on, I hadn't even bought a domain, I had no intention to even build a website for it but when I tried to get it into Google and Apple's beta programs, I realised I needed a privacy policy, so I paused, went on Figma, designed a landing page to at least help people understand what it was... and never used the design in the final website as always.\n\nTo my surprise, even though this was my first mobile app and it sucked (imho) and was a bit unstable (some may say even slow), people received it well, the tweet I made at a time I knew no one was awake somehow still got attention and by the end of the second week, the app had well over 250 users and in the first week, I had started to see some sort of potential and 'what it could be'. I started to borrow ideas from another app idea I was working on but never shipped because at the time, I did not really take the time or had the will to learn React Native (Flutter folks, no, do not\nwrite that comment)\n\n> I personally do not support React Native over Flutter or the other way, React Native made more sense to use since I was already familiar with React.\n\nI had fun working on the app, I probably spent too much time working on it, it was an exciting process really, it even gave me the opportunity to have a talk with the Daniel Gross, whew! I mean, at this point, I just had to go harder, and I did. I added ePub and I hated everything about that, both renderers (PDF and EPUB) weren't great and I had to do some silly workarounds to make the app somewhat usable and I did not like that I had to settle. You see, I wanted it to really have a good UX, it's not the first reader out there, obviously, so I knew I had to make it different by making it minimal and easy to understand. I was stuck; I hated the way I had to do things and I couldn't do them myself either, I had never written a react native library, I did not know Swift or Java for the native bindings even if I wanted to and I thought about rebuilding the whole app in Swift and sticking to one platform that way.\n\nThis is when it started to dawn on me, I built this thing **FOR ME** so I could get some reading done but I never even read any of the books, I had spent more time building an app to help others read but I was getting nowhere with my own books haha. I have always been more of an _\"audio learner\"_ so the books I actually did read were Audiobooks on Audible. I started to ask myself \"Is it worth spending the next few months learning Swift and putting in all that work to build something I don't even use while also losing the Android user base (which was well over 65% of total users at the time)?\" and the answer for me was \"NO\". I did not want to work on the app anymore, I still wanted to have my docs organised and all so I intend to hack on that in the future but do I want to keep spending time building the reading app? not quite.\n\nI know a lot of people still use the app so I am working on one last feature; folders (I might find it useful too), I was initially going to just totally stop working on it but I figured I would open up the source code so that others could add features they wanted to their version or make a PR (I would definitely review and update the app) but none of the major updates if any at all would be coming from me. I might still work on a few things here and there in my free time but I'm not making any promises.\n\nAs soon as I am done working on that feature and doing a bit of refactoring, I would be submitting it for public release on the App Stores and opening up the source on Github too. Thanks a lot for the support when I first released the app and after, can't wait to share other things I am working on with you soon! :)\n","image":"https://og.trulyao.dev/api/v1/images/trulyao/preview?variant=blog&style=blog&size=medium&vars=title%3AWhat+about+Reda%3F%2Cdate%3AApr+2+2023","date_published":"2023-04-02T00:00:00.000Z"}]}