@instantdb/core
Version:
Instant's core local abstraction
195 lines (175 loc) • 4.97 kB
text/typescript
import {
EntityDef,
DataAttrDef,
InstantSchemaDef,
type EntitiesDef,
type AttrsDefs,
type EntitiesWithLinks,
type LinksDef,
type RoomsDef,
type UnknownRooms,
} from './schemaTypes.ts';
// ==========
// API
/**
* @deprecated
* `i.graph` is deprecated. Use `i.schema` instead.
*
* @example
* // Before
* i.graph(entities, links).withRoomSchema<RoomType>();
*
* // After
* i.schema({ entities, links, rooms })
*
* @see
* https://instantdb.com/docs/schema
*/
function graph<
EntitiesWithoutLinks extends EntitiesDef,
const Links extends LinksDef<EntitiesWithoutLinks>,
>(entities: EntitiesWithoutLinks, links: Links) {
return new InstantSchemaDef(
enrichEntitiesWithLinks<EntitiesWithoutLinks, Links>(entities, links),
// (XXX): LinksDef<any> stems from TypeScript’s inability to reconcile the
// type EntitiesWithLinks<EntitiesWithoutLinks, Links> with
// EntitiesWithoutLinks. TypeScript is strict about ensuring that types are
// correctly aligned and does not allow for substituting a type that might
// be broader or have additional properties.
links as LinksDef<any>,
undefined as UnknownRooms,
);
}
/**
* Creates an entity definition, to be used in conjunction with `i.graph`.
*
* @see https://instantdb.com/docs/schema
* @example
* {
* posts: i.entity({
* title: i.string(),
* body: i.string(),
* }),
* comments: i.entity({
* body: i.string(),
* })
* }
*/
function entity<Attrs extends AttrsDefs>(
attrs: Attrs,
): EntityDef<Attrs, {}, void> {
return new EntityDef(attrs, {});
}
function string<StringEnum extends string = string>(): DataAttrDef<
StringEnum,
true,
false
> {
return new DataAttrDef('string', true, false);
}
function number(): DataAttrDef<number, true, false> {
return new DataAttrDef('number', true, false);
}
function boolean(): DataAttrDef<boolean, true, false> {
return new DataAttrDef('boolean', true, false);
}
function date(): DataAttrDef<string | number, true, false> {
return new DataAttrDef('date', true, false);
}
function json<T = any>(): DataAttrDef<T, true, false> {
return new DataAttrDef('json', true, false);
}
function any(): DataAttrDef<any, true, false> {
return new DataAttrDef('json', true, false);
}
// ==========
// internal
function enrichEntitiesWithLinks<
EntitiesWithoutLinks extends EntitiesDef,
Links extends LinksDef<any>,
EnrichedEntities = EntitiesWithLinks<EntitiesWithoutLinks, Links>,
>(entities: EntitiesWithoutLinks, links: Links): EnrichedEntities {
const linksIndex: LinksIndex = { fwd: {}, rev: {} };
for (const linkDef of Object.values(links)) {
linksIndex.fwd[linkDef.forward.on as string] ||= {};
linksIndex.rev[linkDef.reverse.on as string] ||= {};
linksIndex.fwd[linkDef.forward.on as string][linkDef.forward.label] = {
entityName: linkDef.reverse.on as string,
cardinality: linkDef.forward.has,
};
linksIndex.rev[linkDef.reverse.on as string][linkDef.reverse.label] = {
entityName: linkDef.forward.on as string,
cardinality: linkDef.reverse.has,
};
}
const enrichedEntities = Object.fromEntries(
Object.entries(entities).map(([name, def]) => [
name,
new EntityDef(def.attrs, {
...linksIndex.fwd[name],
...linksIndex.rev[name],
}),
]),
);
return enrichedEntities as EnrichedEntities;
}
type LinksIndex = Record<
'fwd' | 'rev',
Record<string, Record<string, { entityName: string; cardinality: string }>>
>;
/**
* Lets you define a schema for your database.
*
* You can define entities, links between entities, and if you use
* presence, you can define rooms.
*
* You can push this schema to your database with the CLI,
* or use it inside `init`, to get typesafety and autocompletion.
*
* @see https://instantdb.com/docs/schema
* @example
* i.schema({
* entities: { },
* links: { },
* rooms: { }
* });
*/
function schema<
EntitiesWithoutLinks extends EntitiesDef,
const Links extends LinksDef<EntitiesWithoutLinks>,
Rooms extends RoomsDef,
>({
entities,
links,
rooms,
}: {
entities: EntitiesWithoutLinks;
links?: Links;
rooms?: Rooms;
}) {
const linksDef = links ?? ({} as Links);
const roomsDef = rooms ?? ({} as Rooms);
return new InstantSchemaDef(
enrichEntitiesWithLinks<EntitiesWithoutLinks, Links>(entities, linksDef),
// (XXX): LinksDef<any> stems from TypeScript’s inability to reconcile the
// type EntitiesWithLinks<EntitiesWithoutLinks, Links> with
// EntitiesWithoutLinks. TypeScript is strict about ensuring that types are
// correctly aligned and does not allow for substituting a type that might
// be broader or have additional properties.
linksDef as LinksDef<any>,
roomsDef,
);
}
export const i = {
// constructs
graph,
schema,
entity,
// value types
string,
number,
boolean,
date,
json,
any,
};