repo_description: A fast, local first, reactive Database for JavaScript Applications https://rxdb.info/
repo_topics: angular browser-database couchdb crdt database firebase graphql indexeddb ionic local-first localstorage nodejs nosql postgres pouchdb react-native realtime realtime-database rxdb sqlite
code: import {
      overwriteGetterForCaching,
      isMaybeReadonlyArray,
      deepEqual
  } from './plugins/utils/index.ts';
  import {
      newRxError,
  } from './rx-error.ts';
  import {
      runPluginHooks
  } from './hooks.ts';
  
  import type {
      DeepMutable,
      DeepReadonly,
      HashFunction,
      MaybeReadonly,
      RxDocument,
      RxDocumentData,
      RxJsonSchema,
      StringKeys
  } from './types/index.d.ts';
  import {
      fillWithDefaultSettings,
      getComposedPrimaryKeyOfDocumentData,
      getFinalFields,
      getPrimaryFieldOfPrimaryKey,
      getSchemaByObjectPath,
      normalizeRxJsonSchema
  } from './rx-schema-helper.ts';
  import { overwritable } from './overwritable.ts';
  import { flatClone } from 'event-reduce-js';
  
  export class RxSchema<RxDocType = any> {
      public indexes: MaybeReadonly<string[]>[];
      public readonly primaryPath: StringKeys<RxDocumentData<RxDocType>>;
      public finalFields: string[];
  
      constructor(
          public readonly jsonSchema: RxJsonSchema<RxDocumentData<RxDocType>>,
          public readonly hashFunction: HashFunction
      ) {
          this.indexes = getIndexes(this.jsonSchema);
  
          // primary is always required
          this.primaryPath = getPrimaryFieldOfPrimaryKey(this.jsonSchema.primaryKey);
  
          /**
           * Many people accidentally put in wrong schema state
           * without the dev-mode plugin, so we need this check here
           * even in non-dev-mode.
           */
          if (!jsonSchema.properties[this.primaryPath].maxLength) {
              throw newRxError('SC39', { schema: jsonSchema });
          }
  
          this.finalFields = getFinalFields(this.jsonSchema);
      }
  
      public get version(): number {
          return this.jsonSchema.version;
      }
  
      public get defaultValues(): { [P in keyof RxDocType]: RxDocType[P] } {
          const values = {} as { [P in keyof RxDocType]: RxDocType[P] };
          Object
              .entries(this.jsonSchema.properties)
              .filter(([, v]) => Object.prototype.hasOwnProperty.call(v, 'default'))
              .forEach(([k, v]) => (values as any)[k] = (v as any).default);
          return overwriteGetterForCaching(
              this,
              'defaultValues',
              values
          );
      }
  
      public getJsonSchemaWithoutMeta(): RxJsonSchema<RxDocumentData<RxDocType>> {
          const jsonSchema: any = flatClone(this.jsonSchema);
          jsonSchema.properties = flatClone(jsonSchema.properties);
          delete jsonSchema.properties._deleted;
          delete jsonSchema.properties._rev;
          delete jsonSchema.properties._meta;
          delete jsonSchema.properties._attachments;
  
          if (jsonSchema.required) {
              jsonSchema.required = jsonSchema.required.filter((r: string) => !r.startsWith('_'));
          }
  
          // remove internal meta fields from indexes, consistent with properties and required cleanup
          if (jsonSchema.indexes) {
              jsonSchema.indexes = jsonSchema.indexes
                  .map((index: string | string[]) => {
                      const arr: string[] = isMaybeReadonlyArray(index) ? [...index] : [index];
                      return arr.filter((field: string) => !field.startsWith('_'));
                  })
                  .filter((index: string[]) => index.length > 0);
          }
  
          return jsonSchema as RxJsonSchema<RxDocumentData<RxDocType>>;
      }
  
      /**
       * @overrides itself on the first call
       */
      public get hash(): Promise<string> {
          return overwriteGetterForCaching(
              this,
              'hash',
              this.hashFunction(JSON.stringify(this.jsonSchema))
          );
      }
  
      /**
       * checks if a given change on a document is allowed
       * Ensures that:
       * - final fields are not modified
       * @throws {Error} if not valid
       */
      validateChange(dataBefore: any, dataAfter: any): void {
          this.finalFields.forEach(fieldName => {
              if (!deepEqual(dataBefore[fieldName], dataAfter[fieldName])) {
                  throw newRxError('DOC9', {
                      dataBefore,
                      dataAfter,
                      fieldName,
                      schema: this.jsonSchema
                  });
              }
          });
      }
  
      /**
       * creates the schema-based document-prototype,
       * see RxCollection.getDocumentPrototype()
       */
      public getDocumentPrototype(): any {
          const proto: any = {};
  
          /**
           * On the top level, we know all keys
           * and therefore do not have to create a new Proxy object
           * for each document. Instead we define the getter in the prototype once.
           */
          const pathProperties = getSchemaByObjectPath(
              this.jsonSchema,
              ''
          );
          Object.keys(pathProperties)
              .forEach(key => {
                  const fullPath = key;
  
                  // getter - value
                  proto.__defineGetter__(
                      key,
                      function (this: RxDocument) {
                          if (!this.get || typeof this.get !== 'function') {
                              /**
                               * When an object gets added to the state of a vuejs-component,
                               * it happens that this getter is called with another scope.
                               * To prevent errors, we have to return undefined in this case
                               */
                              return undefined;
                          }
                          const ret = this.get(fullPath);
                          return ret;
                      }
                  );
                  // getter - observable$
                  Object.defineProperty(proto, key + '$', {
                      get: function () {
                          return this.get$(fullPath);
                      },
                      enumerable: false,
                      configurable: false
                  });
                  // getter - reactivity$$
                  Object.defineProperty(proto, key + '$$', {
                      get: function () {
                          return this.get$$(fullPath);
                      },
                      enumerable: false,
                      configurable: false
                  });
                  // getter - populate_
                  Object.defineProperty(proto, key + '_', {
                      get: function () {
                          return this.populate(fullPath);
                      },
                      enumerable: false,
                      configurable: false
                  });
              });
  
          overwriteGetterForCaching(
              this,
              'getDocumentPrototype',
              () => proto
          );
          return proto;
      }
  
  
      getPrimaryOfDocumentData(
          documentData: Partial<RxDocType>
      ): string {
          return getComposedPrimaryKeyOfDocumentData(
              this.jsonSchema,
              documentData
          );
      }
  }
  
  export function getIndexes<RxDocType = any>(
      jsonSchema: RxJsonSchema<RxDocType>
  ): MaybeReadonly<string[]>[] {
      return (jsonSchema.indexes || []).map(index => isMaybeReadonlyArray(index) ? index : [index]);
  }
  
  /**
   * array with previous version-numbers
   */
  export function getPreviousVersions(schema: RxJsonSchema<any>): number[] {
      const version = schema.version ? schema.version : 0;
      let c = 0;
      return new Array(version)
          .fill(0)
          .map(() => c++);
  }
  
  export function createRxSchema<T>(
      jsonSchema: RxJsonSchema<T>,
      hashFunction: HashFunction,
      runPreCreateHooks = true
  ): RxSchema<T> {
      if (runPreCreateHooks) {
          runPluginHooks('preCreateRxSchema', jsonSchema);
      }
  
      let useJsonSchema = fillWithDefaultSettings(jsonSchema);
      useJsonSchema = normalizeRxJsonSchema(useJsonSchema);
      overwritable.deepFreezeWhenDevMode(useJsonSchema);
  
      const schema = new RxSchema(useJsonSchema, hashFunction);
      runPluginHooks('createRxSchema', schema);
      return schema;
  }
  
  export function isRxSchema(obj: any): boolean {
      return obj instanceof RxSchema;
  }
  
  /**
   * Used as helper function the generate the document type out of the schema via typescript.
   * @link https://github.com/pubkey/rxdb/discussions/3467
   */
  export function toTypedRxJsonSchema<T extends DeepReadonly<RxJsonSchema<any>>>(schema: T): DeepMutable<T> {
      return schema as any;
  }
  