Skip to content

Commit 9923e63

Browse files
relation mapper in create as well with jsdocs
1 parent 8497a0d commit 9923e63

File tree

2 files changed

+104
-23
lines changed

2 files changed

+104
-23
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@januscaler/tsed-helper",
3-
"version": "2.3.2",
3+
"version": "2.3.3",
44
"type": "module",
55
"publishConfig": {
66
"@januscaler:registry": "https://npm.pkg.github.com"

src/baseService.ts

Lines changed: 103 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ export interface IBaseService<M> {
1414
onPreCreate?: Subject<{ data: M }>
1515
onPostCreate: Subject<{ data: M, result: any }>
1616
}
17+
export type RelationMapper = {
18+
19+
relationvalueMapper?: (fieldName: string, value: any) => any
20+
}
21+
22+
export interface CreateRelationMapper extends RelationMapper {
23+
relationOperation?: 'connect' | 'connectOrCreate' | 'create' | 'createMany'
24+
}
25+
26+
export interface UpdateRelationMapper extends RelationMapper {
27+
relationOperation?: 'set' | 'disconnect' | 'delete' | 'connect' | 'disconnectMany' | 'deleteMany' | 'create' | 'createMany' | 'update' | 'updateMany' | 'upsert' | 'upsertMany'
28+
}
1729

1830
@Generics("T", "M")
1931
export class BaseService<T, M> implements OnInit, IBaseService<M> {
@@ -90,9 +102,97 @@ export class BaseService<T, M> implements OnInit, IBaseService<M> {
90102
this.tablesInfo = await prismaMapper.getTablesInfo()
91103
}
92104

93-
async create(data: M) {
105+
/**
106+
* Updates the current Prisma model record and optionally rewrites relation fields in one call.
107+
*
108+
* @param id Record identifier passed to the Prisma `where` clause.
109+
* @param data Plain object that can mix scalar fields (written directly) and relation fields (handled via Prisma relation operations).
110+
* @param options.relationOperation Prisma relation operation (`set`, `connect`, `disconnectMany`, etc.) applied to every relation key found in `data`. Defaults to `set`.
111+
* @param options.relationvalueMapper Optional transformer that receives each relation value from `data` before it is sent to Prisma. Use it to build compound keys, wrap payloads, or handle `connectOrCreate` inputs.
112+
*
113+
* @example
114+
* // Replace the existing tags with two tag ids while mapping the ids to Prisma connect objects.
115+
* await service.update(42, { title: 'Draft', tags: [1, 2] }, {
116+
* relationOperation: 'set',
117+
* relationvalueMapper: (ids) => ids.map((id) => ({ id })),
118+
* });
119+
*
120+
* @example
121+
* // Append comments by turning each comment payload into a Prisma create object.
122+
* await service.update(42, { comments: [{ text: 'Nice!' }] }, {
123+
* relationOperation: 'createMany',
124+
* relationvalueMapper: (comments) => ({ data: comments }),
125+
* });
126+
*/
127+
async update(id: number, data: any, { relationOperation, relationvalueMapper }: UpdateRelationMapper = { relationOperation: 'set' }) {
128+
const defaultRelationValueMapper = (value: any) => (_.isArray(value) ? _.map(value, (id: any) => ({ id })) : { id: value })
129+
const dataWithRelations = _.pick(data, this.fieldNames)
130+
const relationData = _.omit(data, this.fieldNames)
131+
const finalData = _.transform(relationData, (result, value, key) => {
132+
if (_.isNil(value)) {
133+
return
134+
}
135+
if (!relationvalueMapper) {
136+
result[key] = { [relationOperation]: defaultRelationValueMapper(value) }
137+
return;
138+
}
139+
const relationvalue = relationvalueMapper(key, value)
140+
if (_.isNil(relationvalue)) {
141+
return
142+
}
143+
result[key] = { [relationOperation]: relationvalue }
144+
}, {
145+
...dataWithRelations
146+
})
147+
this.onPreUpdate?.next({ id, inputData: data });
148+
const result = await (this.repository as any).update({ where: { id }, data: finalData });
149+
this.onPostUpdate.next({ id, inputData: data, result });
150+
return result;
151+
}
152+
153+
/**
154+
* Creates a Prisma model record while optionally performing relation writes in the same call.
155+
*
156+
* @param data Plain object that can mix scalar columns and relation fields destined for Prisma create operations.
157+
* @param options.relationOperation Prisma relation operation (`connect`, `connectOrCreate`, `create`, `createMany`) applied to every relation key from `data`. Defaults to `connect`.
158+
* @param options.relationvalueMapper Optional transformer invoked per relation value from `data` before passing it to Prisma. Ideal for wrapping ids in `connect` objects or preparing nested `create` payloads.
159+
*
160+
* @example
161+
* // Create a post and connect it with existing tag ids.
162+
* await service.create({ title: 'Draft', tags: [1, 2] }, {
163+
* relationOperation: 'connect',
164+
* relationvalueMapper: (field,ids) => ids.map((id) => ({ id })),
165+
* });
166+
*
167+
* @example
168+
* // Create a post with newly created comments in one transaction.
169+
* await service.create({ title: 'Draft', comments: [{ text: 'Nice!' }] }, {
170+
* relationOperation: 'createMany',
171+
* relationvalueMapper: (field,comments) => ({ data: comments }),
172+
* });
173+
*/
174+
async create(data: M, { relationOperation, relationvalueMapper }: CreateRelationMapper = { relationOperation: 'connect' }) {
94175
this.onPreCreate?.next({ data });
95-
const result = await (this.repository as any).create({ data });
176+
const defaultRelationValueMapper = (value: any) => (_.isArray(value) ? _.map(value, (id: any) => ({ id })) : { id: value })
177+
const dataWithRelations = _.pick(data, this.fieldNames)
178+
const relationData = _.omit(data as any, this.fieldNames)
179+
const finalData = _.transform(relationData, (result, value, key) => {
180+
if (_.isNil(value)) {
181+
return
182+
}
183+
if (!relationvalueMapper) {
184+
result[key] = { [relationOperation]: defaultRelationValueMapper(value) }
185+
return;
186+
}
187+
const relationvalue = relationvalueMapper(key, value)
188+
if (_.isNil(relationvalue)) {
189+
return
190+
}
191+
result[key] = { [relationOperation]: relationvalue }
192+
}, {
193+
...dataWithRelations
194+
})
195+
const result = await (this.repository as any).create({ data: finalData });
96196
this.onPostCreate.next({ data, result });
97197
return result;
98198
}
@@ -108,26 +208,7 @@ export class BaseService<T, M> implements OnInit, IBaseService<M> {
108208
return (await (this.repository as any).findFirst({ where: { id } })) || {};
109209
}
110210

111-
async update(id: number, data: any, { relationOperation, relationvalueMapper }: {
112-
relationOperation?: 'set' | 'disconnect' | 'delete' | 'connect' | 'disconnectMany' | 'deleteMany' | 'create' | 'createMany' | 'update' | 'updateMany' | 'upsert' | 'upsertMany'
113-
relationvalueMapper?: (value: any) => any
114-
} = {}) {
115-
const defaultRelationValueMapper = (value: any) => (_.isArray(value) ? _.map(value, (id: any) => ({ id })) : { id: value })
116-
const dataWithRelations = _.pick(data, this.fieldNames)
117-
const relationData = _.omit(data, this.fieldNames)
118-
const finalData = _.transform(relationData, (result, value, key) => {
119-
if (_.isNil(value)) {
120-
return
121-
}
122-
result[key] = { [relationOperation ?? 'set']: relationvalueMapper ? relationvalueMapper(value) : defaultRelationValueMapper(value) }
123-
}, {
124-
...dataWithRelations
125-
})
126-
this.onPreUpdate?.next({ id, inputData: data });
127-
const result = await (this.repository as any).update({ where: { id }, data: finalData });
128-
this.onPostUpdate.next({ id, inputData: data, result });
129-
return result;
130-
}
211+
131212

132213
readonly MODES = {
133214
EQ: 'EQ',

0 commit comments

Comments
 (0)