
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <id>https://jcalderita.com/blog/feed.xml</id>
    <title>Jorge Calderita's Blog</title>
    <author>
        <name>Jorge Calderita</name>
    </author>
    <link href="https://jcalderita.com" rel="self"></link>
    <updated>2026-04-17T19:07:34Z</updated>
    <entry>
        <id>https://jcalderita.com/blog/astro-to-saga/</id>
        <title>Astro to Saga</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Why I migrated my portfolio from Astro to Saga, a static site generator written in Swift, and how I removed Node from my stack for good.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;My portfolio was running on &lt;span class="high"&gt;Astro&lt;/span&gt; with &lt;span class="high"&gt;Bun&lt;/span&gt;. Everything worked fine. Fast, comfortable, no technical complaints.&lt;/p&gt;
&lt;p&gt;But something didn’t feel right. Every time I opened the project I found a &lt;span class="high"&gt;package.json&lt;/span&gt;, a &lt;span class="high"&gt;tailwind.config&lt;/span&gt;, an &lt;span class="high"&gt;astro.config&lt;/span&gt;, and a &lt;span class="high"&gt;node_modules&lt;/span&gt; folder with hundreds of dependencies I didn’t even know existed. That feeling of losing control over what lives inside your own project bothers me. I like to know what my code runs and why it’s there. I had already removed Tailwind in a &lt;a href="/blog/tailwind-to-css/"&gt;previous migration to vanilla CSS&lt;/a&gt;, but the rest of the JavaScript ecosystem was still there.&lt;/p&gt;
&lt;p&gt;And at the end of the day, I’m a Swift developer. My daily work is Swift. Yet to generate my own portfolio I depended on a completely foreign ecosystem. If someone visited my repository, they wouldn’t see a Swift developer. They’d see just another JavaScript project.&lt;/p&gt;
&lt;p&gt;The question was simple: if I trust Swift for everything else, why don’t I trust Swift for this?&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;I stopped to think about what I actually needed. My portfolio is not a web application. It has no state and no complex interactivity. It’s a set of static HTML pages generated from Markdown. I don’t need React, Vue, or hydration. I need something that reads Markdown, transforms it into HTML, and writes it to disk.&lt;/p&gt;
&lt;p&gt;I found &lt;a href="https://github.com/loopwerk/Saga"&gt;Saga&lt;/a&gt;, a static site generator written in Swift. Deliberately minimalist: it reads files, applies transformations, and writes HTML. What it doesn’t include, you decide. It also features hot reload for development, something I didn’t expect to find in such a small project. That philosophy convinced me more than any feature list.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Advantages&lt;/th&gt;
&lt;th&gt;Disadvantages&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Entire stack in Swift — one language, no context switching&lt;/td&gt;
&lt;td&gt;Small ecosystem — if something doesn’t exist, you build it yourself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiler-verified HTML thanks to the typed DSL&lt;/td&gt;
&lt;td&gt;Learning curve with the DSL syntax&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native image pipeline with no external dependencies&lt;/td&gt;
&lt;td&gt;Image pipeline only works on macOS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node removed from the local environment&lt;/td&gt;
&lt;td&gt;Limited community and documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full control over every dependency in the project&lt;/td&gt;
&lt;td&gt;More upfront work for features that come out of the box in other frameworks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;The underlying reflection&lt;/h3&gt;
&lt;p&gt;The decision wasn’t technical. It was about coherence. I want anyone visiting my repository to see Swift. I’m not saying &lt;span class="high"&gt;Astro&lt;/span&gt; is bad — it’s excellent. But my portfolio is my business card, and that card has to speak about me.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;The site you’re reading right now is generated with &lt;span class="high"&gt;Saga&lt;/span&gt;, compiled with Swift, and deployed on &lt;span class="high"&gt;Cloudflare Workers&lt;/span&gt; without Node ever touching my machine.&lt;/p&gt;
&lt;p&gt;What surprised me wasn’t the technical outcome. It was the feeling of coherence. Opening my portfolio and seeing that everything, from the first line to the last deploy, is Swift gives me a sense of calm that’s hard to explain.&lt;/p&gt;
&lt;p&gt;If you’re a Swift developer and your portfolio runs on Node, I’m not saying you should change. I’m saying it’s worth asking yourself why. In the end, the tool you choose to present yourself says something about you. I chose the one that represents me.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link href="https://jcalderita.com/blog/astro-to-saga/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/AstroToSaga.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/only-dns/</id>
        <title>OnlyDNS</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>How disabling Cloudflare's proxy and switching to DNS-only mode solved in 5 minutes the La Liga block that had been taking down my site for months.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;In a &lt;a href="/blog/la-liga-thanks/"&gt;previous article&lt;/a&gt; I talked about how &lt;span class="high"&gt;La Liga&lt;/span&gt; was blocking my website during matches. People trying to visit my portfolio and blog would find that the page simply would not load, just because my domain went through &lt;span class="high"&gt;Cloudflare&lt;/span&gt;’s infrastructure, and &lt;span class="high"&gt;La Liga&lt;/span&gt; was blocking entire IP ranges from &lt;span class="high"&gt;Cloudflare&lt;/span&gt; to crack down on illegal streams.&lt;/p&gt;
&lt;p&gt;I knew it. I documented it. And I left it there.&lt;/p&gt;
&lt;p&gt;For months I did nothing to fix it. Not out of laziness or because I thought it was complicated — but out of anger. My website worked perfectly fine; it was &lt;span class="high"&gt;La Liga&lt;/span&gt; that was applying indiscriminate blocks and making it unreachable. It was not my fault. So I stood my ground: I should not have to be the one to make a move.&lt;/p&gt;
&lt;p&gt;So I did nothing. For months.&lt;/p&gt;
&lt;p&gt;The thing is, I had &lt;span class="high"&gt;Cloudflare&lt;/span&gt;’s proxy enabled — the famous orange cloud. My site is hosted on &lt;span class="high"&gt;Cloudflare Pages&lt;/span&gt;, so everything lives within the &lt;span class="high"&gt;Cloudflare&lt;/span&gt; ecosystem. But with the proxy turned on, traffic goes through &lt;span class="high"&gt;Cloudflare&lt;/span&gt;’s CDN/proxy layer, which uses shared IP ranges. And those ranges are precisely the ones &lt;span class="high"&gt;La Liga&lt;/span&gt; was blocking.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;The solution is to disable &lt;span class="high"&gt;Cloudflare&lt;/span&gt;’s proxy and switch to &lt;span class="high"&gt;DNS-only&lt;/span&gt; mode — the grey cloud.&lt;/p&gt;
&lt;p&gt;With the orange cloud active, &lt;span class="high"&gt;Cloudflare&lt;/span&gt; acts as a “shopping mall”: every visitor enters through the proxy/CDN door, which uses shared IP ranges alongside thousands of other sites. Those are the IPs that &lt;span class="high"&gt;La Liga&lt;/span&gt; blocks in bulk to stop illegal streams — and my website ends up as collateral damage.&lt;/p&gt;
&lt;p&gt;With the grey cloud, &lt;span class="high"&gt;Cloudflare&lt;/span&gt; becomes just a “traffic director”: it resolves the DNS and points directly to &lt;span class="high"&gt;Cloudflare Pages&lt;/span&gt;. My site is still hosted on &lt;span class="high"&gt;Cloudflare&lt;/span&gt;, but traffic arrives through the &lt;span class="high"&gt;Pages&lt;/span&gt; IP ranges — which are different from the proxy ones and are not on &lt;span class="high"&gt;La Liga&lt;/span&gt;’s blocklist.&lt;/p&gt;
&lt;p&gt;The process in the &lt;span class="high"&gt;Cloudflare&lt;/span&gt; dashboard is straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Log into the &lt;span class="high"&gt;Cloudflare&lt;/span&gt; dashboard and navigate to &lt;strong&gt;DNS &amp;gt; Records&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Find the records pointing to the site — usually an &lt;strong&gt;A&lt;/strong&gt; or &lt;strong&gt;CNAME&lt;/strong&gt; record with the domain name.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Proxy status&lt;/strong&gt; column, click the toggle with the orange cloud to turn it into a grey cloud (&lt;strong&gt;DNS only&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Save the changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The change propagates within minutes. From that point on, traffic arrives through a different route within &lt;span class="high"&gt;Cloudflare&lt;/span&gt; — one that is not in the crosshairs of &lt;span class="high"&gt;La Liga&lt;/span&gt;’s blocks.&lt;/p&gt;
&lt;p&gt;Since my site is static and still hosted on &lt;span class="high"&gt;Cloudflare Pages&lt;/span&gt;, I notice virtually no difference in speed. I lose some proxy-layer features — like CDN-level firewall or caching at their edge nodes — but for a static portfolio website those benefits are marginal compared to the upside of having the site accessible to everyone during matches.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;My website now loads during &lt;span class="high"&gt;La Liga&lt;/span&gt; matches. Anyone trying to visit my portfolio or blog while the Champions League anthem is playing will no longer be greeted by a blank screen.&lt;/p&gt;
&lt;p&gt;Five minutes. One click. Months of problems solved.&lt;/p&gt;
&lt;p&gt;The lesson I take away is not technical — it is pragmatic. Whether I was right or not, my problem was either going to be solved by me or by no one. And I do believe I was right: my website should not have to be collateral damage from &lt;span class="high"&gt;La Liga&lt;/span&gt;’s blocks. Thanks, &lt;span class="high"&gt;La Liga&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link href="https://jcalderita.com/blog/only-dns/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/OnlyDNS.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/my-first-macro/</id>
        <title>My First Macro</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>A macro to automatically generate the ID, timestamps, and namespace in every Fluent model.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;In my project I have dozens of &lt;span class="high"&gt;Fluent&lt;/span&gt; models, and there is a block of code that repeats in absolutely every single one of them:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;public static let space: String? = &amp;quot;sales&amp;quot;

@ID() public var id: UUID?
public init() {}

@Timestamp(.createdAt, on: .create) public var createdAt: Date?
@Timestamp(.updatedAt, on: .update) public var updatedAt: Date?
@Timestamp(.deletedAt, on: .delete) public var deletedAt: Date?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Always the same block. In every model. No exceptions.&lt;/p&gt;
&lt;p&gt;The namespace changes, but the structure is identical. With every new model, I copy and paste, adjust the namespace, and hope I don’t forget anything. With 30 models, that boilerplate becomes noise that makes it harder for me to read what actually matters: the model’s logic.&lt;/p&gt;
&lt;p&gt;My solution in Swift for this kind of problem is a &lt;span class="high"&gt;macro&lt;/span&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;A Swift macro can generate new members in a class or struct at compile time. Exactly what I need. The result I’m looking for is to write this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;@FluentModel(.sales)
public final class ProductModel: Model {
    public static let schema = &amp;quot;products&amp;quot;

    @Field(.name) public var name: String
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And have the compiler automatically inject &lt;span class="high"&gt;space&lt;/span&gt;, &lt;span class="high"&gt;id&lt;/span&gt;, &lt;span class="high"&gt;init()&lt;/span&gt;, and the three &lt;span class="high"&gt;timestamps&lt;/span&gt;. Without writing them. Without maintaining them.&lt;/p&gt;
&lt;h3&gt;Package structure&lt;/h3&gt;
&lt;p&gt;Swift macros require two separate targets: the &lt;strong&gt;public interface&lt;/strong&gt; (what I consume as a developer) and the &lt;strong&gt;plugin&lt;/strong&gt; (the implementation that the compiler executes). In my case, both live in the same &lt;span class="high"&gt;Macros&lt;/span&gt; package:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="high"&gt;Macros&lt;/span&gt; — declares the macro and the &lt;span class="high"&gt;DatabaseSpace&lt;/span&gt; enum&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;MacrosPlugin&lt;/span&gt; — implements the expansion with &lt;span class="high"&gt;SwiftSyntax&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;DatabaseSpace — namespaces as types&lt;/h3&gt;
&lt;p&gt;Before defining the macro, I need to model the available namespaces. Instead of using loose strings, I decided that the &lt;span class="high"&gt;DatabaseSpace&lt;/span&gt; enum should make them exhaustive and safe at compile time:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;public enum DatabaseSpace: String, CaseIterable, Sendable {
    case sales, warehouse
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows writing &lt;span class="high"&gt;@FluentModel(.sales)&lt;/span&gt; instead of &lt;span class="high"&gt;@FluentModel(“sales”)&lt;/span&gt;. If the namespace doesn’t exist, the compiler tells you before anything runs.&lt;/p&gt;
&lt;h3&gt;The macro declaration&lt;/h3&gt;
&lt;p&gt;The public interface of the macro is surprisingly compact:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;@attached(member, names: named(space), named(id), named(init), named(createdAt), named(updatedAt), named(deletedAt))
public macro FluentModel(_ space: DatabaseSpace? = nil) = #externalMacro(
    module: &amp;quot;MacrosPlugin&amp;quot;,
    type: &amp;quot;FluentModelMacro&amp;quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;span class="high"&gt;@attached(member, names:)&lt;/span&gt; attribute tells the compiler two things: that this macro adds members to the declaration where it’s applied, and exactly which names it will generate. Declaring the names is mandatory — Swift needs them to resolve the symbol tree before expanding the macro.&lt;/p&gt;
&lt;h3&gt;The implementation — FluentModelMacro&lt;/h3&gt;
&lt;p&gt;The implementation conforms to the &lt;span class="high"&gt;MemberMacro&lt;/span&gt; protocol and returns an array of &lt;span class="high"&gt;DeclSyntax&lt;/span&gt; — fragments of Swift code that the compiler inserts into the model:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;import SwiftSyntax
import SwiftSyntaxMacros

public struct FluentModelMacro: MemberMacro {
    public static func expansion(
        of node: AttributeSyntax,
        providingMembersOf declaration: some DeclGroupSyntax,
        conformingTo protocols: [TypeSyntax],
        in context: some MacroExpansionContext
    ) throws -&amp;gt; [DeclSyntax] {
        [
            spaceDecl(from: node),
            &amp;quot;@ID() public var id: UUID?&amp;quot;,
            &amp;quot;public init() {}&amp;quot;,
            &amp;quot;@Timestamp(.createdAt, on: .create) public var createdAt: Date?&amp;quot;,
            &amp;quot;@Timestamp(.updatedAt, on: .update) public var updatedAt: Date?&amp;quot;,
            &amp;quot;@Timestamp(.deletedAt, on: .delete) public var deletedAt: Date?&amp;quot;,
        ]
    }

    private static func spaceDecl(from node: AttributeSyntax) -&amp;gt; DeclSyntax {
        let value = node.arguments?.as(LabeledExprListSyntax.self)?
            .first?.expression.as(MemberAccessExprSyntax.self)
            .map { &amp;quot;\&amp;quot;\($0.declName.baseName.text)\&amp;quot;&amp;quot; } ?? &amp;quot;nil&amp;quot;
        return &amp;quot;public static let space: String? = \(raw: value)&amp;quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;span class="high"&gt;expansion&lt;/span&gt; method directly returns the array of declarations, with no intermediate variables. Each element is a Swift literal that the compiler injects as-is into the model.&lt;/p&gt;
&lt;p&gt;The key is &lt;span class="high"&gt;spaceDecl&lt;/span&gt;, which encapsulates all the extraction and generation logic in a single method. It navigates the attribute’s syntax tree with &lt;span class="high"&gt;SwiftSyntax&lt;/span&gt; using optional chaining: it accesses the arguments as &lt;span class="high"&gt;LabeledExprListSyntax&lt;/span&gt;, takes the first expression, casts it to &lt;span class="high"&gt;MemberAccessExprSyntax&lt;/span&gt; (because the argument is an enum case like &lt;span class="high"&gt;.sales&lt;/span&gt;), and with &lt;span class="high"&gt;.map&lt;/span&gt; converts it into a quoted string. If any step in the chain fails, the &lt;span class="high"&gt;??&lt;/span&gt; operator returns &lt;span class="high"&gt;“nil”&lt;/span&gt;. Finally, &lt;span class="high"&gt;\(raw:)&lt;/span&gt; interpolates the value directly into the &lt;span class="high"&gt;DeclSyntax&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Lastly, I need a &lt;span class="high"&gt;CompilerPlugin&lt;/span&gt; to register the macro — it’s the entry point that the compiler loads to know which macros are available:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;import SwiftCompilerPlugin
import SwiftSyntaxMacros

@main
struct MacrosPlugin: CompilerPlugin {
    let providingMacros: [Macro.Type] = [
        FluentModelMacro.self,
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;With the macro installed, each model is clean and noise-free:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;@FluentModel(.sales)
public final class ProductModel: Model {
    public static let schema = &amp;quot;products&amp;quot;

    @Field(.name) public var name: String
    @OptionalField(.description) public var description: String?
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The compiler expands &lt;span class="high"&gt;@FluentModel(.sales)&lt;/span&gt; and automatically generates:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;public static let space: String? = &amp;quot;sales&amp;quot;
@ID() public var id: UUID?
public init() {}
@Timestamp(.createdAt, on: .create) public var createdAt: Date?
@Timestamp(.updatedAt, on: .update) public var updatedAt: Date?
@Timestamp(.deletedAt, on: .delete) public var deletedAt: Date?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if a model doesn’t belong to any namespace — like views — you simply omit the argument:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;@FluentModel()
public final class OrderSummaryModel: Model {
    public static let schema = &amp;quot;order_summaries&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In that case, &lt;span class="high"&gt;space&lt;/span&gt; is generated as &lt;span class="high"&gt;nil&lt;/span&gt; and Fluent ignores the schema prefix.&lt;/p&gt;
&lt;p&gt;The benefits in numbers: &lt;strong&gt;32 models&lt;/strong&gt; with the macro applied. &lt;strong&gt;6 lines removed&lt;/strong&gt; per model. Over &lt;strong&gt;190 lines of boilerplate&lt;/strong&gt; that no longer exist in the repository and will never need to be maintained again.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link href="https://jcalderita.com/blog/my-first-macro/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/MyFirstMacro.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/db-admin-vs-developer/</id>
        <title>DB Admin vs Dev</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Separating database administration from development is not an opinion: it's a responsibility.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;The problem 🤔&lt;/h2&gt;
&lt;p&gt;In many projects I’ve seen how application code ends up doing things it shouldn’t: creating roles, configuring database server parameters, managing instance-level permissions. Everything mixed in the same place, as if it were a single responsibility.&lt;/p&gt;
&lt;p&gt;But it’s not. The database has &lt;strong&gt;two distinct worlds&lt;/strong&gt;: &lt;span class="high"&gt;administration&lt;/span&gt; 🛡️ and &lt;span class="high"&gt;development&lt;/span&gt; 💻.&lt;/p&gt;
&lt;p&gt;Mixing them is one of the most common mistakes, and also one of the most expensive 💸. A developer configuring the instance from their code is crossing a boundary that doesn’t belong to them. And an administrator trying to decide which tables or schemas the application uses is doing the same from the other side.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The idea 🧩&lt;/h2&gt;
&lt;p&gt;The separation is conceptually clean: &lt;strong&gt;administration belongs to the server, development belongs to the code&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;The administrator’s territory 🛡️&lt;/h3&gt;
&lt;p&gt;The administrator manages the database &lt;strong&gt;instance&lt;/strong&gt;. Their work includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🔧 &lt;strong&gt;Server configuration&lt;/strong&gt; — performance parameters, instance-level extensions, settings that require a restart&lt;/li&gt;
&lt;li&gt;👤 &lt;strong&gt;Roles and credentials&lt;/strong&gt; — creating users, assigning secure passwords, defining who can connect&lt;/li&gt;
&lt;li&gt;🗄️ &lt;strong&gt;Databases&lt;/strong&gt; — creating them, setting timeouts, revoking default access&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this is infrastructure 🔐. These are decisions made once, executed by someone with superuser privileges, and they have nothing to do with business logic. Ideally, they live as versioned SQL scripts in the repository, but separate from the application code.&lt;/p&gt;
&lt;h3&gt;The developer’s territory 💻&lt;/h3&gt;
&lt;p&gt;The developer manages the &lt;strong&gt;application’s data structure&lt;/strong&gt;: schemas, tables, indexes, views, and everything that needs to exist for the application to work.&lt;/p&gt;
&lt;p&gt;This responsibility is expressed through &lt;span class="high"&gt;migrations&lt;/span&gt; ✨ — versioned, reversible, and reviewable pieces of code like any other change. It doesn’t matter if you use Swift, Python, Go, or TypeScript: the principle is the same. Your application’s data structure must be automatically reproducible in any environment 🔄.&lt;/p&gt;
&lt;h3&gt;The gray area: admin or development? 🤷&lt;/h3&gt;
&lt;p&gt;There are cases that seem ambiguous. Database roles are a good example. The &lt;strong&gt;role itself&lt;/strong&gt; — with its password and connection permissions — is an infrastructure concept. The administrator creates it 🛡️.&lt;/p&gt;
&lt;p&gt;But &lt;strong&gt;permissions on specific tables&lt;/strong&gt; — what it can read, what it can write, on which schema — that depends on the developer 💻. The table has to exist before it can have permissions, so those permissions go in migrations, not in infrastructure scripts.&lt;/p&gt;
&lt;p&gt;The rule I apply is simple: if the object exists &lt;strong&gt;before&lt;/strong&gt; the application, it’s administration. If its existence &lt;strong&gt;depends&lt;/strong&gt; on the application creating it, it’s development ✅.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The result 🎯&lt;/h2&gt;
&lt;p&gt;When you apply this separation, the setup process becomes clear and reproducible:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;🛡️ The administrator prepares the instance → server configured, roles created, database ready&lt;/li&gt;
&lt;li&gt;💻 The developer runs the migrations → schemas, tables, and application permissions applied automatically&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each layer has its own tool. The administrator doesn’t touch the application code. The developer doesn’t need superuser credentials 🔒. Nobody steps on the other’s territory.&lt;/p&gt;
&lt;p&gt;This separation also makes teamwork easier 🤝: the administration scripts are run by whoever has server access, just once. The migrations are run by any developer on the team in their local environment, as many times as needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The database is not yours alone as a developer. But the data structure of your application is.&lt;/strong&gt; 🏗️&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link href="https://jcalderita.com/blog/db-admin-vs-developer/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/DBAdminVSDeveloper.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/migrate-spaces/</id>
        <title>Migrate Spaces</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>How to automate PostgreSQL namespace creation using FluentKit migrations in Vapor, replacing error-prone manual setup with versioned, reversible code.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;🧩 Problem&lt;/h2&gt;
&lt;p&gt;In the previous article about &lt;a href="/blog/schema-space/"&gt;Schemas and Spaces&lt;/a&gt; we saw how to assign a namespace to a model using the &lt;span class="high"&gt;space&lt;/span&gt; property. Many of you asked the same question: &lt;strong&gt;how are those spaces created in the database?&lt;/strong&gt; 🤔&lt;/p&gt;
&lt;p&gt;The most common answer was to do it manually, running a &lt;span class="high"&gt;CREATE SCHEMA&lt;/span&gt; in the PostgreSQL console before launching the migrations. It works, but it breaks something fundamental: if the database doesn’t exist or the environment is new, &lt;strong&gt;the process fails&lt;/strong&gt; before reaching the actual migrations 💥.&lt;/p&gt;
&lt;p&gt;The other problem is that managing namespaces manually doesn’t scale. As soon as you have multiple environments (development, staging, production) or a team, keeping that manual sync becomes a source of silent errors 😬.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;💡 Solution&lt;/h2&gt;
&lt;p&gt;The solution is to turn namespace creation into just another &lt;span class="high"&gt;migration&lt;/span&gt;. This way, it runs automatically in the correct order, is reversible, and is versioned alongside the rest of the code ✅.&lt;/p&gt;
&lt;p&gt;The key lies in combining &lt;span class="high"&gt;FluentKit&lt;/span&gt; with &lt;span class="high"&gt;SQLKit&lt;/span&gt;. FluentKit manages the migration lifecycle, but to execute arbitrary SQL we need to access the underlying driver through the &lt;span class="high"&gt;SQLDatabase&lt;/span&gt; protocol. To keep the code organized, we encapsulate the namespaces in an enum with an action that determines whether they are created or dropped:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;import FluentKit
import SQLKit

enum SRSchema: String, CaseIterable {
    case account, ai, device, location, sport, task, view

    enum Action {
        case create, drop
    }

    static func execute(_ action: Action, on db: Database) async throws {
        let sql = db as! any SQLDatabase
        let template: (String) -&amp;gt; String =
            switch action {
            case .create: { &amp;quot;CREATE SCHEMA IF NOT EXISTS \($0)&amp;quot; }
            case .drop: { &amp;quot;DROP SCHEMA IF EXISTS \($0) RESTRICT&amp;quot; }
            }
        for schema in allCases {
            try await sql.raw(&amp;quot;\(unsafeRaw: template(schema.rawValue))&amp;quot;).run()
        }
    }
}

struct SchemasMigration: AsyncMigration {
    func prepare(on db: Database) async throws {
        try await SRSchema.execute(.create, on: db)
    }

    func revert(on db: Database) async throws {
        try await SRSchema.execute(.drop, on: db)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some important details 📋:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SRSchema&lt;/strong&gt; as &lt;strong&gt;CaseIterable&lt;/strong&gt;: by iterating over &lt;span class="high"&gt;allCases&lt;/span&gt;, we ensure all defined spaces are created without having to list them manually in the migration. Adding a new space only requires adding a case to the enum 🔄.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Action&lt;/strong&gt;: the inner enum models the two possible operations (&lt;span class="high"&gt;create&lt;/span&gt; and &lt;span class="high"&gt;drop&lt;/span&gt;), allowing the same &lt;span class="high"&gt;execute&lt;/span&gt; method to be reused in both the migration’s &lt;span class="high"&gt;prepare&lt;/span&gt; and &lt;span class="high"&gt;revert&lt;/span&gt; ♻️.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Switch expression&lt;/strong&gt;: a Swift &lt;em&gt;switch expression&lt;/em&gt; is used to select the SQL template based on the action. Each branch returns a closure that generates the corresponding statement, keeping the logic compact and readable 🧹.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CREATE SCHEMA IF NOT EXISTS&lt;/strong&gt;: makes the migration idempotent. If the schema already exists, it doesn’t fail — it simply continues 🛡️.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DROP SCHEMA … RESTRICT&lt;/strong&gt;: in the &lt;span class="high"&gt;revert&lt;/span&gt;, the &lt;span class="high"&gt;RESTRICT&lt;/span&gt; modifier prevents dropping a schema that contains tables. It’s a safety net that avoids accidental data loss when reverting 🔒.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;unsafeRaw&lt;/strong&gt;: used to interpolate the schema name directly into the SQL. It’s safe here because the value comes from our own enum, never from external input ⚠️.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;📊 Result&lt;/h2&gt;
&lt;p&gt;This migration must be the &lt;strong&gt;first&lt;/strong&gt; one registered ☝️. Before creating any table, the namespaces need to exist. In the migration runner, the order looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;fluent.migrations.add(
    SchemasMigration()
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When running migrations in a new environment, the entire process is automatic ⚡:&lt;/p&gt;
&lt;p&gt;And if at any point you need to add a new domain, you just add a case to the &lt;span class="high"&gt;SRSchema&lt;/span&gt; enum and the next time migrations run, the schema appears. &lt;strong&gt;No touching the database manually, no extra documentation to maintain&lt;/strong&gt; 🎯.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link href="https://jcalderita.com/blog/migrate-spaces/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/MigrateSpaces.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/tailwind-to-css/</id>
        <title>Tailwind to CSS</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Why I migrated my portfolio from Tailwind CSS to vanilla CSS and the benefits I gained in performance, bundle size, and full control over the code.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;🎨 Why Touch What Already Works?&lt;/h2&gt;
&lt;p&gt;My portfolio had been running perfectly with &lt;span class="high"&gt;Tailwind CSS&lt;/span&gt; for months. Everything was fine. Styles were in place, components looked great, and the site loaded fast.&lt;/p&gt;
&lt;p&gt;So why change?&lt;/p&gt;
&lt;p&gt;Because &lt;strong&gt;working well&lt;/strong&gt; and &lt;strong&gt;working the best way possible&lt;/strong&gt; are two different things. And when you stop to analyze what’s under the hood, you sometimes discover you’re carrying a layer you &lt;span class="high"&gt;don’t need&lt;/span&gt; 🧅.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;🤔 The Problem with Tailwind (In My Case)&lt;/h2&gt;
&lt;p&gt;Don’t get me wrong: &lt;span class="high"&gt;Tailwind CSS&lt;/span&gt; is an &lt;strong&gt;amazing&lt;/strong&gt; tool. I’ve used it in professional projects and will continue using it where it makes sense. But in a personal portfolio built with &lt;span class="high"&gt;Astro&lt;/span&gt;, I started noticing a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unnecessary dependency&lt;/strong&gt;: 3 extra packages (&lt;span class="high"&gt;tailwindcss&lt;/span&gt;, &lt;span class="high"&gt;@tailwindcss/vite&lt;/span&gt;, &lt;span class="high"&gt;@tailwindcss/typography&lt;/span&gt;) for a project that didn’t really need them 📦&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Abstraction layer&lt;/strong&gt;: Tailwind generates CSS from utility classes. It’s a layer between what you write and what the browser interprets. In a small project, that layer &lt;strong&gt;adds overhead without adding value&lt;/strong&gt; 🧱&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extra weight&lt;/strong&gt;: The generated CSS included utilities I wasn’t always taking full advantage of. ~20KB extra that the user downloaded unnecessarily 📊&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Less control&lt;/strong&gt;: When you want something very specific, you end up fighting the framework instead of writing exactly what you need ⚔️&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;💡 The Decision: Vanilla CSS with Design Tokens&lt;/h2&gt;
&lt;p&gt;The idea was simple: &lt;strong&gt;remove Tailwind&lt;/strong&gt; and replace it with &lt;span class="high"&gt;vanilla CSS&lt;/span&gt; using a &lt;strong&gt;design tokens&lt;/strong&gt; system with CSS &lt;span class="high"&gt;custom properties&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;What are design tokens? They’re CSS variables that define your design system:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;:root {
  --color-primary: oklch(0.55 0.2 260);
  --color-gray-100: oklch(0.97 0 0);
  --color-gray-900: oklch(0.21 0.006 285.75);

  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-xl: 1.25rem;

  --spacing: 0.25rem;
  --radius-lg: 0.5rem;

  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
  --transition-colors: color 0.15s, background-color 0.15s, border-color 0.15s;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this, you get &lt;strong&gt;consistency&lt;/strong&gt; across the entire project without depending on any framework. Just pure CSS that the browser understands &lt;span class="high"&gt;directly&lt;/span&gt; 🎯.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;🔧 The Migration&lt;/h2&gt;
&lt;p&gt;The process involved migrating &lt;strong&gt;38 files&lt;/strong&gt; in a single commit. Each &lt;span class="high"&gt;Astro&lt;/span&gt; component went from using Tailwind classes to having its own scoped &lt;span class="high"&gt;&amp;lt;style&amp;gt;&lt;/span&gt; block:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before (Tailwind):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-astro"&gt;&amp;lt;header class=&amp;quot;flex items-center justify-between px-6 py-4 bg-white dark:bg-gray-900&amp;quot;&amp;gt;
  &amp;lt;nav class=&amp;quot;flex gap-4&amp;quot;&amp;gt;
    &amp;lt;a class=&amp;quot;text-sm font-medium text-gray-700 hover:text-blue-500&amp;quot;&amp;gt;Blog&amp;lt;/a&amp;gt;
  &amp;lt;/nav&amp;gt;
&amp;lt;/header&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;After (Vanilla CSS):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-astro"&gt;&amp;lt;header class=&amp;quot;header&amp;quot;&amp;gt;
  &amp;lt;nav class=&amp;quot;nav&amp;quot;&amp;gt;
    &amp;lt;a class=&amp;quot;nav-link&amp;quot;&amp;gt;Blog&amp;lt;/a&amp;gt;
  &amp;lt;/nav&amp;gt;
&amp;lt;/header&amp;gt;

&amp;lt;style&amp;gt;
  .header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: calc(var(--spacing) * 4) calc(var(--spacing) * 6);
    background-color: var(--color-white);
  }

  :global(.dark) .header {
    background-color: var(--color-gray-900);
  }

  .nav {
    display: flex;
    gap: calc(var(--spacing) * 4);
  }

  .nav-link {
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--color-gray-700);
    transition: var(--transition-colors);
  }

  .nav-link:hover {
    color: var(--color-blue-500);
  }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;More lines? Yes. Clearer and more maintainable? &lt;strong&gt;Absolutely&lt;/strong&gt; ✅.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;📚 What I Learned Along the Way&lt;/h2&gt;
&lt;p&gt;The migration wasn’t just “remove Tailwind and add CSS.” There were interesting pitfalls worth sharing:&lt;/p&gt;
&lt;h3&gt;Scoped styles and &lt;span class="high"&gt;&amp;lt;slot&amp;gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;In &lt;span class="high"&gt;Astro&lt;/span&gt;, styles inside &lt;span class="high"&gt;&amp;lt;style&amp;gt;&lt;/span&gt; are &lt;strong&gt;scoped&lt;/strong&gt; by default. This means each component receives a unique attribute (&lt;span class="high"&gt;data-astro-cid-*&lt;/span&gt;) and styles only affect that component.&lt;/p&gt;
&lt;p&gt;The catch: content passed via &lt;span class="high"&gt;&amp;lt;slot&amp;gt;&lt;/span&gt; &lt;strong&gt;does not receive&lt;/strong&gt; that attribute. If a parent component tries to style slotted content, the styles won’t apply 😱.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The solution&lt;/strong&gt;: use &lt;span class="high"&gt;:global()&lt;/span&gt; for selectors targeting slotted content:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;.container :global(a) {
  color: var(--color-primary);
  text-decoration: underline;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;span class="high"&gt;opacity&lt;/span&gt; vs background transparency&lt;/h3&gt;
&lt;p&gt;With Tailwind, I used classes like &lt;span class="high"&gt;bg-opacity-70&lt;/span&gt;. When migrating, my first instinct was to use the &lt;span class="high"&gt;opacity&lt;/span&gt; property. &lt;strong&gt;Mistake&lt;/strong&gt;: &lt;span class="high"&gt;opacity&lt;/span&gt; affects the entire element, &lt;strong&gt;including its children&lt;/strong&gt; 👶.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The correct solution&lt;/strong&gt;: &lt;span class="high"&gt;color-mix()&lt;/span&gt; for background-only transparency:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;.modal-overlay {
  /* BAD: affects everything */
  opacity: 0.7;

  /* GOOD: only the background is transparent */
  background-color: color-mix(in oklab, var(--color-gray-900) 70%, transparent);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;📊 The Results&lt;/h2&gt;
&lt;p&gt;The numbers speak for themselves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;~20KB less&lt;/strong&gt; CSS delivered to the browser 📉&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3 dependencies removed&lt;/strong&gt; from &lt;code&gt;package.json&lt;/code&gt; 🗑️&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0 abstraction layers&lt;/strong&gt; between your code and the browser 🎯&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Faster builds&lt;/strong&gt; by eliminating Tailwind’s processing step ⚡&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full control&lt;/strong&gt; over every line of CSS generated 🎛️&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;span class="high"&gt;package.json&lt;/span&gt; went from having &lt;span class="high"&gt;tailwindcss&lt;/span&gt;, &lt;span class="high"&gt;@tailwindcss/vite&lt;/span&gt;, and &lt;span class="high"&gt;@tailwindcss/typography&lt;/span&gt; to &lt;strong&gt;no styling dependencies at all&lt;/strong&gt;. Just pure CSS.&lt;/p&gt;
&lt;p&gt;And the best part: the &lt;span class="high"&gt;tailwind.config.mjs&lt;/span&gt; file was completely removed. &lt;span class="high"&gt;One less configuration&lt;/span&gt; to maintain 🧹.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;🏗️ The Final Architecture&lt;/h2&gt;
&lt;p&gt;The styling system ended up organized into 4 CSS files:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;design-tokens.css&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;Colors, typography, spacing, shadows, transitions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;base-reset.css&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;Minimal CSS reset and utilities like &lt;span class="high"&gt;.sr-only&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;prose.css&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;Blog content typography with dark mode support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;layout.css&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;Global layout classes (&lt;span class="high"&gt;.bodyLayout&lt;/span&gt;, &lt;span class="high"&gt;.mainLayout&lt;/span&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All imported from a single &lt;span class="high"&gt;global.css&lt;/span&gt;. Clean, predictable, and no black magic 🧙‍♂️.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;💭 Final Thoughts&lt;/h2&gt;
&lt;p&gt;Migrating from &lt;span class="high"&gt;Tailwind CSS&lt;/span&gt; to &lt;span class="high"&gt;vanilla CSS&lt;/span&gt; isn’t for everyone or every project. In large teams or projects with many developers, Tailwind remains a &lt;strong&gt;fantastic choice&lt;/strong&gt; for its consistency and development speed.&lt;/p&gt;
&lt;p&gt;But in a personal project like a portfolio built with &lt;span class="high"&gt;Astro&lt;/span&gt;, where performance matters and full control is a luxury you can afford, &lt;strong&gt;removing that abstraction layer&lt;/strong&gt; is liberating 🕊️. In fact, I ended up taking this philosophy even further and migrated the entire portfolio from Astro to Swift — I tell the full story in &lt;a href="/blog/astro-to-saga/"&gt;Astro to Saga&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It’s like painting a picture: you can use templates and tools to guide you, or you can pick up the brush and create &lt;strong&gt;exactly&lt;/strong&gt; what you have in mind. Both options are valid. But when the canvas is yours, &lt;span class="high"&gt;painting by hand has its charm&lt;/span&gt; 🎨.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link href="https://jcalderita.com/blog/tailwind-to-css/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/TailwindToCSS.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/cupertino-mcp/</id>
        <title>Cupertino MCP</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Discover Cupertino MCP, the tool that integrates all of Apple's documentation directly into your AI for iOS/macOS development.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;🍎 The Apple Documentation Problem&lt;/h2&gt;
&lt;p&gt;If you develop for &lt;strong&gt;iOS, macOS, or any Apple platform&lt;/strong&gt;, you know the drill:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You’re coding peacefully 💻&lt;/li&gt;
&lt;li&gt;You need to check &lt;strong&gt;how something works in SwiftUI&lt;/strong&gt; 🤔&lt;/li&gt;
&lt;li&gt;You open Safari / your favorite browser 🌐&lt;/li&gt;
&lt;li&gt;You search on Google / DuckDuckGo 🔍&lt;/li&gt;
&lt;li&gt;You land on Apple’s official documentation 📚&lt;/li&gt;
&lt;li&gt;You read, understand, go back to your IDE 🔄&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You repeat this process 47 times a day&lt;/strong&gt; 😵&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Or even worse: &lt;strong&gt;you ask your favorite AI&lt;/strong&gt; and it gives you &lt;span class="high"&gt;outdated&lt;/span&gt; or straight-up &lt;span class="high"&gt;hallucinated&lt;/span&gt; information because its knowledge isn’t up to date with the latest versions of Swift or SwiftUI 🤖❌.&lt;/p&gt;
&lt;p&gt;What if I told you there’s a better way?&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;💡 Cupertino MCP: The Solution&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/mihaelamj/cupertino"&gt;Cupertino MCP&lt;/a&gt;&lt;/strong&gt; is a tool that &lt;span class="high"&gt;locally indexes all of Apple’s documentation&lt;/span&gt; and makes it available to your AI through the &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In plain English: it’s like giving your AI (Claude, in my case) a &lt;strong&gt;direct, verified gateway&lt;/strong&gt; to all of Apple’s official documentation 🎯.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How much documentation are we talking about?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;📄 &lt;strong&gt;302,424+ pages&lt;/strong&gt; of official documentation&lt;br /&gt;&lt;br /&gt;
🧰 &lt;strong&gt;307 frameworks&lt;/strong&gt; indexed&lt;br /&gt;&lt;br /&gt;
📦 &lt;strong&gt;9,699 Swift packages&lt;/strong&gt; cataloged&lt;br /&gt;&lt;br /&gt;
💾 &lt;strong&gt;606 sample code projects&lt;/strong&gt; from Apple&lt;br /&gt;&lt;br /&gt;
📱 Complete &lt;strong&gt;Human Interface Guidelines&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;
📖 &lt;strong&gt;Swift Evolution proposals&lt;/strong&gt; (~400 proposals)&lt;br /&gt;&lt;br /&gt;
🏛️ &lt;strong&gt;Apple Archive guides&lt;/strong&gt; (legacy but valuable documentation)&lt;/p&gt;
&lt;p&gt;All of this, &lt;strong&gt;available offline&lt;/strong&gt;, no internet needed, and with &lt;span class="high"&gt;zero risk of AI hallucinations&lt;/span&gt; 🛡️.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;🎯 Absolute Precision&lt;/h2&gt;
&lt;p&gt;When Claude answers me about Apple APIs, &lt;strong&gt;it no longer guesses&lt;/strong&gt;. It searches the real official documentation and gives me &lt;span class="high"&gt;100% verified&lt;/span&gt; information.&lt;/p&gt;
&lt;p&gt;No more: &lt;em&gt;“I think in SwiftUI 6 you use it like this…”&lt;/em&gt; ❌&lt;/p&gt;
&lt;p&gt;Now it’s: &lt;em&gt;“According to the official SwiftUI 6 documentation…”&lt;/em&gt; ✅&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;⚡ Development Speed&lt;/h2&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Question → Wait for response → Doubt → Open Safari →
Search Google → Read docs → Go back to IDE → Implement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Question → Response with official documentation → Implement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span class="high"&gt;Massive time savings&lt;/span&gt; on every query 🏎️.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;📊 Super Powerful Search&lt;/h2&gt;
&lt;p&gt;Cupertino uses &lt;strong&gt;SQLite FTS5 with BM25 ranking&lt;/strong&gt;. In plain English: &lt;span class="high"&gt;ultra-fast&lt;/span&gt; searches (under 100ms) with relevant results sorted by importance.&lt;/p&gt;
&lt;p&gt;You can filter by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Specific framework 🧰&lt;/li&gt;
&lt;li&gt;Platform version (iOS 17, macOS 14, etc.) 📱&lt;/li&gt;
&lt;li&gt;Documentation type (API, examples, guides) 📚&lt;/li&gt;
&lt;li&gt;Sample code search 💻&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;🧠 Contextualized Learning&lt;/h2&gt;
&lt;p&gt;I don’t just get &lt;strong&gt;the correct API&lt;/strong&gt;, I also get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real code examples from Apple 📝&lt;/li&gt;
&lt;li&gt;Documented best practices 👍&lt;/li&gt;
&lt;li&gt;Official design patterns 🎨&lt;/li&gt;
&lt;li&gt;Alternatives and deprecations ⚠️&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s like having &lt;strong&gt;an Apple mentor inside Claude&lt;/strong&gt; 🍎🤝🤖.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;💭 Final Thoughts&lt;/h2&gt;
&lt;p&gt;Cupertino MCP is a &lt;strong&gt;perfect example&lt;/strong&gt; of how AI tools become &lt;strong&gt;truly useful&lt;/strong&gt; when they have access to &lt;span class="high"&gt;verified, up-to-date information&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;It’s not about giving AI more power to “guess better.” It’s about &lt;span class="high"&gt;connecting it with reliable sources&lt;/span&gt; so it gives you precise answers.&lt;/p&gt;
&lt;p&gt;If you develop for Apple ecosystems, Cupertino MCP is a &lt;strong&gt;5-minute investment&lt;/strong&gt; (installation) that will save you &lt;strong&gt;hours&lt;/strong&gt; every week. And if you use Claude Code like I do, the experience gets &lt;strong&gt;even better&lt;/strong&gt; 💎.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do I recommend it?&lt;/strong&gt; Absolutely. It’s a &lt;strong&gt;must-have&lt;/strong&gt; if you develop with Swift, SwiftUI, or any Apple framework 🍎.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is it for everyone?&lt;/strong&gt; Only if you develop for Apple platforms. If you work with other technologies, look for similar tools (there are surely equivalents for Android, web, etc.) 🔍.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/cupertino-mcp/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/CupertinoMCP.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/which-ai/</id>
        <title>Which AI</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>How to deal with AI tool fatigue as a developer: why chasing every new model is impossible and how to pick the right tools without burning out from FOMO.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;🎭 Déjà Vu: The Web 2.0 Frameworks&lt;/h2&gt;
&lt;p&gt;Remember that golden era when a &lt;span class="high"&gt;new web framework&lt;/span&gt; popped up every week?&lt;/p&gt;
&lt;p&gt;One that was more &lt;strong&gt;modern&lt;/strong&gt;, more &lt;strong&gt;powerful&lt;/strong&gt;, more &lt;strong&gt;performant&lt;/strong&gt;, more &lt;strong&gt;beautiful&lt;/strong&gt;… One that came to &lt;span class="high"&gt;“kill” all the previous ones&lt;/span&gt; and was going to become the &lt;span class="high"&gt;only framework you’d ever need&lt;/span&gt; 💀.&lt;/p&gt;
&lt;p&gt;Spoiler: none of them killed anything, and now we have more frameworks than ever 😂.&lt;/p&gt;
&lt;p&gt;Well then, &lt;span class="high"&gt;welcome to the AI era&lt;/span&gt;: same concept, but on steroids 🚀.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;📱 Notification Fatigue&lt;/h2&gt;
&lt;p&gt;Picture this: you’re peacefully &lt;strong&gt;coding with your trusted AI&lt;/strong&gt;, workflow on point, productivity at its peak… and suddenly:&lt;/p&gt;
&lt;p&gt;📢 &lt;strong&gt;Notification:&lt;/strong&gt; &lt;em&gt;“Revolutionary new AI just launched!”&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;
🎥 &lt;strong&gt;Video:&lt;/strong&gt; &lt;em&gt;“How to use it in 10 minutes”&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;
📝 &lt;strong&gt;Tutorial:&lt;/strong&gt; &lt;em&gt;“Integrate it into your workflow TODAY”&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;
💡 &lt;strong&gt;Thread:&lt;/strong&gt; &lt;em&gt;“Why you should switch right now”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;And here comes the &lt;strong&gt;funniest part&lt;/strong&gt;: the content creator who just 1 hour ago had a video explaining &lt;strong&gt;“Why the old AI is the future”&lt;/strong&gt;, now has another video about &lt;strong&gt;“Why the new AI is better than everything”&lt;/strong&gt; 🎪.&lt;/p&gt;
&lt;p&gt;I don’t blame them, honestly. It’s their job to stay up to date and create relevant content. But as a consumer… &lt;strong&gt;it’s exhausting&lt;/strong&gt; 😅.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;🏃‍♂️ The Impossible Race&lt;/h2&gt;
&lt;p&gt;If it’s already &lt;strong&gt;hard to keep up with the daily updates&lt;/strong&gt; of the AI you’re using (and trust me, there are &lt;strong&gt;so many&lt;/strong&gt; updates), imagine having to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Try every new AI that comes out ✅&lt;/li&gt;
&lt;li&gt;Learn its interface and quirks 📚&lt;/li&gt;
&lt;li&gt;Integrate it into your workflow 🔧&lt;/li&gt;
&lt;li&gt;Compare it with the ones you already use ⚖️&lt;/li&gt;
&lt;li&gt;Decide if the switch is worth it 🤔&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span class="high"&gt;Result:&lt;/span&gt; Mission impossible 🎯.&lt;/p&gt;
&lt;p&gt;And don’t get me wrong: &lt;strong&gt;I love having options&lt;/strong&gt;. In fact, it’s &lt;strong&gt;wonderful&lt;/strong&gt; that so much competition and innovation exists. More options = more better 👍🏻.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;🚫 My Red Line&lt;/h2&gt;
&lt;p&gt;Now, there’s something I’m &lt;strong&gt;adamant&lt;/strong&gt; about:&lt;/p&gt;
&lt;p&gt;&lt;span class="high"&gt;I will never use an AI that requires full permissions from the start&lt;/span&gt; 🔒.&lt;/p&gt;
&lt;p&gt;None of that &lt;em&gt;“Give me full access to your machine and trust me”&lt;/em&gt; stuff. &lt;strong&gt;That’s not for me&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;My philosophy is clear:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;📋 &lt;strong&gt;Tell me what you’re going to do&lt;/strong&gt; (or plan it out)&lt;/li&gt;
&lt;li&gt;👀 &lt;strong&gt;I review the changes&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;I approve&lt;/strong&gt; (or don’t approve)&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Then you proceed&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No free will for AI with admin privileges. &lt;span class="high"&gt;Full control&lt;/span&gt; is my mantra, as I already shared in my article about &lt;a href="/blog/claude-code/"&gt;Claude Code&lt;/a&gt; 💪.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;🌐 It’s No Longer Just a Model&lt;/h2&gt;
&lt;p&gt;And here’s what’s &lt;strong&gt;truly fascinating&lt;/strong&gt;: AI is no longer simply &lt;em&gt;“a model”&lt;/em&gt; or &lt;em&gt;“an app”&lt;/em&gt; that you install and use.&lt;/p&gt;
&lt;p&gt;Now we have &lt;strong&gt;complete ecosystems&lt;/strong&gt; 🏗️:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Base + fine-tuned models ⚙️&lt;/li&gt;
&lt;li&gt;IDE integrations 💻&lt;/li&gt;
&lt;li&gt;Plugins and extensions 🔌&lt;/li&gt;
&lt;li&gt;APIs and SDKs 📡&lt;/li&gt;
&lt;li&gt;Orchestration platforms 🎼&lt;/li&gt;
&lt;li&gt;Specialized tools 🛠️&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The speed at which all of this is evolving is &lt;strong&gt;incredible&lt;/strong&gt;. What was science fiction 2 years ago is now your &lt;span class="high"&gt;daily coding assistant&lt;/span&gt; 🤖.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;🎢 Living in an Incredible Era&lt;/h2&gt;
&lt;p&gt;At the end of the day, I believe we’re living through something &lt;span class="high"&gt;historic&lt;/span&gt; 🎊:&lt;/p&gt;
&lt;p&gt;🥊 &lt;strong&gt;Battle of the IDEs&lt;/strong&gt;: VSCode vs. Cursor vs. WindSurf vs. Zed vs…&lt;br /&gt;&lt;br /&gt;
⚔️ &lt;strong&gt;Battle of the Frameworks&lt;/strong&gt;: React vs. Vue vs. Svelte vs. Solid vs…&lt;br /&gt;&lt;br /&gt;
🤖 &lt;strong&gt;Battle of the AIs&lt;/strong&gt;: Claude vs. ChatGPT vs. Gemini vs. Copilot vs…&lt;/p&gt;
&lt;p&gt;Trying to keep up with everything is &lt;strong&gt;exhausting&lt;/strong&gt;, I’ll admit it. Personally, I find it &lt;span class="high"&gt;impossible&lt;/span&gt; 🙃.&lt;/p&gt;
&lt;p&gt;But it’s also &lt;strong&gt;exciting&lt;/strong&gt;. Every day there’s something new to learn, something to try, something that makes you rethink how you work.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;🎯 Final Thoughts&lt;/h2&gt;
&lt;p&gt;The takeaway from all of this is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You don’t need to use everything&lt;/strong&gt;. Really, you don’t 🙅‍♂️.&lt;/p&gt;
&lt;p&gt;Find &lt;strong&gt;the tools that work for you&lt;/strong&gt;, learn to use them well, stay up to date with &lt;strong&gt;their&lt;/strong&gt; updates, and it’s perfectly fine to explore alternatives from time to time.&lt;/p&gt;
&lt;p&gt;But don’t drive yourself crazy trying to ride &lt;strong&gt;every wave at once&lt;/strong&gt;. Pick your surfboard 🏄‍♂️, learn to use it well, and enjoy the ride.&lt;/p&gt;
&lt;p&gt;And if one day you decide to switch boards, go for it. But make it a &lt;strong&gt;conscious decision&lt;/strong&gt;, not a reaction to the &lt;span class="high"&gt;FOMO&lt;/span&gt; (&lt;em&gt;Fear Of Missing Out&lt;/em&gt;) created by 47 simultaneous notifications 😂.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/which-ai/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/WhichAI.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/conferences-25/</id>
        <title>Conferences 25</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Curated list of the best iOS, Swift, and Apple developer conferences in 2025 with dates, locations, and links to watch every talk for free.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;iOSConfSG&lt;/h2&gt;
&lt;p&gt;📅 January 15-17&lt;br /&gt;&lt;br /&gt;
📍 Singapore, 🇸🇬&lt;br /&gt;&lt;br /&gt;
🔗 &lt;a href="https://2025.iosconf.sg/"&gt;iosconf&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
🎥 &lt;a href="https://youtube.com/watch?v=N1H9lvHwQxc&amp;amp;list=PLED4k3CZkY9RBltAgj-o9xSFOMOhBdmXm"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;AppDevCon&lt;/h2&gt;
&lt;p&gt;📅 March 18–21&lt;br /&gt;&lt;br /&gt;
📍 Amsterdam, 🇳🇱&lt;br /&gt;&lt;br /&gt;
🔗 &lt;a href="https://appdevcon.nl"&gt;appdevcon&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
🎥 &lt;a href="https://appdevcon.nl/videos/"&gt;videos&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Swift Heroes&lt;/h2&gt;
&lt;p&gt;📅 April 8–9&lt;br /&gt;&lt;br /&gt;
📍 Turin, 🇮🇹&lt;br /&gt;&lt;br /&gt;
🔗 &lt;a href="https://swiftheroes.com"&gt;swiftheroes&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
🎥 &lt;a href="https://youtube.com/watch?v=cVNtQK34xdw&amp;amp;list=PLfCiO1zYKkARXhFxrqv3WR6b6JEp4LaAN"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;try! Swift Tokyo&lt;/h2&gt;
&lt;p&gt;📅 April 9–11&lt;br /&gt;&lt;br /&gt;
📍 Tokyo, 🇯🇵&lt;br /&gt;&lt;br /&gt;
🔗 &lt;a href="https://tryswift.jp/en"&gt;tryswift&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
🎥 &lt;a href="https://youtube.com/watch?v=qY09lmDo7GU&amp;amp;list=PLCl5NM4qD3u_Azg7gKw5CK_DqSLeb4QMY"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Deep Dish Swift&lt;/h2&gt;
&lt;p&gt;📅 April 27–29&lt;br /&gt;&lt;br /&gt;
📍 Chicago, 🇺🇸&lt;br /&gt;&lt;br /&gt;
🔗 &lt;a href="https://deepdishswift.com"&gt;deepdishswift&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
🎥 &lt;a href="https://youtube.com/watch?v=eN6NJR67HL4&amp;amp;list=PLGLg44jP5U-4l1OERkMTPrzgG7nQkGGYy"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;iOSKonf&lt;/h2&gt;
&lt;p&gt;📅 May 13–15&lt;br /&gt;&lt;br /&gt;
📍 Skopje, 🇲🇰&lt;br /&gt;&lt;br /&gt;
🔗 &lt;a href="https://ioskonf.mk"&gt;ioskonf&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
🎥 &lt;a href="https://youtube.com/watch?v=AIvCc1v5F4g&amp;amp;list=PLVKQDFwOy1XZXhFOdWfXKdjCz4r_LBeto"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Swift Craft&lt;/h2&gt;
&lt;p&gt;📅 May 19–21&lt;br /&gt;&lt;br /&gt;
📍 Kent, 🇬🇧&lt;br /&gt;&lt;br /&gt;
🔗 &lt;a href="https://swiftcraft.uk"&gt;swiftcraft&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
🎥 &lt;a href="https://youtube.com/watch?v=XR4XrVvHUQo&amp;amp;list=PLugrLwuQvERolaa2XA0dl4UK9Z8XEwiAA"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;WWDC&lt;/h2&gt;
&lt;p&gt;📅 June 9–13&lt;br /&gt;&lt;br /&gt;
📍 Cupertino, 🇺🇸&lt;br /&gt;&lt;br /&gt;
🔗 &lt;a href="https://developer.apple.com/wwdc25"&gt;apple&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
🎥 &lt;a href="https://youtube.com/watch?v=IrGYUq1mklk&amp;amp;list=PLjODKV8YBFHZKEn1wsUCL1n-q7tzysEBM"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;NSSpain&lt;/h2&gt;
&lt;p&gt;📅 September 17-19&lt;br /&gt;&lt;br /&gt;
📍 Logroño, 🇪🇸&lt;br /&gt;&lt;br /&gt;
🔗 &lt;a href="https://nsspain.com"&gt;nsspain&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
🎥 &lt;a href="https://youtube.com/watch?v=NjW7lxtZwIM&amp;amp;list=PLztE34GS_piKKQ6y1dkkuhW76jLBHm3NV"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Swift Connection&lt;/h2&gt;
&lt;p&gt;📅 October 6–7&lt;br /&gt;&lt;br /&gt;
📍 Paris, 🇫🇷&lt;br /&gt;&lt;br /&gt;
🔗 &lt;a href="https://swiftconnection.io"&gt;swiftconnection&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
🎥 &lt;a href="https://youtube.com/watch?v=N05WEfGPp3M&amp;amp;list=PLZsRQnRG-mlIkHsjeax_cRq6kAclNrWBF"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;SwiftLeeds&lt;/h2&gt;
&lt;p&gt;📅 October 7–8&lt;br /&gt;&lt;br /&gt;
📍 Leeds, 🇬🇧&lt;br /&gt;&lt;br /&gt;
🔗 &lt;a href="https://swiftleeds.co.uk"&gt;swiftleeds&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
🎥 &lt;a href="https://youtube.com/watch?v=iNiQHGKULQw&amp;amp;list=PL-wmxEeX64YTpDbpfszWMV76oZZO3wxZH"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Pragma Conference&lt;/h2&gt;
&lt;p&gt;📅 October 30–31&lt;br /&gt;&lt;br /&gt;
📍 Bologna, 🇮🇹&lt;br /&gt;&lt;br /&gt;
🔗 &lt;a href="https://pragmaconference.com"&gt;pragmaconference&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
🎥 &lt;a href="https://youtube.com/watch?v=7o6Fkj3buxM&amp;amp;list=PLAVm70iJlMuvTihK1OzK9S4Vzw_KO71b0"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Do iOS&lt;/h2&gt;
&lt;p&gt;📅 November 12-13&lt;br /&gt;&lt;br /&gt;
📍 Amsterdam, 🇳🇱&lt;br /&gt;&lt;br /&gt;
🔗 &lt;a href="https://do-ios.com"&gt;do-ios&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
🎥 &lt;a href="https://youtube.com/watch?v=q-Y44dh6sTo&amp;amp;list=PLJEA4wsA0WeSP_NB1LcqcA7tBjhcg1u4U"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/conferences-25/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/Conferences25.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/async-concurrent-map/</id>
        <title>Async Concurrent Map</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>How to combine concurrent processing with chunking to optimize resource usage in massive asynchronous operations.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;In backend applications with &lt;span class="high"&gt;Vapor&lt;/span&gt;, when we process &lt;strong&gt;large volumes of data&lt;/strong&gt; concurrently, we face an optimization dilemma:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sequential processing&lt;/strong&gt; with &lt;span class="high"&gt;asyncMap&lt;/span&gt;: guarantees resource control, but is &lt;strong&gt;slow&lt;/strong&gt; by processing elements one by one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fully concurrent processing&lt;/strong&gt; with &lt;span class="high"&gt;concurrentMap&lt;/span&gt;: maximizes speed, but can &lt;strong&gt;saturate resources&lt;/strong&gt; by launching thousands of simultaneous tasks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, when processing 10,000 records with external API calls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="high"&gt;asyncMap&lt;/span&gt;: 10,000 sequential calls → very slow but controlled.&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;concurrentMap&lt;/span&gt;: 10,000 simultaneous calls → very fast but can exhaust connections/memory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We need a solution that &lt;strong&gt;combines both approaches&lt;/strong&gt;: divide the work into manageable groups and process each group concurrently, balancing speed and resource usage.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;We extend &lt;span class="high"&gt;Collection&lt;/span&gt; with a function that combines &lt;strong&gt;chunking&lt;/strong&gt; (division into groups) and &lt;strong&gt;concurrent processing&lt;/strong&gt;, allowing configuration of chunk size and timeout per chunk.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Collection where Element: Sendable {
    func asyncConcurrentMap&amp;lt;T: Sendable&amp;gt;(
        chunkSize: Int? = nil,
        timeout: Double? = nil,
        _ transform: @escaping @Sendable (Element) async throws -&amp;gt; T
    ) async throws -&amp;gt; [T] {
        guard let chunkSize else {
            return try await concurrentMap(transform)
        }

        return try await chunks(ofCount: chunkSize)
            .asyncMap(timeout: timeout) {
                try await $0.concurrentMap(transform)
            }.flatMap { $0 }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If &lt;span class="high"&gt;chunkSize&lt;/span&gt; is not specified, uses pure &lt;span class="high"&gt;concurrentMap&lt;/span&gt; (fully concurrent processing).&lt;/li&gt;
&lt;li&gt;If &lt;span class="high"&gt;chunkSize&lt;/span&gt; is specified, divides the collection into groups with &lt;span class="high"&gt;chunks(ofCount:)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Processes the &lt;strong&gt;chunks sequentially&lt;/strong&gt; with &lt;span class="high"&gt;asyncMap&lt;/span&gt; (with optional timeout).&lt;/li&gt;
&lt;li&gt;Within each chunk, processes the elements &lt;strong&gt;concurrently&lt;/strong&gt; with &lt;span class="high"&gt;concurrentMap&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Flattens the results with &lt;span class="high"&gt;flatMap&lt;/span&gt; to return a unified array.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;// Process 10,000 records in chunks of 100
// 100 concurrent tasks at a time, 100 times
let results = try await records.asyncConcurrentMap(
    chunkSize: 100,
    timeout: 30.0
) { record in
    try await apiClient.process(record)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Benefits of this approach:&lt;/p&gt;
&lt;p&gt;⚡ &lt;strong&gt;Perfect balance&lt;/strong&gt;: combines the speed of concurrent processing with the control of sequential processing by chunks.&lt;br /&gt;&lt;br /&gt;
🎯 &lt;strong&gt;Resource control&lt;/strong&gt;: limits the number of simultaneous tasks to the chunk size, avoiding saturation.&lt;br /&gt;&lt;br /&gt;
⏱️ &lt;strong&gt;Timeout per chunk&lt;/strong&gt;: detects and handles problematic chunks without blocking all processing.&lt;br /&gt;&lt;br /&gt;
🔧 &lt;strong&gt;Full flexibility&lt;/strong&gt;: use chunking when you need it, or pure concurrent processing when you don’t.&lt;br /&gt;&lt;br /&gt;
📊 &lt;strong&gt;Scalability&lt;/strong&gt;: allows processing millions of records by adjusting chunk size according to available resources.&lt;/p&gt;
&lt;p&gt;If you need pure parallel execution, I covered that in &lt;a href="/blog/concurrent-map/"&gt;Concurrent Map&lt;/a&gt;. And if your sequential chunks need rate limiting between them, I added that capability in &lt;a href="/blog/async-map-timeout/"&gt;Async Map Timeout&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This solution is the &lt;strong&gt;natural evolution&lt;/strong&gt; of &lt;span class="high"&gt;asyncMap&lt;/span&gt; and &lt;span class="high"&gt;concurrentMap&lt;/span&gt;, combining the best of both worlds to optimize massive data processing in backend applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/async-concurrent-map/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/AsyncConcurrentMap.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/async-map-timeout/</id>
        <title>Async Map Timeout</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Updating asyncMap to add rate limiting control through timeouts between operations.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;In the &lt;a href="/blog/async-map/"&gt;post about AsyncMap&lt;/a&gt; we saw how to process collections in a &lt;strong&gt;sequential asynchronous&lt;/strong&gt; manner. However, when working with &lt;strong&gt;external APIs&lt;/strong&gt; that implement rate limiting, we face a critical problem:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;// Process 1000 URLs sequentially
let results = try await urls.asyncMap { url in
    try await apiClient.fetch(url)  // ⚠️ 1000 calls without pause
}
// Error 429: Too Many Requests
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Making consecutive calls without pauses can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Exceed rate limits&lt;/strong&gt;: APIs reject with &lt;span class="high"&gt;429 Too Many Requests&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saturate external services&lt;/strong&gt;: overload of simultaneous connections.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Waste resources&lt;/strong&gt;: forcing retries consumes more time and bandwidth.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Temporary blocks&lt;/strong&gt;: some APIs block the IP after multiple violations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We need a way to &lt;strong&gt;control the pace&lt;/strong&gt; of sequential operations, adding intentional pauses between each processing.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;We update &lt;span class="high"&gt;asyncMap&lt;/span&gt; by adding an optional &lt;span class="high"&gt;timeout&lt;/span&gt; parameter that introduces a configurable pause after processing each element.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Sequence {
    func asyncMap&amp;lt;T&amp;gt;(
        timeout: Double? = nil,
        _ transform: (Element) async throws -&amp;gt; T
    ) async throws -&amp;gt; [T] {
        var results = [T]()
        results.reserveCapacity(underestimatedCount)
        for element in self {
            try await results.append(transform(element))
            if let timeout {
                try await Task.sleep(for: .seconds(timeout))
            }
        }
        return results
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Key changes from the original version&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;✨ Optional and backward-compatible &lt;span class="high"&gt;timeout: Double? = nil&lt;/span&gt; parameter.&lt;br /&gt;&lt;br /&gt;
⏱️ If timeout is specified, adds &lt;span class="high"&gt;Task.sleep(for: .seconds(timeout))&lt;/span&gt; after each element.&lt;br /&gt;&lt;br /&gt;
🔄 Maintains original behavior when timeout is not specified (no pauses).&lt;br /&gt;&lt;br /&gt;
📊 Allows dynamic rate limiting adjustment according to each API’s limits.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;// Rate limiting: 1 call per second
let results = try await urls.asyncMap(timeout: 1.0) {
    try await apiClient.fetch($0)
}

// Aggressive rate limiting: 1 call every 5 seconds
let results = try await endpoints.asyncMap(timeout: 5.0) {
    try await scraper.parse($0)
}

// Without timeout: original behavior (maximum speed)
let results = try await localFiles.asyncMap {
    try await processFile($0)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Benefits of this update:&lt;/p&gt;
&lt;p&gt;⏱️ &lt;strong&gt;Configurable rate limiting&lt;/strong&gt;: controls the call pace according to each API’s limits.&lt;br /&gt;&lt;br /&gt;
🛡️ &lt;strong&gt;Block prevention&lt;/strong&gt;: avoids &lt;span class="high"&gt;429&lt;/span&gt; errors and temporary IP suspensions.&lt;br /&gt;&lt;br /&gt;
🔄 &lt;strong&gt;Full backward compatibility&lt;/strong&gt;: without timeout it works exactly as before.&lt;br /&gt;&lt;br /&gt;
🎯 &lt;strong&gt;Flexibility per use case&lt;/strong&gt;: adjusts timeout based on external service tolerance.&lt;br /&gt;&lt;br /&gt;
📊 &lt;strong&gt;Predictable processing&lt;/strong&gt;: easily calculate total time (n elements × timeout).&lt;/p&gt;
&lt;p&gt;This update turns &lt;span class="high"&gt;asyncMap&lt;/span&gt; into a &lt;strong&gt;complete tool for controlled sequential processing&lt;/strong&gt;, ideal for integration with APIs that impose rate limits and need a regulated request flow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/async-map-timeout/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/AsyncMapTimeout.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/copy-to/</id>
        <title>Copy To</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Export large datasets from PostgreSQL to CSV in Vapor using the native COPY TO command, avoiding manual serialization and reducing memory consumption.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;In backend applications with &lt;span class="high"&gt;Vapor&lt;/span&gt;, we often need to &lt;strong&gt;export large volumes of data&lt;/strong&gt; from the database to files for different purposes: backups, offline analysis, integration with external systems, or data audits.&lt;/p&gt;
&lt;p&gt;Using traditional queries with &lt;span class="high"&gt;Fluent&lt;/span&gt; and then manually serializing the results presents several drawbacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;High memory consumption&lt;/strong&gt;: loading thousands of records into memory to process them one by one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slow processing&lt;/strong&gt;: manual serialization to &lt;span class="high"&gt;CSV&lt;/span&gt; requires iterating and formatting each record.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lack of optimization&lt;/strong&gt;: doesn’t leverage the native export capabilities of the database engine.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unnecessary complexity&lt;/strong&gt;: manual management of formats, character escaping, and null value handling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For bulk export scenarios, we need a strategy that leverages &lt;span class="high"&gt;PostgreSQL&lt;/span&gt;’s native capabilities to generate files efficiently.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;We extend &lt;span class="high"&gt;Database&lt;/span&gt; with a function that executes the &lt;span class="high"&gt;COPY … TO&lt;/span&gt; command from &lt;span class="high"&gt;PostgreSQL&lt;/span&gt;, allowing data export directly from the table schema to &lt;span class="high"&gt;CSV&lt;/span&gt; files in the filesystem.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Database {
    func exportCSV(
        _ model: any Model.Type,
        file: PathEnum)
        async throws {
        let query = SQLQueryString(
            &amp;quot;&amp;quot;&amp;quot;
            COPY \&amp;quot;\(unsafeRaw: model.space ?? &amp;quot;public&amp;quot;)\&amp;quot;.
            \&amp;quot;\(unsafeRaw: model.schema)\&amp;quot;
            TO '\(unsafeRaw: file.rawValue)'
            WITH (FORMAT csv, HEADER true, DELIMITER ',',
            QUOTE '&amp;quot;', ESCAPE '&amp;quot;', NULL '')
            &amp;quot;&amp;quot;&amp;quot;
        )

        try await self.sqlDatabase
            .raw(query).run()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses &lt;span class="high"&gt;PostgreSQL&lt;/span&gt;’s &lt;span class="high"&gt;COPY … TO&lt;/span&gt;, the most efficient method for bulk exports.&lt;/li&gt;
&lt;li&gt;The &lt;span class="high"&gt;model.space&lt;/span&gt; parameter supports custom schemas (defaults to &lt;span class="high"&gt;“public”&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;model.schema&lt;/span&gt; automatically obtains the table name from the &lt;span class="high"&gt;Fluent&lt;/span&gt; model.&lt;/li&gt;
&lt;li&gt;Standard &lt;span class="high"&gt;CSV&lt;/span&gt; configuration: headers included, delimiters, and proper null value handling.&lt;/li&gt;
&lt;li&gt;Uses &lt;span class="high"&gt;unsafeRaw&lt;/span&gt; for direct interpolation in the SQL query.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func exportRegions() async throws {
    try await db.exportCSV(
        LocationRegionModel.self,
        file: file
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Benefits of this approach:&lt;/p&gt;
&lt;p&gt;🚀 &lt;strong&gt;Optimal performance&lt;/strong&gt;: &lt;span class="high"&gt;COPY … TO&lt;/span&gt; is much faster than manual serialization.&lt;br /&gt;&lt;br /&gt;
💾 &lt;strong&gt;Resource efficiency&lt;/strong&gt;: &lt;span class="high"&gt;PostgreSQL&lt;/span&gt; writes directly to the file without loading data into application memory.&lt;br /&gt;&lt;br /&gt;
📦 &lt;strong&gt;Consistent format&lt;/strong&gt;: the database engine guarantees a valid &lt;span class="high"&gt;CSV&lt;/span&gt; with proper escaping.&lt;br /&gt;&lt;br /&gt;
🔧 &lt;strong&gt;Native integration&lt;/strong&gt;: leverages optimized capabilities of the &lt;span class="high"&gt;PostgreSQL&lt;/span&gt; engine.&lt;br /&gt;&lt;br /&gt;
📊 &lt;strong&gt;Scalability&lt;/strong&gt;: allows exporting millions of records without impacting application performance.&lt;/p&gt;
&lt;p&gt;This solution is the perfect complement to &lt;span class="high"&gt;importCSV&lt;/span&gt;, forming a pair of functions that enables &lt;strong&gt;bidirectional data movement&lt;/strong&gt; between &lt;span class="high"&gt;PostgreSQL&lt;/span&gt; and the filesystem efficiently and reliably. If you haven’t seen the import side yet, I explain how to build it using PostgreSQL’s &lt;code&gt;COPY FROM&lt;/code&gt; in &lt;a href="/blog/copy-from/"&gt;Copy From&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/copy-to/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/CopyTo.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/copy-from/</id>
        <title>Copy From</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>How to perform bulk inserts with Vapor to speed up your database data insertions.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;In backend applications with &lt;span class="high"&gt;Vapor&lt;/span&gt;, when we need to insert &lt;strong&gt;large volumes of data&lt;/strong&gt; into the database (initial migrations, catalog imports, bulk loading from external APIs), using &lt;span class="high"&gt;.save()&lt;/span&gt; in a loop generates &lt;strong&gt;multiple individual transactions&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This results in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;High latency&lt;/strong&gt;: each insert opens/closes connection and transaction overhead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Poor throughput&lt;/strong&gt;: doesn’t leverage the database engine’s bulk insertion capabilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timeout risk&lt;/strong&gt;: slow operations that may fail in environments with time constraints.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For mass import scenarios (thousands or millions of records), we need a &lt;strong&gt;bulk insert&lt;/strong&gt; strategy that leverages native &lt;span class="high"&gt;PostgreSQL&lt;/span&gt; capabilities.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;We extend &lt;span class="high"&gt;Database&lt;/span&gt; with a function that executes &lt;span class="high"&gt;PostgreSQL’s&lt;/span&gt; &lt;span class="high"&gt;COPY&lt;/span&gt; command, allowing us to import &lt;span class="high"&gt;CSV&lt;/span&gt; files directly from the file system into the table schema.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Database {
    func importCSV(
        _ model: any Model.Type,
        file: PathEnum
        ) async throws {
        let query = SQLQueryString(
            &amp;quot;&amp;quot;&amp;quot;
            COPY \&amp;quot;\(unsafeRaw: model.space ?? &amp;quot;public&amp;quot;)\&amp;quot;.
            \&amp;quot;\(unsafeRaw: model.schema)\&amp;quot;
            FROM '\(unsafeRaw: file.rawValue)'
            WITH (FORMAT csv, HEADER true, DELIMITER ',',
            QUOTE '&amp;quot;', ESCAPE '&amp;quot;', NULL '')
            &amp;quot;&amp;quot;&amp;quot;
        )

        try await self.sqlDatabase
            .raw(query).run()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses &lt;span class="high"&gt;PostgreSQL’s&lt;/span&gt; &lt;span class="high"&gt;COPY&lt;/span&gt;, the fastest method for bulk insert from files.&lt;/li&gt;
&lt;li&gt;The &lt;span class="high"&gt;model.space&lt;/span&gt; parameter supports custom schemas (defaults to &lt;span class="high"&gt;“public”&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;model.schema&lt;/span&gt; automatically gets the table name from the &lt;span class="high"&gt;Fluent&lt;/span&gt; model.&lt;/li&gt;
&lt;li&gt;Standard &lt;span class="high"&gt;CSV&lt;/span&gt; configuration: headers, delimiters, and null value handling.&lt;/li&gt;
&lt;li&gt;Uses &lt;span class="high"&gt;unsafeRaw&lt;/span&gt; for direct interpolation in the SQL query.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;private func importRegions() async throws {
    try await db.importCSV(
        RegionModel.self,
        file: .LocationFile(&amp;quot;regions&amp;quot;, .csv)
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Benefits of this approach:&lt;/p&gt;
&lt;p&gt;🚀 &lt;strong&gt;Extreme performance&lt;/strong&gt;: &lt;span class="high"&gt;COPY&lt;/span&gt; is up to &lt;strong&gt;10-100x faster&lt;/strong&gt; than individual inserts.&lt;br /&gt;&lt;br /&gt;
📦 &lt;strong&gt;Atomic transaction&lt;/strong&gt;: the entire import happens in a single operation, guaranteeing consistency.&lt;br /&gt;&lt;br /&gt;
💾 &lt;strong&gt;Resource efficiency&lt;/strong&gt;: minimizes memory usage and database connections.&lt;br /&gt;&lt;br /&gt;
🔧 &lt;strong&gt;Native integration&lt;/strong&gt;: leverages optimized &lt;span class="high"&gt;PostgreSQL&lt;/span&gt; engine capabilities.&lt;br /&gt;&lt;br /&gt;
📊 &lt;strong&gt;Scalability&lt;/strong&gt;: allows importing millions of records without significant degradation.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;p&gt;This implementation uses &lt;span class="high"&gt;COPY … FROM&lt;/span&gt; with file system files. There is currently an &lt;strong&gt;open issue in Vapor&lt;/strong&gt; to implement native support for &lt;span class="high"&gt;COPY … FROM STDIN&lt;/span&gt;, which would allow performing bulk inserts directly from memory without intermediate files.&lt;/p&gt;
&lt;p&gt;I’m actively monitoring this issue to integrate this functionality when available, which will provide an even more flexible and efficient API for mass import operations.&lt;/p&gt;
&lt;p&gt;If you also need the reverse operation — exporting data from PostgreSQL to CSV files — I covered that in &lt;a href="/blog/copy-to/"&gt;Copy To&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/copy-from/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/CopyFrom.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/swift-package/</id>
        <title>Swift Package</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Complete guide to Swift Package Manager cleanup commands: clean, reset, purge-cache, and when to use each one to resolve dependency issues.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;The broken dependencies dilemma&lt;/h2&gt;
&lt;p&gt;When working with &lt;strong&gt;Swift Package Manager (SPM)&lt;/strong&gt;, you’ll eventually encounter a compilation error that makes no sense. You’ve tried building multiple times, restarted Xcode, but the error persists. The solution often lies in &lt;strong&gt;properly cleaning SPM caches and artifacts&lt;/strong&gt;, but which command should you use?&lt;/p&gt;
&lt;p&gt;There are multiple ways to “clean” in SPM, each with a specific purpose. Using the wrong command may not solve your problem or, worse yet, force you to download gigabytes of dependencies again.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The four paths to clean&lt;/h2&gt;
&lt;p&gt;SPM offers three official commands plus a manual alternative. Each affects different parts of the system:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Deletes local .build&lt;/th&gt;
&lt;th&gt;Deletes Package.resolved&lt;/th&gt;
&lt;th&gt;Deletes global cache&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;swift package clean&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;❌ (only compiled binaries)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;swift package reset&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;✅ (complete)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;swift package purge-cache&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;rm -rf .build&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;✅ (complete)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Swift package clean&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt;&lt;br /&gt;
Removes only the &lt;strong&gt;final compiled binaries&lt;/strong&gt; inside the &lt;span class="high"&gt;.build&lt;/span&gt; folder, but &lt;strong&gt;keeps downloaded dependencies and intermediate files&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;After changing build configurations&lt;/li&gt;
&lt;li&gt;To force a complete rebuild without re-downloading dependencies&lt;/li&gt;
&lt;li&gt;When binaries are corrupted but sources are fine&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 Does not download dependencies again nor resolve package cache issues.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Swift package reset&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package reset
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;Completely&lt;/strong&gt; removes the &lt;span class="high"&gt;.build&lt;/span&gt; folder (including downloaded dependencies) and the &lt;span class="high"&gt;Package.resolved&lt;/span&gt; file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When there are conflicts in dependency versions&lt;/li&gt;
&lt;li&gt;After significant changes to &lt;span class="high"&gt;Package.swift&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;To resolve “dependency not found” errors&lt;/li&gt;
&lt;li&gt;When &lt;span class="high"&gt;Package.resolved&lt;/span&gt; is outdated or corrupted&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ The next build will download and resolve all dependencies from scratch. This can take several minutes depending on the number of packages.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Swift package purge-cache&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package purge-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;💡 Available from Swift 5.7 onwards.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt;&lt;br /&gt;
Removes the &lt;strong&gt;global package cache&lt;/strong&gt; located at:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~/Library/Caches/org.swift.swiftpm/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This cache contains cloned repositories and binary artifacts shared across all your projects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When multiple projects have the same issue&lt;/li&gt;
&lt;li&gt;After updating Xcode or Swift tooling&lt;/li&gt;
&lt;li&gt;To free up disk space (can take up several GB)&lt;/li&gt;
&lt;li&gt;When you suspect the global cache is corrupted&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ All your projects will need to re-download common dependencies.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;rm -rf .build&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;rm -rf .build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt;&lt;br /&gt;
Manually deletes the complete &lt;span class="high"&gt;.build&lt;/span&gt; folder, similar to &lt;span class="high"&gt;reset&lt;/span&gt; but &lt;strong&gt;without touching&lt;/strong&gt; &lt;span class="high"&gt;Package.resolved&lt;/span&gt;. It’s less aggressive than reset because it doesn’t re-resolve dependencies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To clean build artifacts while maintaining version resolution&lt;/li&gt;
&lt;li&gt;In CI/CD scripts where you want full control&lt;/li&gt;
&lt;li&gt;When &lt;span class="high"&gt;swift package reset&lt;/span&gt; is not available&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 Preserves &lt;span class="high"&gt;Package.resolved&lt;/span&gt;, which means dependency versions won’t be re-resolved.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Troubleshooting strategy&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Level 1: Light cleanup&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package clean
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 Try the least invasive first. Solves 30% of issues.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Level 2: Complete project reset&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package reset
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 If level 1 fails. Solves 60% of remaining issues.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Level 3: Purge global cache&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package purge-cache
swift package reset
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 For persistent issues or those affecting multiple projects.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Level 4: Nuclear&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;rm -rf .build
rm Package.resolved
swift package purge-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 Last resort. Complete fresh start.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Real-world use cases&lt;/h2&gt;
&lt;h3&gt;Scenario 1: Error after updating Xcode&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package purge-cache
swift package reset
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 Build tools changed and the cache may have incompatible artifacts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Scenario 2: Dependency version conflicts&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package reset
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 You need to re-resolve all dependencies with the new constraints.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Scenario 3: Disk space full&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package purge-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 The global cache can grow to several GB without you noticing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Scenario 4: CI/CD builds&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;rm -rf .build
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 In continuous integration environments, you want clean but reproducible builds with versioned &lt;code&gt;Package.resolved&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Understanding the components&lt;/h2&gt;
&lt;h3&gt;.build (local)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Current project’s build artifacts&lt;/li&gt;
&lt;li&gt;Project-specific downloaded dependencies&lt;/li&gt;
&lt;li&gt;Intermediate build files&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Package.resolved&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;“Lockfile” that pins exact dependency versions&lt;/li&gt;
&lt;li&gt;Guarantees reproducible builds&lt;/li&gt;
&lt;li&gt;Should be versioned in Git for shared projects&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Global cache&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Shared across all your Swift projects&lt;/li&gt;
&lt;li&gt;Contains clones of dependency repositories&lt;/li&gt;
&lt;li&gt;Pre-compiled binary artifacts&lt;/li&gt;
&lt;li&gt;Can reach several GB over time&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Best practices&lt;/h2&gt;
&lt;p&gt;✅ &lt;strong&gt;Version&lt;/strong&gt; &lt;span class="high"&gt;Package.resolved&lt;/span&gt; in Git to ensure the whole team uses the same versions&lt;br /&gt;&lt;br /&gt;
✅ &lt;strong&gt;Use&lt;/strong&gt; &lt;span class="high"&gt;reset&lt;/span&gt; &lt;strong&gt;after changes to Package.swift&lt;/strong&gt; to ensure clean resolution&lt;br /&gt;&lt;br /&gt;
✅ &lt;strong&gt;Purge the cache periodically&lt;/strong&gt; if you work with many projects&lt;br /&gt;&lt;br /&gt;
✅ &lt;strong&gt;In CI/CD, keep&lt;/strong&gt; &lt;span class="high"&gt;Package.resolved&lt;/span&gt; but delete &lt;span class="high"&gt;.build&lt;/span&gt; for clean builds&lt;br /&gt;&lt;br /&gt;
❌ &lt;strong&gt;Don’t ignore&lt;/strong&gt; &lt;span class="high"&gt;.build&lt;/span&gt; &lt;strong&gt;in .gitignore&lt;/strong&gt; - it’s already ignored by default&lt;br /&gt;&lt;br /&gt;
❌ &lt;strong&gt;Don’t delete&lt;/strong&gt; &lt;span class="high"&gt;Package.resolved&lt;/span&gt; unless you really need to re-resolve versions&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Understanding the difference between these commands saves you time and frustration. Not all build issues need a nuclear cleanup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="high"&gt;clean&lt;/span&gt; → Only compiled binaries&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;reset&lt;/span&gt; → Complete project + Package.resolved&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;purge-cache&lt;/span&gt; → Shared global cache&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;rm -rf&lt;/span&gt; → Surgical manual control&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The next time SPM shows you a strange error, you’ll know exactly which tool to use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/swift-package/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/SwiftPackage.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/sub-process/</id>
        <title>Subprocess</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Migrating from Process to Subprocess, the new cross-platform package for launching processes in Swift.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;When working with external processes in &lt;span class="high"&gt;Swift&lt;/span&gt; applications, the traditional &lt;span class="high"&gt;Process&lt;/span&gt; class presents several significant limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Manual resource management&lt;/strong&gt;: Requires explicit configuration of executable URLs, arguments, and output handling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lack of async/await support&lt;/strong&gt;: Uses synchronous methods that block the execution thread&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex error handling&lt;/strong&gt;: Makes it difficult to capture and process errors from child processes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verbose configuration&lt;/strong&gt;: Each execution requires multiple lines of repetitive configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the previous code, I needed to execute &lt;span class="high"&gt;Ghostscript&lt;/span&gt; to convert PDF files to PNG images, but the implementation with &lt;span class="high"&gt;Process&lt;/span&gt; was extensive and inelegant.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func _PDFToImages(
    _ fileName: PathEnum
) async throws {
    let process = Process()

    process.executableURL = URL(
        fileURLWithPath: &amp;quot;/opt/homebrew/bin/gs&amp;quot;
    )

    process.arguments = [
        &amp;quot;-dNOPAUSE&amp;quot;, &amp;quot;-dBATCH&amp;quot;,
        &amp;quot;-dQUIET&amp;quot;, &amp;quot;-sDEVICE=png16m&amp;quot;,
        &amp;quot;-r300&amp;quot;,
        &amp;quot;-sOutputFile=\(fileName.rawValue)-%d.png&amp;quot;,
        fileName.rawValue.appending(&amp;quot;.pdf&amp;quot;),
    ]

    try process.run()
    process.waitUntilExit()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;Apple’s new &lt;span class="high"&gt;Subprocess&lt;/span&gt; library provides a modern and robust API for process execution in &lt;span class="high"&gt;Swift&lt;/span&gt;. This &lt;strong&gt;cross-platform&lt;/strong&gt; library offers native support for &lt;span class="high"&gt;async/await&lt;/span&gt; and automatic resource management.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Native async/await API&lt;/strong&gt; for non-blocking operations.&lt;br /&gt;&lt;br /&gt;
✅ &lt;strong&gt;Automatic resource management&lt;/strong&gt; and process cleanup.&lt;br /&gt;&lt;br /&gt;
✅ &lt;strong&gt;Granular output control&lt;/strong&gt; (stdout, stderr).&lt;br /&gt;&lt;br /&gt;
✅ &lt;strong&gt;Integrated termination status verification.&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;
✅ &lt;strong&gt;Concise and expressive syntax.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The modernized implementation uses the &lt;span class="high"&gt;run&lt;/span&gt; function from &lt;span class="high"&gt;Subprocess&lt;/span&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func _PDFToImages(
    _ fileName: PathEnum
) async throws {
    let result = try await run(
        .path(.init(&amp;quot;/opt/homebrew/bin/gs&amp;quot;)),
        arguments: [
            &amp;quot;-dNOPAUSE&amp;quot;, &amp;quot;-dBATCH&amp;quot;,
            &amp;quot;-dQUIET&amp;quot;, &amp;quot;-sDEVICE=png16m&amp;quot;,
            &amp;quot;-r300&amp;quot;,
            &amp;quot;-sOutputFile=\(fileName.rawValue)-%d.png&amp;quot;,
            fileName.rawValue.appending(&amp;quot;.pdf&amp;quot;),
        ],
        output: .discarded,
        error: .string(limit: .max)
    )

    try guardAndLogError(
        result.terminationStatus == .exited(0),
        message: result.standardError,
        status: .internalServerError
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Key parameters:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="high"&gt;.path&lt;/span&gt;: Specifies the executable directly&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;output: .discarded&lt;/span&gt;: Discards standard output since we don’t need to process it&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;error: .string(limit: .max)&lt;/span&gt;: Captures errors as a string for logging&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;result.terminationStatus&lt;/span&gt;: Verifies that the process terminated successfully&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;The migration to &lt;span class="high"&gt;Subprocess&lt;/span&gt; transforms process management code into a &lt;strong&gt;cleaner, safer, and more efficient&lt;/strong&gt; solution:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Benefits achieved:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;🚀 &lt;strong&gt;Improved performance&lt;/strong&gt; with true asynchronous operations.&lt;br /&gt;&lt;br /&gt;
🔒 &lt;strong&gt;Better error handling&lt;/strong&gt; with integrated stderr capture.&lt;br /&gt;&lt;br /&gt;
📝 &lt;strong&gt;More readable code&lt;/strong&gt; with less manual configuration.&lt;br /&gt;&lt;br /&gt;
⚡ &lt;strong&gt;Seamless integration&lt;/strong&gt; with the modern Swift concurrency ecosystem.&lt;/p&gt;
&lt;p&gt;The new implementation not only reduces complexity but also &lt;strong&gt;improves system robustness&lt;/strong&gt; by providing better error visibility and more elegant asynchronous execution handling in &lt;span class="high"&gt;Vapor&lt;/span&gt; applications.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Note on DYLD_LIBRARY_PATH&lt;/h2&gt;
&lt;p&gt;In earlier versions of &lt;span class="high"&gt;Swift&lt;/span&gt;, there was a known issue with the &lt;span class="high"&gt;DYLD_LIBRARY_PATH&lt;/span&gt; environment variable when executing external processes. Due to macOS’s &lt;strong&gt;System Integrity Protection (SIP)&lt;/strong&gt; restrictions, this variable was automatically removed when launching subprocesses, causing “Library not loaded” errors in certain cases.&lt;/p&gt;
&lt;p&gt;The temporary solution required manually configuring library paths using &lt;span class="high"&gt;install_name_tool&lt;/span&gt; with &lt;span class="high"&gt;@rpath&lt;/span&gt;, or alternatively setting the &lt;span class="high"&gt;DYLD_LIBRARY_PATH&lt;/span&gt; variable instead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good news!&lt;/strong&gt; 🎉 This issue has been resolved in recent versions of &lt;span class="high"&gt;Swift&lt;/span&gt;. The &lt;span class="high"&gt;Subprocess&lt;/span&gt; library correctly handles system environment variables, including &lt;span class="high"&gt;DYLD_LIBRARY_PATH&lt;/span&gt;, without requiring additional configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/sub-process/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/Subprocess.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/json-snakecase/</id>
        <title>Json Snake Case</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Conveniences for encoding/decoding JSON in snake_case without boilerplate, valid for both iOS client and Vapor server.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;When integrating REST APIs, it’s very common for JSON keys to come in &lt;span class="high"&gt;snake_case&lt;/span&gt; (for example, &lt;strong&gt;first_name&lt;/strong&gt;) while in Swift we model properties in &lt;span class="high"&gt;camelCase&lt;/span&gt; (&lt;strong&gt;firstName&lt;/strong&gt;).&lt;br /&gt;
If we don’t configure anything, we have to manually write &lt;span class="high"&gt;CodingKeys&lt;/span&gt; in each model or accept decoding errors.&lt;/p&gt;
&lt;p&gt;We’re looking for a &lt;strong&gt;centralized&lt;/strong&gt; and &lt;strong&gt;reusable&lt;/strong&gt; way to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Decode JSON without manual &lt;span class="high"&gt;CodingKeys&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Encode our models when sending data.&lt;/li&gt;
&lt;li&gt;Maintain the same behavior in both iOS/macOS apps (URLSession) and &lt;strong&gt;Vapor&lt;/strong&gt; (req/res content).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;We create extensions for &lt;span class="high"&gt;JSONDecoder&lt;/span&gt; and &lt;span class="high"&gt;JSONEncoder&lt;/span&gt; that expose convenience constructors and static &lt;span class="high"&gt;snakeCase&lt;/span&gt; shortcuts.&lt;br /&gt;
Advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zero boilerplate&lt;/strong&gt; in models: avoids repetitive &lt;span class="high"&gt;CodingKeys&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Self-explanatory name&lt;/strong&gt; when using them: &lt;span class="high"&gt;JSONDecoder.snakeCase&lt;/span&gt; / &lt;span class="high"&gt;JSONEncoder.snakeCase&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistency&lt;/strong&gt; across the entire project (client and server).&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension JSONDecoder {
    convenience init(keyDecodingStrategy: KeyDecodingStrategy) {
        self.init()
        self.keyDecodingStrategy = keyDecodingStrategy
    }

    static var snakeCase: JSONDecoder {
        .init(keyDecodingStrategy: .convertFromSnakeCase)
    }
}

extension JSONEncoder {
    convenience init(keyEncodingStrategy: KeyEncodingStrategy) {
        self.init()
        self.keyEncodingStrategy = keyEncodingStrategy
    }

    static var snakeCase: JSONEncoder {
        .init(keyEncodingStrategy: .convertToSnakeCase)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: if a model needs a specific key name, you can still use &lt;span class="high"&gt;CodingKeys&lt;/span&gt; locally; the &lt;span class="high"&gt;snake_case&lt;/span&gt; strategy will act as the default value.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Usage examples&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;// iOS / macOS: reading data
let data: Data = ...
let user = try JSONDecoder.snakeCase
    .decode(UserDTO.self, from: data)

// iOS / macOS: sending data
let body = CreateUserDTO(firstName: &amp;quot;Ada&amp;quot;, lastName: &amp;quot;Lovelace&amp;quot;)
request.httpBody = try JSONEncoder.snakeCase.encode(body)

// Vapor
let input = try req.content
    .decode(CreateUserDTO.self, using: JSONDecoder.snakeCase)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these shortcuts we get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fewer errors&lt;/strong&gt; and greater &lt;strong&gt;readability&lt;/strong&gt;: properties remain in idiomatic Swift camelCase.&lt;/li&gt;
&lt;li&gt;Immediate &lt;strong&gt;interoperability&lt;/strong&gt; with legacy snake_case APIs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single configuration&lt;/strong&gt; reusable throughout the project (tests included).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This standardizes how we serialize/parse JSON without sacrificing clarity or fine-grained control when needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/json-snakecase/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/JsonSnakeCase.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/array-dictionary/</id>
        <title>Array to dictionary</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Convert an Array of Identifiable elements (with optional ID of type UUID?) into a [UUID: Element] dictionary, ignoring null IDs and maintaining O(1) access.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;When working with lists of models (e.g., results, events, or users), we often need &lt;span class="high"&gt;O(1)&lt;/span&gt; access by identifier for lookups, merges, or deduplication.&lt;/p&gt;
&lt;p&gt;However, in many domains the &lt;strong&gt;id&lt;/strong&gt; may be optional (&lt;span class="high"&gt;UUID?&lt;/span&gt;) until the backend assigns it. If we build a dictionary directly, two frictions arise:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We need to &lt;strong&gt;filter out elements without ID&lt;/strong&gt; to avoid invalid entries.&lt;/li&gt;
&lt;li&gt;We must &lt;strong&gt;guarantee uniqueness&lt;/strong&gt; of keys or the &lt;span class="high"&gt;Dictionary(uniqueKeysWithValues:)&lt;/span&gt; constructor will fail at runtime if there are duplicates.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;We extend &lt;span class="high"&gt;Array&lt;/span&gt; (when its elements are &lt;span class="high"&gt;Identifiable&lt;/span&gt; with &lt;span class="high"&gt;ID == UUID?&lt;/span&gt;) to expose &lt;span class="high"&gt;toDictionary()&lt;/span&gt;.&lt;br /&gt;
The function uses &lt;span class="high"&gt;compactMap&lt;/span&gt; to &lt;strong&gt;discard elements without ID&lt;/strong&gt; and builds the dictionary with &lt;span class="high"&gt;Dictionary(uniqueKeysWithValues:)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Precondition&lt;/strong&gt;: existing IDs must be &lt;strong&gt;unique&lt;/strong&gt; within the collection. If you expect collisions, consider a variant with &lt;span class="high"&gt;Dictionary(_, uniquingKeysWith:)&lt;/span&gt; to resolve duplicates.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Array where Element: Identifiable, Element.ID == UUID? {
    func toDictionary() -&amp;gt; [UUID: Element] {
        Dictionary(uniqueKeysWithValues: self.compactMap {
            guard let id = $0.id else { return nil }
            return (id, $0) }
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Elements with &lt;span class="high"&gt;id == nil&lt;/span&gt; are &lt;strong&gt;not&lt;/strong&gt; included.&lt;/li&gt;
&lt;li&gt;Key-based access is &lt;span class="high"&gt;O(1)&lt;/span&gt; and simplifies in-memory merges/joins.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;A small, clear helper to convert from an array to a map by ID:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Faster to query&lt;/strong&gt; &lt;span class="high"&gt;O(1)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Safer&lt;/strong&gt;: avoids inserting elements without identifier.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More expressive&lt;/strong&gt; and reusable in services and view models.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/array-dictionary/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/ToDictionary.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/schedule-queues/</id>
        <title>Schedule Queues</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Schedule the same ScheduledJob multiple times per hour with an expressive API: every N minutes, without repeating configuration or cron strings.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;When using &lt;span class="high"&gt;Vapor Queues&lt;/span&gt; to schedule jobs, the typical API is:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;app.queues
    .schedule(MyJob()).hourly().at(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This schedules once per hour. If we want to run it &lt;strong&gt;every N minutes&lt;/strong&gt; (e.g., every 5, 10, or 15), we have to manually register multiple &lt;span class="high"&gt;.at(…)&lt;/span&gt; calls, which results in &lt;strong&gt;repetitive code&lt;/strong&gt;, prone to errors (duplicated/forgotten minutes), and difficult to read.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;We extract a helper over &lt;span class="high"&gt;Application.Queues&lt;/span&gt; that registers the same &lt;span class="high"&gt;ScheduledJob&lt;/span&gt; &lt;strong&gt;multiple times within the hour&lt;/strong&gt; using a &lt;span class="high"&gt;stride&lt;/span&gt; with step &lt;span class="high"&gt;minutes&lt;/span&gt;. This way, with a single call we express: &lt;em&gt;“run it every N minutes”&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Application.Queues {
    func scheduleEvery(
        _ job: ScheduledJob,
        minutes: Int
    ) {
        for minuteOffset in stride(
            from: 0,
            to: 60,
            by: minutes
        ) {
            schedule(job).hourly()
                .at(.init(integerLiteral: minuteOffset))
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses &lt;span class="high"&gt;stride(from: 0, to: 60, by: minutes)&lt;/span&gt; to generate minute offsets within the hour.&lt;/li&gt;
&lt;li&gt;For each offset it registers &lt;span class="high"&gt;hourly().at(…)&lt;/span&gt;, avoiding logic duplication.&lt;/li&gt;
&lt;li&gt;If &lt;span class="high"&gt;minutes&lt;/span&gt; &lt;strong&gt;doesn’t divide 60&lt;/strong&gt;, the last execution will be the largest multiple &amp;lt; 60 (e.g., &lt;span class="high"&gt;minutes = 7&lt;/span&gt; ⇒ 0, 7, 14, …, 56).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;Usage example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func configure(_ app: Application) throws {
    app.queues.scheduleEvery(MyJob(), minutes: 5)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;strong&gt;concise and self-expressive&lt;/strong&gt; API for short periodic tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Less boilerplate&lt;/strong&gt;: a single call registers all triggers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fewer errors&lt;/strong&gt;: no manual lists of &lt;span class="high"&gt;.at(…)&lt;/span&gt; or duplicated minutes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Readable and testable&lt;/strong&gt;: the pattern is evident and easy to verify.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/schedule-queues/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/ScheduleQueues.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/task-list/</id>
        <title>Async Task List</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Implementation of a robust system for managing asynchronous task lists with database state persistence using Swift and Vapor.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;In distributed systems with microservices, complex processes can create control and reliability challenges. One solution is to &lt;strong&gt;divide&lt;/strong&gt; them into &lt;strong&gt;atomic&lt;/strong&gt; tasks, enabling more granular &lt;strong&gt;flow&lt;/strong&gt; control, improved &lt;strong&gt;observability&lt;/strong&gt;, ensured &lt;strong&gt;idempotency&lt;/strong&gt;, and easier &lt;strong&gt;recovery&lt;/strong&gt; from failures.&lt;/p&gt;
&lt;p&gt;The challenge is to design a pattern that allows for &lt;strong&gt;controlled degradation&lt;/strong&gt;, so that a single task failure doesn’t affect the entire flow, guaranteeing &lt;strong&gt;resilience&lt;/strong&gt; and &lt;strong&gt;fault tolerance&lt;/strong&gt; in production environments.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;Create a &lt;span class="high"&gt;task executor&lt;/span&gt; that orchestrates asynchronous tasks.&lt;br /&gt;
The &lt;span class="high"&gt;execute&lt;/span&gt; function wraps each task, manages errors with &lt;span class="high"&gt;do-catch&lt;/span&gt; as a &lt;span class="high"&gt;circuit breaker&lt;/span&gt;, updates state on &lt;strong&gt;success&lt;/strong&gt;, logs and persists failures, and ensures an &lt;strong&gt;atomic transaction&lt;/strong&gt; to maintain data consistency.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func execute(
    status: StatusEnum,
    process: ProcessModel,
    _ work: () async throws -&amp;gt; ProcessModel
) async throws {
    do {
        let job = try await work()
        process.setStatus(job.status)
    } catch {
        process.setError(type: status, message: &amp;quot;\(error)&amp;quot;)
    }
    try await repo.updateProcess(process)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;This implementation provides a &lt;strong&gt;high-level abstraction&lt;/strong&gt; that enables &lt;strong&gt;precise orchestration&lt;/strong&gt; of complex asynchronous processes with &lt;strong&gt;atomicity&lt;/strong&gt; and &lt;strong&gt;durability&lt;/strong&gt; guarantees.&lt;/p&gt;
&lt;p&gt;🎯 &lt;strong&gt;Separation of Concerns:&lt;/strong&gt; each task isolates its execution context and error handling.&lt;br /&gt;&lt;br /&gt;
🔄 &lt;strong&gt;Workflow Orchestration:&lt;/strong&gt; enables task chaining through the pipeline pattern.&lt;br /&gt;&lt;br /&gt;
📊 &lt;strong&gt;Observability:&lt;/strong&gt; generates a complete audit trail for debugging and monitoring.&lt;br /&gt;&lt;br /&gt;
⚡ &lt;strong&gt;Performance:&lt;/strong&gt; maintains high concurrency without compromising data consistency.&lt;br /&gt;&lt;br /&gt;
🛡️ &lt;strong&gt;Resilience:&lt;/strong&gt; incorporates automatic fail-fast and recovery patterns.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;try await execute(status: .loadImages, process: process) {
    // Your implementation
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/task-list/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/TaskList.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/multi-step/</id>
        <title>Multi-Step Process</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>A Swift pattern for orchestrating complex multi-step processes with state control and fault recovery.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;In distributed systems and backend processes, it’s common for a complex operation to require executing &lt;strong&gt;multiple sequential steps&lt;/strong&gt; with dependencies between them: file creation, upload, response generation, validation, cleanup, etc.&lt;br /&gt;
Controlling this state flow is critical to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ensure each step executes in the &lt;strong&gt;correct order&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Allow for &lt;strong&gt;recovery&lt;/strong&gt; in case of error or system restart.&lt;/li&gt;
&lt;li&gt;Maintain &lt;strong&gt;data consistency&lt;/strong&gt; even if the process is interrupted.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without a clear strategy, the code can become fragile, difficult to scale, and prone to errors.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;stage-based state management&lt;/strong&gt; function is implemented to control the advancement of a &lt;span class="high"&gt;ProcessModel&lt;/span&gt; through its lifecycle.&lt;br /&gt;
The idea is to &lt;strong&gt;encapsulate the state transition logic&lt;/strong&gt; in a single function that evaluates the current state and executes the corresponding action, until the process reaches its final state.&lt;/p&gt;
&lt;p&gt;The pattern relies on a &lt;span class="high"&gt;repeat-while&lt;/span&gt; loop that re-evaluates the state after each operation, ensuring transitions occur in a &lt;strong&gt;deterministic&lt;/strong&gt; and &lt;strong&gt;resilient&lt;/strong&gt; manner.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func checkProcess(
    _ process: ProcessModel
) async throws {
    var process = process
    var status = process.status

    repeat {
        status = process.status
        process = try await checkStatus(process)
    } while status != process.status
}

func checkStatus(
    _ process: ProcessModel
) async throws -&amp;gt; ProcessModel {
    switch process.status {
        case .filesCreated:
            try await _uploadFiles(process)
        case .filesUploaded:
            try await _createResponses(process)
        case .responsesCreated, .responsesReasoning:
            try await _checkResponses(process)
        case .responsesCompleted:
            try await _deleteFiles(process)
        case .filesDeleted:
            try await _deleteResponses(process)
        case .responsesDeleted:
            try await _finishResponses(process)
        case .responsesFinished:
            try await _deleteProcess(process)
        default: process
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Implementation key points:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;State centralization&lt;/strong&gt;: a single control point defines all transitions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous re-evaluation&lt;/strong&gt;: the &lt;span class="high"&gt;repeat-while&lt;/span&gt; loop allows automatic advancement as long as there are state changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operation isolation&lt;/strong&gt;: each &lt;span class="high"&gt;switch&lt;/span&gt; case delegates to specialized functions (&lt;span class="high"&gt;_uploadFiles&lt;/span&gt;, &lt;span class="high"&gt;_createResponses&lt;/span&gt;, etc.), keeping the code clean and testable.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;This &lt;strong&gt;multi-stage process management&lt;/strong&gt; pattern provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Resilience&lt;/strong&gt;: each transition is atomic and can be retried if a failure occurs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: adding new steps only requires adding a new case to the &lt;span class="high"&gt;switch&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clarity&lt;/strong&gt;: the complete process flow is understood with a single read.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Application example: data processing pipelines, content publishing workflows, or any long-running process that requires &lt;strong&gt;precise control of each stage&lt;/strong&gt; without compromising data integrity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/multi-step/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/MultiStep.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/concurrent-map/</id>
        <title>Concurrent Map</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Build a concurrentMap extension on Sequence using Swift structured concurrency to run async transformations in parallel with Sendable safety and error handling.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;When processing collections in Swift, the &lt;span class="high"&gt;map&lt;/span&gt; method executes transformations sequentially.&lt;br /&gt;
For intensive operations or those involving I/O—such as HTTP requests, file reading, or database queries—this can become a bottleneck.&lt;br /&gt;
The goal is to &lt;strong&gt;leverage concurrency&lt;/strong&gt; to execute multiple transformations in parallel, ensuring safety with &lt;span class="high"&gt;Sendable&lt;/span&gt; and error handling.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;A &lt;span class="high"&gt;Sequence&lt;/span&gt; extension is created that adds &lt;span class="high"&gt;concurrentMap&lt;/span&gt;.&lt;br /&gt;
Internally, it uses &lt;span class="high"&gt;withThrowingTaskGroup&lt;/span&gt;, which allows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Launching a task for each element in the sequence.&lt;/li&gt;
&lt;li&gt;Executing all transformations in parallel, respecting Swift’s structured concurrency model.&lt;/li&gt;
&lt;li&gt;Automatically propagating the first error that occurs, canceling the remaining tasks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The use of &lt;span class="high"&gt;@Sendable&lt;/span&gt; ensures that both elements and results are safe in concurrent environments.&lt;br /&gt;
This way, any asynchronous operation can benefit from &lt;strong&gt;parallel execution&lt;/strong&gt; without sacrificing code clarity.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Sequence where Element: Sendable {
    func concurrentMap&amp;lt;T: Sendable&amp;gt;(
        _ transform: @escaping @Sendable (Element) async throws -&amp;gt; T
    ) async throws -&amp;gt; [T] {
        try await withThrowingTaskGroup(of: T.self) { group in
            for element in self {
                group.addTask {
                    try await transform(element)
                }
            }
            return try await group.reduce(into: []) {
                $0.append($1)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;With &lt;span class="high"&gt;concurrentMap&lt;/span&gt;, you achieve &lt;strong&gt;significant performance gains&lt;/strong&gt; in asynchronous operations that can execute in parallel.&lt;br /&gt;
The pattern respects Swift’s &lt;span class="high"&gt;structured concurrency&lt;/span&gt; rules, avoids &lt;em&gt;data races&lt;/em&gt;, and maintains the same declarative style as &lt;span class="high"&gt;map&lt;/span&gt;, making its adoption in existing projects straightforward.&lt;/p&gt;
&lt;p&gt;Usage example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;let urls: [URL] = [...]
let contents = try await urls.concurrentMap {
    try await fetchContent(from: $0)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach is ideal for batch HTTP requests, image processing, bulk data reading, or any scenario requiring &lt;strong&gt;maximum safe parallelism&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If you need &lt;strong&gt;guaranteed ordering&lt;/strong&gt; or want to limit resource consumption to one task at a time, I covered that in &lt;a href="/blog/async-map/"&gt;Async Map&lt;/a&gt;. And if you want to combine both strategies — concurrent processing within controlled chunks — I built that in &lt;a href="/blog/async-concurrent-map/"&gt;Async Concurrent Map&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/concurrent-map/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/ConcurrentMap.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/async-map/</id>
        <title>Async Map</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Build a sequential asyncMap extension on Sequence in Swift to transform collections with async/await while preserving order and limiting resources.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;When processing collections with asynchronous transformations, the standard &lt;span class="high"&gt;map&lt;/span&gt; method falls short because its closures cannot be &lt;span class="high"&gt;async&lt;/span&gt;.&lt;br /&gt;
A naive solution would be to use a &lt;span class="high"&gt;for&lt;/span&gt; loop with &lt;span class="high"&gt;await&lt;/span&gt;, but this sacrifices readability and consistent error handling.&lt;br /&gt;
The goal is to have a &lt;strong&gt;sequential&lt;/strong&gt; &lt;span class="high"&gt;map&lt;/span&gt; that accepts &lt;span class="high"&gt;async throws&lt;/span&gt; functions, preserves element order, and simplifies the workflow.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;We create an extension on &lt;span class="high"&gt;Sequence&lt;/span&gt; that adds &lt;span class="high"&gt;asyncMap&lt;/span&gt;.&lt;br /&gt;
Its execution is &lt;strong&gt;sequential&lt;/strong&gt; within the same task, which allows us to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maintain deterministic ordering of results.&lt;/li&gt;
&lt;li&gt;Limit resource consumption by executing only one operation at a time.&lt;/li&gt;
&lt;li&gt;Uniformly propagate the first error that occurs, stopping the process.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Sequence {
    @inlinable
    func asyncMap&amp;lt;T&amp;gt;(
        _ transform: @escaping @Sendable (Element) async throws -&amp;gt; T
    ) async throws -&amp;gt; [T] {
        var results: [T] = []
        results.reserveCapacity(underestimatedCount)
        for element in self {
            let value = try await transform(element)
            results.append(value)
        }
        return results
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;With &lt;span class="high"&gt;asyncMap&lt;/span&gt;, we achieve a clear and safe workflow for asynchronous transformations &lt;strong&gt;without parallelism&lt;/strong&gt;.&lt;br /&gt;
It’s especially useful when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We need guaranteed ordering.&lt;/li&gt;
&lt;li&gt;We must limit resource consumption (one task in flight).&lt;/li&gt;
&lt;li&gt;There’s dependency between operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you need &lt;strong&gt;parallel execution&lt;/strong&gt; instead of sequential, I explored that approach in &lt;a href="/blog/concurrent-map/"&gt;Concurrent Map&lt;/a&gt;. And if your sequential calls hit rate limits, I added a timeout mechanism in &lt;a href="/blog/async-map-timeout/"&gt;Async Map Timeout&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Usage example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;let urls: [URL] = [...]
let contents = try await urls.asyncMap {
    try await fetchContent(from: $0)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/async-map/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/AsyncMap.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/guard-log/</id>
        <title>Guard and LogError</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Build a generic Swift function that combines guard-let unwrapping, error logging, and exception throwing in a single reusable line for Vapor backends.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;In multiple sections of my code, I need to execute three operations every time I handle an &lt;span class="high"&gt;Optional&lt;/span&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Unwrap&lt;/strong&gt; the &lt;span class="high"&gt;Optional&lt;/span&gt; content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Log an error&lt;/strong&gt; in the logging system if the value is &lt;span class="high"&gt;nil&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Throw an exception&lt;/strong&gt; when the value doesn’t exist&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This pattern repeats frequently, generating duplicate code and reducing maintainability.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;The strategy consists of &lt;strong&gt;encapsulating the three operations&lt;/strong&gt; in reusable functions that work in a coordinated manner.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Helper function:&lt;/strong&gt;&lt;/em&gt; &lt;span class="high"&gt;logError&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func logError(
    _ message: String, 
    status: HTTPResponseStatus
) throws -&amp;gt; Never {
    self.logMessage(message, level: .error)
    throw Abort(status, reason: message)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function executes the &lt;strong&gt;error logging&lt;/strong&gt; in the logging system and subsequently &lt;strong&gt;terminates execution&lt;/strong&gt; by throwing an &lt;span class="high"&gt;Abort&lt;/span&gt; exception. The &lt;span class="high"&gt;Never&lt;/span&gt; return type is fundamental, as it indicates to the compiler that this function &lt;strong&gt;never returns normally&lt;/strong&gt;, ensuring that execution is completely interrupted.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Main function:&lt;/strong&gt;&lt;/em&gt; &lt;span class="high"&gt;guardAndLogError&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func guardAndLogError&amp;lt;T&amp;gt;(
    _ optional: T?,
    message: String,
    status: HTTPResponseStatus = .noContent
) throws -&amp;gt; T {
    guard let optional else {
        try logError(message, status: status)
    }
    return optional
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This &lt;strong&gt;generic function&lt;/strong&gt; implements the complete pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses &lt;span class="high"&gt;guard let&lt;/span&gt; to &lt;strong&gt;safely unwrap the Optional&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;If the value is &lt;span class="high"&gt;nil&lt;/span&gt;, invokes &lt;span class="high"&gt;logError()&lt;/span&gt; to log the failure and terminate execution&lt;/li&gt;
&lt;li&gt;If it contains a value, &lt;strong&gt;returns it successfully&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;strong&gt;genericity&lt;/strong&gt; &lt;span class="high"&gt;T&lt;/span&gt; allows using this function with any type of optional data.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;With this implementation, &lt;strong&gt;a single line of code&lt;/strong&gt; executes the three required operations: safe unwrapping, error logging, and exception handling.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;let fileName = try guardAndLogError(
    fileName, 
    message: &amp;quot;fileName value not found&amp;quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Benefits&lt;/h2&gt;
&lt;p&gt;✅ &lt;strong&gt;Reduction of duplicate code&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;
✅ &lt;strong&gt;Consistent error handling&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;
✅ &lt;strong&gt;Centralized and structured logs&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;
✅ &lt;strong&gt;Reusability through genericity&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Extensibility&lt;/h2&gt;
&lt;p&gt;This pattern can be &lt;strong&gt;extended&lt;/strong&gt; for more specific use cases:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;// For boolean validations
func guardAndLogError(
    _ condition: Bool, 
    message: String
) throws { ... }

// For arrays
func guardAndLogError&amp;lt;T&amp;gt;(
    _ optionals: T?..., 
    message: String
) throws -&amp;gt; [T] { ... }

// For tuples
func guardAndLogError&amp;lt;T, U&amp;gt;(
    _ first: T?, 
    _ second: U?
    , message: String
) throws -&amp;gt; (T, U) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/guard-log/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/GuardLog.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/schema-space/</id>
        <title>Schemas and Namespaces</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Learn how to use the space property in Vapor's Fluent models to organize database tables into namespaces, and avoid a subtle optional type declaration bug.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Schema and Namespace&lt;/h2&gt;
&lt;p&gt;When defining a data model in Vapor, it’s possible to specify the &lt;span class="high"&gt;schema&lt;/span&gt;, which corresponds to the table name in the database. However, to maintain an organized architecture, it’s recommended to group tables into different namespaces based on functional criteria, rather than concentrating them all in the default schema. This practice facilitates logical separation by domains or application modules.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Implementation&lt;/h2&gt;
&lt;p&gt;To assign a table to a specific namespace, you must override the static &lt;span class="high"&gt;space&lt;/span&gt; property in the model. This property allows you to define the namespace where the table will reside, providing more granular organization of the database structure.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;public final class LocationCityModel: Model {
    public static let schema = &amp;quot;cities&amp;quot;
    public static let space: String? = &amp;quot;location&amp;quot;

    @ID() public var id: UUID?
    @Field(.name) public var name: String

    public init() { }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Consideration&lt;/h2&gt;
&lt;p&gt;It’s essential to declare the &lt;span class="high"&gt;space&lt;/span&gt; property as type &lt;span class="high"&gt;String?&lt;/span&gt; &lt;strong&gt;(optional)&lt;/strong&gt;. If declared as &lt;span class="high"&gt;String&lt;/span&gt; &lt;strong&gt;(non-optional)&lt;/strong&gt;, it won’t override the inherited property from the &lt;span class="high"&gt;Model&lt;/span&gt; protocol, but will create a new property with the same name instead. This will cause the framework to ignore the namespace configuration, keeping the tables in the default schema without any apparent error indication.&lt;/p&gt;
&lt;p&gt;If you’re wondering how to create these namespaces automatically in the database, I explain how to turn that into a migration in &lt;a href="/blog/migrate-spaces/"&gt;Migrate Spaces&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/schema-space/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/SchemaSpace.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/partial-index/</id>
        <title>Partial Indexes</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Extend Vapor's SQLCreateIndexBuilder to support partial indexes with WHERE clauses on NULL columns, avoiding raw SQL while keeping type safety in Swift.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Partial Indexes&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;partial index&lt;/strong&gt; is an index that is created only on a subset of rows in a table, defined by a specific &lt;span class="high"&gt;WHERE&lt;/span&gt; condition. Instead of indexing all rows, the index only includes those that meet certain criteria, which can improve performance and reduce space usage.&lt;/p&gt;
&lt;p&gt;In my particular case, I needed to index fields based on whether their values were &lt;span class="high"&gt;NULL&lt;/span&gt; or &lt;span class="high"&gt;NOT NULL&lt;/span&gt;. For example, the following partial indexes in SQL:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE INDEX index_field_null 
ON table(field) 
WHERE field IS NULL;

CREATE INDEX index_field_not_null 
ON table(field) 
WHERE field IS NOT NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s also possible to create partial indexes that involve multiple columns:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE INDEX index_field1_null_field2_null 
ON table(field1, field2) 
WHERE field1 IS NULL AND field2 IS NULL;

CREATE INDEX index_field1_not_null_field2_not_null 
ON table(field1, field2) 
WHERE field1 IS NOT NULL AND field2 IS NOT NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Not all databases support partial indexes. In my particular case, I’m using &lt;span class="high"&gt;PostgreSQL&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;By default, Vapor doesn’t offer direct support for creating partial indexes, since the standard index generation doesn’t include the ability to add a &lt;span class="high"&gt;WHERE&lt;/span&gt; clause in the index definition.&lt;/p&gt;
&lt;p&gt;The following code snippet creates a normal index, either on one or several fields, but doesn’t allow specifying a condition for a partial index:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;try await db.sqlDatabase
    .create($0.key)
    .on(table)
    .colums($0.colums)
    .run()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code works for creating simple indexes, but lacks the ability to add a &lt;span class="high"&gt;WHERE&lt;/span&gt; predicate.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This snippet has been adapted by me; the original builder’s &lt;span class="high"&gt;.create&lt;/span&gt; method exposes more configuration options, but in this example I show a custom and simplified implementation that I currently use.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Alternative&lt;/h2&gt;
&lt;p&gt;The most direct way to create partial indexes is to execute raw SQL statements, as shown in the initial examples. This involves building a method to execute raw &lt;span class="high"&gt;CREATE INDEX&lt;/span&gt; statements with the corresponding &lt;span class="high"&gt;WHERE&lt;/span&gt; clause.&lt;/p&gt;
&lt;p&gt;Although effective, this approach loses the advantage of abstraction and safety that Vapor offers when building migrations and database schemas using Swift code.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;The &lt;span class="high"&gt;SQLCreateIndexBuilder&lt;/span&gt; builder supports an optional &lt;span class="high"&gt;predicate&lt;/span&gt;. If this is not &lt;span class="high"&gt;nil&lt;/span&gt;, it adds a &lt;span class="high"&gt;WHERE&lt;/span&gt; clause to the index.&lt;/p&gt;
&lt;p&gt;Therefore, I extended this builder to include a &lt;span class="high"&gt;where&lt;/span&gt; method that accepts a list of columns and a partial index type (for example, &lt;span class="high"&gt;null&lt;/span&gt; or &lt;span class="high"&gt;not null&lt;/span&gt;). This method builds the appropriate logical expression for the predicate and assigns it to the index.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension SQLCreateIndexBuilder {
    @discardableResult
    func `where`(
        _ columns: [FieldKey],
        _ partialIndex: SQLPartialIndexEnum?
    ) -&amp;gt; Self {
        guard let partialIndex else {
            return self
        }
        let op: SQLBinaryOperator = partialIndex == .null ? .is : .isNot

        let conditions = columns.map {
            self.where($0, op)
        }

        let combined: SQLBinaryExpression = conditions.dropFirst()
            .reduce(conditions[0]) { .init($0, .and, $1) }

        return self.where(combined)
    }

    private func `where`(
        _ column: FieldKey,
        _ binary: SQLBinaryOperator
    ) -&amp;gt; SQLBinaryExpression {
        .init(
            left: SQLIdentifier(column.description),
            op: binary,
            right: SQLLiteral.null
        )
    }

    private func `where`(_ expression: SQLExpression) -&amp;gt; Self {
        self.createIndex.predicate = expression
        return self
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code allows calling the &lt;span class="high"&gt;where&lt;/span&gt; method for one or several fields, specifying whether you want a partial index for &lt;span class="high"&gt;NULL&lt;/span&gt; or &lt;span class="high"&gt;NOT NULL&lt;/span&gt; values. If no value is provided for &lt;span class="high"&gt;partialIndex&lt;/span&gt;, the index is returned without a predicate, behaving like a normal index.&lt;/p&gt;
&lt;p&gt;The logic consists of creating a list of binary expressions &lt;span class="high"&gt;SQLBinaryExpression&lt;/span&gt; for each column, combining them with the logical operator &lt;span class="high"&gt;AND&lt;/span&gt; and assigning the result as the index predicate.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;With this extension, I can now create partial indexes easily in Vapor, adding only one line to the original code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;try await db.sqlDatabase
    .create($0.key)
    .on(table)
    .colums($0.colums)
    .where($0.colums, $0.partial)
    .run()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;span class="high"&gt;$0.partial&lt;/span&gt; is an optional value that indicates the desired partial index type &lt;span class="high"&gt;null&lt;/span&gt; or &lt;span class="high"&gt;not null&lt;/span&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Final Notes&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;This approach is specifically designed for partial indexes based on the presence or absence of &lt;span class="high"&gt;NULL&lt;/span&gt; values in columns. However, the implementation can be adapted to support other conditions and more complex use cases, simply by modifying the predicate construction.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The solution offers a clean and reusable way to create partial indexes within the Vapor ecosystem, maintaining the consistency and safety of Swift code.&lt;/p&gt;
&lt;p&gt;If you also want to organize your tables into namespaces using Fluent’s &lt;code&gt;space&lt;/code&gt; property, I covered that in &lt;a href="/blog/schema-space/"&gt;Schemas and Namespaces&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/partial-index/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/PartialIndex.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/my-first-data-race/</id>
        <title>My First Data Race</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>A technical article explaining how a refactoring for concurrency in Swift led to a data race condition, and how to solve it using actor patterns.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;From refactor to data race in Swift&lt;/h2&gt;
&lt;p&gt;During a refactoring process to convert previously sequential functionality into concurrent processing, I encountered a classic problem: ensuring record uniqueness when multiple tasks attempt to create events simultaneously. While the goal was to improve performance by processing thousands of events in parallel using Swift, the transition exposed a typical concurrency challenge: avoiding duplicates and maintaining data integrity under simultaneous access.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Actor with array&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;private actor EventsActor {
    private var events: [SportEventModel] = []

    func getOrCreate(
        name: String,
        cityId: UUID,
        build: () async throws -&amp;gt; EventModel
    ) async throws -&amp;gt; EventModel {
        if let event = events.first(
            where: {
                $0.normalizedName == name
                &amp;amp;&amp;amp; $0.$city.id == cityId
            }
        ) { return event }

        let event = try await build()
        events.append(event)
        return event
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simplicity.&lt;/li&gt;
&lt;li&gt;Safety against data race conditions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Disadvantage:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Inefficient search for large volumes &lt;span class="high"&gt;O(n)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Actor with dictionary&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;private actor EventsActor {
    private var events: [String: EventModel] = [:]

    func getOrCreate(
        name: String,
        cityId: UUID,
        build: () async throws -&amp;gt; EventModel
    ) async throws -&amp;gt; EventModel {
        let key = &amp;quot;\(name)\(cityId.uuidString)&amp;quot;
        if let event = events[key] {
            return event
        }
        let event = try await build()
        events[key] = event
        return event
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fast search and insertion &lt;span class="high"&gt;O(1)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Ideal for large data volumes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Bug:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logical data race condition&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Logical data race condition&lt;/h2&gt;
&lt;p&gt;When multiple concurrent tasks attempt to create the same event, they can all check that it doesn’t exist and proceed to create it simultaneously.&lt;br /&gt;
Only one of the resulting instances gets stored, while the rest become “orphaned,” causing inconsistencies and broken references in other data structures.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;For example: two concurrent tasks check if an event exists. Both see that it doesn’t, both create it, but only one survives in the dictionary; the other reference is now lost.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;To prevent this, it’s necessary to serialize not only access but also creation by key:&lt;br /&gt;
If there’s already a creation in progress for that key, concurrent tasks must wait for the result of the first one, ensuring that all of them share exactly the same resource.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;private actor EventsActor {
    private var events: [String: EventModel] = [:]
    private var builds: [String: Task&amp;lt;EventModel, Error&amp;gt;] = [:]

    func getOrCreate(
        name: String,
        cityId: UUID,
        build: @Sendable @escaping () async throws -&amp;gt; EventModel
    ) async throws -&amp;gt; EventModel {
        let key = &amp;quot;\(name)\(cityId.uuidString)&amp;quot;
        if let event = events[key] {
            return event
        }

        if let building = builds[key] {
            return try await building.value
        }

        let buildTask = Task {
            try await build()
        }
        builds[key] = buildTask

        let event = try await buildTask.value
        events[key] = event
        builds.removeValue(forKey: key)
        return event
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;Lessons learned&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;An actor alone doesn’t prevent logical data race conditions of the “check-then-act” type.&lt;/li&gt;
&lt;li&gt;In concurrent scenarios, serializing resource construction by key is fundamental to maintaining data integrity.&lt;/li&gt;
&lt;li&gt;It’s essential to test under load and concurrent scenarios, not just in sequential mode.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/my-first-data-race/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/MyFirstDataRace.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/timestamps-migrations/</id>
        <title>Timestamps in Migrations</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>How to avoid repeating createdAt, updatedAt and deletedAt in database migrations using extensions.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Timestamps&lt;/h2&gt;
&lt;p&gt;If you’re like me, when you design a database model you want all tables to always include three key fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="high"&gt;createdAt&lt;/span&gt;: indicates when the record was created.&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;updatedAt&lt;/span&gt;: reflects the last time it was updated.&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;deletedAt&lt;/span&gt;: marks when it was logically deleted (without physically deleting the record).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;As you can see, in each migration we have to manually write the three timestamp fields. This is not only repetitive, but also error-prone if we forget a field or misspell the data type.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;try await db.schema(model)
    .id()
    .field(.name, .string, .required)
    .field(.createdAt, .datetime, .required)
    .field(.updatedAt, .datetime, .required)
    .field(.deletedAt, .datetime)
    .create()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;The solution is to create a &lt;span class="high"&gt;SchemaBuilder&lt;/span&gt; extension that adds a &lt;span class="high"&gt;timestamps()&lt;/span&gt; method. This extension encapsulates the repetitive logic in one place, making the code cleaner and more maintainable.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension SchemaBuilder {
    func timestamps() -&amp;gt; Self {
        self.field(.createdAt, .datetime, .required)
            .field(.updatedAt, .datetime, .required)
            .field(.deletedAt, .datetime)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;Now our migrations are much cleaner and more readable. By calling &lt;span class="high"&gt;.timestamps()&lt;/span&gt; we add the three necessary fields. This reduces the possibility of errors and makes the code easier to maintain.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;try await db.schema(model)
    .id()
    .field(.name, .string, .required)
    .timestamps()
    .create()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/timestamps-migrations/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/Timestamps.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/cloud-server/</id>
        <title>☁️ Cloud Server</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Cloud server, something necessary when you work with backend. Discover the best free options and why you need one.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;☁️ Cloud Server&lt;/h2&gt;
&lt;p&gt;When you work with backend, unless you’re working on a local network and nothing you do will go outside, you’re going to need a cloud server and the sooner the better 🚀.&lt;/p&gt;
&lt;p&gt;Working locally is great, but having a cloud server is a step forward, and it’s better to start as soon as possible to have a development server in the cloud.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;🔧 CPU Architectures&lt;/h2&gt;
&lt;p&gt;As technology advances, there are more and more options both in providers and processor technology 💻:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AMD&lt;/strong&gt; 🔴:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Excellent price/performance ratio&lt;/li&gt;
&lt;li&gt;✅ Mature and stable x64 architecture&lt;/li&gt;
&lt;li&gt;✅ Wide software compatibility&lt;/li&gt;
&lt;li&gt;✅ Ideal for high-performance applications&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Intel&lt;/strong&gt; 🔵:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Historical leader in the server market&lt;/li&gt;
&lt;li&gt;✅ Excellent enterprise support&lt;/li&gt;
&lt;li&gt;✅ Specific optimizations for legacy applications&lt;/li&gt;
&lt;li&gt;✅ Consistent and predictable performance&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ARM&lt;/strong&gt; 🍃:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ The latest to arrive but with great potential&lt;/li&gt;
&lt;li&gt;✅ Superior energy efficiency&lt;/li&gt;
&lt;li&gt;✅ Better price per core&lt;/li&gt;
&lt;li&gt;✅ Ideal for microservices and containers&lt;/li&gt;
&lt;li&gt;✅ Lower consumption = lower operational cost&lt;/li&gt;
&lt;li&gt;⚠️ Some compatibility limitations with legacy software&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;💰 Free Tier - Oracle Cloud&lt;/h2&gt;
&lt;p&gt;I’ve opted for Oracle Cloud and its free tier, which offers you an ARM server, as it’s the option that best fits my needs 🎯.&lt;/p&gt;
&lt;p&gt;🖥️ VM.Standard.A1.Flex (Ampere ARM)&lt;br /&gt;&lt;br /&gt;
⏱️ Up to &lt;strong&gt;3,000 OCPU-hours&lt;/strong&gt; per month&lt;br /&gt;&lt;br /&gt;
🧠 Up to &lt;strong&gt;18,000 GB-hours of memory&lt;/strong&gt; per month&lt;br /&gt;&lt;br /&gt;
🚀 1 server with &lt;strong&gt;4 OCPUs + 24 GB RAM&lt;/strong&gt; running 24/7&lt;br /&gt;&lt;br /&gt;
🔄 Or several smaller instances (example: 4 × 1 OCPU + 6 GB RAM)&lt;br /&gt;&lt;br /&gt;
💾 Up to &lt;strong&gt;200 GB combined&lt;/strong&gt; between boot volumes and blocks&lt;br /&gt;&lt;br /&gt;
♾️ &lt;strong&gt;Free indefinitely&lt;/strong&gt;, always within plan limits&lt;br /&gt;&lt;br /&gt;
⚠️ Oracle may &lt;strong&gt;reclaim inactive instances&lt;/strong&gt; (no significant usage in 7 days)&lt;br /&gt;&lt;br /&gt;
🌍 There may be &lt;strong&gt;regional restrictions&lt;/strong&gt; due to lack of capacity&lt;br /&gt;&lt;br /&gt;
💸 &lt;strong&gt;100% free&lt;/strong&gt; within the Always Free plan&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/cloud-server/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/CloudServer.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/claude-code/</id>
        <title>Claude Code</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Discover how Claude Code has become my favorite development assistant and the rules I apply to maximize its potential.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;🤔 What is Claude Code for me?&lt;/h2&gt;
&lt;p&gt;To move away from what everyone talks about 😂, I’m going to tell you about &lt;strong&gt;artificial intelligence&lt;/strong&gt; from my personal experience.&lt;/p&gt;
&lt;p&gt;I’ve integrated &lt;strong&gt;Claude Code&lt;/strong&gt; into my projects, but for me it’s much more than a tool: it’s my &lt;strong&gt;digital assistant&lt;/strong&gt; 👥, another member of my development team, an extension of my capabilities or, as the image represents well, &lt;strong&gt;my technological shadow&lt;/strong&gt; 🌑.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It doesn’t replace me nor do I think it replaces any developer at the moment. Claude Code is only as good as you are or as you know how to use it 💡.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;📋 My Golden Rules&lt;/h2&gt;
&lt;p&gt;I have &lt;strong&gt;four fundamental rules&lt;/strong&gt; that I religiously apply with Claude Code:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;🔍 &lt;strong&gt;Total Change Control&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;I review everything&lt;/strong&gt; before implementing any changes&lt;/li&gt;
&lt;li&gt;It doesn’t have permissions to modify files without my approval&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flow:&lt;/strong&gt; It provides me the plan → I review the changes → I understand them → I approve them (or not)&lt;/li&gt;
&lt;li&gt;This rule is &lt;strong&gt;super important&lt;/strong&gt; to me. Giving it blind permissions doesn’t align with my philosophy 🚫&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="2"&gt;
&lt;li&gt;📝 &lt;strong&gt;Claude.md as Project Bible&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;I work intensively on the &lt;span class="high"&gt;CLAUDE.md&lt;/span&gt; file&lt;/li&gt;
&lt;li&gt;I define what the project is and establish &lt;strong&gt;clear guidelines&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;It’s my way of “training” Claude on my style and preferences&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Result:&lt;/strong&gt; Responses more aligned with my vision 🎯&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="3"&gt;
&lt;li&gt;⚙️ &lt;strong&gt;Intelligent Configuration&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;I configure its settings with &lt;strong&gt;official documentation paths&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;When I ask questions about specific technologies, it uses official sources&lt;/li&gt;
&lt;li&gt;This guarantees more precise and up-to-date responses 📚&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="4"&gt;
&lt;li&gt;🚀 &lt;strong&gt;Collaborative Work, Not Dependency&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Never&lt;/strong&gt; start from an empty IDE&lt;/li&gt;
&lt;li&gt;I always work on a code base I’ve already developed&lt;/li&gt;
&lt;li&gt;My flow: I start creating → I get stuck → I ask Claude&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recurring question:&lt;/strong&gt; &lt;em&gt;“Is there a cleaner and more efficient way to do this code?”&lt;/em&gt; 🤝&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;💡 Limitless imagination&lt;/h2&gt;
&lt;p&gt;And here comes the interesting part: &lt;strong&gt;Claude Code isn’t just for programming&lt;/strong&gt; 🎨. You can use it for whatever comes to mind. Just ask yourself this question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;How can I make this idea work with Claude Code?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the coolest ways I use it &lt;strong&gt;outside of programming&lt;/strong&gt; is with my technical content consumption:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I consume tons of programming content:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;🎓 Online courses&lt;br /&gt;&lt;br /&gt;
💊 Knowledge pills&lt;br /&gt;&lt;br /&gt;
🎤 Conferences and talks&lt;br /&gt;&lt;br /&gt;
🗣️ Technical debates&lt;br /&gt;&lt;br /&gt;
🎧 Specialized podcasts&lt;br /&gt;&lt;br /&gt;
🛠️ Practical workshops&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The typical problem:&lt;/strong&gt; You’re programming and remember seeing something useful in a video, but… which one was it? At what minute? 😅&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My solution with Claude:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I ask: &lt;em&gt;“Where is this talked about?”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;It returns:
&lt;ul&gt;
&lt;li&gt;📹 The specific video or videos&lt;/li&gt;
&lt;li&gt;⏰ The exact timestamp where it’s mentioned&lt;/li&gt;
&lt;li&gt;📄 A brief summary of the content&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; &lt;strong&gt;Wonderful!&lt;/strong&gt; ✨ Instant access to all my consumed knowledge.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;🎯 Final Reflection&lt;/h2&gt;
&lt;p&gt;Claude Code has become my &lt;strong&gt;technological shadow&lt;/strong&gt;. It’s not magic, it doesn’t do the work for you, but if you know how to use it correctly, it can &lt;strong&gt;enormously enhance&lt;/strong&gt; your productivity and creativity.&lt;/p&gt;
&lt;p&gt;The key is in establishing clear limits, maintaining control and using it for what it really is: &lt;strong&gt;an exceptional assistant&lt;/strong&gt; that amplifies your capabilities, not one that replaces them 🚀.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/claude-code/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/ClaudeCode.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/love-vapor/</id>
        <title>Love Vapor</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Discover why Vapor became my favorite Swift server-side framework at the Full Stack Bootcamp, from gRPC to PassKeys and scheduled tasks.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;❤️ Swift on the server side&lt;/h2&gt;
&lt;p&gt;When I attended the &lt;em&gt;Swift Full Stack Bootcamp&lt;/em&gt; at &lt;a href="https://acoding.academy"&gt;Apple Coding Academy&lt;/a&gt;, one of the modules we were taught was &lt;em&gt;Swift on the server side&lt;/em&gt; using the &lt;a href="https://vapor.codes"&gt;Vapor&lt;/a&gt; framework.&lt;/p&gt;
&lt;p&gt;From the very start of the theory portion, my heart began to race, and by the middle of that very first class, I was already completely in love.&lt;/p&gt;
&lt;p&gt;So, if you ask me &lt;em&gt;“What was your favorite module in the Bootcamp?”&lt;/em&gt;, without a doubt, I’d say this one.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;👍🏻 Why do I like it?&lt;/h2&gt;
&lt;p&gt;I’ve always thought that the &lt;strong&gt;backend&lt;/strong&gt; is the &lt;strong&gt;brain&lt;/strong&gt; of any application or website.&lt;br /&gt;
It’s where all the &lt;em&gt;real action&lt;/em&gt; happens: where information is processed (usually from a database) and prepared so that the end user receives it in the most &lt;strong&gt;simple and transparent&lt;/strong&gt; way possible.&lt;/p&gt;
&lt;p&gt;To be clear: &lt;strong&gt;I’m not undervaluing the frontend&lt;/strong&gt;.&lt;br /&gt;
In fact, I truly admire those who create incredible designs—heroes in my eyes—because that’s something I personally find impossible… although I do get along quite well with &lt;strong&gt;SwiftUI&lt;/strong&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;🧸 Playing with Vapor&lt;/h2&gt;
&lt;p&gt;In future blog posts, I’ll go into much more detail, but I can already share that I’ve been &lt;strong&gt;having a lot of fun with Vapor&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;📦 Standalone package to configure different services&lt;br /&gt;&lt;br /&gt;
🌐 &lt;strong&gt;gRPC&lt;/strong&gt; server&lt;br /&gt;&lt;br /&gt;
🗂 Data model with &lt;strong&gt;views&lt;/strong&gt; (including &lt;em&gt;materialized views&lt;/em&gt;), indexes, per-application permissions, and schemas&lt;br /&gt;&lt;br /&gt;
🧮 Enums with automatic migrations&lt;br /&gt;&lt;br /&gt;
🔑 &lt;strong&gt;PassKey&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;
📊 &lt;em&gt;Bulk inserts&lt;/em&gt; for populating huge datasets&lt;br /&gt;&lt;br /&gt;
⏱ Scheduled tasks with asynchrony&lt;br /&gt;&lt;br /&gt;
… and much more 🚀&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In various blog posts, I’ll be sharing a bit of everything.&lt;br /&gt;
That said, even though I really enjoy Vapor and Swift on the server side, &lt;strong&gt;it won’t all be about backend&lt;/strong&gt;… there will be room for many other topics I’m passionate about.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/love-vapor/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/LoveVapor.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/la-liga-thanks/</id>
        <title>La liga, thanks!</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>How La Liga blocks my website during matches due to anti-piracy measures affecting legitimate sites on Cloudflare.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My website blocked during La Liga matches&lt;/h2&gt;
&lt;p&gt;If you’re trying to visit my site during a La Liga match and it’s not loading, you’re not imagining things — it’s actually being blocked.&lt;/p&gt;
&lt;p&gt;In Spain, &lt;strong&gt;La Liga applies aggressive anti-piracy measures&lt;/strong&gt;, and one of them is &lt;strong&gt;blocking access to websites hosted on Cloudflare’s network&lt;/strong&gt; during live matches. Unfortunately, this means that if your website is hosted on Cloudflare — like mine is — you might get caught in the crossfire, even if you’re not streaming or distributing any illegal content.&lt;/p&gt;
&lt;p&gt;Cloudflare protects and accelerates millions of websites globally, but due to La Liga’s policy, &lt;strong&gt;entire IP ranges or CDN edges may be blacklisted&lt;/strong&gt;, which affects &lt;strong&gt;innumerable legitimate websites&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Why does this happen?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;La Liga uses automated systems and DNS-level blocking to restrict access to what they consider potential piracy sources.&lt;/li&gt;
&lt;li&gt;Cloudflare is widely used by all kinds of sites, including those that host pirated sports streams — so their IPs get flagged.&lt;/li&gt;
&lt;li&gt;As a result, many &lt;strong&gt;innocent websites are blocked&lt;/strong&gt; during games, simply because they share infrastructure.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What can you do?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;If you’re in Spain and you can’t access my portfolio during a match, try again later.&lt;/li&gt;
&lt;li&gt;If you’re curious about the issue, there are online tools to test if a domain is being blocked.&lt;/li&gt;
&lt;li&gt;Or use a VPN to temporarily bypass the restriction (just for browsing my portfolio, of course 😅).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s a frustrating example of how overzealous digital enforcement can harm the open web — even small developer portfolios like mine.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;”Thanks, La Liga!!”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; I eventually found a simple fix for this problem. If you want to know how I solved it in 5 minutes, read &lt;a href="/blog/only-dns/"&gt;Only DNS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/la-liga-thanks/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/LaLiga.webp"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/my-first-article/</id>
        <title>My first article</title>
        <updated>2026-04-17T19:06:06Z</updated>
        <summary>Update for my personal website built with Astro and Tailwind, where I will share articles and experiences as a Swift developer in the Apple ecosystem.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;🚀 Big update on my website!&lt;/h2&gt;
&lt;p&gt;Today I’m launching an &lt;strong&gt;important update&lt;/strong&gt; to my personal website, which is now built with &lt;a href="https://astro.build/"&gt;Astro&lt;/a&gt; and &lt;a href="https://tailwindcss.com/"&gt;Tailwind&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’ve added the ability to &lt;strong&gt;write articles&lt;/strong&gt; — and this is the first one!&lt;br /&gt;
From now on, I’ll be sharing my experiences, discoveries, and daily challenges as a &lt;strong&gt;Swift developer&lt;/strong&gt;.&lt;br /&gt;
I might also share a bit about my personal life.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚡️ &lt;strong&gt;No fixed schedule:&lt;/strong&gt;&lt;br /&gt;
I’ll post whenever I have something relevant to share — whether it’s problems, solutions, curiosities, or new technologies I’m exploring.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With this new version, I can focus solely on writing articles in &lt;strong&gt;Markdown&lt;/strong&gt;; everything else is fully automated.&lt;/p&gt;
&lt;p&gt;If you’re interested in &lt;strong&gt;Swift, iOS, visionOS, Vapor, full-stack development with Swift, and the Web&lt;/strong&gt;, or simply want to join me on this journey — welcome!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;
&lt;hr /&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/my-first-article/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/blog/MyFirstArticle.webp"></media:content>
    </entry>
</feed>