Skip to content

Commit 3818276

Browse files
Merge pull request #707 from dolsem/master
Index decorators
2 parents 1c34e87 + 328912d commit 3818276

File tree

7 files changed

+545
-59
lines changed

7 files changed

+545
-59
lines changed

README.md

Lines changed: 165 additions & 59 deletions
Large diffs are not rendered by default.

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ export * from './model/shared/model-class-getter';
7373
export * from './model/shared/model-service';
7474
export * from './model/table/table';
7575
export * from './model/table/table-options';
76+
export * from './model/index/create-index-decorator';
77+
export * from './model/index/index-decorator';
78+
export * from './model/index/index-service';
7679

7780
export * from './scopes/default-scope';
7881
export * from './scopes/scope-options';
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {addFieldToIndex, IndexOptions, IndexFieldOptions} from './index-service';
2+
3+
interface IndexDecorator {
4+
(fieldOptions: Pick<IndexFieldOptions, Exclude<keyof IndexFieldOptions, 'name'>>): Function;
5+
(target: any, propertyName: string, propertyDescriptor?: PropertyDescriptor): void;
6+
}
7+
8+
export function createIndexDecorator(options: IndexOptions = {}): IndexDecorator {
9+
let indexId: string | number;
10+
return ((...args: any[]) => {
11+
if (args.length >= 2) {
12+
13+
const [target, propertyName] = args;
14+
15+
const fieldOptions = { name: propertyName };
16+
indexId = addFieldToIndex(target, fieldOptions, options, indexId);
17+
return;
18+
}
19+
20+
return (target: any, propertyName: string) => {
21+
const fieldOptions = { name: propertyName, ...args[0] };
22+
indexId = addFieldToIndex(target, fieldOptions, options, indexId);
23+
};
24+
}) as IndexDecorator;
25+
}

src/model/index/index-decorator.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {addFieldToIndex, IndexOptions, IndexFieldOptions} from './index-service';
2+
3+
type IndexDecoratorOptions =
4+
IndexOptions & Pick<IndexFieldOptions, Exclude<keyof IndexFieldOptions, 'name'>>;
5+
6+
export function Index(name: string): Function;
7+
export function Index(options: IndexDecoratorOptions): Function;
8+
export function Index(target: any, propertyName: string, propertyDescriptor?: PropertyDescriptor): void;
9+
export function Index(...args: any[]): Function | void {
10+
11+
if (args.length >= 2) {
12+
13+
const [target, propertyName] = args;
14+
15+
annotateModelWithIndex(target, propertyName);
16+
return;
17+
}
18+
19+
return (target: any, propertyName: string) => {
20+
annotateModelWithIndex(target, propertyName, args[0]);
21+
};
22+
}
23+
24+
export function annotateModelWithIndex(target: any,
25+
propertyName: string,
26+
optionsOrName: IndexDecoratorOptions | string = {},
27+
indexId?: string | number): string | number {
28+
29+
let indexOptions: IndexOptions;
30+
let fieldOptions: IndexFieldOptions;
31+
if (typeof optionsOrName === 'string') {
32+
indexOptions = { name: optionsOrName };
33+
fieldOptions = { name: propertyName };
34+
} else {
35+
const { length, order, collate, ...rest } = optionsOrName;
36+
indexOptions = rest;
37+
fieldOptions = {
38+
name: propertyName,
39+
length,
40+
order,
41+
collate,
42+
};
43+
}
44+
45+
return addFieldToIndex(target, fieldOptions, indexOptions, indexId);
46+
}

src/model/index/index-service.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import 'reflect-metadata';
2+
import {IndexesOptions as SequelizeIndexOptions} from 'sequelize';
3+
4+
const INDEXES_KEY = 'sequelize:indexes';
5+
6+
// https://github.com/sequelize/sequelize/blob/beeff96f/types/lib/query-interface.d.ts#L169
7+
export interface IndexFieldOptions {
8+
name: string;
9+
length?: number;
10+
order?: 'ASC' | 'DESC';
11+
collate?: string;
12+
}
13+
14+
export interface IndexesMeta {
15+
named: { [name: string]: IndexOptions };
16+
unnamed: IndexOptions[];
17+
}
18+
19+
export type IndexOptions = Pick<SequelizeIndexOptions, Exclude<keyof SequelizeIndexOptions, 'fields'>>;
20+
21+
/**
22+
* Returns model indexes from class by restoring this
23+
* information from reflect metadata
24+
*/
25+
export function getIndexes(target: any): IndexesMeta {
26+
const { named = {}, unnamed = [] }: IndexesMeta =
27+
Reflect.getMetadata(INDEXES_KEY, target) || {};
28+
29+
return { named: {...named}, unnamed: [...unnamed] };
30+
}
31+
32+
/**
33+
* Sets indexes
34+
*/
35+
export function setIndexes(target: any, indexes: IndexesMeta): void {
36+
Reflect.defineMetadata(INDEXES_KEY, indexes, target);
37+
}
38+
39+
/**
40+
* Adds field to index by sequelize index and index field options,
41+
* and stores this information through reflect metadata. Returns index ID.
42+
*/
43+
export function addFieldToIndex(target: any,
44+
fieldOptions: IndexFieldOptions,
45+
indexOptions: IndexOptions,
46+
indexId?: string | number): string | number {
47+
const indexes = getIndexes(target);
48+
49+
const chosenId = typeof indexId !== 'undefined'
50+
? indexId
51+
: indexOptions.name || indexes.unnamed.length;
52+
const indexStore = typeof chosenId === 'string'
53+
? indexes.named
54+
: indexes.unnamed;
55+
if (!indexStore[chosenId]) indexStore[chosenId] = {...indexOptions};
56+
57+
const index = indexStore[chosenId];
58+
if (!index.fields) index.fields = [];
59+
index.fields.push(fieldOptions);
60+
61+
setIndexes(target, indexes);
62+
63+
return chosenId;
64+
}

src/sequelize/sequelize/sequelize.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {resolveScopes} from "../../scopes/scope-service";
88
import {installHooks} from "../../hooks/shared/hooks-service";
99
import {getAssociations} from "../../associations/shared/association-service";
1010
import {getAttributes} from "../../model/column/attribute-service";
11+
import {getIndexes} from "../../model/index/index-service";
1112
import {Repository} from "../..";
1213

1314
export class Sequelize extends OriginSequelize {
@@ -84,11 +85,16 @@ export class Sequelize extends OriginSequelize {
8485
return models.map(model => {
8586
const modelName = getModelName(model.prototype);
8687
const attributes = getAttributes(model.prototype);
88+
const indexes = getIndexes(model.prototype);
8789
const modelOptions = getOptions(model.prototype);
8890

8991
if (!modelOptions) throw new Error(`@Table annotation is missing on class "${model['name']}"`);
9092

93+
const indexArray = Object.keys(indexes.named)
94+
.map(key => indexes.named[key])
95+
.concat(indexes.unnamed);
9196
const initOptions: InitOptions & { modelName } = {
97+
...(indexArray.length > 0 && { indexes: indexArray }),
9298
...modelOptions,
9399
modelName,
94100
sequelize: this,

0 commit comments

Comments
 (0)