import type { RoomSchemaShape } from './presence.ts'; import { Expand } from './queryTypes.ts'; export class DataAttrDef< ValueType, IsRequired extends RequirementKind, IsIndexed extends boolean, IsUnique extends boolean = false, > { public metadata: Record = {}; 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( this.valueType, false as unknown as true, this.isIndexed, this.config, ); } optional() { return new DataAttrDef( this.valueType, false, this.isIndexed, this.config, ); } unique() { return new DataAttrDef( this.valueType, this.required, this.isIndexed, { ...this.config, unique: true, }, ); } indexed() { return new DataAttrDef( 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; 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>; export class EntityDef< Attrs extends AttrsDefs, Links extends Record>, AsType, > { constructor( public attrs: Attrs, public links: Links, ) {} asType<_AsType extends Partial>>() { return new EntityDef(this.attrs, this.links); } } export type EntityDefFromSchema< S extends IContainEntitiesAndLinks, K extends keyof S['entities'], > = { [k in keyof S['entities'][K]['attrs']]: S['entities'][K] extends EntityDef< any, any, any > ? S['entities'][K]['attrs'][k] : never; }; export type EntitiesDef = Record>; export type LinksDef = 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 type IsEmptyOrIndexSignature = keyof T extends never ? true : string extends keyof T ? true : false; export type EntitiesWithLinks< Entities extends EntitiesDef, Links extends LinksDef, > = { [EntityName in keyof Entities]: EntityDef< Entities[EntityName]['attrs'], EntityForwardLinksMap & EntityReverseLinksMap, Entities[EntityName] extends EntityDef ? O extends void ? void : O : void >; }; type EntityForwardLinksMap< EntityName extends keyof Entities, Entities extends EntitiesDef, Links extends LinksDef, LinkIndexFwd = LinksIndexedByEntity, > = IsEmptyOrIndexSignature extends true ? {} : 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, RevLinkIndex = LinksIndexedByEntity, > = IsEmptyOrIndexSignature extends true ? {} : 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, 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 = { [K in keyof Attrs]: Attrs[K] extends DataAttrDef ? R extends true ? K : never : never; }[keyof Attrs]; export type UniqueKeys = { [K in keyof Attrs]: Attrs[K] extends DataAttrDef ? U extends true ? K : never : never; }[keyof Attrs]; type OptionalKeys = { [K in keyof Attrs]: Attrs[K] extends DataAttrDef ? R extends false ? K : never : never; }[keyof Attrs]; /** * MappedAttrs: * - Required keys => `key: ValueType` * - Optional keys => `key?: ValueType` */ type MappedAttrs = { [K in RequiredKeys]: Attrs[K] extends DataAttrDef< infer V, any, any, boolean > ? V extends Date ? UseDates extends true ? V : string | number : V : never; } & { [K in OptionalKeys]?: Attrs[K] extends DataAttrDef< infer V, any, any, boolean > ? V extends Date ? UseDates extends true ? V : string | number : V : never; }; export type ResolveEntityAttrs< EDef extends EntityDef, UseDates extends boolean = false, ResolvedAttrs = MappedAttrs, > = EDef extends EntityDef ? AsType extends void ? ResolvedAttrs : Omit & AsType : ResolvedAttrs; export type ResolveAttrs< Entities extends EntitiesDef, EntityName extends keyof Entities, UseDates extends boolean, > = ResolveEntityAttrs; export type RoomsFromDef = { [RoomName in keyof RDef]: { presence: Expand>; topics: { [TopicName in keyof RDef[RoomName]['topics']]: Expand< ResolveEntityAttrs[TopicName]> >; }; }; }; export type RoomsOf = S extends InstantSchemaDef ? RoomsFromDef : never; export type PresenceOf< S, RoomType extends keyof RoomsOf, > = RoomsOf[RoomType] extends { presence: infer P } ? P : {}; export type TopicsOf< S, RoomType extends keyof RoomsOf, > = RoomsOf[RoomType] extends { topics: infer T } ? T : {}; export type TopicOf< S, RoomType extends keyof RoomsOf, TopicType extends keyof TopicsOf, > = TopicsOf[TopicType]; interface RoomDef { presence: EntityDef; topics?: { [TopicName: string]: EntityDef; }; } export interface RoomsDef { [RoomType: string]: RoomDef; } export class InstantSchemaDef< Entities extends EntitiesDef, Links extends LinksDef, Rooms extends RoomsDef, > implements IContainEntitiesAndLinks { 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() * * // 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( 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, RoomSchema extends RoomSchemaShape = {}, > implements IContainEntitiesAndLinks { constructor( public entities: Entities, public links: Links, ) {} withRoomSchema<_RoomSchema extends RoomSchemaShape>() { return new InstantGraph( this.entities, this.links, ); } } type EntityDefFromRoomSlice = EntityDef< { [AttrName in keyof Shape]: DataAttrDef< Shape[AttrName], Shape[AttrName] extends undefined ? false : true, any, boolean >; }, any, void >; type RoomDefFromShape = { [RoomName in keyof RoomSchema]: { presence: EntityDefFromRoomSlice< NonNullable >; topics: { [TopicName in keyof RoomSchema[RoomName]['topics']]: EntityDefFromRoomSlice< NonNullable >; }; }; }; type EntityDefFromShape = EntityDef< { [AttrName in keyof Shape[K]]: DataAttrDef< Shape[K][AttrName], Shape[K][AttrName] extends undefined ? false : true, any, boolean >; }, { [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({...}) * * // After * const db = init>({...}) */ export type BackwardsCompatibleSchema< Shape extends { [k: string]: any }, RoomSchema extends RoomSchemaShape = {}, > = InstantSchemaDef< { [K in keyof Shape]: EntityDefFromShape }, UnknownLinks, RoomDefFromShape >; // ---------- // InstantUnknownSchema export type UnknownEntity = EntityDef< { id: DataAttrDef; [AttrName: string]: DataAttrDef; }, { [LinkName: string]: LinkAttrDef<'many', string> }, void >; export type UnknownEntities = { [EntityName: string]: UnknownEntity; }; export interface UnknownLinks { [LinkName: string]: LinkDef< Entities, string, string, 'many', string, string, 'many' >; } export interface UnknownRooms { [RoomName: string]: { presence: EntityDef; topics: { [TopicName: string]: EntityDef; }; }; } export class InstantUnknownSchemaDef extends InstantSchemaDef< UnknownEntities, UnknownLinks, UnknownRooms > {} export type InstantUnknownSchema = InstantUnknownSchemaDef; export type CreateParams< Schema extends IContainEntitiesAndLinks, EntityName extends keyof Schema['entities'], > = { [AttrName in RequiredKeys< Schema['entities'][EntityName]['attrs'] >]: Schema['entities'][EntityName]['attrs'][AttrName] extends DataAttrDef< infer ValueType, any, any, any > ? ValueType extends Date ? string | number | Date : ValueType : never; } & { [AttrName in OptionalKeys< Schema['entities'][EntityName]['attrs'] >]?: Schema['entities'][EntityName]['attrs'][AttrName] extends DataAttrDef< infer ValueType, false, any, any > ? (ValueType extends Date ? string | number | Date : ValueType) | null : never; }; export type UpdateParams< Schema extends IContainEntitiesAndLinks, EntityName extends keyof Schema['entities'], > = { [AttrName in keyof Schema['entities'][EntityName]['attrs']]?: Schema['entities'][EntityName]['attrs'][AttrName] extends DataAttrDef< infer ValueType, infer IsRequired, any, any > ? IsRequired extends true ? ValueType extends Date ? string | number | Date : ValueType : (ValueType extends Date ? string | number | Date : ValueType) | null : never; }; export type UpdateOpts = { upsert?: boolean | undefined; }; export type LinkParams< Schema extends IContainEntitiesAndLinks, 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; };