Closed jamesgpearce closed 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
Working on this in the beta branch
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
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.
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!
Here's what I plan to implement for the types-only refinement approach:
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!
Please kick the tires on https://github.com/tinyplex/tinybase/releases/tag/v3.1.0-beta.3
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.
I think I can close this out with v3.1 https://tinybase.org/guides/releases/#v3-1
With a schema it should be possible to generate domain-specific providers, hooks, and components, just like we do for store methods.