@appweaver/core 1.0.0

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.
Files changed (271) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +7 -0
  3. package/app/application.d.ts +43 -0
  4. package/app/application.js +112 -0
  5. package/app/create-app.d.ts +32 -0
  6. package/app/create-app.js +38 -0
  7. package/app/index.d.ts +2 -0
  8. package/app/index.js +18 -0
  9. package/app/lifecycle-manager.d.ts +20 -0
  10. package/app/lifecycle-manager.js +42 -0
  11. package/app/load-modules.d.ts +10 -0
  12. package/app/load-modules.js +50 -0
  13. package/app/load-providers.d.ts +8 -0
  14. package/app/load-providers.js +33 -0
  15. package/cache/cache-service.d.ts +72 -0
  16. package/cache/cache-service.js +202 -0
  17. package/cache/cache.d.ts +12 -0
  18. package/cache/cache.js +184 -0
  19. package/cache/caching.d.ts +8 -0
  20. package/cache/caching.js +72 -0
  21. package/cache/eviction/eviction-index.d.ts +35 -0
  22. package/cache/eviction/eviction-index.js +2 -0
  23. package/cache/eviction/fifo-eviction-index.d.ts +7 -0
  24. package/cache/eviction/fifo-eviction-index.js +28 -0
  25. package/cache/eviction/index.d.ts +4 -0
  26. package/cache/eviction/index.js +20 -0
  27. package/cache/eviction/lfu-eviction-index.d.ts +8 -0
  28. package/cache/eviction/lfu-eviction-index.js +82 -0
  29. package/cache/eviction/lru-eviction-index.d.ts +8 -0
  30. package/cache/eviction/lru-eviction-index.js +81 -0
  31. package/cache/index.d.ts +2 -0
  32. package/cache/index.js +18 -0
  33. package/cache/memory-cache.d.ts +4 -0
  34. package/cache/memory-cache.js +12 -0
  35. package/cache/redis-cache.d.ts +4 -0
  36. package/cache/redis-cache.js +12 -0
  37. package/context/context.d.ts +2 -0
  38. package/context/context.js +13 -0
  39. package/context/dependency-injection.d.ts +98 -0
  40. package/context/dependency-injection.js +276 -0
  41. package/context/index.d.ts +2 -0
  42. package/context/index.js +18 -0
  43. package/database/create-client.d.ts +11 -0
  44. package/database/create-client.js +142 -0
  45. package/database/index.d.ts +1 -0
  46. package/database/index.js +17 -0
  47. package/database/prisma-database.d.ts +15 -0
  48. package/database/prisma-database.js +53 -0
  49. package/errors/error-handler.d.ts +3 -0
  50. package/errors/error-handler.js +24 -0
  51. package/errors/error-schema.d.ts +32 -0
  52. package/errors/error-schema.js +31 -0
  53. package/errors/http-error.d.ts +21 -0
  54. package/errors/http-error.js +33 -0
  55. package/errors/index.d.ts +3 -0
  56. package/errors/index.js +19 -0
  57. package/events/node-events.d.ts +7 -0
  58. package/events/node-events.js +39 -0
  59. package/export/export-service.d.ts +19 -0
  60. package/export/export-service.js +154 -0
  61. package/export/index.d.ts +1 -0
  62. package/export/index.js +17 -0
  63. package/factory/create-model.d.ts +2 -0
  64. package/factory/create-model.js +366 -0
  65. package/factory/create-policy.d.ts +2 -0
  66. package/factory/create-policy.js +16 -0
  67. package/factory/create-routes.d.ts +2 -0
  68. package/factory/create-routes.js +21 -0
  69. package/factory/create-service.d.ts +3 -0
  70. package/factory/create-service.js +97 -0
  71. package/factory/index.d.ts +4 -0
  72. package/factory/index.js +20 -0
  73. package/health/health-routes.d.ts +2 -0
  74. package/health/health-routes.js +36 -0
  75. package/health/health-schema.d.ts +45 -0
  76. package/health/health-schema.js +52 -0
  77. package/health/health-service.d.ts +38 -0
  78. package/health/health-service.js +82 -0
  79. package/health/index.d.ts +2 -0
  80. package/health/index.js +18 -0
  81. package/index.d.ts +15 -0
  82. package/index.js +31 -0
  83. package/mailer/email-service.d.ts +36 -0
  84. package/mailer/email-service.js +89 -0
  85. package/mailer/index.d.ts +1 -0
  86. package/mailer/index.js +17 -0
  87. package/mailer/json-mailer.d.ts +7 -0
  88. package/mailer/json-mailer.js +32 -0
  89. package/mailer/smtp-mailer.d.ts +7 -0
  90. package/mailer/smtp-mailer.js +43 -0
  91. package/memory/in-memory.d.ts +24 -0
  92. package/memory/in-memory.js +164 -0
  93. package/memory/redis.d.ts +26 -0
  94. package/memory/redis.js +158 -0
  95. package/package.json +68 -0
  96. package/prisma/client/browser.d.ts +34 -0
  97. package/prisma/client/browser.js +56 -0
  98. package/prisma/client/client.d.ts +53 -0
  99. package/prisma/client/client.js +70 -0
  100. package/prisma/client/commonInputTypes.d.ts +395 -0
  101. package/prisma/client/commonInputTypes.js +11 -0
  102. package/prisma/client/enums.d.ts +1 -0
  103. package/prisma/client/enums.js +11 -0
  104. package/prisma/client/internal/class.d.ts +187 -0
  105. package/prisma/client/internal/class.js +85 -0
  106. package/prisma/client/internal/prismaNamespace.d.ts +987 -0
  107. package/prisma/client/internal/prismaNamespace.js +193 -0
  108. package/prisma/client/internal/prismaNamespaceBrowser.d.ts +125 -0
  109. package/prisma/client/internal/prismaNamespaceBrowser.js +164 -0
  110. package/prisma/client/models/ApiKey.d.ts +1167 -0
  111. package/prisma/client/models/ApiKey.js +2 -0
  112. package/prisma/client/models/File.d.ts +1248 -0
  113. package/prisma/client/models/File.js +2 -0
  114. package/prisma/client/models/OneTimeToken.d.ts +1083 -0
  115. package/prisma/client/models/OneTimeToken.js +2 -0
  116. package/prisma/client/models/Permission.d.ts +1209 -0
  117. package/prisma/client/models/Permission.js +2 -0
  118. package/prisma/client/models/Role.d.ts +1209 -0
  119. package/prisma/client/models/Role.js +2 -0
  120. package/prisma/client/models/Seeder.d.ts +1023 -0
  121. package/prisma/client/models/Seeder.js +2 -0
  122. package/prisma/client/models.d.ts +7 -0
  123. package/prisma/client/models.js +2 -0
  124. package/queue/bull-queue.d.ts +37 -0
  125. package/queue/bull-queue.js +207 -0
  126. package/queue/memory-queue.d.ts +54 -0
  127. package/queue/memory-queue.js +283 -0
  128. package/resource/index.d.ts +4 -0
  129. package/resource/index.js +20 -0
  130. package/resource/resource-loader.d.ts +26 -0
  131. package/resource/resource-loader.js +161 -0
  132. package/resource/resource-routes.d.ts +3 -0
  133. package/resource/resource-routes.js +174 -0
  134. package/resource/resource-schema.d.ts +33 -0
  135. package/resource/resource-schema.js +186 -0
  136. package/resource/resource-service.d.ts +89 -0
  137. package/resource/resource-service.js +772 -0
  138. package/resources.d.ts +9 -0
  139. package/resources.js +24 -0
  140. package/scheduler/cron-scheduler.d.ts +12 -0
  141. package/scheduler/cron-scheduler.js +73 -0
  142. package/security/account/account-routes.d.ts +2 -0
  143. package/security/account/account-routes.js +104 -0
  144. package/security/account/account-schema.d.ts +290 -0
  145. package/security/account/account-schema.js +166 -0
  146. package/security/account/account-service.d.ts +88 -0
  147. package/security/account/account-service.js +218 -0
  148. package/security/account/index.d.ts +3 -0
  149. package/security/account/index.js +19 -0
  150. package/security/api-key/api-key-auth.d.ts +4 -0
  151. package/security/api-key/api-key-auth.js +66 -0
  152. package/security/api-key/index.d.ts +1 -0
  153. package/security/api-key/index.js +17 -0
  154. package/security/auth-routes.d.ts +2 -0
  155. package/security/auth-routes.js +80 -0
  156. package/security/auth-schema.d.ts +215 -0
  157. package/security/auth-schema.js +129 -0
  158. package/security/auth-service.d.ts +117 -0
  159. package/security/auth-service.js +272 -0
  160. package/security/auth.d.ts +3 -0
  161. package/security/auth.js +127 -0
  162. package/security/basic/basic-auth.d.ts +4 -0
  163. package/security/basic/basic-auth.js +32 -0
  164. package/security/basic/index.d.ts +1 -0
  165. package/security/basic/index.js +17 -0
  166. package/security/create-auth-resources.d.ts +6 -0
  167. package/security/create-auth-resources.js +129 -0
  168. package/security/helper.d.ts +127 -0
  169. package/security/helper.js +329 -0
  170. package/security/index.d.ts +4 -0
  171. package/security/index.js +20 -0
  172. package/security/jwt/index.d.ts +2 -0
  173. package/security/jwt/index.js +18 -0
  174. package/security/jwt/jwt-auth.d.ts +4 -0
  175. package/security/jwt/jwt-auth.js +57 -0
  176. package/security/jwt/jwt-keys.d.ts +7 -0
  177. package/security/jwt/jwt-keys.js +43 -0
  178. package/security/oauth2/create-oauth2-plugin.d.ts +17 -0
  179. package/security/oauth2/create-oauth2-plugin.js +85 -0
  180. package/security/oauth2/index.d.ts +3 -0
  181. package/security/oauth2/index.js +19 -0
  182. package/security/oauth2/oauth2-custom.d.ts +7 -0
  183. package/security/oauth2/oauth2-custom.js +40 -0
  184. package/security/oauth2/oauth2-facebook.d.ts +1 -0
  185. package/security/oauth2/oauth2-facebook.js +30 -0
  186. package/security/oauth2/oauth2-google.d.ts +1 -0
  187. package/security/oauth2/oauth2-google.js +28 -0
  188. package/security/oauth2/oauth2-schema.d.ts +10 -0
  189. package/security/oauth2/oauth2-schema.js +53 -0
  190. package/security/recaptcha/index.d.ts +2 -0
  191. package/security/recaptcha/index.js +18 -0
  192. package/security/recaptcha/recaptcha-verify.d.ts +1 -0
  193. package/security/recaptcha/recaptcha-verify.js +33 -0
  194. package/security/recaptcha/recaptcha.d.ts +2 -0
  195. package/security/recaptcha/recaptcha.js +22 -0
  196. package/security/resources/api-key/model.d.ts +2 -0
  197. package/security/resources/api-key/model.js +63 -0
  198. package/security/resources/api-key/policy.d.ts +2 -0
  199. package/security/resources/api-key/policy.js +26 -0
  200. package/security/resources/api-key/routes.d.ts +2 -0
  201. package/security/resources/api-key/routes.js +15 -0
  202. package/security/resources/api-key/service.d.ts +3 -0
  203. package/security/resources/api-key/service.js +33 -0
  204. package/security/resources/one-time-token/model.d.ts +2 -0
  205. package/security/resources/one-time-token/model.js +32 -0
  206. package/security/resources/permission/model.d.ts +2 -0
  207. package/security/resources/permission/model.js +16 -0
  208. package/security/resources/role/model.d.ts +2 -0
  209. package/security/resources/role/model.js +22 -0
  210. package/security/store/database-security-store.d.ts +5 -0
  211. package/security/store/database-security-store.js +51 -0
  212. package/security/store/redis-security-store.d.ts +5 -0
  213. package/security/store/redis-security-store.js +32 -0
  214. package/seeder/create-seeder.d.ts +21 -0
  215. package/seeder/create-seeder.js +17 -0
  216. package/seeder/index.d.ts +2 -0
  217. package/seeder/index.js +18 -0
  218. package/seeder/resources/seeder/model.d.ts +2 -0
  219. package/seeder/resources/seeder/model.js +38 -0
  220. package/seeder/seeder.d.ts +20 -0
  221. package/seeder/seeder.js +175 -0
  222. package/server/create-server.d.ts +20 -0
  223. package/server/create-server.js +160 -0
  224. package/server/index.d.ts +4 -0
  225. package/server/index.js +20 -0
  226. package/server/info-route.d.ts +2 -0
  227. package/server/info-route.js +18 -0
  228. package/server/info-schema.d.ts +37 -0
  229. package/server/info-schema.js +19 -0
  230. package/server/register-model.d.ts +2 -0
  231. package/server/register-model.js +12 -0
  232. package/server/register-plugin.d.ts +2 -0
  233. package/server/register-plugin.js +13 -0
  234. package/server/register-route.d.ts +3 -0
  235. package/server/register-route.js +74 -0
  236. package/server/schemas.d.ts +3 -0
  237. package/server/schemas.js +30 -0
  238. package/server/swagger.d.ts +3 -0
  239. package/server/swagger.js +122 -0
  240. package/storage/file-route.d.ts +2 -0
  241. package/storage/file-route.js +39 -0
  242. package/storage/file-schema.d.ts +9 -0
  243. package/storage/file-schema.js +42 -0
  244. package/storage/file-service.d.ts +81 -0
  245. package/storage/file-service.js +384 -0
  246. package/storage/filesystem-storage.d.ts +10 -0
  247. package/storage/filesystem-storage.js +128 -0
  248. package/storage/index.d.ts +2 -0
  249. package/storage/index.js +18 -0
  250. package/storage/resources/file/model.d.ts +2 -0
  251. package/storage/resources/file/model.js +75 -0
  252. package/types/application-context.d.ts +20 -0
  253. package/types/application-context.js +2 -0
  254. package/types/auth.d.ts +31 -0
  255. package/types/auth.js +2 -0
  256. package/types/fastify-extensions.d.ts +24 -0
  257. package/types/fastify-extensions.js +4 -0
  258. package/types/generated.d.ts +193 -0
  259. package/types/generated.js +3 -0
  260. package/types/index.d.ts +4 -0
  261. package/types/index.js +20 -0
  262. package/types/server.d.ts +4 -0
  263. package/types/server.js +2 -0
  264. package/utils/export-util.d.ts +8 -0
  265. package/utils/export-util.js +25 -0
  266. package/utils/file-util.d.ts +64 -0
  267. package/utils/file-util.js +141 -0
  268. package/utils/index.d.ts +3 -0
  269. package/utils/index.js +19 -0
  270. package/utils/schema-util.d.ts +20 -0
  271. package/utils/schema-util.js +37 -0
@@ -0,0 +1,772 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResourceService = void 0;
4
+ const date_fns_1 = require("date-fns");
5
+ const common_1 = require("@appweaver/common");
6
+ const context_1 = require("../context");
7
+ const security_1 = require("../security");
8
+ const cache_1 = require("../cache");
9
+ const errors_1 = require("../errors");
10
+ class ResourceService {
11
+ modelName;
12
+ /** @internal */
13
+ _db = (0, context_1.inject)(common_1.Database);
14
+ /** @internal */
15
+ _events = (0, context_1.inject)(common_1.Events);
16
+ /** @internal */
17
+ _cacheService = (0, context_1.inject)(cache_1.CacheService);
18
+ /** @internal */
19
+ _client;
20
+ constructor(modelName) {
21
+ this.modelName = modelName;
22
+ this._client = this._db.client()[(0, common_1.uncapitalize)(modelName)];
23
+ if (!this._client) {
24
+ throw new Error(`ResourceService initialized with invalid model name: ${modelName}`);
25
+ }
26
+ }
27
+ get client() {
28
+ return this._client;
29
+ }
30
+ async find(id) {
31
+ const restrictions = await this.readRestrictions('find', id);
32
+ const includeRelations = this.mapRelationInclusions('find');
33
+ let resource;
34
+ try {
35
+ resource = await this._client.findFirst({
36
+ where: { id, ...restrictions },
37
+ include: includeRelations
38
+ });
39
+ }
40
+ catch (e) {
41
+ throw new errors_1.HttpError(`${this._client.name} find error`, 500, e);
42
+ }
43
+ if (!resource || resource.id !== id) {
44
+ throw new errors_1.HttpError(`${this._client.name} data not found`, 404);
45
+ }
46
+ const access = await this.checkAccess('find', resource);
47
+ if (!access) {
48
+ throw new errors_1.HttpError(`${this._client.name} data access is forbidden`, 403);
49
+ }
50
+ this._events.emitResourceEvent(this._client.name, 'find', {
51
+ current: resource
52
+ });
53
+ return this.projectResource(resource);
54
+ }
55
+ async query(filter = {}, page = 1, size = 50, sort = '-createdAt,id') {
56
+ const restrictions = await this.readRestrictions('query', filter);
57
+ const textSearch = this.extractTextSearchQuery(filter);
58
+ const mappedFilter = this.mapQueryFilter(filter);
59
+ const query = { AND: [mappedFilter, textSearch, restrictions] };
60
+ const includeRelations = this.mapRelationInclusions('query');
61
+ const orderBy = this.mapSortValues(sort);
62
+ let resources;
63
+ let totalCount;
64
+ try {
65
+ [resources, totalCount] = await this._db.client().$transaction([
66
+ this._client.findMany({
67
+ where: { ...query },
68
+ include: includeRelations,
69
+ skip: (page - 1) * size,
70
+ take: size,
71
+ orderBy
72
+ }),
73
+ this._client.count({
74
+ where: { ...query }
75
+ })
76
+ ]);
77
+ }
78
+ catch (e) {
79
+ throw new errors_1.HttpError(`${this._client.name} query error`, 500, e);
80
+ }
81
+ this._events.emitResourceEvent(this._client.name, 'query', {
82
+ current: resources
83
+ });
84
+ return {
85
+ resultCount: resources.length,
86
+ totalCount,
87
+ items: resources.map((resource) => this.projectResource(resource))
88
+ };
89
+ }
90
+ async aggregate(filter = {}, select, dateField = 'createdAt', from, to, step, safeIncrement = true) {
91
+ const toDate = (0, date_fns_1.parseISO)(to ?? new Date().toISOString());
92
+ const fromDate = from ? (0, date_fns_1.parseISO)(from) : (0, date_fns_1.subDays)(toDate, 7);
93
+ const iterator = this.makeAggregationIterator(fromDate, toDate, step, safeIncrement);
94
+ const dateRanges = [];
95
+ let currentDate = (0, date_fns_1.parseISO)(fromDate.toISOString());
96
+ while (currentDate < toDate) {
97
+ const date = currentDate;
98
+ const median = iterator.addPeriod(date, (iterator.step - 1) / 2);
99
+ currentDate = iterator.addPeriod(currentDate, iterator.step);
100
+ dateRanges.push({ from: date, to: currentDate, median });
101
+ }
102
+ const aggregateOperations = this.mapAggregationValues(select);
103
+ const restrictions = await this.readRestrictions('aggregate', filter);
104
+ const textSearch = this.extractTextSearchQuery(filter);
105
+ const mappedFilter = this.mapQueryFilter(filter);
106
+ const query = { AND: [mappedFilter, textSearch, restrictions] };
107
+ let total = {};
108
+ let items = [];
109
+ try {
110
+ [total, items] = await this._db.client().$transaction(async (tx) => {
111
+ const txModel = tx[this._client.name];
112
+ const overall = await txModel.aggregate({
113
+ ...aggregateOperations,
114
+ where: {
115
+ AND: [
116
+ query,
117
+ {
118
+ [dateField]: {
119
+ gte: fromDate,
120
+ lt: toDate
121
+ }
122
+ }
123
+ ]
124
+ }
125
+ });
126
+ // Skip executing the same query if only one range value is generated
127
+ if (dateRanges.length === 1) {
128
+ return [overall, [overall]];
129
+ }
130
+ const ranges = await Promise.all(dateRanges.map((dateRange) => txModel.aggregate({
131
+ ...aggregateOperations,
132
+ where: {
133
+ AND: [
134
+ query,
135
+ {
136
+ [dateField]: {
137
+ gte: dateRange.from,
138
+ lt: dateRange.to
139
+ }
140
+ }
141
+ ]
142
+ }
143
+ })));
144
+ return [overall, ranges];
145
+ });
146
+ }
147
+ catch (e) {
148
+ throw new errors_1.HttpError('Error on results aggregation', 500, e);
149
+ }
150
+ return {
151
+ total: this.mapAggregationValues(total, true),
152
+ items: items.map((item, index) => ({
153
+ date: dateRanges[index].median,
154
+ result: this.mapAggregationValues(item, true)
155
+ }))
156
+ };
157
+ }
158
+ async create(data) {
159
+ const createdBy = this.createdByConnect();
160
+ const restrictions = await this.writeRestrictions('create', data);
161
+ const createData = (0, common_1.removeUndefined)({
162
+ ...data,
163
+ ...restrictions
164
+ });
165
+ const access = await this.checkAccess('create', createData);
166
+ if (!access) {
167
+ throw new errors_1.HttpError(`${this._client.name} create action is forbidden`, 403);
168
+ }
169
+ const sanitizedData = this.sanitizeData('create', createData);
170
+ const connectRelations = this.mapRelationActions('create', sanitizedData);
171
+ const includeRelations = this.mapRelationInclusions('create');
172
+ let resource;
173
+ try {
174
+ resource = await this._client.create({
175
+ data: {
176
+ ...sanitizedData,
177
+ ...connectRelations,
178
+ createdBy
179
+ },
180
+ include: includeRelations
181
+ });
182
+ }
183
+ catch (e) {
184
+ throw new errors_1.HttpError(`${this._client.name} create error`, 500, e);
185
+ }
186
+ await this._cacheService.invalidateCache(this._client.name, 'create');
187
+ this._events.emitResourceEvent(this._client.name, 'create', {
188
+ current: resource
189
+ });
190
+ return this.projectResource(resource);
191
+ }
192
+ async update(id, data) {
193
+ const readRestrictions = await this.readRestrictions('update', {
194
+ id,
195
+ ...data
196
+ });
197
+ const writeRestrictions = await this.writeRestrictions('update', {
198
+ id,
199
+ ...data
200
+ });
201
+ const updateData = (0, common_1.removeUndefined)({
202
+ ...data,
203
+ ...writeRestrictions
204
+ });
205
+ const sanitizedData = this.sanitizeData('update', updateData);
206
+ const includeRelations = this.mapRelationInclusions('update');
207
+ let updateResource;
208
+ let resource;
209
+ try {
210
+ [updateResource, resource] = await this._db
211
+ .client()
212
+ .$transaction(async (tx) => {
213
+ const txModel = tx[this._client.name];
214
+ const current = await txModel.findFirst({
215
+ where: { id, ...readRestrictions },
216
+ include: includeRelations
217
+ });
218
+ if (!current || current.id !== id) {
219
+ throw new errors_1.HttpError(`${this._client.name} data not found`, 404);
220
+ }
221
+ const access = await this.checkAccess('update', current);
222
+ if (!access) {
223
+ throw new errors_1.HttpError(`${this._client.name} update action is forbidden`, 403);
224
+ }
225
+ const setRelations = this.mapRelationActions('update', sanitizedData, current);
226
+ const updated = await txModel.update({
227
+ where: { id },
228
+ include: includeRelations,
229
+ data: { ...sanitizedData, ...setRelations }
230
+ });
231
+ return [current, updated];
232
+ });
233
+ }
234
+ catch (e) {
235
+ if (e instanceof errors_1.HttpError) {
236
+ throw e;
237
+ }
238
+ throw new errors_1.HttpError(`${this._client.name} update error`, 500, e);
239
+ }
240
+ await this._cacheService.invalidateCache(this._client.name, 'update');
241
+ this._events.emitResourceEvent(this._client.name, 'update', {
242
+ previous: updateResource,
243
+ current: resource
244
+ });
245
+ return this.projectResource(resource);
246
+ }
247
+ async delete(id) {
248
+ const restrictions = await this.readRestrictions('delete', id);
249
+ let resource;
250
+ try {
251
+ resource = await this._db.client().$transaction(async (tx) => {
252
+ const txModel = tx[this._client.name];
253
+ const current = await txModel.findFirst({
254
+ where: { id, ...restrictions }
255
+ });
256
+ if (!current || current.id !== id) {
257
+ throw new errors_1.HttpError(`${this._client.name} data not found`, 404);
258
+ }
259
+ const access = await this.checkAccess('delete', current);
260
+ if (!access) {
261
+ throw new errors_1.HttpError(`${this._client.name} delete action is forbidden`, 403);
262
+ }
263
+ const includeRelations = this.mapRelationInclusions('delete');
264
+ return await txModel.delete({
265
+ where: { id },
266
+ include: includeRelations
267
+ });
268
+ });
269
+ }
270
+ catch (e) {
271
+ if (e instanceof errors_1.HttpError) {
272
+ throw e;
273
+ }
274
+ throw new errors_1.HttpError(`${this._client.name} delete error`, 500, e);
275
+ }
276
+ await this._cacheService.invalidateCache(this._client.name, 'delete');
277
+ this._events.emitResourceEvent(this._client.name, 'delete', {
278
+ current: resource
279
+ });
280
+ return this.projectResource(resource);
281
+ }
282
+ /**
283
+ * This method should be overridden with custom logic for restricting read
284
+ * operations on specific data for currently logged-in user and other
285
+ * authorization rules. The returned object will be applied as a filter on
286
+ * all actions (except create action) which will prevent unwanted data access
287
+ * and modifications. This method can also cancel current action by throwing
288
+ * an error, recommended is {@link HttpError } with appropriate HTTP error
289
+ * code.
290
+ *
291
+ * @param action The called action method on this service (find, query,
292
+ * aggregate, update, or delete)
293
+ * @param data The passed data to the called function, can be number or
294
+ * object. If the data is a type of number, then it represents the resource
295
+ * id, otherwise it depends on the action and can be one of the following:
296
+ *
297
+ * - query and aggregate -> filter object
298
+ * - update -> combined id and the data object (i.e. { id, ...data })
299
+ *
300
+ * For other actions (find and delete) it represents the resource id.
301
+ * @return The filter containing additional query restrictions.
302
+ */
303
+ async readRestrictions(action, data) {
304
+ return {};
305
+ }
306
+ /**
307
+ * This method should be overridden with custom logic for restricting write
308
+ * operations on specific data for currently logged-in users and other
309
+ * authorization rules. The returned object will be applied as a filter on
310
+ * all actions (create and update) to ensure that users can only modify
311
+ * data they are authorized to access. This method can also cancel the
312
+ * current action by throwing an error, with the recommended type being
313
+ * {@link HttpError} with the appropriate HTTP error code.
314
+ *
315
+ * @param action The called action method on this service (create or update)
316
+ * @param data The passed data to the called function, which should be an
317
+ * object representing the resource data to be created or updated. For
318
+ * update operations, this object will also include the resource ID.
319
+ *
320
+ * @return A partial object containing additional data restrictions to be
321
+ * applied, or an empty object if no restrictions are necessary.
322
+ */
323
+ async writeRestrictions(action, data) {
324
+ return {};
325
+ }
326
+ /**
327
+ * This method should provide logic for checking the access permissions for
328
+ * the provided resource object. If the resource should not be accessible by
329
+ * a currently authenticated user or other logic, this method should return
330
+ * false. Otherwise, it returns true and continues with the request execution.
331
+ *
332
+ * @param action The called action method on this service (find, query,
333
+ * aggregate, create, update, or delete)
334
+ * @param resource The resource object that is being check for access.
335
+ * @returns True if the access for resource is granted, false otherwise.
336
+ */
337
+ async checkAccess(action, resource) {
338
+ return true;
339
+ }
340
+ /**
341
+ * Constructs a query object for performing a text search on resources in the
342
+ * database based on the provided search text. This method will transform the
343
+ * search text into a format suitable for text search functionality, returning
344
+ * a query object that can be used to filter results.
345
+ *
346
+ * @param searchText The text string used for searching resources.
347
+ * @returns A query object that represents the conditions for the text
348
+ * search operation. This will be used by the database query methods to
349
+ * retrieve matching resources.
350
+ */
351
+ textSearchQuery(searchText) {
352
+ return {};
353
+ }
354
+ extractTextSearchQuery(filter) {
355
+ if (filter.searchText) {
356
+ const searchQuery = this.textSearchQuery(filter.searchText);
357
+ delete filter.searchText;
358
+ return searchQuery;
359
+ }
360
+ return {};
361
+ }
362
+ mapSortValues(sort) {
363
+ const sortMap = {};
364
+ const resourceModel = (0, context_1.injectModel)(this._client.name);
365
+ const parts = sort.split(',');
366
+ for (const part of parts) {
367
+ let path = part.trim();
368
+ const order = path.startsWith('-') ? 'desc' : 'asc';
369
+ path = path.replace(/[-+]/g, '');
370
+ // Skip the default sort by createdAt field if not configured
371
+ if (resourceModel.config.audit?.createdAt === false &&
372
+ path === 'createdAt') {
373
+ continue;
374
+ }
375
+ // Map relations count field sort order.
376
+ if ((0, common_1.isCountField)(path)) {
377
+ path = path.replace('Count', '._count');
378
+ }
379
+ (0, common_1.setValue)(sortMap, path, order);
380
+ }
381
+ return Object.entries(sortMap).map(([key, value]) => ({ [key]: value }));
382
+ }
383
+ mapAggregationValues(select, isOutput = false) {
384
+ const aggregationMap = {};
385
+ for (const field in select) {
386
+ const operators = select[field];
387
+ for (const operator in operators) {
388
+ const value = operators[operator];
389
+ const path = isOutput
390
+ ? `${operator}.${field.substring(1)}`
391
+ : `_${operator}.${field}`;
392
+ (0, common_1.setValue)(aggregationMap, path, value);
393
+ }
394
+ }
395
+ return aggregationMap;
396
+ }
397
+ makeAggregationIterator(fromDate, toDate, step, safeIncrement = true) {
398
+ let stepAmount = step;
399
+ let incrementFn = date_fns_1.addSeconds;
400
+ if (!stepAmount) {
401
+ const diffInSeconds = (0, date_fns_1.differenceInSeconds)(toDate, fromDate);
402
+ const diffInMonths = (0, date_fns_1.differenceInMonths)(toDate, fromDate);
403
+ const diffInYears = (0, date_fns_1.differenceInYears)(toDate, fromDate);
404
+ stepAmount = 1;
405
+ // 1-second step if the difference is less than or equal to 1 minute
406
+ if (diffInSeconds <= 60) {
407
+ incrementFn = date_fns_1.addSeconds;
408
+ }
409
+ // 1-minute step if the difference is less than or equal to 1 hour
410
+ else if (diffInSeconds <= 3600) {
411
+ incrementFn = date_fns_1.addMinutes;
412
+ }
413
+ // 1-hour step if the difference is less than or equal to 1 day
414
+ else if (diffInSeconds <= 86400) {
415
+ incrementFn = date_fns_1.addHours;
416
+ }
417
+ // 1-day step if the difference is less than or equal to 1 month
418
+ else if (diffInMonths <= 1) {
419
+ incrementFn = date_fns_1.addDays;
420
+ }
421
+ // 1-month step if the difference is less than or equal to 1 year
422
+ else if (diffInYears <= 1) {
423
+ incrementFn = date_fns_1.addMonths;
424
+ }
425
+ // 1-year step if the difference is equal to 1 year or more
426
+ else {
427
+ incrementFn = date_fns_1.addYears;
428
+ }
429
+ }
430
+ // A higher-order function that adjusts date increments to account for
431
+ // changes in daylight saving time (DST). When incrementing dates in time
432
+ // zones that observe DST, this function ensures that the resulting date
433
+ // remains consistent with the original date's time zone offset.
434
+ const dstAgnosticFn = (fn) => {
435
+ return (date, amount) => {
436
+ const endDate = fn(date, amount);
437
+ return (0, date_fns_1.addMinutes)(endDate, date.getTimezoneOffset() - endDate.getTimezoneOffset());
438
+ };
439
+ };
440
+ return {
441
+ addPeriod: dstAgnosticFn(safeIncrement ? incrementFn : date_fns_1.addSeconds),
442
+ step: stepAmount
443
+ };
444
+ }
445
+ projectResource(resource) {
446
+ const projectedResource = this.projectVirtualFields(resource);
447
+ if (!(0, common_1.isObject)(projectedResource['_count'])) {
448
+ return projectedResource;
449
+ }
450
+ // Create new relation count properties on the resource object
451
+ for (const [key, count] of Object.entries(projectedResource['_count'])) {
452
+ projectedResource[(0, common_1.countFieldName)(key)] = count;
453
+ }
454
+ delete projectedResource['_count'];
455
+ return projectedResource;
456
+ }
457
+ projectVirtualFields(resource, resourceName) {
458
+ if (!resource) {
459
+ return resource;
460
+ }
461
+ const projectedVirtual = { ...resource };
462
+ const resourceModel = (0, context_1.injectModel)(resourceName ?? this._client.name, false);
463
+ const relationsModel = resourceModel?.relationsModel;
464
+ const filesModel = resourceModel?.filesModel;
465
+ // Set output or default value for virtual fields
466
+ for (const [fieldName, virtual] of Object.entries(resourceModel?.config?.virtual ?? {})) {
467
+ const outputValue = virtual.output?.value;
468
+ if ((0, common_1.isFunction)(outputValue)) {
469
+ projectedVirtual[fieldName] = outputValue(projectedVirtual);
470
+ }
471
+ else if (outputValue) {
472
+ projectedVirtual[fieldName] = outputValue;
473
+ }
474
+ else if (virtual.required !== false) {
475
+ projectedVirtual[fieldName] =
476
+ virtual.default ?? (0, common_1.defaultScalarValue)(virtual);
477
+ }
478
+ }
479
+ // Recursively project virtual fields for nested relational objects
480
+ for (const key in projectedVirtual) {
481
+ const value = projectedVirtual[key];
482
+ const relationSchema = (0, common_1.extractSchemaProperties)(relationsModel, key);
483
+ const fileSchema = (0, common_1.extractSchemaProperties)(filesModel, key);
484
+ if ((0, common_1.isObject)(value) || ((0, common_1.isArray)(value) && (0, common_1.isObject)(value[0]))) {
485
+ const resourceName = (0, common_1.extractResourceName)(relationSchema ?? fileSchema);
486
+ if (resourceName) {
487
+ projectedVirtual[key] = (0, common_1.isArray)(value)
488
+ ? value.map((item) => this.projectVirtualFields(item, resourceName))
489
+ : this.projectVirtualFields(value, resourceName);
490
+ }
491
+ }
492
+ }
493
+ return projectedVirtual;
494
+ }
495
+ sanitizeData(action, data, resourceName) {
496
+ const sanitizedData = { ...data };
497
+ const resourceModel = (0, context_1.injectModel)(resourceName ?? this._client.name, false);
498
+ const relationsModel = resourceModel?.relationsModel;
499
+ const filesModel = resourceModel?.filesModel;
500
+ // Delete virtual fields from data object to avoid database errors
501
+ for (const fieldName of Object.keys(resourceModel?.config?.virtual ?? {})) {
502
+ delete sanitizedData[fieldName];
503
+ }
504
+ // Map default values for hidden scalars if a property is required without
505
+ // a provided default value, and scalar values are not set yet, this is only
506
+ // required for create actions
507
+ for (const [fieldName, scalar] of Object.entries(resourceModel?.config?.scalars ?? {})) {
508
+ if (action === 'create' &&
509
+ scalar.hidden &&
510
+ scalar.required !== false &&
511
+ scalar.default === undefined &&
512
+ sanitizedData[fieldName] === undefined) {
513
+ sanitizedData[fieldName] = (0, common_1.defaultScalarValue)(scalar);
514
+ }
515
+ }
516
+ // Recursively sanitize nested objects and arrays of objects
517
+ for (const key in sanitizedData) {
518
+ const value = sanitizedData[key];
519
+ const relationSchema = (0, common_1.extractSchemaProperties)(relationsModel, key);
520
+ const fileSchema = (0, common_1.extractSchemaProperties)(filesModel, key);
521
+ if ((0, common_1.isObject)(value) || ((0, common_1.isArray)(value) && (0, common_1.isObject)(value[0]))) {
522
+ const resourceName = (0, common_1.extractResourceName)(relationSchema ?? fileSchema);
523
+ if (resourceName) {
524
+ sanitizedData[key] = (0, common_1.isArray)(value)
525
+ ? value.map((item) => this.sanitizeData(item, resourceName))
526
+ : this.sanitizeData(action, value, resourceName);
527
+ }
528
+ }
529
+ }
530
+ return sanitizedData;
531
+ }
532
+ mapQueryFilter(filter, resourceName) {
533
+ const queryFilter = {};
534
+ const resourceModel = (0, context_1.injectModel)(resourceName ?? this._client.name, false);
535
+ const readModel = resourceModel?.readModel;
536
+ const relationsModel = resourceModel?.relationsModel;
537
+ const filesModel = resourceModel?.filesModel;
538
+ for (const key in filter) {
539
+ const value = filter[key];
540
+ const readSchema = (0, common_1.extractSchemaProperties)(readModel, key);
541
+ const relationSchema = (0, common_1.extractSchemaProperties)(relationsModel, key);
542
+ const fileSchema = (0, common_1.extractSchemaProperties)(filesModel, key);
543
+ const isArrayType = readSchema?.type === 'array' ||
544
+ relationSchema?.type === 'array' ||
545
+ fileSchema?.type === 'array';
546
+ const isArrayValue = (0, common_1.isArray)(value);
547
+ // Recursively map nested objects and handle arrays of objects
548
+ if ((0, common_1.isObject)(value) || (isArrayValue && (0, common_1.isObject)(value[0]))) {
549
+ const resourceName = (0, common_1.extractResourceName)(relationSchema ?? fileSchema);
550
+ if (resourceName) {
551
+ queryFilter[key] = isArrayValue
552
+ ? value.map((item) => this.mapQueryFilter(item, resourceName))
553
+ : this.mapQueryFilter(value, resourceName);
554
+ }
555
+ }
556
+ // Map ID values for both single and array types of relationships
557
+ else if (relationSchema || fileSchema) {
558
+ const queryId = { id: isArrayValue ? { in: value } : value };
559
+ queryFilter[key] = isArrayType ? { some: queryId } : queryId;
560
+ }
561
+ // Map fields without relationships, supporting array types
562
+ else if (readSchema) {
563
+ if (isArrayType) {
564
+ queryFilter[key] = isArrayValue ? { hasSome: value } : { has: value };
565
+ }
566
+ else if (isArrayValue) {
567
+ // For date and numeric types, apply range filtering with inclusive
568
+ // intervals
569
+ if (value.length > 0 &&
570
+ value.length <= 2 &&
571
+ (['number', 'integer'].includes(readSchema.type) ||
572
+ ['date', 'date-time'].includes(readSchema.format))) {
573
+ queryFilter[key] = {
574
+ gte: value[0],
575
+ lte: value[1]
576
+ };
577
+ }
578
+ // Map array values for inclusion checks
579
+ else {
580
+ queryFilter[key] = { in: value };
581
+ }
582
+ }
583
+ }
584
+ // If no query filter was defined, assign the original value
585
+ if (queryFilter[key] === undefined) {
586
+ queryFilter[key] = value;
587
+ }
588
+ }
589
+ return queryFilter;
590
+ }
591
+ mapRelationInclusions(action, resourceName) {
592
+ const inclusion = {};
593
+ const resourceModel = (0, context_1.injectModel)(resourceName ?? this._client.name);
594
+ const relationConfig = resourceModel.config.relations;
595
+ const fileConfig = resourceModel.config.files;
596
+ const relationModelProps = (0, common_1.extractSchemaProperties)(resourceModel.relationsModel);
597
+ const fileModelProps = (0, common_1.extractSchemaProperties)(resourceModel.filesModel);
598
+ // Add relation and file fields to the inclusion map if the include type is
599
+ // satisfied or the requested action is not specified. Also, add the count
600
+ // aggregation actions for relations if configured.
601
+ for (const key of Object.keys({
602
+ ...relationModelProps,
603
+ ...fileModelProps
604
+ })) {
605
+ const relationField = relationConfig?.[key] || fileConfig?.[key];
606
+ if (relationField?.output?.count) {
607
+ inclusion._count = inclusion._count ?? { select: {} };
608
+ inclusion._count.select[key] = true;
609
+ }
610
+ // Check if the relation should be included based on the output type
611
+ if (this.shouldIncludeRelation(relationField?.output?.type, action)) {
612
+ inclusion[key] = this.buildNestedInclusion(relationField, action);
613
+ }
614
+ }
615
+ return inclusion;
616
+ }
617
+ buildNestedInclusion(relationField, action) {
618
+ const nestedIncludeConfig = relationField?.output?.include;
619
+ if (!nestedIncludeConfig || Object.keys(nestedIncludeConfig).length === 0) {
620
+ return true;
621
+ }
622
+ const nestedInclusion = {};
623
+ for (const [nestedKey, nestedOutput] of Object.entries(nestedIncludeConfig)) {
624
+ // Check if the nested relation should be included
625
+ if (this.shouldIncludeRelation(nestedOutput?.type, action)) {
626
+ // Recursively build nested inclusions
627
+ nestedInclusion[nestedKey] = this.buildNestedInclusion({ output: nestedOutput }, action);
628
+ }
629
+ }
630
+ return Object.keys(nestedInclusion).length > 0
631
+ ? { include: nestedInclusion }
632
+ : true;
633
+ }
634
+ shouldIncludeRelation(outputType, action) {
635
+ if (outputType === 'none') {
636
+ return false;
637
+ }
638
+ if (outputType === 'single' && action === 'query') {
639
+ return false;
640
+ }
641
+ return !(outputType === 'multiple' && action && action !== 'query');
642
+ }
643
+ mapRelationActions(action, data, currentData) {
644
+ const relations = {};
645
+ const resourceModel = (0, context_1.injectModel)(this._client.name);
646
+ const readModel = resourceModel.readModel;
647
+ const relationsModel = resourceModel.relationsModel;
648
+ const relationsConfig = resourceModel.config.relations;
649
+ for (const key in data) {
650
+ let value = data[key];
651
+ const relationSchema = (0, common_1.extractSchemaProperties)(relationsModel, key);
652
+ if (!relationSchema) {
653
+ // Set empty array or undefined value for null array type fields
654
+ if (value === null) {
655
+ if ((0, common_1.extractSchemaProperties)(readModel, key)?.type === 'array') {
656
+ relations[key] = action === 'update' ? [] : undefined;
657
+ }
658
+ else if (action === 'create') {
659
+ relations[key] = undefined;
660
+ }
661
+ }
662
+ // Skip mapping for non-relation fields
663
+ continue;
664
+ }
665
+ const config = relationsConfig?.[key];
666
+ const uniqueKey = config?.input?.uniqueKey || 'id';
667
+ const isArrayType = relationSchema.type === 'array';
668
+ // Normalize array values to single values if a value type is not an array
669
+ if (!isArrayType && (0, common_1.isArray)(value)) {
670
+ value = value[0];
671
+ }
672
+ // Set null values to undefined value for create actions. On update action
673
+ // they will be returned as disconnected relations.
674
+ if (action === 'create' && value === null) {
675
+ relations[key] = undefined;
676
+ continue;
677
+ }
678
+ // Normalize plain unique key values or arrays to object values
679
+ if (isArrayType) {
680
+ if ((0, common_1.isArray)(value) && !(0, common_1.isObject)([0])) {
681
+ value = value.map((v) => ({
682
+ [uniqueKey]: v
683
+ }));
684
+ }
685
+ }
686
+ else {
687
+ if (!(0, common_1.isObject)(value)) {
688
+ value = { [uniqueKey]: value };
689
+ }
690
+ }
691
+ // Map relation connections with an option to create a non-existing entity
692
+ if (config?.createIfNotExists && value) {
693
+ const createdBy = this.createdByConnect(config.model);
694
+ if (isArrayType) {
695
+ relations[key] = {
696
+ connectOrCreate: value.map((v) => ({
697
+ where: v[uniqueKey] ? { [uniqueKey]: v[uniqueKey] } : { id: 0 },
698
+ create: { ...v, createdBy, id: undefined }
699
+ }))
700
+ };
701
+ }
702
+ else {
703
+ relations[key] = {
704
+ connectOrCreate: {
705
+ where: value[uniqueKey]
706
+ ? { [uniqueKey]: value[uniqueKey] }
707
+ : { id: 0 },
708
+ create: { ...value, createdBy, id: undefined }
709
+ }
710
+ };
711
+ }
712
+ }
713
+ else if (value) {
714
+ if (isArrayType && (0, common_1.isArray)(value)) {
715
+ relations[key] = {
716
+ connect: value.map((v) => ({ [uniqueKey]: v[uniqueKey] }))
717
+ };
718
+ }
719
+ else {
720
+ relations[key] = { connect: { [uniqueKey]: value[uniqueKey] } };
721
+ }
722
+ }
723
+ // Map relation disconnects if unique keys are no longer present or the
724
+ // new value is null. Delete relations if orphanRemoval is set to true.
725
+ // Also, do not create a disconnect action if the currentValue is already
726
+ // null.
727
+ if (action === 'update') {
728
+ const currentValue = currentData[key];
729
+ const removalMethod = config?.orphanRemoval ? 'delete' : 'disconnect';
730
+ if (currentValue && isArrayType) {
731
+ const newValueKeys = value?.map((v) => v[uniqueKey]) ?? [];
732
+ const currentValueKeys = currentValue
733
+ .filter((v) => newValueKeys.indexOf(v[uniqueKey]) === -1)
734
+ .map((v) => ({
735
+ [uniqueKey]: v[uniqueKey]
736
+ }));
737
+ if (currentValueKeys.length > 0) {
738
+ relations[key] = {
739
+ [removalMethod]: currentValueKeys,
740
+ ...(relations[key] ?? {})
741
+ };
742
+ }
743
+ }
744
+ else if (currentValue && value === null) {
745
+ relations[key] = {
746
+ [removalMethod]: { [uniqueKey]: currentValue[uniqueKey] },
747
+ ...(relations[key] ?? {})
748
+ };
749
+ }
750
+ else if (!currentValue) {
751
+ relations[key] = undefined;
752
+ }
753
+ }
754
+ }
755
+ return relations;
756
+ }
757
+ createdByConnect(resourceName) {
758
+ const resourceModel = (0, context_1.injectModel)(resourceName ?? this._client.name, false);
759
+ if (resourceModel?.config.audit?.createdById === false) {
760
+ return undefined;
761
+ }
762
+ const currentUser = (0, security_1.currentAuthUser)();
763
+ return currentUser
764
+ ? {
765
+ connect: {
766
+ id: currentUser.id
767
+ }
768
+ }
769
+ : undefined;
770
+ }
771
+ }
772
+ exports.ResourceService = ResourceService;