Kitten

Explore Kitten’s interactive shell (REPL) to inspect your database and get introduced to the concept of Kitten components.

Topics covered

Let’s take a little aside to explore another neat feature of Kitten: its interactive shell.

💡 If you’re of a geekier persuasion, you may know this by a different name, a Read-Evaluate-Print Loop or a REPL.

When you start Kitten, you will notice a message telling you that you can press the s key to start an interactive shell.

💡 If you want to connect to the interactive shell in production (when your app is running as a systemd service/daemon), ssh into your server and run telnet 127.0.0.1 1337.

Kitten’s interactive shell allows you to use a shell like Node.js’s own shell (the one you get when you type node without any arguments) to interactively explore that state of your running Kitten app.

💡 In fact, Kitten uses Node.js’s REPL.

So let’s use it to take a look under the covers to play with Kitten’s features interactively, from the command-line, and also to see how the database is being updated.

  1. While Kitten is running, press the s key to launch Kitten’s shell.

  2. You should see an initial welcome message that shows you the keys of the global kitten object. It should look something like this:

    [
     'version',           'domain',
     'port',              'app',
     'databaseDirectory', 'projectIdentifier',
     'deploy',            'WebSocket',
     'html',              'libraries',
     'page',              'events',
     'css',               'js',
     'markdown',          'md',
     'safelyAddHtml',     'sanitise',
     'uploadsDirectory',  '_db',
     'uploads',           'package',
     'db'
    ]
    

    Look at all those lovely objects you have to play with!

    💡 You can use the handy .ls command to see the keys again later (it’s just an alias for entering the more verbose Object.keys(kitten) into to the shell).

    To see a list of all available shell commands, type .help

    (Another useful one is the .settings command which will show you Kitten’s Settings link for the currently-running app.)

  3. To start with, enter the following statement:

    kitten.version
    

    You should see the output of an object with Kitten’s version information.

    That’s pretty neat. We have full access to the running Kitten server’s internals.

    💡 You can use the and (up and down arrow keys) to cycle through your shell’s history. History is saved scoped to your Kitten application’s project identifier (which includes the domain and port that it is running on) so it will still be there the next time you serve your app using Kitten.

  4. How about we explore how things work interactively? We haven’t seen it yet but you can use components in Kitten to break up your pages into reusable parts. Let’s create a simple one that greets you by name. Enter the following statement into Kitten’s shell:

    Hello = ({ name = 'Aral' }={}) => kitten.html`<h1>Hello, ${name}</h1>`
    

    💡 You should type the above all as one line. However, if you want to use multiple lines, you can enter the shell’s editor mode using the .editor command. For a full list of available commands, use the .help command.

    Now, let’s use the component:

    kitten.html`<${Hello} />`
    

    You should see it print out:

    '<h1>Hello, Aral</h1>'
    

    That’s pretty neat. We called it without passing a name and it defaulted to Aral’s name. Given it’s highly unlikely that your name is Aral too, let’s now call it with a property (or prop for short):

    kitten.html`<${Hello} name='your name' />`
    

    Once you’ve entered that, replacing your name with, well, your name, you should see:

    '<h1>Hello, your name</h1>'
    

    And now you pretty much know how components work in Kitten. And you learned all about it from the command-line. Don’t you feel especially geeky now?

    But let’s not get too distracted… we were going to look at our kittens table in the database. Let’s do that next.

  5. Enter the following statement:

    kitten.db.kittens
    

    You should see output that resembles the following:

    Proxy [
      { count: 20 },
      {
        get: [Function: bound getHandler],
        set: [Function: bound setHandler],
        defineProperty: [Function: bound definePropertyHandler],
        deleteProperty: [Function: bound deletePropertyHandler]
      }
    ]
    

    So that’s what your kittens table looks like in memory.

    💡 Don’t worry too much about the Proxy stuff, it’s how JSDB works its magic. If you’re interested in how it all works under the hood, read the dispelling the magic and pointing out a couple of gotchas section of the JSDB documentation.

    Take a look at the count property.

    Let’s change it and see what happens:

    kitten.db.kittens.count = 1
    

    Now refresh the page in your browser and you should see that you’re back to having two kittens. (The one you just set and the one that was added when you refreshed the browser.)

    So you can directly update your database from the shell. That’s fun!

    💡 Like all fun things, laughter can turn to tears if you try this out on your production servers. This is not to say that you shouldn’t but just be aware of what you’re doing and maybe take a backup of your database before you go poking around it.

    Next, let’s see how we can see the database being updated in real time as you hit your page.

  6. In JSDB, we can listen for events on tables. One of these events is the persist event that tells us when a value has been persisted to disk.

    First, let’s create out listener:

    onPersist = (table, change) => console.info(`${table.tableName}: ${change}`)
    

    Next, let’s add the listener to our table:

    kitten.db.kittens.__table__.addListener('persist', onPersist)
    

    Now, go and refresh the page in the browser and you should see updates similar to the following logged in your console:

    kittens: _['count'] = 21;
    kittens: _['count'] = 22;
    kittens: _['count'] = 23;
    kittens: _['count'] = 24;
    

    And now for the reason we created the listener separately instead of inline, let’s remove it so we stop getting updates:

    kitten.db.kittens.__table__.removeListener('persist', onPersist)
    

    Finally, let’s dive in really deep and actually call the route interactively from the shell.

  7. You will rarely have to do this but here’s a little example to show you how powerful Kitten’s interactive shell is. We’re now going to dive into Kitten’s app router (which uses Trouter as extended by Polka) and call our index page route interactively from the shell.

    First, let’s see which handlers run for GET request for the base route of our app (/):

    kitten.app.router.find('GET', '/')
    

    You should see output similar to the following:

    {
      params: {},
      handlers: [
        [Function: corsMiddleware],
        [Function (anonymous)],
        [Function (anonymous)],
        [Function (anonymous)],
        [Function (anonymous)],
        [AsyncFunction (anonymous)],
        [AsyncFunction (anonymous)],
        [Function: bound lazilyLoadedHandler] AsyncFunction {
        filePath: '/var/home/aral/Projects/kitten/app/examples/persisted-kitten-count/index.page.js'
        }
      ]
    }
    

    So there are quite a few handlers that run before the handler we defined in index.page.js and they do important things that let us concentrate on just what we want to do.

    If you want to see exactly what any of them do, you can list their source. Let’s see that the sixth one does (remember, arrays are indexed starting at zero):

    console.log(
      kitten.app.router.find('GET', '/').handlers[5].toString()
    )
    

    You should see a function listing similar to the following:

    async (request, response, next) => {
      request.session = await Sessions.getInstance().session(request, response);
      next();
    }
    

    That’s the piece of middleware that puts the magic session property into your request objects. So maybe there isn’t actually any magic here at all and you could, if you wanted to, learn about exactly how Kitten does things. Either interactively, through the shell, or by browsing Kitten’s source code.

    The route we’re interested is the one we wrote, however, and if you count the items in the array, it’s the one at the 7th index.

    Before we call it, we need to know that it expects a Request instance and a Response instance as arguments. But, really, all it uses is the request’s url property and the response’s end() method, so we can simply mock those objects for our purposes:

    kitten.app.router.find('GET', '/').handlers[7]({ url: '/' }, { end: html => console.info(html) })
    

    By issuing that command, you’re simulating a GET request to your route. And, instead of the browser rendering it, we are simply outputting the resulting HTML into the console. So you should see output that resembles the following in your terminal:

    <!doctype html>
    <html lang="en">
    <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="icon" href="data:,"><title>Kitten count</title></head>
    <body ><h1>Kitten count</h1>
    <p>🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️</p>
      <script>
        // Kitten development time hot reload script.
        let __kittenConnectionCount = 0
        let __kittenDevelopmentSocket
        …
    

    Run the command a few more times and notice that the number of kittens increases, just as if we had hit it in the browser.

    In fact, go hit it in the browser to confirm that you’re working with the same kitten count.

    There’s a lot you can do with Kitten’s interactive shell and I hope you play around with it both to help debug your Small Web apps and also to learn more about how Kitten works in general.

Well, that got geeky very quickly. How about we return to the relative safety of our Kitten Count example again in the next tutorial and use it to learn about conditional statements. If you want to that is. Yes? Then let’s proceed…

Next tutorial: Conditionals

Like this? Fund us!

Small Technology Foundation is a tiny, independent not-for-profit.

We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.