@opinio-dev/models 1.1.1-item-types.5 → 1.1.1-item-types.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Determines if the given item is a book
3
+ * @param {Item} item The item to check
4
+ * @returns {boolean} True if the item is a book, false otherwise
5
+ */
6
+ export function isBook(item) {
7
+ return item.type === 'book';
8
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Determines if the given item is a game
3
+ * @param {Item} item The item to check
4
+ * @returns {boolean} True if the item is a game, false otherwise
5
+ */
6
+ export function isGame(item) {
7
+ return item.type === 'game';
8
+ }
@@ -0,0 +1,66 @@
1
+ export const VideoGenres = [
2
+ 'Action',
3
+ 'Adventure',
4
+ 'Biography',
5
+ 'Comedy',
6
+ 'Drama',
7
+ 'Fantasy',
8
+ 'Horror',
9
+ 'Mystery',
10
+ 'Romance',
11
+ 'Science Fiction',
12
+ 'Thriller',
13
+ 'Crime',
14
+ 'Documentary',
15
+ 'Animation',
16
+ 'Family',
17
+ 'Historical',
18
+ 'Musical',
19
+ 'War',
20
+ 'Western'
21
+ ];
22
+ export const GameGenres = [
23
+ ...VideoGenres,
24
+ 'RPG',
25
+ 'Shooter',
26
+ 'Fighting',
27
+ 'Platformer',
28
+ 'Puzzle',
29
+ 'Open World',
30
+ 'Racing',
31
+ 'Simulation',
32
+ 'Sports',
33
+ 'Strategy',
34
+ 'Survival',
35
+ 'Sandbox',
36
+ 'MOBA',
37
+ 'MMO',
38
+ 'Roguelike'
39
+ ];
40
+ export const BookGenres = [
41
+ 'Fiction',
42
+ 'Non-Fiction',
43
+ 'Fantasy',
44
+ 'Science Fiction',
45
+ 'Mystery',
46
+ 'Thriller',
47
+ 'Horror',
48
+ 'Romance',
49
+ 'Historical',
50
+ 'Biography',
51
+ 'Autobiography',
52
+ 'Memoir',
53
+ 'Self-Help',
54
+ 'Poetry',
55
+ 'Drama',
56
+ 'Young Adult',
57
+ "Children's",
58
+ 'Graphic Novel',
59
+ 'Classic',
60
+ 'Philosophy',
61
+ 'Science',
62
+ 'Travel',
63
+ 'Religion',
64
+ 'True Crime',
65
+ 'Humor'
66
+ ];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ export const ItemType = {
2
+ Book: 'book',
3
+ Game: 'game',
4
+ Movie: 'movie',
5
+ Series: 'series'
6
+ };
7
+ export const ItemTypes = Object.values(ItemType);
8
+ /**
9
+ * Determines if the item type is valid
10
+ * @param {string|ItemType} itemType The item type to check
11
+ * @returns {boolean} Whether the item type is valid
12
+ */
13
+ export function isItemType(itemType) {
14
+ return ItemTypes.includes(itemType);
15
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Determines if the given item is a movie
3
+ * @param {Item} item The item to check
4
+ * @returns {boolean} True if the item is a movie, false otherwise
5
+ */
6
+ export function isMovie(item) {
7
+ return item.type === 'movie';
8
+ }
@@ -0,0 +1,9 @@
1
+ export const SeriesStatuses = ['ongoing', 'completed', 'cancelled'];
2
+ /**
3
+ * Determines if the given item is a series
4
+ * @param {Item} item The item to check
5
+ * @returns {boolean} True if the item is a series, false otherwise
6
+ */
7
+ export function isSeries(item) {
8
+ return item.type === 'series';
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ /**
2
+ * A base validator providing common validation logic.
3
+ */
4
+ export class BaseValidator {
5
+ /**
6
+ * @inheritdoc
7
+ */
8
+ validate(item, ignoredFields) {
9
+ const errors = this._validateFields(item);
10
+ const filteredErrors = this.__filterIgnoredFields(errors, ignoredFields);
11
+ return this.__createValidationResult(filteredErrors);
12
+ }
13
+ /**
14
+ * Filters out errors for ignored fields.
15
+ * @template T The type of the item being validated.
16
+ * @param {Record<string, string>} errors The errors to filter.
17
+ * @param {Array<keyof T>} ignoredFields Fields to ignore during validation.
18
+ * @returns {Record<string, string>} The filtered errors.
19
+ */
20
+ __filterIgnoredFields(errors, ignoredFields) {
21
+ if (!ignoredFields || ignoredFields.length === 0) {
22
+ return errors;
23
+ }
24
+ const filteredErrors = {};
25
+ for (const [field, error] of Object.entries(errors)) {
26
+ if (!ignoredFields.includes(field)) {
27
+ filteredErrors[field] = error;
28
+ }
29
+ }
30
+ return filteredErrors;
31
+ }
32
+ /**
33
+ * Creates a validation result from errors.
34
+ * @param {Record<string, string>} errors The validation errors.
35
+ * @returns {IValidationResult} The validation result.
36
+ */
37
+ __createValidationResult(errors) {
38
+ return {
39
+ valid: Object.keys(errors).length === 0,
40
+ errors
41
+ };
42
+ }
43
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ /**
2
+ * A registry for managing validators.
3
+ */
4
+ export class ValidatorRegistry {
5
+ validators = new Map();
6
+ /**
7
+ * Retrieves a validator.
8
+ * @param {string} key The key of the validator to retrieve.
9
+ * @param {IValidator<T>} validator The validator to register.
10
+ * @returns {void}
11
+ */
12
+ register(key, validator) {
13
+ this.validators.set(key, validator);
14
+ }
15
+ /**
16
+ * Validates an item using a registered validator.
17
+ * @param {string} key The key of the validator to use.
18
+ * @param {Partial<T>} item The item to validate.
19
+ * @param {Array<keyof T>=} ignoredFields Fields to ignore during validation.
20
+ * @returns {IValidationResult} The result of the validation.
21
+ */
22
+ validate(key, item, ignoredFields) {
23
+ const validator = this.validators.get(key);
24
+ if (!validator) {
25
+ throw new Error(`Validator with key ${key} not found.`);
26
+ }
27
+ return validator.validate(item, ignoredFields);
28
+ }
29
+ }
@@ -0,0 +1,41 @@
1
+ import { ItemTypes } from '../../types/items/ItemType';
2
+ import { BaseValidator } from '../BaseValidator';
3
+ /**
4
+ * A base validator for items, providing common validation logic.
5
+ */
6
+ export class BaseItemValidator extends BaseValidator {
7
+ /**
8
+ * @inheritdoc
9
+ */
10
+ _validateFields(item) {
11
+ const baseResult = this._validateBaseFields(item);
12
+ const typeResult = this._validateTypeSpecificFields(item);
13
+ return { ...baseResult, ...typeResult };
14
+ }
15
+ /**
16
+ * Validates common item fields that apply to all item types.
17
+ * @param {Partial<T>} item The item to validate.
18
+ * @returns {Record<string, string>} A record of validation errors.
19
+ */
20
+ _validateBaseFields(item) {
21
+ const { id, type, name, releaseYear } = item;
22
+ const errors = {};
23
+ if (!id) {
24
+ errors.id = 'Item id is required';
25
+ }
26
+ if (!type) {
27
+ errors.type = 'Item type is required';
28
+ }
29
+ if (type && !ItemTypes.includes(type)) {
30
+ errors.type = `Item type must be one of: ${ItemTypes.join(', ')}`;
31
+ }
32
+ if (!name) {
33
+ errors.name = 'Item name is required';
34
+ }
35
+ const currentYear = new Date().getFullYear();
36
+ if (releaseYear && (releaseYear < 1800 || releaseYear > currentYear)) {
37
+ errors.releaseYear = `Release year must be between 1800 and ${currentYear}`;
38
+ }
39
+ return errors;
40
+ }
41
+ }
@@ -0,0 +1,27 @@
1
+ import { BookGenres } from '../../types/items/Genre';
2
+ import { BaseItemValidator } from './BaseItemValidator';
3
+ /**
4
+ * A validator for book items.
5
+ */
6
+ export class BookValidator extends BaseItemValidator {
7
+ /**
8
+ * @inheritdoc
9
+ */
10
+ _validateTypeSpecificFields(book) {
11
+ const { genres, author, publisher } = book;
12
+ const errors = {};
13
+ if (!genres?.length) {
14
+ errors.genres = 'Book genres are required';
15
+ }
16
+ if (!genres?.every((genre) => !BookGenres.includes(genre))) {
17
+ errors.genres = `One or more book genres are invalid. Valid genres are: ${BookGenres.join(', ')}`;
18
+ }
19
+ if (!author) {
20
+ errors.author = 'Book author is required';
21
+ }
22
+ if (!publisher) {
23
+ errors.publisher = 'Book publisher is required';
24
+ }
25
+ return errors;
26
+ }
27
+ }
@@ -0,0 +1,30 @@
1
+ import { GameGenres } from '../../types/items/Genre';
2
+ import { BaseItemValidator } from './BaseItemValidator';
3
+ /**
4
+ * A validator for game items.
5
+ */
6
+ export class GameValidator extends BaseItemValidator {
7
+ /**
8
+ * @inheritdoc
9
+ */
10
+ _validateTypeSpecificFields(game) {
11
+ const { genres, platforms, developer, publisher } = game;
12
+ const errors = {};
13
+ if (!genres?.length) {
14
+ errors.genres = 'Game genres are required';
15
+ }
16
+ if (!genres?.every((genre) => !GameGenres.includes(genre))) {
17
+ errors.genres = `One or more game genres are invalid. Valid genres are: ${GameGenres.join(', ')}`;
18
+ }
19
+ if (!platforms?.length) {
20
+ errors.platforms = 'Game platforms are required';
21
+ }
22
+ if (!developer) {
23
+ errors.developer = 'Game developer is required';
24
+ }
25
+ if (!publisher) {
26
+ errors.publisher = 'Game publisher is required';
27
+ }
28
+ return errors;
29
+ }
30
+ }
@@ -0,0 +1,10 @@
1
+ import { ValidatorRegistry } from '../ValidatorRegistry';
2
+ import { BookValidator } from './BookValidator';
3
+ import { GameValidator } from './GameValidator';
4
+ import { MovieValidator } from './MovieValidator';
5
+ import { SeriesValidator } from './SeriesValidator';
6
+ export const ItemValidatorRegistry = new ValidatorRegistry();
7
+ ItemValidatorRegistry.register('movie', new MovieValidator());
8
+ ItemValidatorRegistry.register('series', new SeriesValidator());
9
+ ItemValidatorRegistry.register('game', new GameValidator());
10
+ ItemValidatorRegistry.register('book', new BookValidator());
@@ -0,0 +1,33 @@
1
+ import { VideoGenres } from '../../types/items/Genre';
2
+ import { BaseItemValidator } from './BaseItemValidator';
3
+ /**
4
+ * A validator for movie items.
5
+ */
6
+ export class MovieValidator extends BaseItemValidator {
7
+ /**
8
+ * @inheritdoc
9
+ */
10
+ _validateTypeSpecificFields(movie) {
11
+ const { genres, director, duration, cast } = movie;
12
+ const errors = {};
13
+ if (!genres?.length) {
14
+ errors.genres = 'Movie genres are required';
15
+ }
16
+ if (!genres?.every((genre) => !VideoGenres.includes(genre))) {
17
+ errors.genres = `One or more movie genres are invalid. Valid genres are: ${VideoGenres.join(', ')}`;
18
+ }
19
+ if (duration === undefined) {
20
+ errors.duration = 'Movie duration is required';
21
+ }
22
+ if (duration !== undefined && duration <= 0) {
23
+ errors.duration = 'Movie duration is invalid. Duration must be a positive number.';
24
+ }
25
+ if (!director) {
26
+ errors.director = 'Movie director is required';
27
+ }
28
+ if (!cast?.length) {
29
+ errors.cast = 'Movie cast is required';
30
+ }
31
+ return errors;
32
+ }
33
+ }
@@ -0,0 +1,40 @@
1
+ import { VideoGenres } from '../../types/items/Genre';
2
+ import { SeriesStatuses } from '../../types/items/Series';
3
+ import { BaseItemValidator } from './BaseItemValidator';
4
+ /**
5
+ * A validator for series items.
6
+ */
7
+ export class SeriesValidator extends BaseItemValidator {
8
+ /**
9
+ * @inheritdoc
10
+ */
11
+ _validateTypeSpecificFields(series) {
12
+ const { genres, status, seasons, creator, cast } = series;
13
+ const errors = {};
14
+ if (!genres?.length) {
15
+ errors.genres = 'Series genres are required';
16
+ }
17
+ if (!genres?.every((genre) => !VideoGenres.includes(genre))) {
18
+ errors.genres = `One or more series genres are invalid. Valid genres are: ${VideoGenres.join(', ')}`;
19
+ }
20
+ if (!status) {
21
+ errors.status = 'Series status is required';
22
+ }
23
+ if (status && !SeriesStatuses.includes(status)) {
24
+ errors.status = `Series status is invalid. Valid statuses are: ${SeriesStatuses.join(', ')}`;
25
+ }
26
+ if (seasons === undefined) {
27
+ errors.seasons = 'Series seasons is required';
28
+ }
29
+ if (seasons !== undefined && seasons <= 0) {
30
+ errors.seasons = 'Series seasons is invalid. Seasons must be a positive number.';
31
+ }
32
+ if (!creator) {
33
+ errors.creator = 'Series creator is required';
34
+ }
35
+ if (!cast?.length) {
36
+ errors.cast = 'Series cast is required';
37
+ }
38
+ return errors;
39
+ }
40
+ }
@@ -0,0 +1,35 @@
1
+ import { BaseValidator } from '../BaseValidator';
2
+ /**
3
+ * A validator for item leaderboardItems.
4
+ */
5
+ export class LeaderboardItemValidator extends BaseValidator {
6
+ /**
7
+ * @inheritdoc
8
+ */
9
+ _validateFields(leaderboardItem) {
10
+ const { id, itemId, rank, rating, numberOfRatings } = leaderboardItem;
11
+ const errors = {};
12
+ if (!id) {
13
+ errors.id = 'Rating id is required';
14
+ }
15
+ if (!itemId) {
16
+ errors.itemId = 'Item id is required for the leaderboardItem';
17
+ }
18
+ if (rank === undefined || rank === null) {
19
+ errors.rank = 'LeaderboardItem rank is required';
20
+ }
21
+ if (rating === undefined || rating === null) {
22
+ errors.rating = 'LeaderboardItem rating is required';
23
+ }
24
+ if (rating && rating < 1) {
25
+ errors.rating = 'LeaderboardItem rating must be a positive number';
26
+ }
27
+ if (numberOfRatings === undefined || numberOfRatings === null) {
28
+ errors.rating = 'Number of ratings is required';
29
+ }
30
+ if (numberOfRatings && numberOfRatings < 1) {
31
+ errors.rating = 'Number of ratings must be a positive number';
32
+ }
33
+ return errors;
34
+ }
35
+ }
@@ -0,0 +1,30 @@
1
+ import { ItemTypes } from '../../types/items/ItemType';
2
+ import { BaseValidator } from '../BaseValidator';
3
+ /**
4
+ * A validator for item leaderboardSnapshots.
5
+ */
6
+ export class LeaderboardSnapshotValidator extends BaseValidator {
7
+ /**
8
+ * @inheritdoc
9
+ */
10
+ _validateFields(leaderboardSnapshot) {
11
+ const { id, itemType, leaderboardItems, date } = leaderboardSnapshot;
12
+ const errors = {};
13
+ if (!id) {
14
+ errors.id = 'Rating id is required';
15
+ }
16
+ if (itemType === undefined || itemType === null) {
17
+ errors.itemType = 'LeaderboardSnapshot itemType is required';
18
+ }
19
+ if (itemType && !ItemTypes.includes(itemType)) {
20
+ errors.itemType = `LeaderboardSnapshot itemType must be one of: ${ItemTypes.join(', ')}`;
21
+ }
22
+ if (!leaderboardItems) {
23
+ errors.leaderboardItems = 'Leaderboard items are required';
24
+ }
25
+ if (!date) {
26
+ errors.date = 'Rating date is required';
27
+ }
28
+ return errors;
29
+ }
30
+ }
@@ -0,0 +1,32 @@
1
+ import { BaseValidator } from '../BaseValidator';
2
+ /**
3
+ * A validator for item ratings.
4
+ */
5
+ export class RatingValidator extends BaseValidator {
6
+ /**
7
+ * @inheritdoc
8
+ */
9
+ _validateFields(rating) {
10
+ const { id, value, itemId, userId, date } = rating;
11
+ const errors = {};
12
+ if (!id) {
13
+ errors.id = 'Rating id is required';
14
+ }
15
+ if (value === undefined || value === null) {
16
+ errors.value = 'Rating value is required';
17
+ }
18
+ if (value && (value < 1 || value > 100)) {
19
+ errors.value = 'Rating value must be between 1 and 100 (inclusive)';
20
+ }
21
+ if (!itemId) {
22
+ errors.itemId = 'Item id is required for the rating';
23
+ }
24
+ if (!userId) {
25
+ errors.userId = 'User id is required for the rating';
26
+ }
27
+ if (!date) {
28
+ errors.date = 'Rating date is required';
29
+ }
30
+ return errors;
31
+ }
32
+ }
package/package.json CHANGED
@@ -1,19 +1,18 @@
1
1
  {
2
2
  "name": "@opinio-dev/models",
3
- "version": "1.1.1-item-types.5",
3
+ "version": "1.1.1-item-types.6",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
7
7
  ],
8
8
  "exports": {
9
- "./types/*": "./dist/types/*.d.ts",
10
- "./validation/*": "./dist/validation/*.js"
9
+ ".": "./dist"
11
10
  },
12
11
  "scripts": {
13
12
  "lint": "eslint .",
14
13
  "format": "prettier --write .",
15
14
  "format:check": "prettier --check .",
16
- "build": "rimraf dist && node build.js && tsc",
15
+ "build": "rimraf dist && tsc",
17
16
  "build:ts": "tsc --noEmit",
18
17
  "publish": "npm run build && npm publish --access public --ignore-scripts",
19
18
  "prepare": "husky"
@@ -23,7 +22,6 @@
23
22
  "description": "",
24
23
  "devDependencies": {
25
24
  "@eslint/js": "^9.33.0",
26
- "esbuild": "^0.27.0",
27
25
  "eslint": "^9.33.0",
28
26
  "eslint-config-prettier": "^10.1.8",
29
27
  "eslint-plugin-import": "^2.32.0",