From 8cd6e7337a60fd61f16ccfc2d64f74d9acfa41b5 Mon Sep 17 00:00:00 2001 From: jlenon7 Date: Mon, 6 Oct 2025 16:43:04 -0300 Subject: [PATCH] feat(helpers): add new helpers for resource class --- package-lock.json | 12 +-- package.json | 4 +- src/resource/BaseResource.ts | 127 +++++++++++++++++++++--- tests/unit/resource/BaseResourceTest.ts | 92 +++++++++++++++++ 4 files changed, 211 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5f9094b..3dc0ddd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "@athenna/resource", - "version": "5.2.0", + "version": "5.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@athenna/resource", - "version": "5.2.0", + "version": "5.3.0", "license": "MIT", "devDependencies": { "@athenna/artisan": "^5.7.0", - "@athenna/common": "^5.17.0", + "@athenna/common": "^5.23.0", "@athenna/config": "^5.4.0", "@athenna/ioc": "^5.2.0", "@athenna/logger": "^5.10.0", @@ -571,9 +571,9 @@ } }, "node_modules/@athenna/common": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@athenna/common/-/common-5.17.0.tgz", - "integrity": "sha512-j/0N/8xfXOIZ6508hcvSvF2mzmWa83t4ZFb3hyHW5lM+0eYkztu3a1a9T5ySC87FE9bEWsSKHGDY4blYaTSAcg==", + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/@athenna/common/-/common-5.23.0.tgz", + "integrity": "sha512-7tP6/YY8WuErhjIemytJIYcQkOJv1EftEEnC67SaScvMhQex19X6gRjp6+ow4SEtVqkg4rmpxMSSMXj2uCoPlw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 8461446..5b95161 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@athenna/resource", - "version": "5.2.0", + "version": "5.3.0", "description": "Add transformation layers between your application data transfer.", "license": "MIT", "author": "João Lenon ", @@ -53,7 +53,7 @@ }, "devDependencies": { "@athenna/artisan": "^5.7.0", - "@athenna/common": "^5.17.0", + "@athenna/common": "^5.23.0", "@athenna/config": "^5.4.0", "@athenna/ioc": "^5.2.0", "@athenna/logger": "^5.10.0", diff --git a/src/resource/BaseResource.ts b/src/resource/BaseResource.ts index cf96bbe..f1f046e 100644 --- a/src/resource/BaseResource.ts +++ b/src/resource/BaseResource.ts @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import { Is } from '@athenna/common' +import { Is, Json } from '@athenna/common' export abstract class BaseResource { /** @@ -19,6 +19,12 @@ export abstract class BaseResource { // TODO Convert to symbol private items: I[] + private defaultValues: { + key: string + value?: any + closure?: (item?: I) => any + }[] + /** * Indicates if the item received in the constructor * was an array of not. This is important to validate @@ -28,6 +34,7 @@ export abstract class BaseResource { private readonly isArray: boolean public constructor(data: I | I[]) { + this.defaultValues = [] this.isArray = Is.Array(data) this.items = this.isArray ? (data as I[]) : [data as I] } @@ -38,35 +45,123 @@ export abstract class BaseResource { */ protected abstract schema(item: I): R - public toJSON(): R[] - public toJSON(): R - /** * Transform the data of the resource to a valid JSON. + * + * @example + * ```ts + * const items = [...] + * const json = new MyResource(items).toJSON() + * ``` + */ + public toJSON(): T { + const transformed = this.items.map(item => { + const json = this.schema(item) + + if (this.defaultValues.length) { + Object.keys(json).forEach(key => { + const defaultValue = this.defaultValues.find(v => v.key === key) + + if (!defaultValue) { + return + } + + if (defaultValue.closure) { + Json.set(json, key, defaultValue.closure(item)) + + return + } + + Json.set(json, key, defaultValue.value) + }) + } + + return json + }) + + return this.isArray ? (transformed as any) : (transformed[0] as any as T) + } + + /** + * Get values from a key defined inside the items of the resource. + * Items are the raw value set for the resource used to build the + * JSON structure. + * + * @example + * ```ts + * const items = [{ hello: { world: 'hello' }}] + * const hellos = new MyResource(items).getFromItem('hello.world') // ['hello'] + * ``` */ - public toJSON(): R | R[] { - const transformed = this.items.map(item => this.schema(item)) + public getFromItem(key: string): T { + const values = this.items.map(item => Json.get(item, key)) - return this.isArray ? transformed : transformed[0] + return this.isArray ? (values as any) : (values[0] as any as T) } /** - * Change the value of an item inside your resource. - * If your resource is an array, all keys in the array will - * be changed. + * Get values from a key defined inside the JSON of the resource. + * The JSON of the resource are the items parsed using the structure + * you have defined in the `schema()` method. + * + * @example + * ```ts + * const items = [{ hello: { world: 'hello' }}] + * const hellos = new MyResource(items).getFromJson('schemaHello') // ['hello'] + * ``` */ - public setValue(key: string, value: any) { + public getFromJson(key: string): T { + const json = this.toJSON() + + if (!json) { + return undefined + } + if (this.isArray) { - this.items = this.items.map(item => { - item[key] = value + return json.map(json => Json.get(json, key)) + } - return item - }) + return Json.get(json, key) + } + + /** + * Set a value that will overwritte the original value defined in + * your original item structure. This method is useful when you let + * your framework manually call the `toJSON()` method and want to + * transform. + * + * @example + * ```ts + * const items = [{ hello: { world: 'hello' }}] + * new MyResource(items).setInItem('hello.world', 'hello2') + * ``` + */ + public setInItem(key: string, value: any) { + this.items.map(item => Json.set(item, key, value)) + + return this + } + + /** + * Set a value that will overwritte the original value created by + * your original structure defined in your `schema()` method. This + * method is useful when you want to change the value directly from + * your JSON structure. + * + * @example + * ```ts + * const items = [{ hello: { world: 'hello' }}] + * new MyResource(items).setInJson('schemaHello', 'hello2') + * ``` + */ + public setInJson(key: string, value: any) { + if (Is.Function(value)) { + this.defaultValues.push({ key, closure: value }) return this } - this.items[key] = value + this.defaultValues.push({ key, value }) return this } diff --git a/tests/unit/resource/BaseResourceTest.ts b/tests/unit/resource/BaseResourceTest.ts index b82e310..5beea5e 100644 --- a/tests/unit/resource/BaseResourceTest.ts +++ b/tests/unit/resource/BaseResourceTest.ts @@ -60,4 +60,96 @@ export default class BaseResourceTest { } ]) } + + @Test() + public async shouldBeAbleToChangeItemValuesFromResourceClass({ assert }: Context) { + const item = { + _id: '1', + _name: 'John Doe', + _email: 'john.doe@example.com' + } + + const resource = new UserResource(item) + + resource.setInItem('_id', 2) + + assert.instanceOf(resource, UserResource) + assert.deepEqual(resource.toJSON(), { + id: 2, + name: 'John Doe', + email: 'john.doe@example.com' + }) + } + + @Test() + public async shouldBeAbleToGetItemValuesFromResourceClass({ assert }: Context) { + const item = { + _id: '1', + _name: 'John Doe', + _email: 'john.doe@example.com' + } + + const resource = new UserResource(item) + + assert.deepEqual(resource.getFromItem('_id'), '1') + } + + @Test() + public async shouldReturnUndefinedIfCannotFindValueInsideItemFromResourceClass({ assert }: Context) { + const item = { + _id: '1', + _name: 'John Doe', + _email: 'john.doe@example.com' + } + + const resource = new UserResource(item) + + assert.deepEqual(resource.getFromItem('__id'), undefined) + } + + @Test() + public async shouldBeAbleToChangeJsonValuesFromResourceClass({ assert }: Context) { + const item = { + _id: '1', + _name: 'John Doe', + _email: 'john.doe@example.com' + } + + const resource = new UserResource(item) + + resource.setInJson('id', 2) + + assert.instanceOf(resource, UserResource) + assert.deepEqual(resource.toJSON(), { + id: 2, + name: 'John Doe', + email: 'john.doe@example.com' + }) + } + + @Test() + public async shouldBeAbleToGetJsonValuesFromResourceClass({ assert }: Context) { + const item = { + _id: '1', + _name: 'John Doe', + _email: 'john.doe@example.com' + } + + const resource = new UserResource(item) + + assert.deepEqual(resource.getFromJson('id'), '1') + } + + @Test() + public async shouldReturnUndefinedIfCannotFindValueInsideJsonFromResourceClass({ assert }: Context) { + const item = { + _id: '1', + _name: 'John Doe', + _email: 'john.doe@example.com' + } + + const resource = new UserResource(item) + + assert.deepEqual(resource.getFromJson('_id'), undefined) + } }