pudate packages

This commit is contained in:
AntoXa PRO 2023-07-11 10:00:11 +03:00
commit 39283423a6
11 changed files with 3455 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
dist

9
.prettierrc Normal file
View File

@ -0,0 +1,9 @@
{
"useTabs": true,
"tabWidth": 2,
"singleQuote": true,
"semi": false,
"trailingComma": "none",
"arrowParens": "avoid",
"printWidth": 79
}

2996
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

31
package.json Normal file
View File

@ -0,0 +1,31 @@
{
"name": "axp-mongoose-helper",
"version": "1.2.1",
"description": "My helper library",
"author": "AntoXa PRO <info@antoxa.pro>",
"homepage": "https://antoxahub.ru/antoxa/axp-mongoose-helper",
"repository": {
"type": "git",
"url": "https://antoxahub.ru/antoxa/axp-mongoose-helper.git"
},
"type": "module",
"main": "dist/index.js",
"files": [
"dist",
"tsconfig.json"
],
"types": "dist/index.d.ts",
"scripts": {
"build": "rollup -c --configPlugin @rollup/plugin-typescript",
"prepare": "npm run build"
},
"dependencies": {
"axp-ts": "^1.9.6",
"mongoose": "^6.11.2"
},
"devDependencies": {
"@rollup/plugin-typescript": "^11.1.2",
"prettier": "^2.8.8",
"rollup": "^3.26.2"
}
}

12
rollup.config.ts Normal file
View File

@ -0,0 +1,12 @@
import { defineConfig } from 'rollup'
import typescript from '@rollup/plugin-typescript'
export default defineConfig({
input: 'src/index.ts',
output: {
dir: 'dist',
format: 'es'
},
external: ['axp-ts', 'mongoose'],
plugins: [typescript()]
})

1
src/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './repository'

View File

@ -0,0 +1,43 @@
import { PopulateOptions } from 'mongoose'
import { TRepositoryOptions } from '.'
export abstract class _BaseAction {
/**
* Настройки репозитория.
*/
protected _options: TRepositoryOptions
constructor(options: TRepositoryOptions) {
this._options = options
}
/**
* Сброс или изменение конфигурации по умолчанию.
*/
options(options: false | TRepositoryOptions): this {
if (options === false) {
this._options = {}
} else {
Object.assign(this._options, options)
}
return this
}
/**
* Выборка полей.
*/
select(arg: string | string[]): this {
const items = typeof arg === 'string' ? [arg] : arg
this._options.select = Object.assign(this._options.select || [], items)
return this
}
/**
* Заполнение сущностей.
*/
populate(arg: PopulateOptions | Array<PopulateOptions>): this {
this._options.populate = arg
return this
}
}

78
src/repository/index.ts Normal file
View File

@ -0,0 +1,78 @@
export * from './_base-action'
export * from './query-entity-action'
export * from './query-collection-action'
import { Model, FilterQuery, PopulateOptions, ToObjectOptions } from 'mongoose'
import { QueryCollection } from './query-collection-action'
import { QueryEntity } from './query-entity-action'
/**
* Настройки репозитория по умолчанию.
*/
export type TRepositoryOptions = {
select?: string[]
populate?: PopulateOptions | Array<PopulateOptions>
sort?: any
toObject?: false | ToObjectOptions
pagination?: {
limit?: number
maxLimit?: number
}
}
export class Repository<T extends Object> {
/**
* Конфигурация сервиса.
*/
private _options: TRepositoryOptions
/**
* Модель монгуса.
*/
public model: Model<T>
constructor(model: Model<T>, options?: TRepositoryOptions) {
this.model = model
this._options = options || {}
}
/**
* Копирует и возвращает клон объекта настроек для использования
* при инициализации других классов внутри этого.
*/
getOptions(): TRepositoryOptions {
return Object.assign({}, this._options)
}
/**
* Выборка из коллекции.
*/
find(filter: FilterQuery<T> = {}): QueryCollection<T> {
return new QueryCollection(filter, this.model, this.getOptions())
}
/**
* Одна сущность.
*/
findOne(filter: FilterQuery<T>): QueryEntity<T> {
const query = this.model.findOne(filter)
return new QueryEntity(query, this.getOptions())
}
/**
* Одна сущность по Ид.
*/
findById(id: string): QueryEntity<T> {
const query = this.model.findById(id)
return new QueryEntity(query, this.getOptions())
}
/**
* Проверка ObjectId
*/
isValidObjectId(id: string): boolean {
if (typeof id !== 'string') return false
return id.match(/^[a-f\d]{24}$/i) ? true : false
}
}

View File

@ -0,0 +1,193 @@
import { FilterQuery, Model, Query, HydratedDocument } from 'mongoose'
import { DataResultEntity, Pagination } from 'axp-ts'
import { TRepositoryOptions } from '.'
import { _BaseAction } from './_base-action'
type urlQueryArgs = {
page?: number | string
limit?: number | string
sort?: string
}
export class QueryCollection<T extends Object> extends _BaseAction {
/**
* Пагинация.
*/
public pagination: Pagination
/**
* Фильтр запроса.
*/
private _filter: FilterQuery<T>
/**
* Модель монгуса.
*/
private _model: Model<T>
/**
* Запрос.
*/
private _query: Query<HydratedDocument<T>[], HydratedDocument<T>>
constructor(
filter: FilterQuery<T>,
model: Model<T>,
options: TRepositoryOptions
) {
super(options)
this.pagination = new Pagination(
this._options.pagination,
this._options.pagination?.maxLimit || 100
)
this._filter = filter
this._model = model
this._query = this._model.find(this._filter)
}
/**
* Устанавливает параметры фильтра (false полный сброс).
*/
filter(filter: FilterQuery<T> | false): this {
if (filter === false) {
this._filter = {}
} else {
Object.assign(this._filter, filter)
}
return this
}
/**
* Сортировка.
*/
sort(value: any): this {
this._options.sort = value
return this
}
/**
* Применение параметров из URL.
*/
setUrlQuery(args: urlQueryArgs) {
// Постраничное разбиение.
const { page, limit } = args
this.pagination.set({ page, limit })
// Сортировка.
if (args.sort) {
this.sort(args.sort)
}
return this
}
/**
* Перед выполнением запроса.
*/
private preExec(): void {
try {
// Сортировка.
if (this._options.sort) {
this._query.sort(this._options.sort)
// console.log('Sort', this._options.sort);
}
// Применение пагинации.
this._query.skip(this.pagination.skip)
this._query.limit(this.pagination.limit)
// Выборка полей в запросе.
if (this._options.select) {
this._query.select(this._options.select)
// console.log('Select:', this._options.select);
}
// Заполнение связей.
if (this._options.populate) {
this._query.populate(this._options.populate)
// console.log('Populate:', this._options.populate);
}
} catch (ex: any) {
console.log('Ex ProExec:', ex.message)
}
}
/**
* Выполнение запроса.
*/
exec(): Promise<T[]> {
return new Promise<T[]>(async (resolve, reject) => {
try {
// Коллекция для возврата.
let items: HydratedDocument<T, {}, {}>[] = []
// Кол-во сущностей относительно фильтра.
const total = await this._model.countDocuments(this._filter)
// console.log('Total:', total)
// Если есть данные.
if (total > 0) {
// Устанавливаем значение общего кол-ва.
this.pagination.set({ total })
// Применяем параметры перед запросом.
this.preExec()
// Выполнение запроса.
items = await this._query.exec()
// Трансформация в объект.
if (this._options.toObject !== false) {
resolve(
items.map(e =>
e.toObject<T>(this._options.toObject || undefined)
)
)
} else {
resolve(items)
}
} else {
// Возвращаем результат.
resolve([])
}
} catch (ex: any) {
// Возвращаем ошибку.
reject(ex)
}
})
}
/**
* Возвращает промис с моделью результата данных.
*/
dataResult(
cb?: (dR: DataResultEntity<T[]>) => void
): Promise<DataResultEntity<T[]>> {
return new Promise<DataResultEntity<T[]>>(async resolve => {
const dR = new DataResultEntity<T[]>()
try {
const data = await this.exec()
if (data) {
dR.setData(data)
dR.info.pagination = this.pagination.toObject()
} else {
dR.status = 404
dR.message = 'Not found'
dR.errors.push({ code: 'not_found', text: 'Resource not found' })
}
} catch (ex: any) {
dR.status = 500
dR.message = 'Server Error'
dR.errors.push({ code: 'server', text: ex.message })
}
if (cb) cb(dR)
resolve(dR)
})
}
}

View File

@ -0,0 +1,73 @@
import { DataResultEntity } from 'axp-ts'
import { Query, HydratedDocument } from 'mongoose'
import { TRepositoryOptions } from '.'
import { _BaseAction } from './_base-action'
export class QueryEntity<T extends Object> extends _BaseAction {
private _query: Query<HydratedDocument<T> | null, HydratedDocument<T>>
constructor(
query: Query<HydratedDocument<T> | null, HydratedDocument<T>>,
options: TRepositoryOptions
) {
super(options)
this._query = query
}
exec(): Promise<T | null> {
return new Promise<T | null>(async (resolve, reject) => {
let result: HydratedDocument<T> | null = null
try {
// Выборка полей в запросе.
if (this._options.select) {
this._query.select(this._options.select)
}
// Заполнение связей.
if (this._options.populate) {
this._query.populate(this._options.populate)
}
result = await this._query.exec()
if (result && this._options.toObject) {
resolve(result.toObject<T>(this._options.toObject || undefined))
return
}
} catch (ex: any) {
reject(ex)
}
resolve(result)
})
}
dataResult(
cb?: (dR: DataResultEntity<T | null>) => void
): Promise<DataResultEntity<T | null>> {
return new Promise(async resolve => {
const dR = new DataResultEntity<T | null>()
try {
const result = await this.exec()
if (result) {
dR.setData(result)
} else {
dR.status = 404
dR.message = 'Not Found'
dR.errors.push({ code: 'not_found', text: 'Resource not found' })
dR.setData(null)
}
} catch (ex: any) {
dR.status = 500
dR.message = 'Server Error'
dR.errors.push({ code: 'server', text: ex.message })
}
if (cb) cb(dR)
resolve(dR)
})
}
}

17
tsconfig.json Normal file
View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ES6",
"baseUrl": ".",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"declaration": true
},
"exclude": ["rollup.config.ts"]
}