import { overwriteGetterForCaching, isMaybeReadonlyArray, deepEqual } from 'nxdb-old/src/plugins/utils'; import { newRxError, } from 'nxdb-old/src/rx-error'; import { runPluginHooks } from 'nxdb-old/src/hooks'; import { defineGetterSetter } from 'nxdb-old/src/rx-document'; import type { DeepMutable, DeepReadonly, HashFunction, MaybeReadonly, RxDocumentData, RxJsonSchema, StringKeys } from 'nxdb-old/src/types'; import { fillWithDefaultSettings, getComposedPrimaryKeyOfDocumentData, getFinalFields, getPrimaryFieldOfPrimaryKey, normalizeRxJsonSchema } from 'nxdb-old/src/rx-schema-helper'; import { overwritable } from 'nxdb-old/src/overwritable'; export class RxSchema { public indexes: MaybeReadonly[]; public readonly primaryPath: StringKeys>; public finalFields: string[]; constructor( public readonly jsonSchema: RxJsonSchema>, public readonly hashFunction: HashFunction ) { this.indexes = getIndexes(this.jsonSchema); // primary is always required this.primaryPath = getPrimaryFieldOfPrimaryKey(this.jsonSchema.primaryKey); 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]) => (v as any).hasOwnProperty('default')) .forEach(([k, v]) => (values as any)[k] = (v as any).default); return overwriteGetterForCaching( this, 'defaultValues', values ); } /** * @overrides itself on the first call * * TODO this should be a pure function that * caches the hash in a WeakMap. */ public get hash(): 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 = {}; defineGetterSetter(this, proto, ''); overwriteGetterForCaching( this, 'getDocumentPrototype', () => proto ); return proto; } getPrimaryOfDocumentData( documentData: Partial ): string { return getComposedPrimaryKeyOfDocumentData( this.jsonSchema, documentData ); } } export function getIndexes( jsonSchema: RxJsonSchema ): MaybeReadonly[] { return (jsonSchema.indexes || []).map(index => isMaybeReadonlyArray(index) ? index : [index]); } /** * array with previous version-numbers */ export function getPreviousVersions(schema: RxJsonSchema): number[] { const version = schema.version ? schema.version : 0; let c = 0; return new Array(version) .fill(0) .map(() => c++); } export function createRxSchema( jsonSchema: RxJsonSchema, hashFunction: HashFunction, runPreCreateHooks = true ): RxSchema { 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/nxpkg/nxdb/discussions/3467 */ export function toTypedRxJsonSchema>>(schema: T): DeepMutable { return schema as any; }