npm package: @feedmepos/custom-attributes
Description: ## Package Features
Latest version: 0.0.1-rc.5

# @feedmepos/custom-attributes

## Package Features

The `@feedmepos/custom-attributes` package provides a set of tools to manage and filter custom attributes for entities such as restaurants. The main features include:

1. **Filter Builder**: `FmCustomAttributeFilter` A vue component that enables users to build complex filter queries based on custom attributes.
2. **Rule Validation Function**: `validateRule` A function that evaluates MongoDB-style queries against custom attribute data to determine if they match the specified rules.

### Rule Validation Function

The `mongoQuery` function is used to evaluate a set of rules against custom attribute data. It takes two parameters: the data to be evaluated and the rule to be applied. The function returns a boolean indicating whether the data matches the rule.

### Development

```bash
pnpm install
pnpm dev
```

### Release Package

#### Typescript Package
- Create a new release and tag on GitHub (`custom-attributes-v*`  Eg. custom-attributes-v0.0.0-alpha)
- The package will be published to npm and github registry by [@feedmepos/custom-attributes-action](https://github.com/feedmepos/custom-attributes/actions/workflows/publish.yml)

#### Dart Package
- Create a new release and tag on GitHub (custom_attributes_dart@semantic-version eg. custom_attributes_dart@0.0.0-alpha)
---

## Add to existing project

### Install packages

```bash
pnpm install @feedmepos/custom-attributes
```

### Add style.css to your App.vue

```ts
import '@feedmepos/custom-attributes/style.css';
```

### How to use the components

```ts
import { FmCustomAttributeFilter } from '@feedmepos/custom-attributes';

<FmCustomAttributeFilter
  :attributes="[
    { key: 'name', type: 'string', entity: 'restaurant' },
    { key: 'region', type: 'string', entity: 'restaurant' },
    { key: 'staffCount', type: 'number', entity: 'restaurant' },
    { key: 'memberCount', type: 'number', entity: 'restaurant' },
  ]"
  :model-value="filter"
  @update:model-value="(v) => (filter = v)"
/>
```

### Filter Data

```ts
/**
 * filter = { "logic": "AND", "rules": [ { "logic": "AND", "rules": [ { "property": "name", "operator": "$eq", "value": "123" } ] }, { "logic": "AND", "rules": [ { "property": "region", "operator": "$eq", "value": "123" } ] }, { "logic": "AND", "rules": [ { "property": "name", "operator": "$eq", "value": "123123" }, { "property": "staffCount", "operator": "$eq", "value": 123 } ] } ] }
 *
 * values: FdoCustomAttribute = {
 *   name: '123',
 *   region: '123',
 *   staffCount: 123,
 *   memberCount: 123123
 * }
 */
const testResult = validateRule(values, filter);

const result = restaurants.filter((r) => validateRule(r.customAttribute, filter));
```

## Screenshot

#### Multiple group

![alt text](image.png)

#### One group filter

![alt text](image-1.png)

---

## Type-Safe MongoQuery

This package provides utilities to convert between FDO rules and MongoDB queries, and to validate data against MongoDB-style queries.

### Operators

The following MongoDB operators are supported:

- `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$regex`
- `$between` (for FDO compatibility, mapped to `$gte` and `$lte`)

#### Supported Operators by Type

**String fields**
Supported: `$eq`, `$ne`, `$in`,`$nin`, `$regex`, `$gt`, `$gte`, `$lt`, `$lte`

**Number fields**
Supported: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`

**Boolean fields**
Supported: `$eq`, `$ne`, `$in`, `$nin`

**Date fields**
Supported: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`

**Array fields**
Supported:`$in`, `$nin`

---

## Usage

#### 1. FdoRuleToMongoQueryBuilder

Converts an FDO rule object to a MongoDB query object.

```ts
import { FdoRuleToMongoQueryBuilder } from 'feedmepos/custom-attributes';

const fdoRule = {
  property: 'age',
  operator: '$gt',
  value: 18,
  type: 'number'
};

const mongoQuery = FdoRuleToMongoQueryBuilder.build(fdoRule);
// mongoQuery: { age: { $gt: 18 } }
```

#### 2. MongoQueryBuilderToFdoRule

Converts a MongoDB query object back to an FDO rule object.

```ts
import { MongoQueryBuilderToFdoRule } from '@feedmepos/custom-attributes';

const mongoQuery = { age: { $gt: 18 } };
const fdoRule = MongoQueryBuilderToFdoRule.build(mongoQuery);
// fdoRule: { property: 'age', operator: '$gt', value: 18, type: 'number'}
```

#### 3. validateMongoQuery

Validates a data object against a MongoDB-style query.

```ts
import { validateMongoQuery, MongoQuery } from 'feedmepos/custom-attributes';

const user: User = {
  name: 'John',
  age: 25,
  active: true,
  tags: ['user', 'admin'],
  createdAt: new Date('2024-01-01'),
  profile: { email: 'john@example.com', score: 85 }
};

const query: MongoQuery<User> = {
  age: { $gt: 18 },
  active: { $eq: true },
  tags: { $in: ['admin'] },
  profile: { score: { $gte: 80 } }
};

const isValid = validateMongoQuery(user, query); // true
```

#### Example Usage of Type-Safe MongoQuery

```ts
import { MongoQuery, validateMongoQuery } from '@feedmepos/custom-attributes';

interface Profile {
  email: string;
  score: number;
}
interface User {
  name: string;
  age: number;
  active: boolean;
  tags: string[];
  createdAt: string | Date;
  profile: Profile;
}

const query: MongoQuery<User> = {
  name: { $eq: 'Alice' },
  age: { $gte: 18 },
  profile: { score: { $gt: 80 } },
  tags: { $in: ['admin'] },
  createdAt: { $gte: '2024-01-01' }
};

validateMongoQuery(user, query);

// OR

validateMongoQuery(user, {
  name: { $eq: 'Alice' },
  age: { $gte: 18 },
  profile: { score: { $gt: 80 } },
  tags: { $in: ['admin'] },
  createdAt: { $gte: '2024-01-01' }
});
```

**TypeScript will show errors for:**

- Invalid keys (e.g., `'profile.score'` instead of `profile: { score: ... }`)
- Invalid operators (e.g., `{ age: { $regex: 'foo' } }`)
- Wrong value types (e.g., `{ age: { $eq: 'Alice' } }`)

```ts
// example of invalid query
const invalidQuery: MongoQuery<User> = {
  name: { $eq: 123, $regex: 456 }, // $eq expects string, $regex expects string
  age: { $regex: 'foo', $gt: 'twenty' }, // $regex not allowed for number, $gt expects number
  active: { $gte: true }, // $gte not allowed for boolean
  tags: { $eq: 'admin', $nin: 'user' }, // $eq not allowed for array, $nin expects string[]
  createdAt: { $regex: 2024, $gt: false }, // $regex expects string, $gt expects string or Date
  profile: {
    score: { $regex: 'foo' }, // $regex not allowed for number
    notAProfileField: { $eq: 1 } // 'notAProfileField' does not exist in Profile
  },
  notAUserField: { $eq: 1 }, // 'notAUserField' does not exist in User
  $and: [
    { name: { $eq: 'Alice' } },
    { 'profile.email': { $eq: 'foo@bar.com' } } // dot notation not allowed
  ]
};
```

#### Notes

- **Always use object notation for nested fields** (not dot notation).
- **Annotate your query** as `MongoQuery<YourType>`for best type safety.
- **If you use inline objects,** use the generic on `validateMongoQuery` to enforce type checking.