diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e9c6591..71e5f25 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -3,7 +3,7 @@ name: Test Suite on: push: tags: - - "v*" + - 'v*' workflow_dispatch: diff --git a/examples/README.md b/examples/README.md index 06a5127..ea95cbd 100644 --- a/examples/README.md +++ b/examples/README.md @@ -31,8 +31,8 @@ For extending the model with instance methods, statics, or virtuals, please refe 1. Copy the `.env.example` file to `.env` and update the MONGODB_URI value. 2. To run the project in development mode: - ```bash - pnpm run dev - ``` + ```bash + pnpm run dev + ``` Feel free to modify [./src/index.ts](./src/index.ts) to test different functionalities. diff --git a/examples/package.json b/examples/package.json index 81dc295..97f6704 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { - "private": true, "type": "module", + "private": true, "scripts": { "dev": "tsx watch --env-file=./.env ./src/index.ts" }, diff --git a/examples/src/index.ts b/examples/src/index.ts index 6d8039b..634ace9 100644 --- a/examples/src/index.ts +++ b/examples/src/index.ts @@ -1,8 +1,11 @@ +/* eslint-disable no-console */ + import { setCustomMongooseOptions } from '@kikiutils/mongoose/options'; import s from '@kikiutils/mongoose/schema-builders'; import { buildMongooseModel } from '@kikiutils/mongoose/utils'; import { Schema } from 'mongoose'; import type { ProjectionType, QueryOptions, Types } from 'mongoose'; +import { env } from 'node:process'; import type { Except } from 'type-fest'; // Load global types @@ -16,7 +19,7 @@ import type {} from '@kikiutils/mongoose/types/data'; * the actual project shouldn't have this line * (unless you want to handle it the same way as the default). */ -process.env.MONGODB_URI ||= 'mongodb://127.0.0.1:27017/kikiutils-mongoose-example?directConnection=true'; +env.MONGODB_URI ||= 'mongodb://127.0.0.1:27017/kikiutils-mongoose-example?directConnection=true'; /** * Set custom mongoose options. @@ -27,7 +30,7 @@ process.env.MONGODB_URI ||= 'mongodb://127.0.0.1:27017/kikiutils-mongoose-exampl * Please make sure to set it before using buildMongooseModel, * as it will not affect models that have already been built before setting it. */ -setCustomMongooseOptions('beforeModelBuild', (schema) => { +setCustomMongooseOptions('beforeModelBuild', (_schema) => { // console.log('building model with schema: ', schema); }); @@ -78,20 +81,20 @@ interface UserMethodsAndOverrides { interface UserModel extends BaseMongoosePaginateModel { // Model static methods - findByAccount(account: string, projection?: ProjectionType | null, options?: QueryOptions | null): MongooseFindOneReturnType; + findByAccount: (account: string, projection?: ProjectionType | null, options?: QueryOptions | null) => MongooseFindOneReturnType; } -type UserDocument = MongooseHydratedDocument; +export type UserDocument = MongooseHydratedDocument; // Define schema const userSchema = new Schema({ account: s.string().maxlength(16).trim.unique.required, - // @ts-expect-error + // @ts-expect-error Ignore this error. // Use setRoundAndToFixedSetter to round up on save and setToStringGetter to convert to string on get balance: s.decimal128().setRoundAndToFixedSetter().setToStringGetter.required, email: s.string().lowercase.trim.nonRequired, enabled: s.boolean().default(false).required, - password: s.string().private.required + password: s.string().private.required, }); // Set methods @@ -113,7 +116,7 @@ const user = await UserModel.create({ account: Array.from({ length: 8 }, () => String.fromCharCode((Math.random() > 0.5 ? 97 : 65) + Math.floor(Math.random() * 26))).join(''), balance: '1000.501', email: 'example@example.com', - password: 'test-password' + password: 'test-password', }); console.log('created user: ', user); @@ -129,7 +132,7 @@ console.log('verifying user password'); console.log('verified user password: ', user.verifyPassword('test-password')); // Define user log data interface -interface UserLogData extends BaseMongooseModelData { +export interface UserLogData extends BaseMongooseModelData { content: string; type: number; user: Partial; @@ -140,14 +143,14 @@ interface UserLog extends BaseMongooseDocType, true, user: Types.Decimal128; } -type UserLogDocument = MongooseHydratedDocument; -type UserLogModel = BaseMongoosePaginateModel; +export type UserLogDocument = MongooseHydratedDocument; +export type UserLogModel = BaseMongoosePaginateModel; // Define schema const userLogSchema = new Schema({ content: s.string().trim.required, type: s.number().required, - user: s.ref('User').required + user: s.ref('User').required, }); // Build model @@ -158,7 +161,7 @@ console.log('creating user log'); const userLog = await UserLogModel.create({ content: 'test content', type: 1, - user: user._id + user: user._id, }); console.log('created user log: ', userLog); diff --git a/examples/tsconfig.json b/examples/tsconfig.json index a9f8af9..f0abbf9 100644 --- a/examples/tsconfig.json +++ b/examples/tsconfig.json @@ -1,10 +1,10 @@ { "extends": "@kikiutils/tsconfigs/esnext/es2022.json", "compilerOptions": { - "outDir": "./dist", "paths": { "@/*": ["./src/*"] - } + }, + "outDir": "./dist" }, "include": ["./src"] } diff --git a/jest.config.js b/jest.config.js index 7de760a..c1b1d8f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,5 @@ /** @type {import('jest').Config} */ module.exports = { testEnvironment: 'node', - transform: { '^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.jest.json' }] } + transform: { '^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.jest.json' }] }, }; diff --git a/package.json b/package.json index 1873678..5c77835 100644 --- a/package.json +++ b/package.json @@ -2,38 +2,12 @@ "name": "@kikiutils/mongoose", "version": "2.1.0", "description": "A Mongoose plugin for enhanced JSON normalization and common schema creation, with built-in support for pagination and automatic Decimal128 conversion.", - "license": "MIT", "author": "kiki-kanri", + "license": "MIT", "repository": { "type": "git", "url": "git+https://github.com/kiki-kanri/kikiutils-node-mongoose.git" }, - "scripts": { - "build": "ts-project-builder ./src/constants.ts ./src/options.ts ./src/schema-builders/index.ts ./src/utils.ts --clean --preserve-modules", - "bumplog": "changelogen --bump", - "release": "pnpm run build && pnpm run test && changelogen --push --release && sh ./build-and-publish.sh", - "test": "jest --coverage" - }, - "dependencies": { - "decimal.js": "^10.4.3", - "lodash": "^4.17.21", - "mongoose": "^8.8.1", - "mongoose-aggregate-paginate-v2": "^1.1.2", - "mongoose-paginate-v2": "^1.8.5", - "type-fest": "^4.27.0" - }, - "devDependencies": { - "@kikiutils/changelogen": "^0.7.0", - "@kikiutils/eslint-config": "^0.3.0", - "@kikiutils/tsconfigs": "^3.0.2", - "@types/jest": "^29.5.14", - "@types/lodash": "^4.17.13", - "@types/node": "^22.9.0", - "jest": "^29.7.0", - "ts-jest": "^29.2.5", - "ts-project-builder": "^3.3.2", - "tslib": "^2.8.1" - }, "keywords": [ "Decimal128", "json", @@ -44,24 +18,21 @@ "plugin", "schema" ], - "engines": { - "node": ">=18.12.1" - }, "exports": { "./*": { + "types": "./*.d.ts", "import": "./*.mjs", - "require": "./*.cjs", - "types": "./*.d.ts" + "require": "./*.cjs" }, "./_internals": { + "types": null, "import": null, - "require": null, - "types": null + "require": null }, "./schema-builders": { + "types": "./schema-builders/index.d.ts", "import": "./schema-builders/index.mjs", - "require": "./schema-builders/index.cjs", - "types": "./schema-builders/index.d.ts" + "require": "./schema-builders/index.cjs" }, "./types": { "types": "./types/index.d.ts" @@ -69,5 +40,34 @@ "./types/*": { "types": "./types/*.d.ts" } + }, + "engines": { + "node": ">=18.12.1" + }, + "scripts": { + "build": "ts-project-builder ./src/constants.ts ./src/options.ts ./src/schema-builders/index.ts ./src/utils.ts --clean --preserve-modules", + "bumplog": "changelogen --bump", + "release": "pnpm run build && pnpm run test && changelogen --push --release && sh ./build-and-publish.sh", + "test": "jest --coverage" + }, + "dependencies": { + "decimal.js": "^10.4.3", + "lodash": "^4.17.21", + "mongoose": "^8.8.1", + "mongoose-aggregate-paginate-v2": "^1.1.2", + "mongoose-paginate-v2": "^1.8.5", + "type-fest": "^4.27.0" + }, + "devDependencies": { + "@kikiutils/changelogen": "^0.7.0", + "@kikiutils/eslint-config": "^0.3.1", + "@kikiutils/tsconfigs": "^3.0.2", + "@types/jest": "^29.5.14", + "@types/lodash": "^4.17.13", + "@types/node": "^22.9.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "ts-project-builder": "^3.3.2", + "tslib": "^2.8.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f3191a..91e1f12 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,8 +31,8 @@ importers: specifier: ^0.7.0 version: 0.7.0 '@kikiutils/eslint-config': - specifier: ^0.3.0 - version: 0.3.0(@typescript-eslint/utils@8.14.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(@vue/compiler-sfc@3.5.13)(jiti@2.4.0)(typescript@5.6.3) + specifier: ^0.3.1 + version: 0.3.1(@typescript-eslint/utils@8.14.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(@vue/compiler-sfc@3.5.13)(jiti@2.4.0)(typescript@5.6.3) '@kikiutils/tsconfigs': specifier: ^3.0.2 version: 3.0.2 @@ -610,8 +610,8 @@ packages: resolution: {integrity: sha512-eGf5owO3OnT4GaR3/ydbPuPjA8TwKN1vE1G3ImveVzENLTZXnD7L3S20iZjsiQuKF68d2UwQ6ng3nefc+dYPMQ==} hasBin: true - '@kikiutils/eslint-config@0.3.0': - resolution: {integrity: sha512-ZG96RsUhKYjiNnPJzKAR3soyZn/C44E+glvsm324RLpP7jgYSaG4s+63p94i5TNAYUiucOKXA1Y75zsFavU2aA==} + '@kikiutils/eslint-config@0.3.1': + resolution: {integrity: sha512-tpB6AHuHFzKs3wc3juGWvcRCy9X19hFtKvwsZfsZjPBiv7MNDUAShXMW71iywC8tzAyZQYxztzl993zX3i6s7g==} engines: {node: '>=18.12.1'} '@kikiutils/tsconfigs@3.0.2': @@ -3633,7 +3633,7 @@ snapshots: transitivePeerDependencies: - magicast - '@kikiutils/eslint-config@0.3.0(@typescript-eslint/utils@8.14.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(@vue/compiler-sfc@3.5.13)(jiti@2.4.0)(typescript@5.6.3)': + '@kikiutils/eslint-config@0.3.1(@typescript-eslint/utils@8.14.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(@vue/compiler-sfc@3.5.13)(jiti@2.4.0)(typescript@5.6.3)': dependencies: '@antfu/eslint-config': 3.9.1(@typescript-eslint/utils@8.14.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(@vue/compiler-sfc@3.5.13)(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3) eslint: 9.15.0(jiti@2.4.0) diff --git a/src/plugins/normalize.ts b/src/plugins/normalize.ts index fd3abea..d760809 100644 --- a/src/plugins/normalize.ts +++ b/src/plugins/normalize.ts @@ -17,7 +17,7 @@ const { get: lodashGet, set: lodashSet, unset: lodashUnset } = lodash; * * @param schema - The Mongoose schema to apply the plugin to. */ -export const mongooseNormalizePlugin = (schema: S) => { +export function mongooseNormalizePlugin(schema: S) { const toJSON = schema.get('toJSON'); const toJSONTransform = toJSON?.transform; schema.set('toJSON', { @@ -37,8 +37,8 @@ export const mongooseNormalizePlugin = (schema: S) => { delete copiedRet.__v; if (toJSONTransform && typeof toJSONTransform !== 'boolean') return toJSONTransform(doc, copiedRet, options); return copiedRet; - } + }, }); -}; +} export default mongooseNormalizePlugin; diff --git a/src/schema-builders/base.ts b/src/schema-builders/base.ts index f2e7610..57507cc 100644 --- a/src/schema-builders/base.ts +++ b/src/schema-builders/base.ts @@ -1,28 +1,34 @@ import type { Schema } from 'mongoose'; -export const createBaseSchemaBuilderFactory = >>(type: BooleanConstructor | DateConstructor | NumberConstructor | Schema.Types.ObjectId['constructor'] | StringConstructor) => { +const isFunctionKeys = new Set([ + 'default', + 'enum', + 'index', + 'max', + 'maxlength', + 'min', + 'minlength', +]); + +export function createBaseSchemaBuilderFactory>>(type: BooleanConstructor | DateConstructor | NumberConstructor | Schema.Types.ObjectId['constructor'] | StringConstructor) { return (schema: Record = {}) => { schema.type = type; return new Proxy(Object.freeze({}), { get(_, key, receiver) { if (typeof key === 'symbol') throw new Error('Cannot use symbol as a schema attribute'); if (key in schema) throw new Error(`Duplicate schema attribute: ${key}`); - if (isFunctionKeys.has(key)) return (value: any) => ((schema[key] = value), receiver); + if (isFunctionKeys.has(key)) { + return (value: any) => { + schema[key] = value; + return receiver; + }; + } + if (key === 'nonRequired') return { ...schema }; if (key === 'required') return { ...schema, required: true }; schema[key] = true; return receiver; - } + }, }) as Builder; }; -}; - -const isFunctionKeys = new Set([ - 'default', - 'enum', - 'index', - 'max', - 'maxlength', - 'min', - 'minlength' -]); +} diff --git a/src/schema-builders/boolean.ts b/src/schema-builders/boolean.ts index 0804000..ace4dc9 100644 --- a/src/schema-builders/boolean.ts +++ b/src/schema-builders/boolean.ts @@ -3,10 +3,13 @@ import type { Merge } from 'type-fest'; import { createBaseSchemaBuilderFactory } from './base'; -type BaseProps = { type: BooleanSchemaDefinition }; -export type ExtendBooleanSchemaBuilder = Omit, ExtraOmitFields | keyof Props>; +export type ExtendBooleanSchemaBuilder = Omit, ExtraOmitFields | keyof Props>; -export interface BooleanSchemaBuilder { +export interface BaseBooleanSchemaProps { + type: BooleanSchemaDefinition; +} + +export interface BooleanSchemaBuilder { default: | ((this: any, doc: any) => DefaultType) | null, D extends boolean>(value: T) => ExtendBooleanSchemaBuilder, ExtraOmitFields>; index: (value: T) => ExtendBooleanSchemaBuilder, ExtraOmitFields>; nonRequired: Props; diff --git a/src/schema-builders/date.ts b/src/schema-builders/date.ts index ae9c414..fc3b502 100644 --- a/src/schema-builders/date.ts +++ b/src/schema-builders/date.ts @@ -1,24 +1,25 @@ -import type { DefaultType, DateSchemaDefinition, IndexDirection, IndexOptions } from 'mongoose'; +import type { DateSchemaDefinition, DefaultType, IndexDirection, IndexOptions } from 'mongoose'; import type { Merge } from 'type-fest'; -import { createBaseSchemaBuilderFactory } from './base'; import type { Readonlyable } from '../types/utils'; -type BaseProps = { type: DateSchemaDefinition }; +import { createBaseSchemaBuilderFactory } from './base'; + export type ExtendDateSchemaBuilder = Omit, ExtraOmitFields | keyof Props>; +interface BaseProps { + type: DateSchemaDefinition; +} + export interface DateSchemaBuilder { default: | ((this: any, doc: any) => DefaultType) | null, D extends NativeDate>(value: T) => ExtendDateSchemaBuilder, ExtraOmitFields>; enum: < T extends - | Readonlyable> - | { - message?: M; - values: Readonlyable>; - } - | { [path: string]: D | null }, + | Readonlyable> + | { message?: M; values: Readonlyable> } + | { [path: string]: D | null }, D extends NativeDate, - M extends string + M extends string, >( value: T ) => ExtendDateSchemaBuilder, ExtraOmitFields>; diff --git a/src/schema-builders/decimal128.ts b/src/schema-builders/decimal128.ts index e556c6e..6751c1f 100644 --- a/src/schema-builders/decimal128.ts +++ b/src/schema-builders/decimal128.ts @@ -3,26 +3,33 @@ import { Schema } from 'mongoose'; import type { DefaultType, IndexDirection, IndexOptions, Types } from 'mongoose'; import type { Merge } from 'type-fest'; -import { createBaseSchemaBuilderFactory } from './base'; import type { Readonlyable } from '../types/utils'; -type BaseProps = { type: Schema.Types.Decimal128 }; +import { createBaseSchemaBuilderFactory } from './base'; + export type ExtendDecimal128SchemaBuilder = Omit, ExtraOmitFields | keyof Props>; -type ToStringGetterSchema = { get: (value: Types.Decimal128) => string }; -type ToStringSetterSchema = { set: (value: { toString(): string }) => string }; + +interface BaseProps { + type: Schema.Types.Decimal128; +} + +interface ToStringGetterSchema { + get: (value: Types.Decimal128) => string; +} + +interface ToStringSetterSchema { + set: (value: { toString: () => string }) => string; +} export interface Decimal128SchemaBuilder { default: | ((this: any, doc: any) => DefaultType) | null, D extends Types.Decimal128>(value: T) => ExtendDecimal128SchemaBuilder, ExtraOmitFields>; enum: < T extends - | Readonlyable> - | { - message?: M; - values: Readonlyable>; - } - | { [path: string]: D | null }, + | Readonlyable> + | { message?: M; values: Readonlyable> } + | { [path: string]: D | null }, D extends Types.Decimal128, - M extends string + M extends string, >( value: T ) => ExtendDecimal128SchemaBuilder, ExtraOmitFields>; @@ -59,20 +66,25 @@ export interface Decimal128SchemaBuilder { + +export function decimal128SchemaBuilder() { const schema: Record = {}; const baseBuilder = baseBuilderFactory(schema); return new Proxy(baseBuilder, { get(target, key, receiver) { if (key === 'setRoundAndToFixedSetter') { return (places: number = 2, rounding: Decimal.Rounding = Decimal.ROUND_DOWN) => { - schema['set'] = (value: { toString(): string }) => new Decimal(value.toString()).toFixed(places, rounding); + schema.set = (value: { toString: () => string }) => new Decimal(value.toString()).toFixed(places, rounding); return receiver; }; } - if (key === 'setToStringGetter') return (schema['get'] = (value: Types.Decimal128) => value.toString()), receiver; + if (key === 'setToStringGetter') { + (schema.get = (value: Types.Decimal128) => value.toString()); + return receiver; + } + return Reflect.get(target, key, receiver); - } + }, }) as Decimal128SchemaBuilder; -}; +} diff --git a/src/schema-builders/index.ts b/src/schema-builders/index.ts index 851e9e9..c81d3eb 100644 --- a/src/schema-builders/index.ts +++ b/src/schema-builders/index.ts @@ -1,26 +1,26 @@ +import { booleanSchemaBuilder } from './boolean'; import { dateSchemaBuilder } from './date'; import { decimal128SchemaBuilder } from './decimal128'; -import { booleanSchemaBuilder } from './boolean'; import { numberSchemaBuilder } from './number'; import { objectIdSchemaBuilder } from './object-id'; import { refSchemaBuilder } from './ref'; import { stringSchemaBuilder } from './string'; +export * from './boolean'; export * from './date'; export * from './decimal128'; -export * from './boolean'; export * from './number'; export * from './ref'; export * from './string'; export const schemaBuilders = { + boolean: booleanSchemaBuilder, date: dateSchemaBuilder, decimal128: decimal128SchemaBuilder, - boolean: booleanSchemaBuilder, number: numberSchemaBuilder, objectId: objectIdSchemaBuilder, ref: refSchemaBuilder, - string: stringSchemaBuilder + string: stringSchemaBuilder, }; export default schemaBuilders; diff --git a/src/schema-builders/number.ts b/src/schema-builders/number.ts index e92c01f..c569391 100644 --- a/src/schema-builders/number.ts +++ b/src/schema-builders/number.ts @@ -1,24 +1,25 @@ import type { DefaultType, IndexDirection, IndexOptions, NumberSchemaDefinition } from 'mongoose'; import type { Merge } from 'type-fest'; -import { createBaseSchemaBuilderFactory } from './base'; import type { Readonlyable } from '../types/utils'; -type BaseProps = { type: NumberSchemaDefinition }; +import { createBaseSchemaBuilderFactory } from './base'; + export type ExtendNumberSchemaBuilder = Omit, ExtraOmitFields | keyof Props>; +interface BaseProps { + type: NumberSchemaDefinition; +} + export interface NumberSchemaBuilder { default: | ((this: any, doc: any) => DefaultType) | null, D extends number>(value: T) => ExtendNumberSchemaBuilder, ExtraOmitFields>; enum: < T extends - | Readonlyable> - | { - message?: M; - values: Readonlyable>; - } - | { [path: string]: N | null }, + | Readonlyable> + | { message?: M; values: Readonlyable> } + | { [path: string]: N | null }, M extends string, - N extends number + N extends number, >( value: T ) => ExtendNumberSchemaBuilder, ExtraOmitFields>; diff --git a/src/schema-builders/object-id.ts b/src/schema-builders/object-id.ts index 69d96b0..5d9b735 100644 --- a/src/schema-builders/object-id.ts +++ b/src/schema-builders/object-id.ts @@ -2,24 +2,25 @@ import { Schema } from 'mongoose'; import type { DefaultType, IndexDirection, IndexOptions, ObjectIdSchemaDefinition, Types } from 'mongoose'; import type { Merge } from 'type-fest'; -import { createBaseSchemaBuilderFactory } from './base'; import type { Readonlyable } from '../types/utils'; -type BaseProps = { type: ObjectIdSchemaDefinition }; +import { createBaseSchemaBuilderFactory } from './base'; + export type ExtendObjectIdSchemaBuilder = Omit, ExtraOmitFields | keyof Props>; +interface BaseProps { + type: ObjectIdSchemaDefinition; +} + export interface ObjectIdSchemaBuilder { default: | ((this: any, doc: any) => DefaultType) | null, D extends Types.ObjectId>(value: T) => ExtendObjectIdSchemaBuilder, ExtraOmitFields>; enum: < T extends - | Readonlyable> - | { - message?: M; - values: Readonlyable>; - } - | { [path: string]: O | null }, + | Readonlyable> + | { message?: M; values: Readonlyable> } + | { [path: string]: O | null }, M extends string, - O extends Types.ObjectId + O extends Types.ObjectId, >( value: T ) => ExtendObjectIdSchemaBuilder, ExtraOmitFields>; diff --git a/src/schema-builders/ref.ts b/src/schema-builders/ref.ts index 8797a2d..dc4d66b 100644 --- a/src/schema-builders/ref.ts +++ b/src/schema-builders/ref.ts @@ -2,24 +2,25 @@ import { Schema } from 'mongoose'; import type { DefaultType, IndexDirection, IndexOptions, Model, ObjectIdSchemaDefinition, Types } from 'mongoose'; import type { Merge } from 'type-fest'; -import { createBaseSchemaBuilderFactory } from './base'; import type { Readonlyable } from '../types/utils'; -type BaseProps = { type: ObjectIdSchemaDefinition }; +import { createBaseSchemaBuilderFactory } from './base'; + export type ExtendRefSchemaBuilder = Omit, ExtraOmitFields | keyof Props>; +interface BaseProps { + type: ObjectIdSchemaDefinition; +} + export interface RefSchemaBuilder { default: | ((this: any, doc: any) => DefaultType) | null, D extends Types.ObjectId>(value: T) => ExtendRefSchemaBuilder, ExtraOmitFields>; enum: < T extends - | Readonlyable> - | { - message?: M; - values: Readonlyable>; - } - | { [path: string]: O | null }, + | Readonlyable> + | { message?: M; values: Readonlyable> } + | { [path: string]: O | null }, M extends string, - O extends Types.ObjectId + O extends Types.ObjectId, >( value: T ) => ExtendRefSchemaBuilder, ExtraOmitFields>; diff --git a/src/schema-builders/string.ts b/src/schema-builders/string.ts index 02c40f1..b6c9be9 100644 --- a/src/schema-builders/string.ts +++ b/src/schema-builders/string.ts @@ -1,26 +1,30 @@ import type { DefaultType, IndexDirection, IndexOptions, StringSchemaDefinition } from 'mongoose'; -import net from 'net'; +import net from 'node:net'; import type { Merge } from 'type-fest'; -import { createBaseSchemaBuilderFactory } from './base'; import type { Readonlyable } from '../types/utils'; -type BaseProps = { type: StringSchemaDefinition }; +import { createBaseSchemaBuilderFactory } from './base'; + export type ExtendStringSchemaBuilder = Omit, ExtraOmitFields | keyof Props>; -type IPSchema = { trim: true; validate: { message: T; validator: (value: string) => boolean } }; + +interface BaseProps { + type: StringSchemaDefinition; +} +interface IPSchema { + trim: true; + validate: { message: T; validator: (value: string) => boolean }; +} export interface StringSchemaBuilder { default: | ((this: any, doc: any) => DefaultType) | null, D extends string>(value: T) => ExtendStringSchemaBuilder, ExtraOmitFields>; enum: < T extends - | Readonlyable> - | { - message?: M; - values: Readonlyable>; - } - | { [path: string]: S | null }, + | Readonlyable> + | { message?: M; values: Readonlyable> } + | { [path: string]: S | null }, M extends string, - S extends string + S extends string, >( value: T ) => ExtendStringSchemaBuilder, ExtraOmitFields>; @@ -72,7 +76,8 @@ export interface StringSchemaBuilder { + +export function stringSchemaBuilder() { const schema: Record = {}; const baseBuilder = baseBuilderFactory(schema); return new Proxy(baseBuilder, { @@ -93,8 +98,14 @@ export const stringSchemaBuilder = () => { }; } - if (key === 'length') return (value: any) => ((schema['maxlength'] = schema['minlength'] = value), receiver); + if (key === 'length') { + return (value: any) => { + (schema.maxlength = schema.minlength = value); + return receiver; + }; + } + return Reflect.get(target, key, receiver); - } + }, }) as StringSchemaBuilder; -}; +} diff --git a/src/types/aggregate-paginate.ts b/src/types/aggregate-paginate.ts index 421834b..ef13c18 100644 --- a/src/types/aggregate-paginate.ts +++ b/src/types/aggregate-paginate.ts @@ -65,12 +65,14 @@ declare module 'mongoose' { [customLabel: string]: T[] | number | boolean | null | undefined; } - interface AggregatePaginateModel extends Model { - aggregatePaginate(query?: Aggregate, options?: AggregatePaginateOptions, callback?: (err: any, result: AggregatePaginateResult) => void): Promise>; + interface AggregatePaginateModel extends Model { + aggregatePaginate: (query?: Aggregate, options?: AggregatePaginateOptions, callback?: (err: any, result: AggregatePaginateResult) => void) => Promise>; } } declare function mongooseAggregatePaginate(schema: Schema): void; + +/* eslint-disable-next-line ts/no-namespace */ declare namespace mongooseAggregatePaginate { const PREPAGINATION_PLACEHOLDER: string; const aggregatePaginate: { options: AggregatePaginateOptions }; diff --git a/src/types/index.ts b/src/types/index.ts index bf97758..fa17bf9 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -3,8 +3,9 @@ import type { AggregatePaginateModel, Connection, HydratedDocument, PaginateMode export type {} from './aggregate-paginate'; export type {} from './paginate'; -// @ts-expect-error -export interface BaseModelStatics {} +// @ts-expect-error Ignore this error. +// eslint-disable-next-line unused-imports/no-unused-vars +export interface BaseModelStatics {} export interface MongooseConnections { default?: Connection; @@ -22,9 +23,7 @@ declare global { * @template CreatedAtField - A boolean flag indicating whether the `createdAt` field should be included. * @template UpdatedAtField - A boolean flag indicating whether the `updatedAt` field should be included. */ - type BaseMongooseDocType = Omit & - (CreatedAtField extends true ? { createdAt: Date } : {}) & - (UpdatedAtField extends true ? { updatedAt: Date } : {}); + type BaseMongooseDocType = Omit & (CreatedAtField extends true ? { createdAt: Date } : object) & (UpdatedAtField extends true ? { updatedAt: Date } : object); /** * Type definition for a Mongoose model with pagination and aggregation capabilities. @@ -36,9 +35,7 @@ declare global { * @template InstanceMethodsAndOverrides - Optional type parameter for instance methods and overrides. * @template QueryHelpers - Optional type parameter for additional query helper methods. */ - type BaseMongoosePaginateModel = AggregatePaginateModel & - PaginateModel & - BaseModelStatics; + type BaseMongoosePaginateModel = AggregatePaginateModel & PaginateModel & BaseModelStatics; /** * Type definition for a Mongoose document or ObjectId. @@ -65,7 +62,7 @@ declare global { * @template QueryHelpers - Optional type parameter for additional query helper methods. * @template InstanceMethodsAndOverrides - Optional type parameter for specifying custom instance methods and overrides on the document. */ - type MongooseFindOneReturnType = QueryWithHelpers; + type MongooseFindOneReturnType = QueryWithHelpers; /** * Type definition for a hydrated Mongoose document. @@ -79,5 +76,5 @@ declare global { * @template InstanceMethodsAndOverrides - Optional type parameter for instance methods and overrides. * @template QueryHelpers - Optional type parameter for additional query helper methods. */ - type MongooseHydratedDocument = HydratedDocument; + type MongooseHydratedDocument = HydratedDocument; } diff --git a/src/types/options.ts b/src/types/options.ts index 3e2abc8..7c7d264 100644 --- a/src/types/options.ts +++ b/src/types/options.ts @@ -46,7 +46,7 @@ export interface CustomMongooseOptions { /** * A function that will be executed before the final build when using {@link buildMongooseModel}. */ - beforeModelBuild?: , InstanceMethodsAndOverrides = {}, QueryHelpers = {}>( + beforeModelBuild?: , InstanceMethodsAndOverrides = object, QueryHelpers = object>( schema: Schema ) => void; } diff --git a/src/types/paginate.ts b/src/types/paginate.ts index 836d43a..010b1cb 100644 --- a/src/types/paginate.ts +++ b/src/types/paginate.ts @@ -72,40 +72,47 @@ declare module 'mongoose' { [customLabel: string]: T[] | number | boolean | null | undefined; } - type PaginateDocument = O['lean'] extends true ? (O['leanWithId'] extends true ? T & { id: string } : T) : HydratedDocument; + type PaginateDocument = O['lean'] extends true ? (O['leanWithId'] extends true ? T & { id: string } : T) : HydratedDocument; - interface PaginateModel extends Model { - paginate( - query?: FilterQuery, - options?: O, - callback?: (err: any, result: PaginateResult>) => void - ): Promise>>; + interface PaginateModel extends Model { + paginate: { + ( + query?: FilterQuery, + options?: O, + callback?: (err: any, result: PaginateResult>) => void + ): Promise>>; - paginate( - query?: FilterQuery, - options?: O, - callback?: (err: any, result: PaginateResult>) => void - ): Promise>>; + ( + query?: FilterQuery, + options?: O, + callback?: (err: any, result: PaginateResult>) => void + ): Promise>>; - paginate( - query?: FilterQuery, - options?: PaginateOptions, - callback?: (err: any, result: PaginateResult>) => void - ): Promise>>; + ( + query?: FilterQuery, + options?: PaginateOptions, + callback?: (err: any, result: PaginateResult>) => void + ): Promise>>; + }; } - // @ts-expect-error - interface Query, RawDocType = DocType, QueryOp = 'find', TInstanceMethods = Record> { - paginate(options?: O): Promise>>; - paginate(options?: O): Promise>>; - paginate(options?: PaginateOptions): Promise>>; + // @ts-expect-error Ignore this error. + interface Query, RawDocType = DocType, _QueryOp = 'find', TInstanceMethods = Record> { + paginate: { + (options?: O): Promise>>; + (options?: O): Promise>>; + (options?: PaginateOptions): Promise>>; + }; } } declare function _(schema: Schema): void; + +/* eslint-disable-next-line ts/no-namespace */ declare namespace _ { const paginate: { options: PaginateOptions }; const paginateSubDocs: { options: PaginateOptions }; + class PaginationParameters { constructor(request: { query?: Record }); get: () => [FilterQuery, O]; diff --git a/src/utils.ts b/src/utils.ts index 574030e..d8e6090 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,10 +1,11 @@ import mongoose, { Types } from 'mongoose'; import mongooseAggregatePaginate from 'mongoose-aggregate-paginate-v2'; import mongoosePaginate from 'mongoose-paginate-v2'; +import { env } from 'node:process'; import { customMongooseOptions } from './_internals'; import { mongooseConnections } from './constants'; -import { setCustomMongooseOptions } from './options'; +import type { setCustomMongooseOptions } from './options'; import mongooseNormalizePlugin from './plugins/normalize'; import type { BuildMongooseModelOptions } from './types/options'; @@ -37,18 +38,18 @@ export type DoNotRemoveOrUseThisType = typeof setCustomMongooseOptions; * * @returns The created Mongoose model. */ -export function buildMongooseModel, InstanceMethodsAndOverrides = {}, QueryHelpers = {}>( +export function buildMongooseModel, InstanceMethodsAndOverrides = object, QueryHelpers = object>( collection: string, name: string, schema: mongoose.Schema, - options?: BuildMongooseModelOptions + options?: BuildMongooseModelOptions, ) { if (options?.enableNormalizePlugin !== false) schema.plugin(mongooseNormalizePlugin); schema.plugin(mongooseAggregatePaginate); schema.plugin(mongoosePaginate); schema.set('timestamps', options?.timestamps ?? true); customMongooseOptions.beforeModelBuild?.(schema); - return (options?.connection || mongooseConnections.default || (mongooseConnections.default = mongoose.createConnection(process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017'))).model(name, schema, collection); + return (options?.connection || mongooseConnections.default || (mongooseConnections.default = mongoose.createConnection(env.MONGODB_URI || 'mongodb://127.0.0.1:27017'))).model(name, schema, collection); } /** @@ -71,7 +72,7 @@ export function buildMongooseModel, DocType, InstanceMethodsAndOverrides, QueryHelpers>( documentOrObjectId: MongooseDocumentOrObjectId, model: BaseMongoosePaginateModel, - selectFields?: string[] + selectFields?: string[], ): Promise { if (typeof documentOrObjectId === 'string' || documentOrObjectId instanceof Types.ObjectId) return (await model.findById(documentOrObjectId).select(selectFields || [])) as D | null; return documentOrObjectId; diff --git a/tests/schema-builders/base.test.ts b/tests/schema-builders/base.test.ts index 8ad2155..7db14c9 100644 --- a/tests/schema-builders/base.test.ts +++ b/tests/schema-builders/base.test.ts @@ -15,7 +15,7 @@ describe('createBaseSchemaBuilderFactory', () => { expect(createBaseSchemaBuilderFactory(String)().private.unique.nonRequired).toEqual({ private: true, type: String, - unique: true + unique: true, }); }); @@ -68,7 +68,7 @@ describe('createBaseSchemaBuilderFactory', () => { it('should throw an error when using a symbol as a schema attribute', () => { const schemaBuilder = createBaseSchemaBuilderFactory(Boolean)(); - // @ts-expect-error - expect(() => schemaBuilder[Symbol()]).toThrow('Cannot use symbol as a schema attribute'); + // @ts-expect-error Ignore this error. + expect(() => schemaBuilder[Symbol('test')]).toThrow('Cannot use symbol as a schema attribute'); }); }); diff --git a/tests/schema-builders/ref.test.ts b/tests/schema-builders/ref.test.ts index 58e007b..570d134 100644 --- a/tests/schema-builders/ref.test.ts +++ b/tests/schema-builders/ref.test.ts @@ -1,4 +1,5 @@ import { model, Schema } from 'mongoose'; + import { refSchemaBuilder } from '../../src/schema-builders/ref'; describe('refSchemaBuilder', () => { diff --git a/tests/schema-builders/string.test.ts b/tests/schema-builders/string.test.ts index d60aa83..7e4443f 100644 --- a/tests/schema-builders/string.test.ts +++ b/tests/schema-builders/string.test.ts @@ -12,8 +12,8 @@ describe('stringSchemaBuilder', () => { type: String, validate: { message: '`{VALUE}` is not a valid IPv4 address for path `{PATH}`.', - validator: expect.any(Function) - } + validator: expect.any(Function), + }, }); expect(schema.validate.validator('192.168.1.1')).toBe(true); @@ -30,8 +30,8 @@ describe('stringSchemaBuilder', () => { type: String, validate: { message: '`{VALUE}` is not a valid IPv6 address for path `{PATH}`.', - validator: expect.any(Function) - } + validator: expect.any(Function), + }, }); expect(schema.validate.validator('2001:0db8:85a3:0000:0000:8a2e:0370:7334')).toBe(true); @@ -45,7 +45,7 @@ describe('stringSchemaBuilder', () => { expect(stringSchemaBuilder().length(10).nonRequired).toEqual({ maxlength: 10, minlength: 10, - type: String + type: String, }); }); }); diff --git a/tsconfig.json b/tsconfig.json index 7c8fd42..c65ed95 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,13 @@ { "extends": "@kikiutils/tsconfigs/esnext/es2022.json", "compilerOptions": { - "declaration": true, - "declarationDir": "./dist", - "emitDeclarationOnly": true, "paths": { "mongoose-aggregate-paginate-v2": ["./src/types/aggregate-paginate"], "mongoose-paginate-v2": ["./src/types/paginate"] - } + }, + "declaration": true, + "declarationDir": "./dist", + "emitDeclarationOnly": true }, "include": ["./src"] }