UNPKG

@snapdm/model

Version:

An opinionated snapshot oriented modeling system for Cloud Firestore

400 lines (388 loc) 16 kB
import { DeepPartial } from 'ts-essentials'; interface FieldValue { isEqual(other: FieldValue): boolean; } interface FieldValueFactory { /** * Returns a sentinel used with `set()` or `update()` to include a * server-generated timestamp in the written data. */ serverTimestamp(): FieldValue; /** * Returns a sentinel for use with `update()` to mark a field for deletion. */ delete(): FieldValue; /** * Returns a special value that can be used with `set()` or `update()` that tells * the server to union the given elements with any array value that already * exists on the server. Each specified element that doesn't already exist in * the array will be added to the end. If the field being modified is not * already an array it will be overwritten with an array containing exactly * the specified elements. * * @param elements The elements to union into the array. * @return The FieldValue sentinel for use in a call to `set()` or `update()`. */ arrayUnion(...elements: any[]): FieldValue; /** * Returns a special value that can be used with `set()` or `update()` that tells * the server to remove the given elements from any array value that already * exists on the server. All instances of each element specified will be * removed from the array. If the field being modified is not already an * array it will be overwritten with an empty array. * * @param elements The elements to remove from the array. * @return The FieldValue sentinel for use in a call to `set()` or `update()`. */ arrayRemove(...elements: any[]): FieldValue; /** * Returns a special value that can be used with `set()` or `update()` that tells * the server to increment the field's current value by the given value. * * If either the operand or the current field value uses floating point precision, * all arithmetic follows IEEE 754 semantics. If both values are integers, * values outside of JavaScript's safe number range (`Number.MIN_SAFE_INTEGER` to * `Number.MAX_SAFE_INTEGER`) are also subject to precision loss. Furthermore, * once processed by the Firestore backend, all integer operations are capped * between -2^63 and 2^63-1. * * If the current field value is not of type `number`, or if the field does not * yet exist, the transformation sets the field to the given value. * * @param n The value to increment by. * @return The FieldValue sentinel for use in a call to `set()` or `update()`. */ increment(n: number): FieldValue; } declare type IdFactory = () => string; declare type DocumentData = Readonly<{ [field: string]: unknown; }>; interface DocumentReference<T = DocumentData> { /** The identifier of the document within its collection. */ readonly id: string; /** * A reference to the Collection to which this DocumentReference belongs. */ readonly parent: CollectionReference<T>; /** * A string representing the path of the referenced document (relative * to the root of the database). */ readonly path: string; } /** * A `CollectionReference` object can be used for adding documents, getting * document references, and querying for documents (using {@link query}). */ interface CollectionReference<T = DocumentData> { /** The identifier of the collection. */ readonly id: string; /** * A reference to the containing Document if this is a subcollection, else * null. */ readonly parent: DocumentReference<T> | null; /** * A string representing the path of the referenced collection (relative * to the root of the database). */ readonly path: string; } declare type ReferenceFactory = (collection: string, id: string, parent?: DocumentReference) => DocumentReference; interface Timestamp { /** * The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. */ readonly seconds: number; /** * The non-negative fractions of a second at nanosecond resolution. */ readonly nanoseconds: number; toDate(): Date; toMillis(): number; isEqual(other: Timestamp): boolean; } interface TimestampFactory { /** * Creates a new timestamp with the current date, with millisecond precision. * * @return A new `Timestamp` representing the current date. */ now(): Timestamp; /** * Creates a new timestamp from the given date. * * @param date The date to initialize the `Timestamp` from. * @return A new `Timestamp` representing the same point in time as the * given date. */ fromDate(date: Date): Timestamp; /** * Creates a new timestamp from the given number of milliseconds. * * @param milliseconds Number of milliseconds since Unix epoch * 1970-01-01T00:00:00Z. * @return A new `Timestamp` representing the same point in time as the * given number of milliseconds. */ fromMillis(milliseconds: number): Timestamp; } declare type Adapter = Readonly<{ /** * The IdGenerator to use when */ ids: IdFactory; fieldValues: FieldValueFactory; timestamps: TimestampFactory; references: ReferenceFactory; }>; declare function adapter(): Adapter; interface SnapdmOptions { readonly adapter: Adapter; } declare function initialize(options: SnapdmOptions): void; declare type SnapshotData = object; declare type SnapshotAttributes<T extends SnapshotData = SnapshotData> = Readonly<{ type: string; id: string; ref: DocumentReference<Snapshot<T>>; createdAt: Timestamp; updatedAt: Timestamp; }>; /** * A document snapshot model. */ declare type Snapshot<T extends SnapshotData = SnapshotData> = SnapshotAttributes<T> & T; declare type SnapshotUpdates<Data = SnapshotData> = { readonly updatedAt: Timestamp; } & DeepPartial<Data>; /** * An object containing error keys and reasons. */ declare type ValidationErrors = Record<string, any>; /** * A validator is a function that takes a value and return null if that value * is valid, or an object containing error keys and reasons. */ declare type Validator<T> = (value: T) => ValidationErrors | null; declare type Type<T> = new (...args: any[]) => T; declare type AbstractType<T> = abstract new (...args: any[]) => T; declare type AnyType<T> = Type<T> | AbstractType<T>; declare type ModelIdentifiers = 'type' | 'id' | 'ref'; declare type ModelImmutableAttributes = ModelIdentifiers | 'createdAt' | 'updatedAt'; declare type ModelAttributes<Data extends SnapshotData> = Omit<Data, ModelImmutableAttributes>; declare type ModelClassAttributes = Readonly<{ type?: string; collection: string; }>; declare type ModelParent<Data extends SnapshotData> = Readonly<{ model: Type<AnyModel> & ModelClassAttributes & Readonly<{ parent?: any; }>; attribute: keyof ModelAttributes<Data>; }>; declare type InitializeFunction<Data extends SnapshotData, Initializer> = (init: Initializer) => ModelInit<Data>; declare type InitializeFunctionWithBase<Base extends RootModel & AnyModel, Data extends ModelData<Base>, Initializer> = (init: Initializer) => ModelInit<Data>; declare type ModelOptions<Data extends SnapshotData, Initializer> = ModelClassAttributes & Readonly<{ /** * Metadata about this model's parent. */ parent?: ModelParent<Data>; /** * An initializing function that converts a model's initializer into * its internal data. This method is where data defaults should be * set. */ initialize?: InitializeFunction<Data, Initializer>; /** * An optional list of validators to apply to the model's snapshot * before writing it to the database. */ validators?: Validator<Data>[]; }>; declare type ModelData<T extends RootModel & AnyModel> = Omit<T['snapshot'], ModelImmutableAttributes>; declare type ModelWithBaseOptions<Base extends RootModel & AnyModel, Data extends ModelData<Base>, Initializer> = Readonly<{ type?: string; /** * An initializing function that converts a model's initializer into * its internal data. This method is where data defaults should be * set. */ initialize?: InitializeFunctionWithBase<Base, Data, Initializer>; /** * An optional list of validators to apply to the model's snapshot * before writing it to the database. */ validators?: Validator<Data>[]; }>; declare type ExtendedModelOptions<Base extends RootModel & AnyModel, Data extends ModelData<Base>, Initializer> = Readonly<{ extends: AnyType<Base>; }> & ModelWithBaseOptions<Base, Data, Initializer>; /** * A type that accurately represents the interface of typeof AnyModel. */ declare type ModelClass<T extends AnyModel> = Type<T> & Exclude<ModelOptions<T['snapshot'], any>, 'validators'> & Readonly<{ validator: Validator<T['snapshot']>; }>; declare type AnyModelClass<T extends AnyModel> = AnyType<T> & Exclude<ModelOptions<T['snapshot'], any>, 'validators'> & Readonly<{ validator: Validator<T['snapshot']>; }>; declare type ModelInit<Data extends SnapshotData> = Omit<Data, ModelImmutableAttributes> & { type?: string; id?: string; }; /** * A reference to another model. */ declare type ModelRef<T extends AnyModel> = Readonly<{ type: string; id: string; ref: DocumentReference<T['snapshot']>; }>; interface AnyModel<Data extends SnapshotData = object> { readonly type: string; readonly id: string; readonly ref: DocumentReference<Snapshot<Data>>; readonly createdAt: Timestamp; readonly updatedAt: Timestamp; readonly snapshot: Snapshot<Data>; readonly updates?: SnapshotUpdates<Data>; readonly isNew: boolean; toRef<Keys extends keyof Data>(...includeAttributes: Keys[]): ModelRef<AnyModel<Data>> & Pick<Data, Keys>; } declare type ModelCtrOptions<Data extends SnapshotData> = Readonly<{ updates?: SnapshotUpdates<Data>; isNew?: boolean; }>; interface RootModel extends Snapshot { } declare abstract class RootModel { constructor(initializer: any); constructor(snapshot: any, options?: any); } /** * Mixin function for creating a new model. * @param options The options for configuring this model * @returns A model class with the provided options mixed int the class. */ declare function Model<Data extends ModelData<Base>, Initializer, Base extends RootModel & AnyModel = any>(options: ModelOptions<Data, Initializer> | ExtendedModelOptions<Base, Data, Initializer>): { new (init: Initializer): { /** * Get the current snapshot of the underlying JSON document. */ readonly snapshot: Snapshot<Data>; /** * A flag indicating if this model is new i.e. it was created from * and initializer and not a snapshot. */ readonly isNew: boolean; /** * An object containing the differences between this object and * the object it was copied from. An entity should only be saved if it * isNew or its updates are defined. This object can also be used * perform a partial update of the underlying document. */ readonly updates?: SnapshotUpdates<Data>; /** * Meta method for getting the constructor of `this` object to access static * methods defined on subclasses, or construct instances of subclasses in * the base class. */ readonly model: ModelClass<any>; /** * Convert this model into a reference object. * @param includeAttributes Optional fields in this model to include in the reference. * @returns A ref to this model. */ toRef<Keys extends "type" | "id" | "ref" | "createdAt" | "updatedAt" | keyof Data>(...includeAttributes: Keys[]): Readonly<{ type: string; id: string; ref: DocumentReference<Snapshot<Data>>; }> & Pick<Snapshot<Data>, Keys>; /** * Creates a new instance of this Model containing the provided * updates to the internal snapshot. This method is not intended to be * consumed externally to the class and exists primarily to be used as * an implementation detail of more domain oriented transformations. * @param updates A patch to apply to the current snapshot in creating * the new one. * @returns A new model with the given patch applied. */ clone(updates?: DeepPartial<Data>): this; readonly type: string; readonly id: string; readonly ref: DocumentReference<Snapshot<object>>; readonly createdAt: Timestamp; readonly updatedAt: Timestamp; }; new (snapshot: Snapshot<Data>, options?: ModelCtrOptions<Data>): { /** * Get the current snapshot of the underlying JSON document. */ readonly snapshot: Snapshot<Data>; /** * A flag indicating if this model is new i.e. it was created from * and initializer and not a snapshot. */ readonly isNew: boolean; /** * An object containing the differences between this object and * the object it was copied from. An entity should only be saved if it * isNew or its updates are defined. This object can also be used * perform a partial update of the underlying document. */ readonly updates?: SnapshotUpdates<Data>; /** * Meta method for getting the constructor of `this` object to access static * methods defined on subclasses, or construct instances of subclasses in * the base class. */ readonly model: ModelClass<any>; /** * Convert this model into a reference object. * @param includeAttributes Optional fields in this model to include in the reference. * @returns A ref to this model. */ toRef<Keys extends "type" | "id" | "ref" | "createdAt" | "updatedAt" | keyof Data>(...includeAttributes: Keys[]): Readonly<{ type: string; id: string; ref: DocumentReference<Snapshot<Data>>; }> & Pick<Snapshot<Data>, Keys>; /** * Creates a new instance of this Model containing the provided * updates to the internal snapshot. This method is not intended to be * consumed externally to the class and exists primarily to be used as * an implementation detail of more domain oriented transformations. * @param updates A patch to apply to the current snapshot in creating * the new one. * @returns A new model with the given patch applied. */ clone(updates?: DeepPartial<Data>): this; readonly type: string; readonly id: string; readonly ref: DocumentReference<Snapshot<object>>; readonly createdAt: Timestamp; readonly updatedAt: Timestamp; }; readonly type: string; readonly collection: string; readonly parent: Readonly<{ model: Type<AnyModel<object>> & Readonly<{ type?: string; collection: string; }> & Readonly<{ parent?: any; }>; attribute: Exclude<keyof Data, ModelImmutableAttributes>; }>; readonly initialize: InitializeFunction<Data, Initializer> | typeof identity; readonly validator: Validator<Data>; }; declare function identity<T>(e: T): T; declare type ModelFactory<T extends AnyModel> = ModelClass<T> | Readonly<{ type: AnyModelClass<T>; factory: (data: DocumentData) => T; }>; declare function buildModel<T extends AnyModel>(factory: ModelFactory<T>, data: DocumentData): T; export { Adapter, AnyModel, InitializeFunction, Model, ModelClass, ModelFactory, ModelOptions, ModelRef, SnapdmOptions, Timestamp, ValidationErrors, Validator, adapter, buildModel, initialize };