Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
dbfca70
feat(rest): Created rest package
TriPSs Nov 9, 2023
49cd3a4
refactor(rest): Linting
TriPSs Nov 9, 2023
488a39c
refactor(rest): Improved `@Field` decorator
TriPSs Dec 1, 2023
24a6196
refactor(rest): Improved `@Field` decorator
TriPSs Dec 4, 2023
8178f02
refactor(rest): Improved `@Field` and `@FilterableField` decorators
TriPSs Dec 5, 2023
4529d18
ci: Added dependabot.yml and stale.yml
TriPSs Dec 9, 2023
f16f6c6
refactor(rest): Improved `@Field` decorator
TriPSs Dec 9, 2023
612a43a
refactor(rest): Updated lint
TriPSs Dec 23, 2023
0d0b9f5
test(rest): Added basic test case for one endpoint
TriPSs Dec 23, 2023
221ca7b
refactor: Lint examples
TriPSs Dec 23, 2023
f8daf99
refactor(rest): Improved `@Field` decorator
TriPSs Jan 25, 2024
064a447
Merge branch 'master' into feature/rest
TriPSs Feb 1, 2024
49b16fd
chore: Merge dev
TriPSs Feb 1, 2024
5bd8ccb
refactor(rest): Improved controller methods swagger implementation
TriPSs Feb 8, 2024
9636397
Merge branch 'refs/heads/master' into feature/rest
TriPSs Jun 14, 2024
dc97f26
chore: Merge master
TriPSs Jun 14, 2024
887910b
fix(query-rest): Fixed paging not working when there are no additiona…
TriPSs Jun 14, 2024
3eb2454
fix(query-rest): Added `IsArray` validation to `@Field` if field is a…
TriPSs Jun 14, 2024
45932c9
feat(query-rest): Added `forceArray` option to to `@Field` decorator
TriPSs Jun 14, 2024
85393ad
fix(query-rest): Prevent non-valid options from being passed to swagg…
TriPSs Jun 14, 2024
d4d4553
Merge branch 'refs/heads/improvements' into feature/rest
TriPSs Jun 21, 2024
7245c19
chore: Updated lockfile
TriPSs Jun 21, 2024
ad5c987
feat(rest): Added support for `@FilterableField`
TriPSs Jun 21, 2024
f1ef57f
fix(rest): Fixed filterable fields always required
TriPSs Jun 21, 2024
67a9196
fix(rest): Fixed `forceArray` and `skipIsEnum` not working for `@Field`
TriPSs Jun 21, 2024
b70eacd
fix(query-rest): Add min and max validation to field decorator
TriPSs Aug 21, 2024
697e90f
feat(query-rest): Introduce `FindOneArgsType` and refactor resolvers …
TriPSs Oct 8, 2024
87b7c05
fix(query-rest): Read resolver not respecting `disabled` opt
TriPSs Oct 9, 2024
4111575
feat(query-rest): Add `idOnly` option to IDFieldOptions in id-field.d…
TriPSs Oct 9, 2024
7d46be2
fix(query-rest): Add name option to Expose decorator in field decorator
TriPSs Oct 10, 2024
ee816fb
refactor(query-rest): Update unique name values for DTO classes to in…
TriPSs Oct 10, 2024
465589a
Merge branch 'master' into feature/rest
TriPSs Oct 10, 2024
84a6b2e
chore: Updated lockfile
TriPSs Oct 10, 2024
d3cfb9f
refactor(query-rest): Simplify usage of DTOClass parameters and add A…
TriPSs Oct 10, 2024
1a33964
fix(nestjs-rest): Fix decorator metadata retrieval and class naming
TriPSs Oct 14, 2024
e2353ec
feat(query-rest): Add `IsDate` validation to field decorator
TriPSs Oct 14, 2024
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
22 changes: 18 additions & 4 deletions examples/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,34 @@
"targets": {
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": ["examples/**/*.ts"]
"lintFilePatterns": [
"examples/**/*.ts"
]
}
},
"e2e": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/examples"],
"outputs": [
"{workspaceRoot}/coverage/examples"
],
"options": {
"jestConfig": "jest.e2e.ts",
"runInBand": true
}
}
},
"tags": [],
"implicitDependencies": ["core", "query-graphql", "query-mongoose", "query-sequelize", "query-typegoose", "query-typeorm"]
"implicitDependencies": [
"core",
"query-graphql",
"query-mongoose",
"query-sequelize",
"query-typegoose",
"query-typeorm",
"query-rest"
]
}
3 changes: 2 additions & 1 deletion jest.preset.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ module.exports = {
'@ptc-org/nestjs-query-typeorm': process.cwd() + '/packages/query-typeorm/src',
'@ptc-org/nestjs-query-sequelize': process.cwd() + '/packages/query-sequelize/src',
'@ptc-org/nestjs-query-typegoose': process.cwd() + '/packages/query-typegoose/src',
'@ptc-org/nestjs-query-mongoose': process.cwd() + '/packages/query-mongoose/src'
'@ptc-org/nestjs-query-mongoose': process.cwd() + '/packages/query-mongoose/src',
'@ptc-org/nestjs-query-rest': process.cwd() + '/packages/query-rest/src'
},
testEnvironment: 'node',
setupFilesAfterEnv: ['jest-extended'],
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@nestjs/passport": "10.0.2",
"@nestjs/platform-express": "10.2.7",
"@nestjs/sequelize": "10.0.0",
"@nestjs/swagger": "^7.1.15",
"@nestjs/typeorm": "^10.0.0",
"class-validator": "0.14.0",
"clsx": "^2.0.0",
Expand Down
10 changes: 10 additions & 0 deletions packages/query-rest/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"presets": [
[
"@nrwl/web/babel",
{
"useBuiltIns": "usage"
}
]
]
Comment on lines +4 to +9
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Switch to Node preset and avoid injecting polyfills in a library.

This package is server‑side NestJS; @nrwl/web/babel plus useBuiltIns: "usage" will pull browser polyfills and mutate globals, which is undesirable for a published library. Prefer the Node preset and disable polyfill injection; let consuming apps polyfill.

Apply:

-      "@nrwl/web/babel",
-      {
-        "useBuiltIns": "usage"
-      }
+      "@nrwl/node/babel",
+      {
+        "useBuiltIns": false,
+        "targets": { "node": "18" }
+      }

If you intentionally target browsers from this package, keep the web preset but add an explicit core‑js version:

       "@nrwl/web/babel",
       {
-        "useBuiltIns": "usage"
+        "useBuiltIns": "usage",
+        "corejs": 3
       }

Run to verify engines and dependencies:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Detected Node engines (root and package):"
fd -a package.json | sort | xargs -I{} sh -c 'printf "\n## {}\n"; jq -r ".engines?.node // \"(none)\"" "{}"'

echo -e "\nCheck for core-js dependency if useBuiltIns != false:"
fd -a package.json | sort | xargs -I{} sh -c 'printf "\n## {}\n"; jq -r ".dependencies[\"core-js\"] // .devDependencies[\"core-js\"] // \"(absent)\"" "{}"'

echo -e "\nOther babel configs that could override this one:"
fd -a -HI 'babel.config.*' '.babelrc*' | sed 's/^/ - /'

Length of output: 1257


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo
echo "== packages/query-rest/.babelrc =="
if [ -f packages/query-rest/.babelrc ]; then
  printf "\n--- packages/query-rest/.babelrc ---\n"
  sed -n '1,240p' packages/query-rest/.babelrc || true
else
  echo "(missing)"
fi

echo
echo "== packages/query-rest/package.json =="
if [ -f packages/query-rest/package.json ]; then
  printf "\n--- packages/query-rest/package.json ---\n"
  if command -v jq >/dev/null 2>&1; then
    jq . packages/query-rest/package.json
  else
    cat packages/query-rest/package.json
  fi
else
  echo "(missing)"
fi

echo
echo "== All babel config files (paths and first 120 lines) =="
find . -type f \( -name "babel.config.*" -o -name ".babelrc" -o -name ".babelrc.json" -o -name ".babelrc.js" -o -name ".babelrc.*" \) -print -exec sh -c 'echo "---- {} ----"; sed -n "1,120p" "{}"; echo' \; || true

echo
echo "== Quick grep for core-js and engines fields across package.json files =="
grep -RIn --line-number '"core-js"' package.json packages || true
grep -RIn --line-number '"engines"' package.json packages || true

Length of output: 3046


Switch to the Node Babel preset and stop injecting browser polyfills in library packages.

This repo's packages are server-side NestJS libraries; using @nrwl/web/babel with "useBuiltIns":"usage" pulls browser polyfills and requires core-js (core-js is not declared in any package.json). Replace the web preset with the node preset and disable polyfill injection — or, if you truly target browsers from this package, keep the web preset but add corejs: 3.

Apply:

-      "@nrwl/web/babel",
-      {
-        "useBuiltIns": "usage"
-      }
+      "@nrwl/node/babel",
+      {
+        "useBuiltIns": false,
+        "targets": { "node": "18" }
+      }

If this package intentionally targets browsers, instead add core-js:

      "@nrwl/web/babel",
      {
-        "useBuiltIns": "usage"
+        "useBuiltIns": "usage",
+        "corejs": 3
      }

Files to update: packages/query-rest/.babelrc (also update the same pattern in packages/core/.babelrc, packages/query-graphql/.babelrc, packages/query-mongoose/.babelrc, packages/query-sequelize/.babelrc, packages/query-typegoose/.babelrc, packages/query-typeorm/.babelrc).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@nrwl/web/babel",
{
"useBuiltIns": "usage"
}
]
]
"@nrwl/node/babel",
{
"useBuiltIns": false,
"targets": { "node": "18" }
}
]
]

}
36 changes: 36 additions & 0 deletions packages/query-rest/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"extends": [
"../../.eslintrc.json"
],
"ignorePatterns": [
"!**/*"
],
Comment on lines +5 to +7
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Dangerous ignore pattern: this disables all ignores (may lint node_modules/dist).

"ignorePatterns": ["!**/*"] re-includes everything and can explode lint time/noise. Replace with an explicit safe ignore list.

Apply:

-  "ignorePatterns": [
-    "!**/*"
-  ],
+  "ignorePatterns": [
+    "dist/",
+    "node_modules/",
+    "coverage/",
+    "**/*.d.ts"
+  ],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"ignorePatterns": [
"!**/*"
],
"ignorePatterns": [
"dist/",
"node_modules/",
"coverage/",
"**/*.d.ts"
],
🤖 Prompt for AI Agents
In packages/query-rest/.eslintrc.json around lines 5 to 7, the ignorePatterns
entry uses "!**/*" which effectively disables all ignore rules and causes
linting of generated and third-party files; remove the "!**/*" entry and replace
it with an explicit, safe ignore list (e.g. "node_modules/", "dist/", "build/",
"coverage/", ".next/", "out/") appropriate for this repo, ensuring only source
files are linted; update package or workspace-level eslint configs if needed to
avoid duplicating ignores and run a quick lint to verify the changed pattern
excludes expected folders.

"parserOptions": {
"project": "./tsconfig.json"
},
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}
23 changes: 23 additions & 0 deletions packages/query-rest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<p align="center">
<a href="https://tripss.github.io/nestjs-query" target="blank"><img src="https://tripss.github.io/nestjs-query/img/logo.svg" width="120" alt="Nestjs-query Logo" /></a>
</p>

[![npm version](https://img.shields.io/npm/v/@ptc-org/nestjs-query-rest.svg)](https://www.npmjs.org/package/@ptc-org/nestjs-query-rest)
[![Test](https://github.com/tripss/nestjs-query/workflows/Test/badge.svg?branch=master)](https://github.com/tripss/nestjs-query/actions?query=workflow%3ATest+and+branch%3Amaster+)
[![Coverage Status](https://codecov.io/gh/TriPSs/nestjs-query/branch/master/graph/badge.svg?token=29EX71ID2P)](https://codecov.io/gh/TriPSs/nestjs-query)
[![Known Vulnerabilities](https://snyk.io/test/github/tripss/nestjs-query/badge.svg?targetFile=packages/query-rest/package.json)](https://snyk.io/test/github/tripss/nestjs-query?targetFile=packages/query-rest/package.json)

# `@ptc-org/nestjs-query-rest`

This package provides a code first implementation of rest CRUD endpoints. It is built on top of
of [nestjs](https://nestjs.com/).

## Installation

[Install Guide](https://tripss.github.io/nestjs-query/docs/introduction/install)

## Getting Started

The get started with the `@ptc-org/nestjs-query-rest` package checkout
the [Getting Started](https://tripss.github.io/nestjs-query/docs/rest/getting-started) docs.

18 changes: 18 additions & 0 deletions packages/query-rest/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* eslint-disable */
// eslint-disable-next-line import/no-default-export
export default {
displayName: 'query-rest',
preset: '../../jest.preset.js',
globals: {},
testEnvironment: 'node',
transform: {
'^.+\\.[tj]sx?$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.spec.json'
}
]
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/packages/query-rest'
}
49 changes: 49 additions & 0 deletions packages/query-rest/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@ptc-org/nestjs-query-rest",
"version": "4.3.0",
"description": "Nestjs rest query adapter",
"author": "doug-martin <[email protected]>",
"homepage": "https://github.com/tripss/nestjs-query#readme",
"keywords": [
"rest",
"crud",
"nestjs"
],
"license": "MIT",
"main": "src/index.js",
"types": "src/index.d.ts",
"directories": {
"lib": "src",
"test": "__tests__"
},
"files": [
"src/**"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tripss/nestjs-query.git",
"directory": "packages/query-rest"
},
"bugs": {
"url": "https://github.com/tripss/nestjs-query/issues"
},
"dependencies": {
"lodash.omit": "^4.5.0",
"lower-case-first": "^2.0.2",
"pluralize": "^8.0.0",
"tslib": "^2.6.2",
"upper-case-first": "^2.0.2"
},
"peerDependencies": {
"@nestjs/common": "^9.0.0 || ^10.0.0",
"@nestjs/core": "^9.0.0 || ^10.0.0",
"@nestjs/graphql": "^11.0.0 || ^12.0.0",
"@nestjs/swagger": "^7.0.0",
"class-transformer": "^0.5",
"class-validator": "^0.14.0",
"ts-morph": "^19.0.0"
}
}
48 changes: 48 additions & 0 deletions packages/query-rest/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "query-rest",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/query-rest/src",
"projectType": "library",
"targets": {
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["packages/query-rest/**/*.ts"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/packages/query-rest"],
"options": {
"jestConfig": "packages/query-rest/jest.config.ts",
"passWithNoTests": true
}
},
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/query-rest",
"tsConfig": "packages/query-rest/tsconfig.lib.json",
"packageJson": "packages/query-rest/package.json",
"main": "packages/query-rest/src/index.ts",
"assets": ["packages/query-rest/*.md"],
"updateBuildableProjectDepsInPackageJson": true,
"buildableProjectDepsInPackageJsonType": "dependencies"
}
},
"version": {
"executor": "@jscutlery/semver:version",
"options": {}
},
"publish": {
"executor": "nx:run-commands",
"options": {
"command": "npm publish ./dist/packages/query-rest --access public"
}
}
},
"tags": [],
"implicitDependencies": ["core"]
}
47 changes: 47 additions & 0 deletions packages/query-rest/src/auth/authorizer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Filter } from '@ptc-org/nestjs-query-core'

export enum OperationGroup {
READ = 'read',
AGGREGATE = 'aggregate',
CREATE = 'create',
UPDATE = 'update',
DELETE = 'delete'
}

export interface AuthorizationContext {
/** The name of the method that uses the @AuthorizeFilter decorator */
readonly operationName: string

/** The group this operation belongs to */
readonly operationGroup: OperationGroup

/** If the operation does not modify any entities */
readonly readonly: boolean

/** If the operation can affect multiple entities */
readonly many: boolean
}

export interface CustomAuthorizer<DTO> {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,@typescript-eslint/no-explicit-any
authorize(context: any, authorizerContext: AuthorizationContext): Promise<Filter<DTO>>

authorizeRelation?(
relationName: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: any,
authorizerContext: AuthorizationContext
): Promise<Filter<unknown> | undefined>
}

export interface Authorizer<DTO> extends CustomAuthorizer<DTO> {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,@typescript-eslint/no-explicit-any
authorize(context: any, authorizerContext: AuthorizationContext): Promise<Filter<DTO>>

authorizeRelation(
relationName: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: any,
authorizerContext: AuthorizationContext
): Promise<Filter<unknown> | undefined>
}
99 changes: 99 additions & 0 deletions packages/query-rest/src/auth/default-crud.authorizer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Inject, Injectable, Optional } from '@nestjs/common'
import { ModuleRef } from '@nestjs/core'
import { Class, Filter } from '@ptc-org/nestjs-query-core'

// import { getAuthorizer } from '../decorators'
// import { ResolverRelation } from '../resolvers/relations'
import { AuthorizationContext, Authorizer, CustomAuthorizer } from './authorizer'
import { getCustomAuthorizerToken } from './tokens'

export interface AuthorizerOptions<DTO> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
authorize: (context: any, authorizationContext: AuthorizationContext) => Filter<DTO> | Promise<Filter<DTO>>
}

// const createRelationAuthorizer = (opts: AuthorizerOptions<unknown>): Authorizer<unknown> => ({
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// async authorize(context: any, authorizationContext: AuthorizationContext): Promise<Filter<unknown>> {
// return opts.authorize(context, authorizationContext) ?? {}
// },
// authorizeRelation(): Promise<Filter<unknown>> {
// return Promise.reject(new Error('Not implemented'))
// }
// })

export function createDefaultAuthorizer<DTO>(
DTOClass: Class<DTO>,
opts?: CustomAuthorizer<DTO> | AuthorizerOptions<DTO> // instance of class or authorizer options
): Class<Authorizer<DTO>> {
@Injectable()
class DefaultAuthorizer implements Authorizer<DTO> {
readonly authOptions?: AuthorizerOptions<DTO> | CustomAuthorizer<DTO> = opts

readonly relationsAuthorizers: Map<string, Authorizer<unknown> | undefined>

// private readonly relations: Map<string, ResolverRelation<unknown>>

constructor(
private readonly moduleRef: ModuleRef,
@Optional() @Inject(getCustomAuthorizerToken(DTOClass)) private readonly customAuthorizer?: CustomAuthorizer<DTO>
) {
this.relationsAuthorizers = new Map<string, Authorizer<unknown> | undefined>()
// this.relations = this.getRelations()
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
public async authorize(context: any, authorizationContext: AuthorizationContext): Promise<Filter<DTO>> {
return (
this.customAuthorizer?.authorize(context, authorizationContext) ??
this.authOptions?.authorize(context, authorizationContext) ??
{}
)
}

public async authorizeRelation(
relationName: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: any,
authorizationContext: AuthorizationContext
): Promise<Filter<unknown>> {
if (this.customAuthorizer && typeof this.customAuthorizer.authorizeRelation === 'function') {
const filterFromCustomAuthorizer = await this.customAuthorizer.authorizeRelation(
relationName,
context,
authorizationContext
)

if (filterFromCustomAuthorizer) {
return filterFromCustomAuthorizer
}
}
return {}
// this.addRelationAuthorizerIfNotExist(relationName)
// return this.relationsAuthorizers.get(relationName)?.authorize(context, authorizationContext) ?? {}
}

private addRelationAuthorizerIfNotExist(relationName: string) {
if (!this.relationsAuthorizers.has(relationName)) {
return
// const relation = this.relations.get(relationName)
// if (!relation) return
// if (relation.auth) {
// this.relationsAuthorizers.set(relationName, createRelationAuthorizer(relation.auth))
// } else if (getAuthorizer(relation.DTO)) {
// this.relationsAuthorizers.set(relationName, this.moduleRef.get(getAuthorizerToken(relation.DTO), { strict: false }))
// }
}
}

// private getRelations(): Map<string, ResolverRelation<unknown>> {
// const { many = {}, one = {} } = {}// getRelations(DTOClass)
// const relationsMap = new Map<string, ResolverRelation<unknown>>()
// Object.keys(many).forEach((relation) => relationsMap.set(relation, many[relation]))
// Object.keys(one).forEach((relation) => relationsMap.set(relation, one[relation]))
// return relationsMap
// }
}

return DefaultAuthorizer
}
3 changes: 3 additions & 0 deletions packages/query-rest/src/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './authorizer'
export * from './default-crud.authorizer'
export * from './tokens'
Loading