Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented support for creating neogma models with decorators #75

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = {
{
types: {
Object: false,
Function: false,
},
},
],
Expand Down
19 changes: 9 additions & 10 deletions src/Decorators/Decorators.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Neogma } from '../Neogma';
import * as dotenv from 'dotenv';
import { Model } from './model';
import { Model, Props } from './model';
import { Property } from './property';
import { QueryBuilder } from '../Queries';
import {
Expand All @@ -10,7 +10,6 @@ import {
parseModelMetadata,
} from './shared';
import { Relation } from './relation';
import { NeogmaModel } from '../ModelOps';

let neogma: Neogma;

Expand Down Expand Up @@ -258,13 +257,9 @@ describe('Decorators', () => {
age: number;
}

const Users: NeogmaModel<{ name: string; age: number }, any, any, any> =
neogma.addModel(User) as unknown as NeogmaModel<
{ name: string; age: number },
any,
any,
any
>;
type UserProps = Props<User>;

const Users = neogma.addModel<UserProps>(User);

const userData: User = {
name: 'John',
Expand Down Expand Up @@ -323,7 +318,11 @@ describe('Decorators', () => {
projects: Project[];
}

const Workers = neogma.addModel(Worker);
type AllWorkerProps = Props<Worker>;

type WorkerProps = Omit<AllWorkerProps, 'projects'>;

const Workers = neogma.addModel<WorkerProps>(Worker);

const workerData = {
name: 'John',
Expand Down
21 changes: 14 additions & 7 deletions src/Decorators/model.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
/* eslint-disable @typescript-eslint/ban-types */
import { setModelName, addOptions } from './shared/model-service';
import { ModelClassDecoratorOptions } from './shared/data-types';
import { Neo4jSupportedTypes } from 'Queries';

export function Model(options?: ModelClassDecoratorOptions): Function;
export function Model(target: Function): void;
export function Model(arg: any): void | Function {
export function Model(arg: unknown): void | Function {
if (typeof arg === 'function') {
annotate(arg);
} else {
const options: ModelClassDecoratorOptions = {
...arg,
...(arg as object),
};
return (target: any) => annotate(target, options);
return (target: Function | Object) => annotate(target, options);
}
}

function annotate(target: any, options: ModelClassDecoratorOptions = {}): void {
setModelName(target.prototype, options.label || target.name);
addOptions(target.prototype, options);
function annotate(
target: Function | Object,
options: ModelClassDecoratorOptions = {},
): void {
setModelName(target['prototype'], options.label || target['name']);
addOptions(target['prototype'], options);
}

export type Props<U extends object> = {
[property in keyof U]: Neo4jSupportedTypes;
};
27 changes: 12 additions & 15 deletions src/Decorators/property.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/ban-types */
import { ModelPropertyDecoratorOptions } from './shared/data-types';
import { addProperty } from './shared/property-service';
import { DataType } from './shared/data-types';
Expand All @@ -23,15 +22,15 @@ function annotate(
target: any,
propertyName: string,
propertyDescriptor?: PropertyDescriptor,
optionsOrType: Partial<ModelPropertyDecoratorOptions> | DataType = {},
options: Partial<ModelPropertyDecoratorOptions> = {},
): void {
const options: Partial<ModelPropertyDecoratorOptions> = {
...(optionsOrType as ModelPropertyDecoratorOptions),
const parsedOptions: Partial<ModelPropertyDecoratorOptions> = {
...(options as ModelPropertyDecoratorOptions),
themetalfleece marked this conversation as resolved.
Show resolved Hide resolved
};

if (!options?.schema) {
options.schema = {
...options.schema,
if (!parsedOptions?.schema) {
parsedOptions.schema = {
...parsedOptions.schema,
type: Reflect.getMetadata(
'design:type',
target,
Expand All @@ -40,14 +39,12 @@ function annotate(
};
}

if (propertyDescriptor) {
if (propertyDescriptor.get) {
options.get = propertyDescriptor.get;
}
if (propertyDescriptor.set) {
options.set = propertyDescriptor.set;
}
if (propertyDescriptor?.get) {
parsedOptions.get = propertyDescriptor.get;
}
if (propertyDescriptor?.set) {
parsedOptions.set = propertyDescriptor.set;
}

addProperty(target, propertyName, options);
addProperty(target, propertyName, parsedOptions);
}
4 changes: 1 addition & 3 deletions src/Decorators/shared/data-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import {
} from '../../ModelOps';
import { Neo4jSupportedProperties, Neo4jSupportedTypes } from '../../Queries';

export type PropertySchema =
| Revalidator.ISchema<Neo4jSupportedProperties>
| Revalidator.JSONSchema<Neo4jSupportedProperties>;
export type PropertySchema = Revalidator.ISchema<Neo4jSupportedProperties>;

export type DataType =
| 'string'
Expand Down
11 changes: 3 additions & 8 deletions src/Decorators/shared/model-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function getOptions(
}

/**
* Sets seuqlize define options to class prototype
* Sets model definition options to class prototype
*/
export function setOptions(
target: any,
Expand All @@ -50,13 +50,8 @@ export function addOptions(
target: any,
options: ModelClassDecoratorOptions,
): void {
let _options = getOptions(target);

if (!_options) {
_options = {};
}
setOptions(target, {
..._options,
...getOptions(target),
...options,
});
}
Expand All @@ -67,7 +62,7 @@ export function addOptions(
* So that {model: () => Person} will be converted to
* {model: Person}
*/
export function resolveModelGetter(options: any): any {
export function resolveModelGetter(options: ModelClassDecoratorOptions): any {
const maybeModelGetter = (value) =>
typeof value === 'function' && value.length === 0;
const isModel = (value) => value && value.prototype;
Expand Down
48 changes: 9 additions & 39 deletions src/Decorators/shared/property-service.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import { ModelPropertyDecoratorOptions } from './data-types';
import { deepAssign } from '../../utils/object';
import { Neo4jSingleTypes, Neo4jSupportedTypes } from '../../Queries';
import {
DateTime as Neo4jDateTime,
Date as Neo4jDate,
Point as Neo4jPoint,
Time as Neo4jTime,
Integer as Neo4jInteger,
LocalDateTime as Neo4jLocalDateTime,
LocalTime as Neo4jLocalTime,
Duration as Neo4jDuration,
} from 'neo4j-driver';

const PROPERTIES_KEY = 'neogma:properties';

/**
* Returns model properties from class by restoring this
* information from reflect metadata
*/
export function getProperties(target: any): any | undefined {
export function getProperties(
target: any,
): Partial<ModelPropertyDecoratorOptions> | undefined {
const properties = Reflect.getMetadata(PROPERTIES_KEY, target);

if (properties) {
Expand All @@ -33,7 +24,7 @@ export function getProperties(target: any): any | undefined {
/**
* Sets properties
*/
export function setProperties(target: any, properties: any): void {
export function setProperties(target: any, properties: object): void {
Reflect.defineMetadata(PROPERTIES_KEY, { ...properties }, target);
}

Expand All @@ -42,7 +33,11 @@ export function setProperties(target: any, properties: any): void {
* neogma property options and stores this information
* through reflect metadata
*/
export function addProperty(target: any, name: string, options: any): void {
export function addProperty(
target: any,
name: string,
options: Partial<ModelPropertyDecoratorOptions>,
): void {
let properties = getProperties(target);

if (!properties) {
Expand Down Expand Up @@ -74,28 +69,3 @@ export function addPropertyOptions(

setProperties(target, properties);
}

/** Type guard for Neo4jSingleTypes */
export function isNeo4jSingleType(value: any): value is Neo4jSingleTypes {
return (
typeof value === 'number' ||
typeof value === 'string' ||
typeof value === 'boolean' ||
value instanceof Neo4jInteger ||
value instanceof Neo4jPoint ||
value instanceof Neo4jDate ||
value instanceof Neo4jTime ||
value instanceof Neo4jLocalTime ||
value instanceof Neo4jDateTime ||
value instanceof Neo4jLocalDateTime ||
value instanceof Neo4jDuration
);
}

/** Type guard for Neo4jSupportedTypes */
export function isNeo4jSupportedType(value: any): value is Neo4jSupportedTypes {
return (
isNeo4jSingleType(value) ||
(Array.isArray(value) && value.every(isNeo4jSingleType))
);
}
9 changes: 7 additions & 2 deletions src/Decorators/shared/relation-service.ts
themetalfleece marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ const RELATIONS_KEY = 'neogma:relations';
* Returns model relations from class by restoring this
* information from reflect metadata
*/
export function getRelations(target: any): any | undefined {
export function getRelations(
target: any,
): Record<string, ModelRelationDecoratorOptions> | undefined {
const relations = Reflect.getMetadata(RELATIONS_KEY, target);

if (relations) {
Expand All @@ -23,7 +25,10 @@ export function getRelations(target: any): any | undefined {
/**
* Sets relations
*/
export function setRelations(target: any, relations: any): void {
export function setRelations(
target: any,
relations: Record<string, ModelRelationDecoratorOptions>,
): void {
Reflect.defineMetadata(RELATIONS_KEY, { ...relations }, target);
}

Expand Down
22 changes: 11 additions & 11 deletions src/Decorators/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ import { NeogmaModel } from '../../ModelOps';
type AnyObject = Record<string, any>;

type RelationshipsI<RelatedNodesToAssociateI extends AnyObject> = {
/** the alias of the relationship definitions is the key */
/** the alias of the relation definitions is the key */
[alias in keyof RelatedNodesToAssociateI]: {
/** the related model. It could be the object of the model, or "self" for this model */
model: NeogmaModel<any, any, any, any> | 'self';
/** the name of the relationship */
/** the name of the relation */
name: string;
/** the direction of the relationship */
/** the direction of the relation */
direction: 'out' | 'in' | 'none';
/** relationship properties */
/** relation properties */
properties?: {
/** the alias of the relationship property is the key */
[relationshipPropertyAlias in keyof RelatedNodesToAssociateI[alias]['CreateRelationshipProperties']]: {
/** the actual property to be used on the relationship */
/** the alias of the relation property is the key */
[relationPropertyAlias in keyof RelatedNodesToAssociateI[alias]['CreateRelationshipProperties']]: {
/** the actual property to be used on the relation */
property: keyof RelatedNodesToAssociateI[alias]['RelationshipProperties'];
/** validation for the property */
schema: Revalidator.ISchema<AnyObject>;
Expand Down Expand Up @@ -77,10 +77,10 @@ export const getModelMetadata = (target: Object | string) => {

export const getRelatedModelMetadata = (target: Object | Function) => {
return {
name: getModelName(target as Object),
options: getOptions(target as Object),
properties: getProperties(target as Object),
relations: getRelations(target as Object),
name: getModelName(target),
options: getOptions(target),
properties: getProperties(target),
relations: getRelations(target),
} as NeogmaModelMetadata;
};

Expand Down
47 changes: 26 additions & 21 deletions src/Neogma.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import * as neo4j_driver from 'neo4j-driver';
import { Config, Driver, Session, Transaction } from 'neo4j-driver';
import { ModelFactory, NeogmaModel } from './ModelOps';
import { QueryRunner, Runnable } from './Queries/QueryRunner';
import {
Neo4jSupportedProperties,
QueryRunner,
Runnable,
} from './Queries/QueryRunner';
import { getRunnable, getSession, getTransaction } from './Sessions/Sessions';
import { NeogmaConnectivityError } from './Errors/NeogmaConnectivityError';
import {
Expand Down Expand Up @@ -84,36 +88,37 @@ export class Neogma {
return getRunnable<T>(runInExisting, callback, this.driver);
};

public generateModelFromMetadata = (
public generateModelFromMetadata = <T extends Neo4jSupportedProperties>(
metadata: NeogmaModelMetadata,
): NeogmaModel<any, any, any, any> => {
): NeogmaModel<T, any, object, object> => {
const parsedMetadata = parseModelMetadata(metadata);
const relations = parsedMetadata.relationships;
if (relations) {
for (const relation in relations) {
const relatedModel = metadata.relations[relation].model;
if (relatedModel !== 'self') {
const relatedModelLabel = relatedModel['name'];
if (this.modelsByName[relatedModelLabel]) {
relations[relation].model = this.modelsByName[relatedModelLabel];
} else {
const relatedModelMetadata = getRelatedModelMetadata(relatedModel);
relations[relation].model =
this.generateModelFromMetadata(relatedModelMetadata);
}
}
const relations = parsedMetadata.relationships ?? [];
for (const relation in relations) {
const relatedModel = metadata.relations[relation].model;
if (relatedModel === 'self') {
continue;
}
const relatedModelLabel = relatedModel['name'];
if (this.modelsByName[relatedModelLabel]) {
relations[relation].model = this.modelsByName[relatedModelLabel];
} else {
const relatedModelMetadata = getRelatedModelMetadata(relatedModel);
relations[relation].model =
this.generateModelFromMetadata(relatedModelMetadata);
}
}

return ModelFactory(parsedMetadata, this) as unknown as NeogmaModel<
T,
any,
any,
any,
any
object,
object
>;
};

public addModel = (model: Object): NeogmaModel<any, any, any, any> => {
public addModel = <T extends Neo4jSupportedProperties>(
model: Object,
): NeogmaModel<T, object, object, object> => {
const metadata = getModelMetadata(model);
return this.generateModelFromMetadata(metadata);
};
Expand Down
Loading