Options
All
  • Public
  • Public/Protected
  • All
Menu

First, check out Installation.

Now, here's a 5-minute rundown of what Jine has to offer, and how to use it:

import { codec, encodesTo, NativelyStorable, NativelyIndexable, MigrationTx, Database, Connection, Store, Index, Transaction } from 'jinedb';
const assert = require('assert').strict;


// == // == // INITIALIZATION // == // == //

// What we'll be storing
class Recipe {
  constructor(
    public name: string,
    public url: string,
    public servings: number,
    public ingredients: Array<string>,
  ) { }
}

// Let typescript know what our database will looks like
interface $$ {
  recipes: Store<Recipe> & {
    by: {
      name: Index<Recipe, string>;
      servings: Index<Recipe, number>;
      ingredients: Index<Recipe, string>;
      ingredientCount: Index<Recipe, number>;
    }
  };
}

// Define the database migrations
const migrations = [
  async (genuine: boolean, tx: MigrationTx) => {
    // Create a item store for recipes
    const recipes = tx.addStore<Recipe>('recipes');

    // Track recipes by their name
    // Require names to be unique
    await recipes.addIndex<string>('name', '.name', { unique: true });

    // Track recipes by their serving count
    await recipes.addIndex<number>('servings', '.servings');

    // Track recipes by their ingredients
    await recipes.addIndex<string>('ingredients', '.ingredients', { explode: true });
    // ^^ The flag 'explode: true' means that a recipe where
    //  recipe.ingredients = ['milk', 'chocolate']
    // will get indexed for 'milk' and 'chocolate' individually
    // rather than indexed for the array as a whole

    // Track recipes by their ingredient count
    await recipes.addIndex<number>(
      'ingredientCount',
      (recipe: Recipe) => recipe.ingredients.length,
    );
  }
];

// Define the custom type
const types = [
  codec(Recipe, 'Recipe', {
    encode(recipe: Recipe): NativelyStorable {
      return {
        name: recipe.name,
        servings: recipe.servings,
        url: recipe.url,
        ingredients: recipe.ingredients,
      };
    },
    decode(encoded: any): Recipe {
      const { name, url, servings, ingredients } = encoded;
      return new Recipe(name, url, servings, ingredients);
    },
  }),
];

// Let typescript know about the custom type
interface Recipe {
  [encodesTo]: NativelyStorable;
}

// Create the database!
const jine = new Database<$$>('recipes', { migrations, types });


// Open a connection to the database (if top-level await isn't available)
jine.connect(async conn => {



// == // == // POPULATION // == // == //

// Some recipes
const pancakes = new Recipe(
  "Todd's Famous Blueberry Pancakes",  // (who the hell is Todd??)
  'allrecipes.com/recipe/20177',
  6,
  ['flour', 'eggs', 'salt', 'milk', 'baking powder', 'butter',
   'white sugar', 'blueberries'],
);
const waffles = new Recipe(
  'Cinnamon Roll Waffles',
  'allrecipes.com/recipe/240386',
  6,
  ['flour', 'brown sugar', 'white sugar', 'butter',
   'baking powder', 'cinnamon', 'salt', 'milk', 'eggs',
   'vanilla extract', 'confectioners sugar', 'cream cheese'],
);
const biscuits = new Recipe(
  'Basic Biscuits',
  'allrecipes.com/recipe/20075',
  10,
  ['flour', 'baking powder', 'salt', 'shortening', 'milk'],
);
const tacros = new Recipe(
  'Tacros',  // croissant tacos... apparently
  'allrecipes.com/recipe/262970',
  10,
  ['masa harina', 'bread flour', 'vital wheat gluten',
   'white sugar', 'salt', 'instant yeast', 'milk', 'lard',
   'butter'],
);

// Add the recipes!
await conn.$.recipes.add(pancakes);
await conn.$.recipes.add(waffles);
await conn.$.recipes.add(biscuits);
await conn.$.recipes.add(tacros);



// == // == // QUERIES // == // == //

// I have a recipe's name
assert.deepEqual(biscuits, await conn.$.recipes.by.name.getOne('Basic Biscuits'));
// Note that the returned item is correctly of rich type `Recipe`!
// .selectOne only works on unique indexes and returns one item, or errors if no item is found

// I have some eggs I want to cook
const eggRecipes = await conn.$.recipes.by.ingredients.getAll('eggs');
assert.deepEqual([pancakes, waffles], eggRecipes);
// .find returns all items matching a given trait

// I'm gonna have a lot of guests over
const partyRecipes = await conn.$.recipes.by.servings.select({ above: 7 }).array()
assert.deepEqual([biscuits, tacros], partyRecipes);
// It's just me for this meal, and I don't want leftovers
const aloneRecipes = await conn.$.recipes.by.servings.select({ below: 3 }).array()
assert.deepEqual([], aloneRecipes);
// I want to try something complicated
const complexRecipes = await conn.$.recipes.by.ingredientCount.select({ above: 10 }).array();
assert.deepEqual([waffles], complexRecipes);
// .select accepts queries in the form:
//   { above  : val }  for x > val
//   { from   : val }  for x >= val
//   { below  : val }  for x < val
//   { through: val }  for x <= val
//   { above: lo, below  : hi }  for lo < x < hi
//   { from : lo, below  : hi }  for lo <= x < hi
//   { above: lo, through: hi }  for lo < x <= hi
//   { from : lo, through: hi }  for lo <= x <= hi
//   { equals: val }  for x === val
//   'everything'  for everything

// Just want to know how many recipes I have
assert.equal(4, await conn.$.recipes.count());



// == // == // TRANSACTIONS // == // == //

const beforeCount = await conn.$.recipes.count();

const bananaBread = new Recipe(
  "Joy's Easy Banana Bread",
  'allrecipes.com/recipe/241707',
  10,
  ['bananas', 'white sugar', 'egg', 'butter', 'flour',
   'baking soda', 'salt'],
);

await conn.transact([conn.$.recipes], 'rw', async tx => {
  await tx.$.recipes.add(bananaBread);
  tx.abort();
  // or throw Error();
});

const afterCount = await conn.$.recipes.count();
assert.equal(beforeCount, afterCount);  // transaction atomically aborted

await conn.transact([conn.$.recipes], 'rw', async tx => {
  // Transactions are auto-committed when not in use
  await new Promise(resolve => setTimeout(resolve, 0));
  // The following is now an error:
  assert.rejects(tx.$.recipes.add(bananaBread));
});



// == // == // RESET // == // == //

await conn.$.recipes.clear();
assert.equal(0, await conn.$.recipes.count());




});

Generated using TypeDoc