@instantdb/core
Version:
Instant's core local abstraction
576 lines (519 loc) • 13.4 kB
text/typescript
import type { RoomSchemaShape } from './presence.ts';
export class DataAttrDef<
ValueType,
IsRequired extends RequirementKind,
IsIndexed extends boolean,
> {
public metadata: Record<string, unknown> = {};
constructor(
public valueType: ValueTypes,
public required: IsRequired,
public isIndexed: IsIndexed,
public config: {
indexed: boolean;
unique: boolean;
// clientValidator?: (value: ValueType) => boolean;
} = { indexed: false, unique: false },
) {}
/**
* @deprecated Only use this temporarily for attributes that you want
* to treat as required in frontend code but can’t yet mark as required
* and enforced for backend
*/
clientRequired() {
return new DataAttrDef<ValueType, true, IsIndexed>(
this.valueType,
false as unknown as true,
this.isIndexed,
this.config,
);
}
optional() {
return new DataAttrDef<ValueType, false, IsIndexed>(
this.valueType,
false,
this.isIndexed,
this.config,
);
}
unique() {
return new DataAttrDef<ValueType, IsRequired, IsIndexed>(
this.valueType,
this.required,
this.isIndexed,
{
...this.config,
unique: true,
},
);
}
indexed() {
return new DataAttrDef<ValueType, IsRequired, true>(
this.valueType,
this.required,
true,
{
...this.config,
indexed: true,
},
);
}
// clientValidate(clientValidator: (value: ValueType) => boolean) {
// return new DataAttrDef(this.valueType, this.required, {
// ...this.config,
// clientValidator,
// });
// }
}
export class LinkAttrDef<
Cardinality extends CardinalityKind,
EntityName extends string,
> {
constructor(
public cardinality: Cardinality,
public entityName: EntityName,
) {}
}
export interface IContainEntitiesAndLinks<
Entities extends EntitiesDef,
Links extends LinksDef<Entities>,
> {
entities: Entities;
links: Links;
}
// ==========
// base types
export type ValueTypes = 'string' | 'number' | 'boolean' | 'date' | 'json';
export type CardinalityKind = 'one' | 'many';
// true - force required
// false - optional, not required
export type RequirementKind = true | false;
export type AttrsDefs = Record<string, DataAttrDef<any, any, any>>;
export class EntityDef<
Attrs extends AttrsDefs,
Links extends Record<string, LinkAttrDef<any, any>>,
AsType,
> {
constructor(
public attrs: Attrs,
public links: Links,
) {}
asType<_AsType extends Partial<MappedAttrs<Attrs>>>() {
return new EntityDef<Attrs, Links, _AsType>(this.attrs, this.links);
}
}
export type EntitiesDef = Record<string, EntityDef<any, any, any>>;
export type LinksDef<Entities extends EntitiesDef> = Record<
string,
LinkDef<
Entities,
keyof Entities,
string,
CardinalityKind,
keyof Entities,
string,
CardinalityKind
>
>;
export type LinkDef<
Entities extends EntitiesDef,
FwdEntity extends keyof Entities,
FwdAttr extends string,
FwdCardinality extends CardinalityKind,
RevEntity extends keyof Entities,
RevAttr extends string,
RevCardinality extends CardinalityKind,
> = {
forward: {
on: FwdEntity;
label: FwdAttr;
has: FwdCardinality;
required?: RequirementKind;
onDelete?: 'cascade';
};
reverse: {
on: RevEntity;
label: RevAttr;
has: RevCardinality;
onDelete?: 'cascade';
};
};
// ==========
// derived types
export type EntitiesWithLinks<
Entities extends EntitiesDef,
Links extends LinksDef<Entities>,
> = {
[EntityName in keyof Entities]: EntityDef<
Entities[EntityName]['attrs'],
EntityForwardLinksMap<EntityName, Entities, Links> &
EntityReverseLinksMap<EntityName, Entities, Links>,
Entities[EntityName] extends EntityDef<any, any, infer O>
? O extends void
? void
: O
: void
>;
};
type EntityForwardLinksMap<
EntityName extends keyof Entities,
Entities extends EntitiesDef,
Links extends LinksDef<Entities>,
LinkIndexFwd = LinksIndexedByEntity<Entities, Links, 'reverse'>,
> = EntityName extends keyof LinkIndexFwd
? {
[LinkName in keyof LinkIndexFwd[EntityName]]: LinkIndexFwd[EntityName][LinkName] extends LinkDef<
Entities,
infer RelatedEntityName,
any,
any,
any,
any,
infer Cardinality
>
? {
entityName: RelatedEntityName;
cardinality: Cardinality;
}
: never;
}
: {};
type EntityReverseLinksMap<
EntityName extends keyof Entities,
Entities extends EntitiesDef,
Links extends LinksDef<Entities>,
RevLinkIndex = LinksIndexedByEntity<Entities, Links, 'forward'>,
> = EntityName extends keyof RevLinkIndex
? {
[LinkName in keyof RevLinkIndex[EntityName]]: RevLinkIndex[EntityName][LinkName] extends LinkDef<
Entities,
any,
any,
infer Cardinality,
infer RelatedEntityName,
any,
any
>
? {
entityName: RelatedEntityName;
cardinality: Cardinality;
}
: never;
}
: {};
type LinksIndexedByEntity<
Entities extends EntitiesDef,
Links extends LinksDef<Entities>,
Direction extends 'forward' | 'reverse',
> = {
[FwdEntity in keyof Entities]: {
[LinkName in keyof Links as Links[LinkName][Direction]['on'] extends FwdEntity
? Links[LinkName][Direction]['label']
: never]: Links[LinkName] extends LinkDef<
Entities,
infer FwdEntity,
infer FwdAttr,
infer FwdCardinality,
infer RevEntity,
infer RevAttr,
infer RevCardinality
>
? LinkDef<
Entities,
FwdEntity,
FwdAttr,
FwdCardinality,
RevEntity,
RevAttr,
RevCardinality
>
: never;
};
};
type RequiredKeys<Attrs extends AttrsDefs> = {
[K in keyof Attrs]: Attrs[K] extends DataAttrDef<any, infer R, any>
? R extends true
? K
: never
: never;
}[keyof Attrs];
type OptionalKeys<Attrs extends AttrsDefs> = {
[K in keyof Attrs]: Attrs[K] extends DataAttrDef<any, infer R, any>
? R extends false
? K
: never
: never;
}[keyof Attrs];
/**
* MappedAttrs:
* - Required keys => `key: ValueType`
* - Optional keys => `key?: ValueType`
*/
type MappedAttrs<Attrs extends AttrsDefs> = {
[K in RequiredKeys<Attrs>]: Attrs[K] extends DataAttrDef<infer V, any, any>
? V
: never;
} & {
[K in OptionalKeys<Attrs>]?: Attrs[K] extends DataAttrDef<infer V, any, any>
? V
: never;
};
export type ResolveEntityAttrs<
EDef extends EntityDef<any, any, any>,
ResolvedAttrs = MappedAttrs<EDef['attrs']>,
> =
EDef extends EntityDef<any, any, infer AsType>
? AsType extends void
? ResolvedAttrs
: Omit<ResolvedAttrs, keyof AsType> & AsType
: ResolvedAttrs;
export type ResolveAttrs<
Entities extends EntitiesDef,
EntityName extends keyof Entities,
> = ResolveEntityAttrs<Entities[EntityName]>;
export type RoomsFromDef<RDef extends RoomsDef> = {
[RoomName in keyof RDef]: {
presence: ResolveEntityAttrs<RDef[RoomName]['presence']>;
topics: {
[TopicName in keyof RDef[RoomName]['topics']]: ResolveEntityAttrs<
RDef[RoomName]['topics'][TopicName]
>;
};
};
};
export type RoomsOf<S> =
S extends InstantSchemaDef<any, any, infer RDef> ? RoomsFromDef<RDef> : never;
export type PresenceOf<
S,
RoomType extends keyof RoomsOf<S>,
> = RoomsOf<S>[RoomType] extends { presence: infer P } ? P : {};
export type TopicsOf<
S,
RoomType extends keyof RoomsOf<S>,
> = RoomsOf<S>[RoomType] extends { topics: infer T } ? T : {};
export type TopicOf<
S,
RoomType extends keyof RoomsOf<S>,
TopicType extends keyof TopicsOf<S, RoomType>,
> = TopicsOf<S, RoomType>[TopicType];
interface RoomDef {
presence: EntityDef<any, any, any>;
topics?: {
[TopicName: string]: EntityDef<any, any, any>;
};
}
export interface RoomsDef {
[RoomType: string]: RoomDef;
}
export class InstantSchemaDef<
Entities extends EntitiesDef,
Links extends LinksDef<Entities>,
Rooms extends RoomsDef,
> implements IContainEntitiesAndLinks<Entities, Links>
{
constructor(
public entities: Entities,
public links: Links,
public rooms: Rooms,
) {}
/**
* @deprecated
* `withRoomSchema` is deprecated. Define your schema in `rooms` directly:
*
* @example
* // Before:
* const schema = i.schema({
* // ...
* }).withRoomSchema<RoomSchema>()
*
* // After
* const schema = i.schema({
* rooms: {
* // ...
* }
* })
*
* @see https://instantdb.com/docs/presence-and-topics#typesafety
*/
withRoomSchema<_RoomSchema extends RoomSchemaShape>() {
type RDef = RoomDefFromShape<_RoomSchema>;
return new InstantSchemaDef<Entities, Links, RDef>(
this.entities,
this.links,
{} as RDef,
);
}
}
/**
* @deprecated
* `i.graph` is deprecated. Use `i.schema` instead.
*
* @see https://instantdb.com/docs/modeling-data
*/
export class InstantGraph<
Entities extends EntitiesDef,
Links extends LinksDef<Entities>,
RoomSchema extends RoomSchemaShape = {},
> implements IContainEntitiesAndLinks<Entities, Links>
{
constructor(
public entities: Entities,
public links: Links,
) {}
withRoomSchema<_RoomSchema extends RoomSchemaShape>() {
return new InstantGraph<Entities, Links, _RoomSchema>(
this.entities,
this.links,
);
}
}
type EntityDefFromRoomSlice<Shape extends { [k: string]: any }> = EntityDef<
{
[AttrName in keyof Shape]: DataAttrDef<
Shape[AttrName],
Shape[AttrName] extends undefined ? false : true,
any
>;
},
any,
void
>;
type RoomDefFromShape<RoomSchema extends RoomSchemaShape> = {
[RoomName in keyof RoomSchema]: {
presence: EntityDefFromRoomSlice<RoomSchema[RoomName]['presence']>;
topics: {
[TopicName in keyof RoomSchema[RoomName]['topics']]: EntityDefFromRoomSlice<
RoomSchema[RoomName]['topics'][TopicName]
>;
};
};
};
type EntityDefFromShape<Shape, K extends keyof Shape> = EntityDef<
{
[AttrName in keyof Shape[K]]: DataAttrDef<
Shape[K][AttrName],
Shape[K][AttrName] extends undefined ? false : true,
any
>;
},
{
[LinkName in keyof Shape]: LinkAttrDef<
'many',
LinkName extends string ? LinkName : string
>;
},
void
>;
/**
* If you were using the old `schema` types, you can use this to help you
* migrate.
*
* @example
* // Before
* const db = init<Schema, Rooms>({...})
*
* // After
* const db = init<BackwardsCompatibleSchema<Schema, Rooms>>({...})
*/
export type BackwardsCompatibleSchema<
Shape extends { [k: string]: any },
RoomSchema extends RoomSchemaShape = {},
> = InstantSchemaDef<
{ [K in keyof Shape]: EntityDefFromShape<Shape, K> },
UnknownLinks<EntitiesDef>,
RoomDefFromShape<RoomSchema>
>;
// ----------
// InstantUnknownSchema
export type UnknownEntity = EntityDef<
{
id: DataAttrDef<string, true, true>;
[AttrName: string]: DataAttrDef<any, any, any>;
},
{ [LinkName: string]: LinkAttrDef<'many', string> },
void
>;
export type UnknownEntities = {
[EntityName: string]: UnknownEntity;
};
export interface UnknownLinks<Entities extends EntitiesDef> {
[LinkName: string]: LinkDef<
Entities,
string,
string,
'many',
string,
string,
'many'
>;
}
export interface UnknownRooms {
[RoomName: string]: {
presence: EntityDef<any, any, any>;
topics: {
[TopicName: string]: EntityDef<any, any, any>;
};
};
}
export type InstantUnknownSchema = InstantSchemaDef<
UnknownEntities,
UnknownLinks<UnknownEntities>,
UnknownRooms
>;
export type CreateParams<
Schema extends IContainEntitiesAndLinks<any, any>,
EntityName extends keyof Schema['entities'],
> = {
[AttrName in RequiredKeys<
Schema['entities'][EntityName]['attrs']
>]: Schema['entities'][EntityName]['attrs'][AttrName] extends DataAttrDef<
infer ValueType,
any,
any
>
? ValueType
: never;
} & {
[AttrName in OptionalKeys<
Schema['entities'][EntityName]['attrs']
>]?: Schema['entities'][EntityName]['attrs'][AttrName] extends DataAttrDef<
infer ValueType,
any,
false
>
? ValueType | null
: never;
};
export type UpdateParams<
Schema extends IContainEntitiesAndLinks<any, any>,
EntityName extends keyof Schema['entities'],
> = {
[AttrName in keyof Schema['entities'][EntityName]['attrs']]?: Schema['entities'][EntityName]['attrs'][AttrName] extends DataAttrDef<
infer ValueType,
infer IsRequired,
any
>
? IsRequired extends true
? ValueType
: ValueType | null
: never;
};
export type UpdateOpts = {
upsert?: boolean | undefined;
};
export type LinkParams<
Schema extends IContainEntitiesAndLinks<any, any>,
EntityName extends keyof Schema['entities'],
> = {
[LinkName in keyof Schema['entities'][EntityName]['links']]?: Schema['entities'][EntityName]['links'][LinkName] extends LinkAttrDef<
infer Cardinality,
any
>
? Cardinality extends 'one'
? string
: string | string[]
: never;
};
export type RuleParams = {
[key: string]: any;
};