TinyBase logoTinyBase β

Generating APIs

TinyBase can take a TablesSchema and/or ValuesSchema (which have either been explicitly set, or inferred) and generate the code for type definitions and ORM-like implementations to wrap the Store.

This is most likely to be used as part of a build script which takes a TablesSchema or ValuesSchema as input (or imported data) to generate TypeScript definition (.d.ts) files and .ts and .tsx implementations. These can then be linked into the development and compilation of an application as a whole.

Using The getStoreApi Method

This is performed from the Tools object - which is returned from the createTools function - and via the getStoreApi method.

The method takes a parameter which serves as the 'name' for your wrapped, typed, Store:

import {createStore} from 'tinybase';
import {createTools} from 'tinybase/tools';

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});

const tools = createTools(store);

const [dTs, ts, uiReactDTs, uiReactTsx] = tools.getStoreApi('shop');

In this case, the resulting four strings will need to be written to shop.d.ts, shop.ts, shop-ui-react.d.ts, and shop-ui-react.tsx files respectively. TinyBase does not perform that step for you, so you'll need to do it explicitly:

fs.writeFileSync('shop.d.ts', dTs, 'utf-8');
fs.writeFileSync('shop.ts', ts, 'utf-8');
fs.writeFileSync('shop-ui-react.d.ts', uiReactDTs, 'utf-8');
fs.writeFileSync('shop-ui-react.tsx', uiReactTsx, 'utf-8');

Prettified Code

The getPrettyStoreApi method is an asynchronous method that attempts to run a locally installed prettier module to ensure the file is reasonably formatted:

const [prettyDTs, prettyTs, prettyUiReactDTs, prettyUiReactTsx] =
  await tools.getPrettyStoreApi('shop');
fs.writeFileSync('shop.d.ts', prettyDTs, 'utf-8');
fs.writeFileSync('shop.ts', prettyTs, 'utf-8');
fs.writeFileSync('shop-ui-react.d.ts', prettyUiReactDTs, 'utf-8');
fs.writeFileSync('shop-ui-react.tsx', prettyUiReactTsx, 'utf-8');

This will produce a file containing the types for your wrapped Store, for example:

//...
/**
 * Represents the 'pets' Table.
 */
export type PetsTable = {[rowId: Id]: PetsRow};

/**
 * Represents a Row when getting the content of the 'pets' Table.
 */
export type PetsRow = {species: string};
//...

The .ts implementation will contain the createShop entry point, and will look something like this:

//...
export const createShop: typeof createShopDecl = () => {
  // ...
  const store = createStore().setTablesSchema({
    pets: {
      species: {type: 'string', default: 'dog'},
    },
  });
  return {
    hasPetsTable: (): boolean => store.hasTable('pets'),
    getPetsTable: (): PetsTable => store.getTable('pets') as PetsTable,
    // ...
  };
};

Examples

For the full set of methods and types generated by these code generation methods, inspect the output directly. Suffice to say that the generated APIs above will allow you to write domain-specific code like this:

import {createShop} from './shop';

const shop = createShop()
  .setPetsTable({fido: {species: 'dog'}})
  .setPetsRow('felix', {species: 'cat'});
console.log(shop.getPetsSpeciesCell('felix'));
// -> 'cat'

And so on. There is complete equivalence between the generated methods in your domain-specific API and the underlying Store interface. Similarly, every React hook and component has a domain-specific equivalent:

import React from 'react';
import {createRoot} from 'react-dom/client';

const App = () => {
  shop = useCreateShop(() =>
    createShop()
      .setPetsTable({fido: {species: 'dog'}})
      .setPetsRow('felix', {species: 'cat'}),
  );
  return (
    <Provider shop={shop}>
      <PetsRowView rowId="fido" />
      <PetsSpeciesCellView rowId="felix" />
    </Provider>
  );
};

createRoot(document.body).render(<App />);

Other Notes

Whether pretty or not, the resulting files can easily be included in your main application. Note that the implementations (eg shop.ts) import types from the definitions (eg shop.d.ts) and so it's required that the four files are next to each other in the file system.

If the Store has neither an explicit schema, nor can one be inferred, the four strings will be empty, since no type information can be usefully generated for the Store.

If you do not wish to handle the programmatic steps described above, and simply want to take schemas stored in a file and generate these definitions directly, see the Command Line guide in this section.

You can also read the brief Gathering Statistics guide.