import { ulid } from 'ulid' ; import { RedisError } from '..' ; import { SearchDataStructure } from '../client' ; import Entity from "../entity/entity" ; import { EntityConstructor } from '../entity/entity' ; import SchemaBuilder from './schema-builder' ; import { FieldDefinition , IdStrategy , SchemaDefinition , StopWordOptions } from './schema-definitions' ; import { SchemaOptions } from './schema-options' ; /**
 * Defines a schema that determines how an  Entity is mapped to Redis
 * data structures. Construct by passing in an  EntityConstructor ,
 * a  SchemaDefinition , and  SchemaOptions :
 *
 * ```typescript
 * let schema = new Schema(Foo, {
 *   aString: { type: 'string' },
 *   aNumber: { type: 'number' },
 *   aBoolean: { type: 'boolean' },
 *   anArray: { type: 'array' }
 * }, {
 *   dataStructure: 'JSON'
 * });
 * ```
 *
 * A Schema is primarily used by a  Repository which requires a Schema in
 * its constructor.
 *
 *  template TEntity The  Entity this Schema defines.
  export default class Schema < TEntity extends Entity > { /**
     * The provided  EntityConstructor .
     *  internal readonly entityCtor : EntityConstructor < TEntity > ; /**
     * The provided  SchemaDefinition .
     *  internal readonly definition : SchemaDefinition ; private options ? : SchemaOptions ; template TEntity The  Entity this Schema defines.
     *  param ctor A constructor that creates an  Entity of type TEntity.
     *  param schemaDef param options constructor ( ctor , schemaDef , options ? ) { this . entityCtor = ctor ; this . definition = schemaDef ; this . options = options ; this . validateOptions ( ) ; this . defineProperties ( ) ; } /** The configured keyspace prefix in Redis for this Schema. */ get prefix ( ) : string { return this . options ?. prefix ?? this . entityCtor . name ; } /** The configured name for the RediSearch index for this Schema. */ get indexName ( ) : string { return this . options ?. indexName ?? `${ this . prefix }:index` ; } /**
     * The configured data structure, a string with the value of either `HASH` or `JSON`,
     * that this Schema uses to store  Entity in Redis.
     *  get dataStructure ( ) : SearchDataStructure { return this . options ?. dataStructure ?? 'HASH' ; } /**
     * The configured usage of stop words, a string with the value of either `OFF`, `DEFAULT`,
     * or `CUSTOM`. See  SchemaOptions . useStopWords and  SchemaOptions . stopWords * for more details.
      get useStopWords ( ) : StopWordOptions { return this . options ?. useStopWords ?? 'DEFAULT' ; } /**
     * The configured stop words. Ignored if  Schema . useStopWords is anything other
     * than `CUSTOM`.
      get stopWords ( ) : string [ ] { return this . options ?. stopWords ?? [ ] ; } internal get redisSchema ( ) : string [ ] { return new SchemaBuilder ( this ) . redisSchema ; } /**
     * Generates a unique string using the configured  IdStrategy .
     *  returns generateId ( ) { let ulidStrategy = ( ) => ulid ( ) ; return ( this . options ?. idStrategy ?? ulidStrategy ) ( ) ; } private defineProperties ( ) { let entityName = this . entityCtor . name ; for ( let field in this . definition ) { this . validateFieldDef ( field ) ; let fieldDef = this . definition [ field ] ; let fieldType = fieldDef . type ; let fieldAlias = fieldDef . alias ?? field ; Object . defineProperty ( this . entityCtor . prototype , field , { configurable : true , get : function ( ) : any { return this . entityData [ fieldAlias ] ?? null ; } , set : function ( value ) : void { if ( value === undefined ) { throw Error ( `Property '${ field }' on entity of type '${ entityName }' cannot be set to undefined. Use null instead.` ) ; } else if ( value === null ) { delete this . entityData [ fieldAlias ] ; } else { let isArray = Array . isArray ( value ) ; let valueType = isArray ? 'array' : typeof ( value ) ; if ( fieldType === valueType ) { if ( isArray ) { this . entityData [ fieldAlias ] = value . map ( ( v ) => v . toString ( ) ) ; } else { this . entityData [ fieldAlias ] = value ; } } else { throw new RedisError ( `Property '${ field }' expected type of '${ fieldType }' but received type of '${ valueType }'.` ) ; } } } } ) ; } } private validateOptions ( ) { if ( ! [ 'HASH' , 'JSON' ] . includes ( this . dataStructure ) ) throw Error ( `'${ this . dataStructure }' in an invalid data structure. Valid data structures are 'HASH' and 'JSON'.` ) ; if ( ! [ 'OFF' , 'DEFAULT' , 'CUSTOM' ] . includes ( this . useStopWords ) ) throw Error ( `'${ this . useStopWords }' in an invalid value for stop words. Valid values are 'OFF', 'DEFAULT', and 'CUSTOM'.` ) ; if ( this . options ?. idStrategy && ! ( this . options . idStrategy instanceof Function ) ) throw Error ( "ID strategy must be a function that takes no arguments and returns a string." ) ; if ( this . prefix === '' ) throw Error ( `Prefix must be a non-empty string.` ) ; if ( this . indexName === '' ) throw Error ( `Index name must be a non-empty string.` ) ; } private validateFieldDef ( field ) { let fieldDef = this . definition [ field ] ; if ( ! [ 'array' , 'boolean' , 'number' , 'string' ] . includes ( fieldDef . type ) ) throw Error ( `The field '${ field }' is configured with a type of '${ fieldDef . type }'. Valid types include 'array', 'boolean', 'number', and 'string'.` ) ; } }