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 kitten shell.

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.

    Screenshot of the Kitten REPL, showing the welcome message and a key: 🐢 Kitten Interactive Shell (REPL) ctrl+d to exit; .help for assistance. Key: 🐱 Kitten, 📄 Page, 🛜 Server event, 🧩 Component, ◉━◉ Connected, ◉╌◌ Disconnected
    Kitten welcomes you to the REPL and shows you a key of the icons used in it.
  2. The welcome message tells you that you can use the .help command for assistance. Run that, and you will see a list that looks like the following:

    .break      Sometimes you get stuck, this gets you out
    .clear      Alias for .break
    .editor     Enter editor mode
    .exit       Exit the REPL
    .help       Print this help message
    .id         Show your Small Web place ID (and create it and your secret if necessary).
    .load       Load JS from a file into the REPL session
    .ls         List keys of global kitten object.
    .save       Save all evaluated commands in this REPL session to a file
    .settings   Display the Kitten Settings page link
    .sign-in    Display the Kitten sign-in page link
    .sign-out   Display the Kitten sign out page link
    .stats      Show current statistics on Kitten server
    

    The entries highlighted above are the ones that are specific to Kitten’s REPL. All the others are ones inherited from the Node.js REPL that Kitten’s REPL is built on.

  3. Let’s use the .ls command to see the keys of the global kitten object:

    [
      'version',                'log',
      '__db',                   'domain',
      'port',                   'requests',
      'Component',              'Page',
      'Upload',                 'slugify',
      'yaml',                   'pages',
      'utils',                  'app',
      'projectIdentifier',      'paths',
      'deploy',                 'uploadLimits',
      'WebSocket',              'html',
      'markAsTrusted',          'icons',
      'crypto',                 'libraries',
      'page',                   'events',
      'css',                    'js',
      'markdown',               'md',
      'safelyAddUntrustedHtml', 'sanitise',
      'components',             '$showEventsOnTable',
      '___valueAtKeypath',      '_db',
      'uploads',                'package',
      'db',                     '__replIsActive'
    ]
    

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

    The most useful ones for debugging a running Kitten app are shown as highlighted in the list above.

    The .ls command is just an alias for entering the more verbose Object.keys(kitten) statement into to the shell.

  4. Through the Kitten REPL, you have full access to the running Kitten server’s internals.

    Try the following statement:

    kitten.version
    

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

    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.

  5. 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 see the shell confirm that you’ve just created a function called hello:

    We just created a Kitten component interactively.

    Simple, stateless Kitten components like the one we just created are just render functions that return kitten.html. Later, we’ll see how you can also create stateful components, which are classes.

    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. Given it’s highly unlikely that your name is Aral too, let’s now call it while passing it your name as 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 it using the command-line. Don’t you feel geeky?

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

  6. 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 one kitten. (The page shows the count you just set and then adds one to it for your next visit.)

    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.

  7. Next, let’s see how we can see the database being updated in real time as you hit your page. We’ll use the Kitten Introspection API to get notified whenever a property changes on the kittens table:

    kitten.$showEventsOnTable('kittens')
    

    When you refresh the page in the browser, you should see output similar to the following in the Kitten shell:

    💾 Kittens updated in memory.
    
    { count: 20 } => count = 21
    
    
    💾 Kittens persisted to disk.
    
    kitten.db.kittens.count = 21
    
    Persisted as:
    
    '_[[`count`]] = 21;\n'
    

    We borrow the relational database term “table” to refer to such objects when they’re created directly on the database (kitten.db). Each JSDB table is stored in its own file.

    So how does the $showEventsOnTable() method work?

    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.

    Follow along in the shell to see how it works as this is a feature you might want to use when building Kitten apps:

    1. First, enter editor mode in the shell:

      .editor
      
    2. Then, add the following code and press ctrl+d:

      function onPersist ({ type, table, keypath, value }) {
        console.info(`${type} ${table.tableName}.${keypath}: ${value}`)
      }
      
      kittensTable = kitten.db.kittens['__table__']
      JSTable = kittensTable.constructor
      
      kittensTable.addListener(JSTable.PERSIST, onPersist)
      
    3. Finally, go and refresh the page in the browser, and you should see updates similar to the following logged in your console:

      update kittens.count: 22
      update kittens.count: 23
      update kittens.count: 24
      update kittens.count: 25
      

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

    kittensTable.removeListener(JSTable.PERSIST, onPersist)
    
  8. Finally, let’s dive in really deep and actually call the route interactively from the shell.

    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: nosniff],
        [Function: requestLog],
        [Function: corsMiddleware],
        [Function: stats],
        [Function: requestAndResponseHelpers],
        [Function: domainRedirect],
        [Function: trailingSlashes],
        [Function: localRedirect],
        [AsyncFunction: sessions],
        
    [Function: wrappedHandler] { filePath: '/Users/aral/Projects/kitten/app/examples/persisted-kitten-count/index.page.js' }
    ] }

    So there are quite a few handlers (called “middleware”) that run before the handler we defined in index.page.js (which is highlighted in the list above), 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 what the ninth one does (remember, arrays are indexed starting at zero):

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

    You should see a function listing similar to the following:

    async function sessions(request, response, next) {
      const session = await Sessions.getInstance().session(request);
      request.session = request.upgrade === true ? session : storeOnUse(session, request, response);
      next();
    }
    

    That’s the piece of middleware that puts the magic session property into your request objects. It doesn’t store the session. For a request that isn’t an upgrade, it wraps the session in a proxy that stores it the first time your route writes to it or reads its id.

    So maybe there isn’t actually any magic here at all, and you could, if you wanted to, learn exactly how Kitten does things. (Either interactively, through the shell, or by browsing Kitten’s source code.)

    To learn how to use sessions in Kitten, please see the Sessions Tutorial.

    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 index 9. (Kitten wraps each route’s handler in wrappedHandler, which decides whether the request requires sign-in and then runs your route.)

    Before we call it, we need to know that it expects a KittenRequest instance and a KittenResponse instance as positional arguments. However, all it will access in this call is the request’s url, path, and session properties and the response’s setHeader(), writeHead(), and end() methods, so we can simply mock those objects for our purposes.

    Again in .editor mode, add the following code and press ctrl+d to run it:

    getRouteHandlers = kitten.app.router.find('GET', '/').handlers
    indexPageHandler = getRouteHandlers[9]
    indexPageHandler(
      /* request = */  { url: '/', path: '/', session: {} },
      /* response = */ { setHeader: () => {}, writeHead: () => {}, end: html => console.info(html) }
    )
    

    Mocking aspects of requests and responses is something we do quite often when writing Kitten’s unit tests. For an example, see this PageRoute test.

    By issuing that command, you’re simulating a GET request to your route. And, instead of the browser rendering it, we simply output the resulting HTML to the console. So you should see output that resembles the following in your terminal (with the boot id of your Kitten process in place of the … in data-kitten-boot-id):

    <!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><script type="module" src="/🐱/library/kitten-1.js"></script><script type='module' src='/🐱/library/kitten-hot-reload-1.js' data-kitten-boot-id='…'></script></head>
    <body><h1>Kitten count</h1>
    <p>🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️🐱️</p><div id="kitten-toast" aria-live="assertive" hx-swap-oob="morph settle:1.25s" hx-ext="morph"><style inline>
            #kitten-toast.settling { opacity: 1; top: 1em; }
            …
    

    If you are still introspecting the kittens table, you should also see a message telling you that the kitten count has been updated.

    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.