{
"path": "/3maakvzgwxk27",
"site": "at://did:plc:xg2vq45muivyy3xwatcehspu/site.standard.publication/3m3zpxhfl3c2s",
"tags": [
"FSharp",
".NET",
"kdl",
"golang",
"baseball",
"api",
"postgres",
"docker",
"devlog"
],
"$type": "site.standard.document",
"title": "Community",
"content": {
"$type": "pub.leaflet.content",
"pages": [
{
"id": "019b2718-f7e2-7ffe-b133-f23fa0cf5e3f",
"$type": "pub.leaflet.pages.linearDocument",
"blocks": [
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"facets": [],
"plaintext": "Advent of F#"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [],
"plaintext": "For advent of F# this year I submitted an essay about parsing and lexing with the language. It really cemented my love for Leaflet and the community of folks I've found on bluesky. I'm really grateful for all the kindness I've gotten and everything I've learned. I'm excited to continue working and sharing my projects with everyone."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"src": "https://desertthunder.leaflet.pub/3ma63k23t7c27",
"$type": "pub.leaflet.blocks.website",
"title": "Parsing and Lexing in F# - oh hey, it's owais",
"description": "For the F# advent calendar this year, I wrote a KDL parser.",
"previewImage": {
"$type": "blob",
"ref": {
"$link": "bafkreigrjsbioejeam3j6rro64ktxcqsczbrg5hsb3j4a5bergqzk776di"
},
"mimeType": "image/png",
"size": 28745
}
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"facets": [],
"plaintext": "KDL"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 94,
"byteStart": 86
},
"features": [
{
"uri": "https://kdl.dev/",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 157,
"byteStart": 146
},
"features": [
{
"uri": "https://angular.dev/?uwu=",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "I like KDL and was first exposed to it when using Zellij last year. I mean look at at its logo! It's like those UwU style logos you find with the ?uwu= param."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"src": "https://kdl.dev/",
"$type": "pub.leaflet.blocks.website",
"title": "The KDL Document Language",
"description": "kdl is a document language, mostly based on SDLang, with xml-like semantics that looks like you're invoking a bunch of CLI commands!",
"previewImage": {
"$type": "blob",
"ref": {
"$link": "bafkreigungf2g33a5cgpg7ay767zkvfwwkna3ipmoljtf7k42kz7rn6mai"
},
"mimeType": "image/png",
"size": 16413
}
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [],
"plaintext": "I'd expected to leave my parser library as is for a few days but I kept working on it because it felt...incomplete. It doesn't feel incomplete now but now part of me wants to have this on Nuget. There's a great KDL library on Nuget for C# called KDLSharp. I'd like to make mine an F# version with functional patterns. "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"src": "https://www.nuget.org/packages/KdlSharp",
"$type": "pub.leaflet.blocks.website",
"title": "KdlSharp 1.0.0",
"description": "A comprehensive C# library for the KDL Document Language featuring a high-level object serializer, v2/v1 parsing, schema validation, and query support.",
"previewImage": {
"$type": "blob",
"ref": {
"$link": "bafkreie2rkxnwqeczogvrqv2xjntojihmiqmo7y4r2vx6ipsooccjxoq3m"
},
"mimeType": "image/png",
"size": 38426
}
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [],
"plaintext": "Anyways, today I implemented more CLI features and way to serialize & deserialize records."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "fsharp",
"plaintext": "open KDLFSharp.Core\nopen KDLFSharp.Core.Serialize\nopen KDLFSharp.Core.Deserialize\n\ntype Person = \n { Name: string\n Age: int\n Height: float\n IsActive: bool }\n\nlet person = \n { Name = \"Alice\"\n Age = 30\n Height = 5.6\n IsActive = true }\n\nlet repr = \n person \n |> SerializeConfig.Default \n |> Serialize.toString\n\nmatch repr with\n| Ok kdl -> printfn \"%s\" kdl\n| Error e -> printfn \"Error: %O\" e",
"syntaxHighlightingTheme": "gruvbox-dark-medium"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 165,
"byteStart": 155
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "You can also convert to and from KDL, XML, and JSON through the CLI. The repo has a couple of examples in the data directory pulled from the main example (austin.kdl) of park & library data in Austin."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"src": "https://github.com/stormlightlabs/KDLFsharp",
"$type": "pub.leaflet.blocks.website",
"title": "GitHub - stormlightlabs/KDLFsharp",
"description": "Contribute to stormlightlabs/KDLFsharp development by creating an account on GitHub.",
"previewImage": {
"$type": "blob",
"ref": {
"$link": "bafkreic6t2dgib5hjzqdqpqoye5qllbubk3wcidk6jdyymcc52nvqz2xyu"
},
"mimeType": "image/png",
"size": 23949
}
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 270,
"byteStart": 255
},
"features": [
{
"uri": "https://funcui.avaloniaui.net/",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "To round out the \"release candidate\" I think I'm going to implement a Document object model, querying, and schema validation. I've never read through API footprints in the F# & greater .NET ecosystem (though the breadth of some of them is impressive like Avalonia.FuncUI)."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"facets": [],
"plaintext": "Baseball API"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [],
"plaintext": "Today I'm continuing to pre-aggregate leaderboards and sure up deployment."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 81,
"byteStart": 71
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 133,
"byteStart": 122
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "I added a routes command to walk the codebase's AST and parse calls to HandleFunc to create a list of routes. Inspired by rake routes in Rails and some deployment utilities. "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"src": "https://github.com/stormlightlabs/baseball",
"$type": "pub.leaflet.blocks.website",
"title": "GitHub - stormlightlabs/baseball: baseball data api",
"description": "baseball data api. Contribute to stormlightlabs/baseball development by creating an account on GitHub.",
"previewImage": {
"$type": "blob",
"ref": {
"$link": "bafkreihfmb6as2dyifmpyeydlaqeh35dit43xsjuhczitg6qyiu4qrurdm"
},
"mimeType": "image/png",
"size": 25676
}
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 3,
"facets": [],
"plaintext": "Deployment"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [],
"plaintext": "Basically this involved peeking at the state of the database and invoking docker commands. I plan to host the image on dockerhub since the project is entirely open source. This is what the deployment output looks like! It's all behind the CLI. "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.image",
"image": {
"$type": "blob",
"ref": {
"$link": "bafkreiaidzhv7e3g5de3qa4gv2t2jklmzvs3urfadt47nwszck3mju7kmu"
},
"mimeType": "image/png",
"size": 664523
},
"aspectRatio": {
"width": 3307,
"height": 2280
}
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 3,
"facets": [],
"plaintext": "Testing"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [],
"plaintext": "The way I've handled testing for this project is not how I usually approach development. Documentation and tests are often high priorities for me to help me through architecting systems and in this project I tested primarily with curl. "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [],
"plaintext": "To remedy this I ended up making a test chassis with testcontainers and a toy dataset of Dodgers, Mariners, and Yankees data from 2023. "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [],
"plaintext": "Honestly I don't know why I put this off for so long. The whole process is pretty straightforward. Build CSVs by querying the database, then use that to seed the test database after you run migrations. Tests just validate test responses. Auth may be a little complicated but similarly doable."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [],
"plaintext": "Tomorrow I'll deploy and add more tests. I'll have to think through benchmarking because I like that package testing provides utilities for them."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.horizontalRule"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 115,
"byteStart": 97
},
"features": [
{
"did": "did:plc:xg2vq45muivyy3xwatcehspu",
"$type": "pub.leaflet.richtext.facet#didMention"
}
]
}
],
"plaintext": "Thanks for reading! Any thoughts? Questions? Feel free to reach out here or DM/tag me on bluesky @desertthunder.dev. "
}
}
]
}
]
},
"bskyPostRef": {
"cid": "bafyreid23w2q7nvrvnrytsn7e7n5k5p2a74pbydkaxb6jy5powmv6bgj2a",
"uri": "at://did:plc:xg2vq45muivyy3xwatcehspu/app.bsky.feed.post/3maakw7izvk27",
"commit": {
"cid": "bafyreieg74x6nvrpnzt2js2vrpemea55kxovl4rv2u7qxx6jtczvx5qaoi",
"rev": "3maakw7lfex2m"
},
"validationStatus": "valid"
},
"description": "Dev Log 38: 2025-12-18",
"publishedAt": "2025-12-18T06:39:11.406Z"
}