This repository was archived by the owner on Mar 1, 2026. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathresult-processor.ts
More file actions
154 lines (135 loc) · 5.57 KB
/
result-processor.ts
File metadata and controls
154 lines (135 loc) · 5.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import type { BuiltinType, FieldDef, GetModels, SchemaDef } from '../schema';
import { DELEGATE_JOINED_FIELD_PREFIX } from './constants';
import { getCrudDialect } from './crud/dialects';
import type { BaseCrudDialect } from './crud/dialects/base-dialect';
import type { ClientOptions } from './options';
import { ensureArray, getField, getIdValues } from './query-utils';
export class ResultProcessor<Schema extends SchemaDef> {
private dialect: BaseCrudDialect<Schema>;
constructor(
private readonly schema: Schema,
options: ClientOptions<Schema>,
) {
this.dialect = getCrudDialect(schema, options);
}
processResult(data: any, model: GetModels<Schema>, args?: any) {
const result = this.doProcessResult(data, model);
// deal with correcting the reversed order due to negative take
this.fixReversedResult(result, model, args);
return result;
}
private doProcessResult(data: any, model: GetModels<Schema>) {
if (Array.isArray(data)) {
data.forEach((row, i) => (data[i] = this.processRow(row, model)));
return data;
} else {
return this.processRow(data, model);
}
}
private processRow(data: any, model: GetModels<Schema>) {
if (!data || typeof data !== 'object') {
return data;
}
for (const [key, value] of Object.entries<any>(data)) {
if (value === undefined) {
continue;
}
if (key === '_count') {
// underlying database provider may return string for count
data[key] = typeof value === 'string' ? JSON.parse(value) : value;
continue;
}
if (key.startsWith(DELEGATE_JOINED_FIELD_PREFIX)) {
// merge delegate descendant fields
if (value) {
// descendant fields are packed as JSON
const subRow = this.dialect.transformOutput(value, 'Json', false);
// process the sub-row
const subModel = key.slice(DELEGATE_JOINED_FIELD_PREFIX.length) as GetModels<Schema>;
const idValues = getIdValues(this.schema, subModel, subRow);
if (Object.values(idValues).some((v) => v === null || v === undefined)) {
// if the row doesn't have a valid id, the joined row doesn't exist
delete data[key];
continue;
}
const processedSubRow = this.processRow(subRow, subModel);
// merge the sub-row into the main row
Object.assign(data, processedSubRow);
}
delete data[key];
continue;
}
const fieldDef = getField(this.schema, model, key);
if (!fieldDef) {
continue;
}
if (value === null) {
// scalar list defaults to empty array
if (fieldDef.array && !fieldDef.relation && value === null) {
data[key] = [];
}
continue;
}
if (fieldDef.relation) {
data[key] = this.processRelation(value, fieldDef);
} else {
data[key] = this.processFieldValue(value, fieldDef);
}
}
return data;
}
private processFieldValue(value: unknown, fieldDef: FieldDef) {
const type = fieldDef.type as BuiltinType;
if (Array.isArray(value)) {
value.forEach((v, i) => (value[i] = this.dialect.transformOutput(v, type, false)));
return value;
} else {
return this.dialect.transformOutput(value, type, !!fieldDef.array);
}
}
private processRelation(value: unknown, fieldDef: FieldDef) {
let relationData = value;
if (typeof value === 'string') {
// relation can be returned as a JSON string
try {
relationData = JSON.parse(value);
} catch {
return value;
}
}
// Filter out objects with no properties from array relations (occurs when all select fields are false)
if (Array.isArray(relationData) && fieldDef.array) {
relationData = relationData.filter((item) => {
if (item && typeof item === 'object') {
return Object.keys(item).length > 0;
}
return true;
});
}
return this.doProcessResult(relationData, fieldDef.type as GetModels<Schema>);
}
private fixReversedResult(data: any, model: GetModels<Schema>, args: any) {
if (!data) {
return;
}
if (Array.isArray(data) && typeof args === 'object' && args && args.take !== undefined && args.take < 0) {
data.reverse();
}
const selectInclude = args?.include ?? args?.select;
if (!selectInclude) {
return;
}
for (const row of ensureArray(data)) {
for (const [field, value] of Object.entries<any>(selectInclude)) {
if (typeof value !== 'object' || !value) {
continue;
}
const fieldDef = getField(this.schema, model, field);
if (!fieldDef || !fieldDef.relation || !fieldDef.array) {
continue;
}
this.fixReversedResult(row[field], fieldDef.type as GetModels<Schema>, value);
}
}
}
}