tinyplex / tinybase

The reactive data store for local‑first apps.
https://tinybase.org
MIT License
3.72k stars 78 forks source link

Generate schema-specific hooks and components API from tools module #50

Closed jamesgpearce closed 1 year ago

jamesgpearce commented 1 year ago

With a schema it should be possible to generate domain-specific providers, hooks, and components, just like we do for store methods.

WonderPanda commented 1 year ago

This would be a really great addition. Since we're building react applications we primarily interact through hooks for binding data so we're missing out on some typesafety

jamesgpearce commented 1 year ago

Working on this in the beta branch

WonderPanda commented 1 year ago

Hey I saw that this is work in progress on the beta branch, but wanted to chime in on the potential approach being taken here.

We're definitely not opposed to using some codegen tooling to bring more type safety to our TinyBase usage but I wanted to propose an alternative approach in terms of implementation from what exists currently.

Right now, we end up with an enormous amount of individual functions granular all the way down to the cell level, eg: getUserNameCell() or I'm assuming for the hooks it'll end up being something like const name = useGetUserNameCell().

From a developer consuming the generated API, it feels like a ton of overhead to keep thinking about the specific named function I'm going to need to import for a specific use case. Instead, I feel like it would be much nicer to have a typesafe API that's generated against the overall structure of the schema which would allow for something like this instead:

const name = useCell('user', 'userId', 'name'); where both the table name and cell name are inherently type safe.

I put together a super quick example in Typescript Playground here to show how it can be accomplished:

type TinybaseSchema = Record<string, Record<string, string | number | boolean>>;

type Schema = {
  users: {
    id: string;
    name: string;
    updated_at: string;
    age: number;
  };
};

type StoreWrapper<TSchema extends TinybaseSchema> = {
  getRow<TTable extends keyof TSchema>(
    tableName: TTable,
    id: string
  ): TSchema[TTable];

  getCell<TTable extends keyof TSchema, TCell extends keyof TSchema[TTable]>(
    tableName: TTable,
    id: string,
    cell: TCell
  ): TSchema[TTable][TCell];
};

const store = null as unknown as StoreWrapper<Schema>;

const rowExample = store.getRow('users', '123');
const cellExample = store.getCell('users', "123", 'name');
const cellExample2 = store.getCell('users', '123', 'notACell') // error, 'notACell' is not valid
const cellExample3 = store.getCell('user', '123', 'notACell') // error, 'user' is not valid
jamesgpearce commented 1 year ago

Definitely! The ORM autogenerated methods trope is pretty verbose (and getting worse) so generating type definitions for a casted store is an elegant alternative. I suspect we will end up with both options in 3.1.

jamesgpearce commented 1 year ago

OK, more or less finished with the explicit generated API (https://github.com/tinyplex/tinybase/releases/tag/v3.1.0-beta.2) - going to get the 'lighter' typing approach available for beta.3... coming soon!

jamesgpearce commented 1 year ago

Here's what I plan to implement for the types-only refinement approach:

image

As you might discern from https://github.com/tinyplex/tinybase/commit/adb9fb8801b0753c7f5030446c91d1e40de2c810 (see src/tools/refinement/core.ts), there's a full passthrough of the type definitions by default, so we only need to code-generate those that are refined by the schema, and everything else will still be present for importing.

Fortunately the boilerplate for refining the whole module ends up being pretty slick, and the passthrough means we don't even need to take it to any or unknown and back!

jamesgpearce commented 1 year ago

Please kick the tires on https://github.com/tinyplex/tinybase/releases/tag/v3.1.0-beta.3

jamesgpearce commented 1 year ago

Pretty excited about the work leading up to https://github.com/tinyplex/tinybase/commit/c396b91819e963b95fa991c5097c4d07bf67d89f - I think I can go all in on the core module being schema-typed, and remove a bunch of codegen. New beta coming soon.

jamesgpearce commented 1 year ago

I think I can close this out with v3.1 https://tinybase.org/guides/releases/#v3-1