Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

json-api-query

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

json-api-query - npm Package Compare versions

Comparing version
1.0.19
to
2.0.0-next.0
+66
test/Models/IBooking.ts
import { IBookingException } from "./IBookingException";
import { IProfileDetails } from "./IProfileDetails";
import { IVenue } from "./IVenue";
// export type RecurrenceFrequencyType = 'None' | 'Daily' |'Weekly' | 'Monthly' |'Yearly'
export enum RecurrenceFrequencyType{
None = 0,
// Secondly = 1,
// Minutely = 2,
// Hourly = 3,
Daily = 4,
Weekly = 5,
Monthly = 6,
Yearly = 7,
}
export enum Frequency {
YEARLY = 0,
MONTHLY = 1,
WEEKLY = 2,
DAILY = 3,
HOURLY = 4,
MINUTELY = 5,
SECONDLY = 6
}
// export enum RRulerFrequency {
// YEARLY = 0,
// MONTHLY = 1,
// WEEKLY = 2,
// DAILY = 3,
// HOURLY = 4,
// MINUTELY = 5,
// SECONDLY = 6
// }
export class IBooking {
approvedBy: string;
id: string;
firstName: string;
lastName: string;
email: string;
stageName: string;
avatar: string;
dateRequested: Date;
approved: boolean;
rejected: boolean;
progress: number;
phone: string;
city: string;
artists: Array<IProfileDetails>;
recurrenceCount: number;
recurrenceFrequency: RecurrenceFrequencyType;
frequencyType: number;
startDate: string;
endDate: string;
duration: number;
venue: IVenue;
"booking-exceptions": Array<IBookingException>;
approvedDate: Date
calFile: string
recurrencePattern: string
}
import { IProfileDetails } from "./IProfileDetails";
import { IBooking } from "./IBooking";
export interface IBookingException {
id: string;
artists: Array<IProfileDetails>;
booking: IBooking;
exceptionDate: string;
"booking-id": string;
}
export class IConnectedAccounts {
google: boolean;
github: boolean;
stack: boolean;
}
export interface IDeactivateAccount {
confirm: boolean;
}
export interface IEmailPreferences {
successfulPayments: boolean;
payouts: boolean;
freeCollections: boolean;
customerPaymentDispute: boolean;
refundAlert: boolean;
invoicePayments: boolean;
webhookAPIEndpoints: boolean;
}
export interface IImage {
id: string;
url: string;
}
export interface INotifications {
notifications: {
email: boolean;
phone: boolean;
};
billingUpdates: {
email: boolean;
phone: boolean;
};
newTeamMembers: {
email: boolean;
phone: boolean;
};
completeProjects: {
email: boolean;
phone: boolean;
};
newsletters: {
email: boolean;
phone: boolean;
};
}
import { ITown } from "./ITown";
import { IImage } from "./IImage";
import { IBooking } from "./IBooking";
import { IBookingException } from "./IBookingException";
export interface IProfileDetails {
id: string;
avatar: string;
firstName: string;
lastName: string;
email: string;
stageName: string;
isActive: boolean;
facebookUrl: string;
youTubeUrl: string;
instagramUrl: string;
legacyId: string;
images: IImage[];
description: string;
mobile: string;
videoUrl: string;
biography: string;
keywords: string;
quote1: string;
quote2: string;
quote3: string;
quote4: string;
address1: string;
address2: string;
city: string;
postcode: string;
termsAcknowledged: boolean;
dbsApproved: boolean;
dbsExpires: Date;
pliApproved: boolean;
pliExpiry: Date;
caeApproved: boolean;
longitude: number;
latitude: number;
travelMiles: number;
rating: number;
town: ITown;
company: string;
contactPhone: string;
companySite: string;
country: string;
language: string;
timeZone: string;
currency: string;
communications: {
email: boolean | undefined;
phone: boolean | undefined;
};
allowMarketing: boolean;
"booking-exceptions": IBookingException[]
}
export interface ITown {
id: string;
name: string;
}
export interface IUpdateEmail {
newEmail: string;
confirmPassword: string;
}
export interface IUpdatePassword {
currentPassword: string;
newPassword: string;
passwordConfirmation: string;
}
import { ITown } from "./ITown";
export interface IVenue {
longitude: string;
latitude: string;
id: string;
name: string;
address1: string;
address2: string;
address3: string;
postCode: string;
email: string;
phone: string;
notes: string;
invoiceEmail: string;
xeroId: string;
town: ITown;
}
import { NestedTestClass } from "./NestedTestClass";
export interface NestedNestedTestClass {
property1Nested: string;
property2Nested: boolean;
nested: NestedTestClass;
}
import { NestedNestedTestClass } from "./NestedNestedTestClass";
export interface NestedTestClass {
property1Nested: string;
property2Nested: boolean;
nestedAgain: NestedNestedTestClass;
}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"Frequency": {
"enum": [
0,
1,
2,
3,
4,
5,
6
],
"type": "number"
},
"IBooking": {
"properties": {
"approved": {
"type": "boolean"
},
"approvedBy": {
"type": "string"
},
"approvedDate": {
"format": "date-time",
"type": "string"
},
"artists": {
"items": {
"$ref": "#/definitions/IProfileDetails"
},
"type": "array"
},
"avatar": {
"type": "string"
},
"booking-exceptions": {
"items": {
"$ref": "#/definitions/IBookingException"
},
"type": "array"
},
"calFile": {
"type": "string"
},
"city": {
"type": "string"
},
"dateRequested": {
"format": "date-time",
"type": "string"
},
"duration": {
"type": "number"
},
"email": {
"type": "string"
},
"endDate": {
"type": "string"
},
"firstName": {
"type": "string"
},
"frequencyType": {
"type": "number"
},
"id": {
"type": "string"
},
"lastName": {
"type": "string"
},
"phone": {
"type": "string"
},
"progress": {
"type": "number"
},
"recurrenceCount": {
"type": "number"
},
"recurrenceFrequency": {
"$ref": "#/definitions/RecurrenceFrequencyType"
},
"recurrencePattern": {
"type": "string"
},
"rejected": {
"type": "boolean"
},
"stageName": {
"type": "string"
},
"startDate": {
"type": "string"
},
"venue": {
"$ref": "#/definitions/IVenue"
}
},
"type": "object"
},
"IBookingException": {
"properties": {
"artists": {
"items": {
"$ref": "#/definitions/IProfileDetails"
},
"type": "array"
},
"booking": {
"$ref": "#/definitions/IBooking"
},
"booking-id": {
"type": "string"
},
"exceptionDate": {
"type": "string"
},
"id": {
"type": "string"
}
},
"type": "object"
},
"IConnectedAccounts": {
"properties": {
"github": {
"type": "boolean"
},
"google": {
"type": "boolean"
},
"stack": {
"type": "boolean"
}
},
"type": "object"
},
"IDeactivateAccount": {
"properties": {
"confirm": {
"type": "boolean"
}
},
"type": "object"
},
"IEmailPreferences": {
"properties": {
"customerPaymentDispute": {
"type": "boolean"
},
"freeCollections": {
"type": "boolean"
},
"invoicePayments": {
"type": "boolean"
},
"payouts": {
"type": "boolean"
},
"refundAlert": {
"type": "boolean"
},
"successfulPayments": {
"type": "boolean"
},
"webhookAPIEndpoints": {
"type": "boolean"
}
},
"type": "object"
},
"IImage": {
"properties": {
"id": {
"type": "string"
},
"url": {
"type": "string"
}
},
"type": "object"
},
"INotifications": {
"properties": {
"billingUpdates": {
"properties": {
"email": {
"type": "boolean"
},
"phone": {
"type": "boolean"
}
},
"type": "object"
},
"completeProjects": {
"properties": {
"email": {
"type": "boolean"
},
"phone": {
"type": "boolean"
}
},
"type": "object"
},
"newTeamMembers": {
"properties": {
"email": {
"type": "boolean"
},
"phone": {
"type": "boolean"
}
},
"type": "object"
},
"newsletters": {
"properties": {
"email": {
"type": "boolean"
},
"phone": {
"type": "boolean"
}
},
"type": "object"
},
"notifications": {
"properties": {
"email": {
"type": "boolean"
},
"phone": {
"type": "boolean"
}
},
"type": "object"
}
},
"type": "object"
},
"IProfileDetails": {
"properties": {
"address1": {
"type": "string"
},
"address2": {
"type": "string"
},
"allowMarketing": {
"type": "boolean"
},
"avatar": {
"type": "string"
},
"biography": {
"type": "string"
},
"booking-exceptions": {
"items": {
"$ref": "#/definitions/IBookingException"
},
"type": "array"
},
"caeApproved": {
"type": "boolean"
},
"city": {
"type": "string"
},
"communications": {
"properties": {
"email": {
"type": "boolean"
},
"phone": {
"type": "boolean"
}
},
"type": "object"
},
"company": {
"type": "string"
},
"companySite": {
"type": "string"
},
"contactPhone": {
"type": "string"
},
"country": {
"type": "string"
},
"currency": {
"type": "string"
},
"dbsApproved": {
"type": "boolean"
},
"dbsExpires": {
"format": "date-time",
"type": "string"
},
"description": {
"type": "string"
},
"email": {
"type": "string"
},
"facebookUrl": {
"type": "string"
},
"firstName": {
"type": "string"
},
"id": {
"type": "string"
},
"images": {
"items": {
"$ref": "#/definitions/IImage"
},
"type": "array"
},
"instagramUrl": {
"type": "string"
},
"isActive": {
"type": "boolean"
},
"keywords": {
"type": "string"
},
"language": {
"type": "string"
},
"lastName": {
"type": "string"
},
"latitude": {
"type": "number"
},
"legacyId": {
"type": "string"
},
"longitude": {
"type": "number"
},
"mobile": {
"type": "string"
},
"pliApproved": {
"type": "boolean"
},
"pliExpiry": {
"format": "date-time",
"type": "string"
},
"postcode": {
"type": "string"
},
"quote1": {
"type": "string"
},
"quote2": {
"type": "string"
},
"quote3": {
"type": "string"
},
"quote4": {
"type": "string"
},
"rating": {
"type": "number"
},
"stageName": {
"type": "string"
},
"termsAcknowledged": {
"type": "boolean"
},
"timeZone": {
"type": "string"
},
"town": {
"$ref": "#/definitions/ITown"
},
"travelMiles": {
"type": "number"
},
"videoUrl": {
"type": "string"
},
"youTubeUrl": {
"type": "string"
}
},
"type": "object"
},
"ITown": {
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"type": "object"
},
"IUpdateEmail": {
"properties": {
"confirmPassword": {
"type": "string"
},
"newEmail": {
"type": "string"
}
},
"type": "object"
},
"IUpdatePassword": {
"properties": {
"currentPassword": {
"type": "string"
},
"newPassword": {
"type": "string"
},
"passwordConfirmation": {
"type": "string"
}
},
"type": "object"
},
"IVenue": {
"properties": {
"address1": {
"type": "string"
},
"address2": {
"type": "string"
},
"address3": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
},
"invoiceEmail": {
"type": "string"
},
"latitude": {
"type": "string"
},
"longitude": {
"type": "string"
},
"name": {
"type": "string"
},
"notes": {
"type": "string"
},
"phone": {
"type": "string"
},
"postCode": {
"type": "string"
},
"town": {
"$ref": "#/definitions/ITown"
},
"xeroId": {
"type": "string"
}
},
"type": "object"
},
"NestedNestedTestClass": {
"properties": {
"nested": {
"$ref": "#/definitions/NestedTestClass"
},
"property1Nested": {
"type": "string"
},
"property2Nested": {
"type": "boolean"
}
},
"type": "object"
},
"NestedTestClass": {
"properties": {
"nestedAgain": {
"$ref": "#/definitions/NestedNestedTestClass"
},
"property1Nested": {
"type": "string"
},
"property2Nested": {
"type": "boolean"
}
},
"type": "object"
},
"RecurrenceFrequencyType": {
"enum": [
0,
4,
5,
6,
7
],
"type": "number"
},
"TestClass": {
"properties": {
"PropertyAny": {
"type": "string"
},
"a": {
"type": "string"
},
"c": {
"type": "string"
},
"caeApproved": {
"type": "boolean"
},
"firstName": {
"type": "string"
},
"isActive": {
"type": "boolean"
},
"lastName": {
"type": "string"
},
"nested": {
"$ref": "#/definitions/NestedTestClass"
},
"nestedArray": {
"items": {
"$ref": "#/definitions/NestedTestClass"
},
"type": "array"
},
"not1": {
"type": "string"
},
"numProp": {
"type": "number"
},
"or1": {
"type": "string"
},
"or3": {
"type": "string"
},
"prop12": {
"type": "string"
},
"property1": {
"type": "string"
},
"property2": {
"type": "boolean"
},
"stageName": {
"type": "string"
}
},
"type": "object"
}
}
}
import { IConnectedAccounts } from "./IConnectedAccounts"
import { IDeactivateAccount } from "./IDeactivateAccount"
import { IEmailPreferences } from "./IEmailPreferences"
import { INotifications } from "./INotifications"
import { IUpdateEmail } from "./IUpdateEmail"
import { IUpdatePassword } from "./IUpdatePassword"
export const updateEmail: IUpdateEmail = {
newEmail: 'support@keenthemes.com',
confirmPassword: '',
}
export const updatePassword: IUpdatePassword = {
currentPassword: '',
newPassword: '',
passwordConfirmation: '',
}
export const connectedAccounts: IConnectedAccounts = {
google: true,
github: true,
stack: false,
}
export const emailPreferences: IEmailPreferences = {
successfulPayments: false,
payouts: true,
freeCollections: false,
customerPaymentDispute: true,
refundAlert: false,
invoicePayments: true,
webhookAPIEndpoints: false,
}
export const notifications: INotifications = {
notifications: {
email: true,
phone: true,
},
billingUpdates: {
email: true,
phone: true,
},
newTeamMembers: {
email: true,
phone: false,
},
completeProjects: {
email: false,
phone: true,
},
newsletters: {
email: false,
phone: false,
},
}
export const deactivateAccount: IDeactivateAccount = {
confirm: false,
}
import { NestedTestClass } from "./NestedTestClass"
export interface TestClass {
property1: string
property2: boolean
nested: NestedTestClass
nestedArray: NestedTestClass[]
a: string
c: string
not1: string
or1: string
or3: string
PropertyAny: string
prop12: string
numProp: number
stageName: string
lastName: string
firstName: string
caeApproved: boolean
isActive: boolean
}
{
"compilerOptions": {
"outDir": "dist",
"module": "commonjs",
"declaration": true,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"lib": ["es2017"],
"sourceMap": false,
"strict": false,
"strictPropertyInitialization": false,
"resolveJsonModule": true
},
"include": ["test/Models"],
"exclude": ["node_modules","src"],
"compileOnSave": false,
"buildOnSave": false
}
+6
-4
{
"name": "json-api-query",
"version": "1.0.19",
"version": "2.0.0-next.0",
"description": "A query builder for JSONAPIDotNetCore",
"main": "index.js",
"scripts": {
"test": "nyc ./node_modules/.bin/_mocha 'test/**/*.test.ts'",
"build": "tsc"
"schema": "node_modules/typescript-json-schema/bin/typescript-json-schema tsconfig.schema.json \"*\" > test/Models/schema.json",
"test": "node_modules/typescript-json-schema/bin/typescript-json-schema tsconfig.schema.json \"*\" > test/Models/schema.json && nyc ./node_modules/.bin/_mocha 'test/**/*.test.ts'",
"build": "node_modules/typescript-json-schema/bin/typescript-json-schema tsconfig.schema.json \"*\" > test/Models/schema.json && tsc"
},

@@ -29,4 +30,5 @@ "repository": "https://github.com/boon-bo/json-api-query",

"prettier": "^2.6.2",
"source-map-support": "^0.5.21"
"source-map-support": "^0.5.21",
"typescript-json-schema": "^0.53.0"
}
}
+39
-13

@@ -11,10 +11,16 @@ ## JSON-API-QUERY

The query builder supports all of the terms found in the [since v4.0] docs found here: https://www.jsonapi.net/usage/reading/filtering.html
The query builder aims to support all of the terms found in the [since v4.0] docs found here: https://www.jsonapi.net/usage/reading/filtering.html
Legacy syntax is not supported, there is some consideration in the code to add this later (as well as supporting evolved syntax later on) but I am not sure how worthwhile that would be
Legacy syntax is not supported, there is some consideration in the code to add this later (as well as supporting evolved syntax later on) but I am not sure how worthwhile that would be.
The latest version of this package now uses `typescript-json-schema`. This was requireed so that the types can be infered in the query bulder code. Unlike C#, Typescript's type system is unavailable at runtime, so tailoring the query generation for `HasMany` relations is AFAICS impossible without providing some schema information.
To this end I have introduced `typescript-json-schema`. Generating schemas for your models is as easy as adding `"typescript-json-schema tsconfig.schema.json \"*\" > test/Models/schema.json` to your NPM commands section. See this projects `package.json` for an example.
### Basic filtering:
```typescript
new QueryBuilder<TestClass>()
import * as schema from "./Models/schema.json"
new QueryBuilder<TestClass>("TestClass", schema as TJS.Definition)
.find({

@@ -35,3 +41,5 @@ where: {

```typescript
new QueryBuilder<TestClass>()
import * as schema from "./Models/schema.json"
new QueryBuilder<TestClass>("TestClass", schema as TJS.Definition)
.find({

@@ -52,3 +60,5 @@ where: {

```typescript
new QueryBuilder<TestClass>()
import * as schema from "./Models/schema.json"
new QueryBuilder<TestClass>("TestClass", schema as TJS.Definition)
.find({

@@ -69,3 +79,5 @@ where: {

```typescript
new QueryBuilder<TestClass>()
import * as schema from "./Models/schema.json"
new QueryBuilder<TestClass>("TestClass", schema as TJS.Definition)
.find({

@@ -86,3 +98,5 @@ where: {

```typescript
new QueryBuilder<TestClass>()
import * as schema from "./Models/schema.json"
new QueryBuilder<TestClass>("TestClass", schema as TJS.Definition)
.find({

@@ -103,3 +117,5 @@ where: {

```typescript
new QueryBuilder<TestClass>()
import * as schema from "./Models/schema.json"
new QueryBuilder<TestClass>("TestClass", schema as TJS.Definition)
.find({

@@ -125,3 +141,5 @@ where: [

```typescript
new QueryBuilder<TestClass>()
import * as schema from "./Models/schema.json"
new QueryBuilder<TestClass>("TestClass", schema as TJS.Definition)
.find({

@@ -144,3 +162,5 @@ where: {

```typescript
new QueryBuilder<TestClass>()
import * as schema from "./Models/schema.json"
new QueryBuilder<TestClass>("TestClass", schema as TJS.Definition)
.find({

@@ -163,3 +183,5 @@ relations: {

```typescript
new QueryBuilder<TestClass>()
import * as schema from "./Models/schema.json"
new QueryBuilder<TestClass>("TestClass", schema as TJS.Definition)
.find({

@@ -180,3 +202,5 @@ fields: {

```typescript
new QueryBuilder<TestClass>()
import * as schema from "./Models/schema.json"
new QueryBuilder<TestClass>("TestClass", schema as TJS.Definition)
.find({

@@ -203,3 +227,5 @@ order: {

```typescript
new QueryBuilder<TestClass>()
import * as schema from "./Models/schema.json"
new QueryBuilder<TestClass>("TestClass", schema as TJS.Definition)
.find({

@@ -206,0 +232,0 @@ where: {

import { IComparisonOperator } from '../../IComparisonOperator'
import * as TJS from "typescript-json-schema";

@@ -7,3 +8,3 @@ export class EqualsOperator implements IComparisonOperator {

constructor(public property: string, public value: string) {
constructor(public property: string, public value: string, public parent: string = null) {
this._property = property

@@ -14,2 +15,12 @@ this._value = value

toString(): string {
// if(this.parent){
// // special case for null
// if (this._value + '' != 'null' || this._value != null) {
// return `filter[${this.parent}]=equals(${this._property},'${this._value}')`
// } else {
// return `filter[${this.parent}]=equals(${this._property},${this._value})`
// }
// }
// special case for null

@@ -16,0 +27,0 @@ if (this._value + '' != 'null' || this._value != null) {

@@ -16,21 +16,38 @@ import {

} from './operators/dialect'
import {IComparisonOperator} from './IComparisonOperator'
import {IPageInfo} from './IPageInfo'
import {FindOptionsWhere} from './FindOptionsWhere'
import {FindManyOptions} from './FindManyOptions'
import {InstanceChecker} from './InstanceChecker'
import {SparseFieldSet} from './SparseFieldSet'
import {FindOperator} from './FindOperator'
import {FindOptionsRelations} from './FindOptionsRelations'
import {FindOptionsSelect} from './FindOptionsSelect'
import {SparseField} from './SparseField'
import {FindOptionsOrder} from './FindOptionsOrder'
import {FindOptionsOrderValue} from './FindOptionsOrderValue'
import {Sorts} from './Sorts'
import {SortField} from './SortField'
import { IComparisonOperator } from './IComparisonOperator'
import { IPageInfo } from './IPageInfo'
import { FindOptionsWhere } from './FindOptionsWhere'
import { FindManyOptions } from './FindManyOptions'
import { InstanceChecker } from './InstanceChecker'
import { SparseFieldSet } from './SparseFieldSet'
import { FindOperator } from './FindOperator'
import { FindOptionsRelations } from './FindOptionsRelations'
import { FindOptionsSelect } from './FindOptionsSelect'
import { SparseField } from './SparseField'
import { FindOptionsOrder } from './FindOptionsOrder'
import { FindOptionsOrderValue } from './FindOptionsOrderValue'
import { Sorts } from './Sorts'
import { SortField } from './SortField'
import { resolve } from 'path'
import * as TJS from 'typescript-json-schema'
import { Definition, DefinitionOrBoolean } from 'typescript-json-schema'
export class IOpDef {
prop: string
op: FindOperator<any>
}
// For convenience
type Primitive = string | number | bigint | boolean | undefined | symbol
// To infinity and beyond >:D
export type PropertyStringPath<T, Prefix = ''> = {
[K in keyof T]: T[K] extends Primitive | Array<any>
? `${string & Prefix}${string & K}`
: `${string & Prefix}${string & K}` | PropertyStringPath<T[K], `${string & Prefix}${string & K}.`>
}[keyof T]
export class QueryBuilder<T> {
private _operators: Array<IComparisonOperator> = []
private readonly _model: string = ''
private _pageInfo: IPageInfo | null = null

@@ -40,11 +57,31 @@ private _includes: Array<string> = []

private _fields: SparseFieldSet | null = null
private readonly _childQueryBuilder: QueryBuilder<T> | null = null
private _childQueryBuilders: QueryBuilder<T>[] = []
private _findOptions: FindManyOptions<T> | undefined
private readonly _originalSchema: TJS.Definition | TJS.DefinitionOrBoolean
constructor(public childQueryBuilder: QueryBuilder<T> | null = null, public model: string = '') {
this._childQueryBuilder = childQueryBuilder
this._model = model
constructor(
public modelType: string = '',
public schema: Definition | DefinitionOrBoolean = null,
public property: string = '',
public isToManyFromParent: boolean = false,
public parentQueryBuilder: QueryBuilder<T> | null = null,
public childQueryBuilder: QueryBuilder<T> | null = null,
) {
this.childQueryBuilder = childQueryBuilder
this.property = property
this._originalSchema = schema
this.schema = (schema as TJS.Definition).definitions[modelType]
this.isToManyFromParent = isToManyFromParent
this.modelType = modelType
this.parentQueryBuilder = parentQueryBuilder
}
public getPrentPath() {
if (this.parentQueryBuilder && this.parentQueryBuilder.getPrentPath() !== '') {
return `${this.parentQueryBuilder.getPrentPath()}.${this.property}`
}
return this.property
}
/**

@@ -78,3 +115,3 @@ * Finds entities that match given find options.

protected buildSorts(selects: FindOptionsOrder<T> | undefined): Sorts {
let fields = new Sorts(this._model)
let fields = new Sorts(this.property)
for (let key in selects) {

@@ -129,3 +166,3 @@ if (typeof selects[key] === 'string') {

protected buildSparseFieldsets(selects: FindOptionsSelect<T>): SparseFieldSet {
let fields = new SparseFieldSet(this._model)
let fields = new SparseFieldSet(this.property)
for (let key in selects) {

@@ -159,8 +196,2 @@ if (typeof selects[key] === 'boolean' && (selects[key] as boolean) == true) {

...where.map((whereItem) => {
// for (let key in whereItem) {
// if (typeof whereItem[key] == "object" && !InstanceChecker.isFindOperator(whereItem[key])) {
// throw Error('You can\'t do an implicit OR using nested properties')
// }
// }
return this.buildWhere(whereItem)

@@ -178,2 +209,9 @@ }),

if (where[key] === undefined || where[key] === null) continue
let isToMany = false
let schema = (this.schema as TJS.Definition).properties[key] as TJS.Definition
if (schema.type && schema.type === 'array') isToMany = true
if (!InstanceChecker.isFindOperator(where[key])) {

@@ -185,12 +223,48 @@ if (where[key] == null) {

// create the child QB otherwise we need to do Equals(parent.child,'something')
let cqb = new QueryBuilder(null, key)
cqb.find({where: where[key]})
this._childQueryBuilders.push(cqb)
let t = isToMany
? ((this.schema as TJS.Definition).properties[key] as TJS.Definition).items['$ref'].replace(
'#/definitions/',
'',
)
: ((this.schema as TJS.Definition).properties[key] as TJS.Definition)['$ref'].replace(
'#/definitions/',
'',
)
if (isToMany) {
let cqb = new QueryBuilder(
t,
this._originalSchema as TJS.Definition,
key,
isToMany,
this,
null,
)
cqb.find({ where: where[key] })
this._childQueryBuilders.push(cqb)
} else {
// search down the where[key] until we get a findOperator
// use the path to build the operator
let op: IComparisonOperator[] | null = this.getChildOperators(where[key], key)
if (op) {
ops.push(...op)
}
}
continue
} else {
ops.push(new EqualsOperator(key, where[key]))
ops.push(new EqualsOperator(key, where[key], key))
continue
}
}
let op: IComparisonOperator | null = this.getOperator(where[key], key)
let path = this.getPrentPath()
if (path && !this.isToManyFromParent) {
path = `${path}.${key}`
} else {
path = key
}
let op: IComparisonOperator | null = this.getOperator(where[key], `${path}`)
if (op) {

@@ -211,2 +285,64 @@ ops.push(op)

private getChildOperators(whereElement: any, parentKey: string) {
let ops = this.findOperators(whereElement)
let result = []
for (var i = 0; i < ops.length; i++) {
let o = ops[i]
//for (let key in o as any) {
let path = `${parentKey}.${this.getPath(whereElement, o.prop)}`
let op: IComparisonOperator | null = this.getOperator(o.op, `${path}`)
if (op) {
result.push(op)
}
//}
}
return result
}
getPath(obj, key) {
let paths = []
function getPaths(obj, path) {
if (obj instanceof Object && !(obj instanceof Array)) {
for (var k in obj) {
paths.push(path + '.' + k)
getPaths(obj[k], path + '.' + k)
}
}
}
getPaths(obj, '')
return paths
.map(function (p) {
return p.slice(p.lastIndexOf('.') + 1) == key ? p.slice(1) : ''
})
.sort(function (a, b) {
return b.split('.').length - a.split('.').length
})[0]
}
findOperators(o: any): Array<IOpDef> {
let ops = Array<IOpDef>()
if (o instanceof Array) {
for (let i = 0; i < o.length; i++) {
ops.push(...this.findOperators(o[i]))
}
} else {
for (let prop in o) {
console.log(prop + ': ' + o[prop])
if (InstanceChecker.isFindOperator(o[prop])) {
ops.push({
prop: prop,
op: o[prop],
} as IOpDef)
} else {
ops.push(...this.findOperators(o[prop]))
}
}
}
return ops
}
// TODO: refactor this into a factory class

@@ -305,4 +441,4 @@ getOperator(op: FindOperator<any> | undefined, key: string): IComparisonOperator | null {

if (!this.isNullOrWhiteSpace(this._model)) {
filterPropertyExpression = `filter[${this._model}]`
if (this.isToManyFromParent && this.property) {
filterPropertyExpression = `filter[${this.property}]`
}

@@ -320,4 +456,4 @@

if (this._childQueryBuilder) {
final += this._childQueryBuilder.build(final)
if (this.childQueryBuilder) {
final += this.childQueryBuilder.build(final)
}

@@ -324,0 +460,0 @@

@@ -1,50 +0,8 @@

import {suite, test, should, expect, chai} from './utility'
import {suite, test, should, expect, chai, timeout} from './utility'
import {QueryBuilder} from '../src'
import {AndOperator, AnyOperator, EqualsOperator, NotOperator, OrOperator} from '../src/operators/dialect'
import {GreaterThan} from '../src/operators/GreaterThan'
import {SparseFieldSet} from '../src'
import {Any} from '../src/operators/Any'
import {Contains} from '../src/operators/Contains'
import {EndsWith} from '../src/operators/EndsWith'
import {Has} from '../src/operators/Has'
import {StartsWith} from '../src/operators/StartsWith'
import {Or} from '../src/operators/Or'
import {Not} from '../src/operators/Not'
import {LessThanOrEqual} from '../src/operators/LessThanOrEqual'
import {LessThan} from '../src/operators/LessThan'
import {GreaterThanOrEqual} from '../src/operators/GreaterThanOrEqual'
import {Equals} from '../src/operators/Equals'
abstract class TestClass {
property1: string
property2: boolean
nested: NestedTestClass
nestedArray: NestedTestClass[]
a: string
c: string
not1: string
or1: string
or3: string
PropertyAny: string
prop12: string
numProp: number
stageName: string
lastName: string
firstName: string
caeApproved: boolean
isActive: boolean
}
class NestedNestedTestClass {
property1Nested: string
property2Nested: boolean
nested: NestedTestClass
}
class NestedTestClass {
property1Nested: string
property2Nested: boolean
nestedAgain: NestedNestedTestClass
}
import {GreaterThan, Any, Contains, EndsWith, Has, StartsWith, Or, Not, LessThanOrEqual, LessThan, GreaterThanOrEqual, Equals} from '../src'
import * as schema from "./Models/schema.json"
import * as TJS from "typescript-json-schema";
import {TestClass} from "./Models/TestClass";
import { IBooking } from 'Models/IBooking';
should()

@@ -56,9 +14,10 @@

// todo: test with modelname
before() {
this.sut = new QueryBuilder<TestClass>()
this.sut = new QueryBuilder<TestClass>("TestClass", schema as TJS.Definition)
}
@test 'Can construct'() {
@timeout(40000)
@test 'Can construct'(done) {
expect(this.sut).should.be.not.undefined
done()
}

@@ -302,3 +261,3 @@

expect(result).to.equal("?filter[nested]=equals(property1Nested,'test')")
expect(result).to.equal("?filter=equals(nested.property1Nested,'test')")
}

@@ -362,3 +321,3 @@

expect(result).to.equal(
'?fields[nested]=property2Nested&fields[nested.nestedAgain]=property1Nested&fields=property2',
'?fields[nested]=property2Nested&fields[nestedAgain]=property1Nested&fields=property2',
)

@@ -445,3 +404,3 @@ }

.build()
expect(result).to.equal('?page[size]=0&page[number]=10')
expect(result).to.equal('?page[size]=10,something:20&page[number]=10,something:20')
}

@@ -465,16 +424,2 @@

// @test 'two wheres makes an or - nested - throws'() {
// expect(() => this.sut.find({
// where: [{
// nested: {
// property1Nested: "test"
// }
// }, {
// nested: {
// property2Nested: false
// }
// }]
// }).build()).to.throw('You can\'t do an implicit OR using nested properties')
// }
@test 'ors and and'() {

@@ -508,2 +453,19 @@ let result = this.sut

@test 'two wheres make an and'() {
let result = this.sut
.find({
where:
{
stageName: StartsWith('Andy'),
isActive: true,
}
})
.build()
expect(result).to.equal(
"?filter=and(startsWith(stageName,'Andy'),equals(isActive,'true'))",
)
}
@test 'single where with multiple props generates ands'() {

@@ -523,2 +485,48 @@ let result = this.sut

@test
'regression 20 may 2022'(){
let sut = new QueryBuilder<IBooking>("IBooking", schema as TJS.Definition)
let result = sut
.find({
size: 10,
number: 1,
where: {
approvedDate: Equals(null),
venue: {
name: StartsWith('Andy'),
},
},
relations: {
artists: {
images: true,
},
venue: true
},
})
.build();
expect(result).to.equal('?filter=and(equals(approvedDate,null),startsWith(venue.name,\'Andy\'))&include=artists.images,venue&page[size]=10&page[number]=1')
}
@test
'regression 20 may 2022 2 wheres with nesting make an and'(){
let sut = new QueryBuilder<IBooking>("IBooking", schema as TJS.Definition)
let result = sut
.find({
where: {
approvedDate: Equals(null),
venue: {
name: StartsWith("Andy"),
}
},
})
.build();
expect(result).to.equal('?filter=and(equals(approvedDate,null),startsWith(venue.name,\'Andy\'))')
}
@test
'complex query works'() {

@@ -598,5 +606,5 @@ let result = this.sut

expect(result).to.equal(
"?sort=property2&sort[nested]=property1Nested&filter=and(contains(a,'lol'),not(equals(not1,'not5')))&include=nested&fields=firstName,lastName&fields[nested]=property2Nested&page[size]=0&page[number]=10&filter[nested]=and(equals(property2Nested,'true'),has(property1Nested,one,two))&filter[nestedAgain]=endsWith(property1Nested,'wot')",
"?sort=property2&sort[nested]=property1Nested&filter=and(contains(a,'lol'),not(equals(not1,'not5')),has(nested.nestedAgain.property1Nested,one,two),endsWith(nested.nestedAgain.property1Nested,'wot'))&include=nested&fields=firstName,lastName&fields[nested]=property2Nested&page[size]=0&page[number]=10",
)
}
}

@@ -1,2 +0,2 @@

export { suite, test, params, skip, only } from '@testdeck/mocha'
export { suite, test, params, skip, only, timeout } from '@testdeck/mocha'

@@ -3,0 +3,0 @@ import * as _chai from 'chai'

@@ -15,3 +15,4 @@ {

"strict": false,
"strictPropertyInitialization": false
"strictPropertyInitialization": false,
"resolveJsonModule": true
},

@@ -18,0 +19,0 @@ "exclude": ["node_modules"],