diff --git a/CHANGELOG.md b/CHANGELOG.md index 807c020..b506ac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # drizzle-toolbelt +## 1.2.0 + +### Minor Changes + +- Add `transform` method with data-first and data-last overloads + ## 1.1.1 ### Patch Changes diff --git a/package.json b/package.json index 23bb1aa..3d9c5b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-toolbelt", - "version": "1.1.1", + "version": "1.2.0", "description": "Set of tools for drizzle-orm.", "scripts": { "build": "tsup --dts --dts-resolve", diff --git a/src/aggregate.ts b/src/aggregate.ts index dfec3b3..5a5d2f4 100644 --- a/src/aggregate.ts +++ b/src/aggregate.ts @@ -1,4 +1,5 @@ import get from 'lodash/get'; +import type { Prettify } from './utils'; /** More performant version of Object.values */ function extractValues(obj: T) { @@ -9,8 +10,6 @@ function extractValues(obj: T) { return values; } -// eslint-disable-next-line @typescript-eslint/ban-types -export type Prettify = { [K in keyof T]: T[K] } & {}; export type FlatKey> = { [K in keyof T]: NonNullable extends PropertyKey ? K diff --git a/src/index.ts b/src/index.ts index ec61a0c..bb42845 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export { aggregate } from './aggregate'; export { takeFirst, takeFirstOrThrow } from './take'; +export { transform } from './transform'; diff --git a/src/transform.test.ts b/src/transform.test.ts new file mode 100644 index 0000000..5543ef2 --- /dev/null +++ b/src/transform.test.ts @@ -0,0 +1,91 @@ +import { describe, test, expect } from 'bun:test'; +import { transform } from './transform'; +import { aggregate } from './aggregate'; + +describe('transform', () => { + test('data-first', async () => { + const rows = [ + { id: 1, name: 'salem', age: 8, post: { id: 1, title: '1' } }, + { id: 1, name: 'salem', age: 8, post: { id: 4, title: '4' } }, + { id: 2, name: 'mimo', age: 7, post: { id: 2, title: '2' } }, + { id: 3, name: 'tapi', age: 6, post: { id: 3, title: '3' } }, + ]; + + const transformed = transform({ + rows, + fields: { + name: (row) => row.name.toUpperCase(), + post: (row) => ({ id: row.post.id, title: +row.post.title }), + }, + }); + + expect(transformed).toEqual([ + { id: 1, name: 'SALEM', age: 8, post: { id: 1, title: 1 } }, + { id: 1, name: 'SALEM', age: 8, post: { id: 4, title: 4 } }, + { id: 2, name: 'MIMO', age: 7, post: { id: 2, title: 2 } }, + { id: 3, name: 'TAPI', age: 6, post: { id: 3, title: 3 } }, + ]); + }); + + test('data-last', async () => { + const getRows = async () => [ + { id: 1, name: 'salem', age: 8, post: { id: 1, title: '1' } }, + { id: 1, name: 'salem', age: 8, post: { id: 4, title: '4' } }, + { id: 2, name: 'mimo', age: 7, post: { id: 2, title: '2' } }, + { id: 3, name: 'tapi', age: 6, post: { id: 3, title: '3' } }, + ]; + + const transformed = await getRows().then( + transform({ + fields: { + age: (row) => row.age + 1, + }, + }), + ); + + expect(transformed).toEqual([ + { id: 1, name: 'salem', age: 9, post: { id: 1, title: '1' } }, + { id: 1, name: 'salem', age: 9, post: { id: 4, title: '4' } }, + { id: 2, name: 'mimo', age: 8, post: { id: 2, title: '2' } }, + { id: 3, name: 'tapi', age: 7, post: { id: 3, title: '3' } }, + ]); + }); + + test('with aggregate', async () => { + const getRows = async () => [ + { id: 1, name: 'salem', age: 8, post: { id: 1, title: '1' } }, + { id: 1, name: 'salem', age: 8, post: { id: 4, title: '4' } }, + { id: 2, name: 'mimo', age: 7, post: { id: 2, title: '2' } }, + { id: 3, name: 'tapi', age: 6, post: { id: 3, title: '3' } }, + ]; + + const transformed = await getRows() + .then( + aggregate({ + pkey: 'id', + fields: { posts: 'post.id' }, + }), + ) + .then( + transform({ + fields: { + age: (row) => row.age + 1, + }, + }), + ); + + expect(transformed).toEqual([ + { + id: 1, + name: 'salem', + age: 9, + posts: [ + { id: 1, title: '1' }, + { id: 4, title: '4' }, + ], + }, + { id: 2, name: 'mimo', age: 8, posts: [{ id: 2, title: '2' }] }, + { id: 3, name: 'tapi', age: 7, posts: [{ id: 3, title: '3' }] }, + ]); + }); +}); diff --git a/src/transform.ts b/src/transform.ts new file mode 100644 index 0000000..6259d25 --- /dev/null +++ b/src/transform.ts @@ -0,0 +1,44 @@ +import type { Prettify } from './utils'; + +export function transform< + TRow extends Record, + TTransform extends Partial<{ [K in keyof TRow]: (row: TRow) => any }>, +>(params: { + rows: TRow[]; + fields: TTransform; +}): Prettify< + Omit & { + [K in keyof TTransform]: TTransform[K] extends (params: any) => infer Ret ? Ret : never; + } +>[]; + +export function transform< + TRow extends Record, + TTransform extends Partial<{ [K in keyof TRow]: (row: TRow) => any }>, +>(params: { + fields: TTransform; +}): (rows: TRow[]) => Prettify< + Omit & { + [K in keyof TTransform]: TTransform[K] extends (params: any) => infer Ret ? Ret : never; + } +>[]; + +export function transform< + TRow extends Record, + TTransform extends Partial<{ [K in keyof TRow]: (row: TRow) => any }>, +>(params: { + rows?: TRow[]; + fields: TTransform; +}) { + // if rows is missing, build a data-last function + if (params.rows === undefined) return (rows: TRow[]) => transform({ rows, ...params }); + + for (const row of params.rows) { + for (const key in params.fields) { + const transform = params.fields[key]; + row[key] = transform?.(row); + } + } + + return params.rows; +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..259e8fb --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,3 @@ +export type Prettify = { + [K in keyof T]: T[K]; +} & {};