wabe 0.6.9 → 0.6.11

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 (162) hide show
  1. package/README.md +156 -50
  2. package/bucket/b.txt +1 -0
  3. package/dev/index.ts +215 -0
  4. package/dist/authentication/Session.d.ts +4 -1
  5. package/dist/authentication/interface.d.ts +16 -0
  6. package/dist/cron/index.d.ts +0 -1
  7. package/dist/database/DatabaseController.d.ts +41 -13
  8. package/dist/database/interface.d.ts +1 -0
  9. package/dist/email/DevAdapter.d.ts +0 -1
  10. package/dist/email/interface.d.ts +1 -1
  11. package/dist/graphql/resolvers.d.ts +4 -2
  12. package/dist/hooks/index.d.ts +8 -2
  13. package/dist/index.d.ts +0 -1
  14. package/dist/index.js +32144 -32058
  15. package/dist/schema/Schema.d.ts +2 -1
  16. package/dist/server/index.d.ts +4 -2
  17. package/dist/utils/crypto.d.ts +7 -0
  18. package/dist/utils/helper.d.ts +5 -1
  19. package/generated/schema.graphql +22 -14
  20. package/generated/wabe.ts +4 -4
  21. package/package.json +23 -23
  22. package/src/authentication/OTP.test.ts +69 -0
  23. package/src/authentication/OTP.ts +64 -0
  24. package/src/authentication/Session.test.ts +629 -0
  25. package/src/authentication/Session.ts +493 -0
  26. package/src/authentication/defaultAuthentication.ts +209 -0
  27. package/src/authentication/index.ts +3 -0
  28. package/src/authentication/interface.ts +155 -0
  29. package/src/authentication/oauth/GitHub.test.ts +91 -0
  30. package/src/authentication/oauth/GitHub.ts +121 -0
  31. package/src/authentication/oauth/Google.test.ts +91 -0
  32. package/src/authentication/oauth/Google.ts +101 -0
  33. package/src/authentication/oauth/Oauth2Client.test.ts +219 -0
  34. package/src/authentication/oauth/Oauth2Client.ts +135 -0
  35. package/src/authentication/oauth/index.ts +2 -0
  36. package/src/authentication/oauth/utils.test.ts +33 -0
  37. package/src/authentication/oauth/utils.ts +27 -0
  38. package/src/authentication/providers/EmailOTP.test.ts +127 -0
  39. package/src/authentication/providers/EmailOTP.ts +84 -0
  40. package/src/authentication/providers/EmailPassword.test.ts +176 -0
  41. package/src/authentication/providers/EmailPassword.ts +116 -0
  42. package/src/authentication/providers/EmailPasswordSRP.test.ts +208 -0
  43. package/src/authentication/providers/EmailPasswordSRP.ts +179 -0
  44. package/src/authentication/providers/GitHub.ts +24 -0
  45. package/src/authentication/providers/Google.ts +24 -0
  46. package/src/authentication/providers/OAuth.test.ts +185 -0
  47. package/src/authentication/providers/OAuth.ts +106 -0
  48. package/src/authentication/providers/PhonePassword.test.ts +176 -0
  49. package/src/authentication/providers/PhonePassword.ts +115 -0
  50. package/src/authentication/providers/QRCodeOTP.test.ts +77 -0
  51. package/src/authentication/providers/QRCodeOTP.ts +58 -0
  52. package/src/authentication/providers/index.ts +6 -0
  53. package/src/authentication/resolvers/refreshResolver.test.ts +30 -0
  54. package/src/authentication/resolvers/refreshResolver.ts +19 -0
  55. package/src/authentication/resolvers/signInWithResolver.inte.test.ts +59 -0
  56. package/src/authentication/resolvers/signInWithResolver.test.ts +293 -0
  57. package/src/authentication/resolvers/signInWithResolver.ts +92 -0
  58. package/src/authentication/resolvers/signOutResolver.test.ts +38 -0
  59. package/src/authentication/resolvers/signOutResolver.ts +18 -0
  60. package/src/authentication/resolvers/signUpWithResolver.test.ts +180 -0
  61. package/src/authentication/resolvers/signUpWithResolver.ts +65 -0
  62. package/src/authentication/resolvers/verifyChallenge.test.ts +133 -0
  63. package/src/authentication/resolvers/verifyChallenge.ts +62 -0
  64. package/src/authentication/roles.test.ts +49 -0
  65. package/src/authentication/roles.ts +40 -0
  66. package/src/authentication/utils.test.ts +97 -0
  67. package/src/authentication/utils.ts +39 -0
  68. package/src/cache/InMemoryCache.test.ts +62 -0
  69. package/src/cache/InMemoryCache.ts +45 -0
  70. package/src/cron/index.test.ts +17 -0
  71. package/src/cron/index.ts +43 -0
  72. package/src/database/DatabaseController.test.ts +613 -0
  73. package/src/database/DatabaseController.ts +1007 -0
  74. package/src/database/index.test.ts +1372 -0
  75. package/src/database/index.ts +9 -0
  76. package/src/database/interface.ts +302 -0
  77. package/src/email/DevAdapter.ts +7 -0
  78. package/src/email/EmailController.test.ts +29 -0
  79. package/src/email/EmailController.ts +13 -0
  80. package/src/email/index.ts +2 -0
  81. package/src/email/interface.ts +36 -0
  82. package/src/email/templates/sendOtpCode.ts +120 -0
  83. package/src/file/FileController.ts +28 -0
  84. package/src/file/FileDevAdapter.ts +51 -0
  85. package/src/file/hookDeleteFile.ts +25 -0
  86. package/src/file/hookReadFile.ts +66 -0
  87. package/src/file/hookUploadFile.ts +50 -0
  88. package/src/file/index.test.ts +932 -0
  89. package/src/file/index.ts +2 -0
  90. package/src/file/interface.ts +39 -0
  91. package/src/graphql/GraphQLSchema.test.ts +4408 -0
  92. package/src/graphql/GraphQLSchema.ts +880 -0
  93. package/src/graphql/index.ts +2 -0
  94. package/src/graphql/parseGraphqlSchema.ts +85 -0
  95. package/src/graphql/parser.test.ts +203 -0
  96. package/src/graphql/parser.ts +542 -0
  97. package/src/graphql/pointerAndRelationFunction.ts +191 -0
  98. package/src/graphql/resolvers.ts +442 -0
  99. package/src/graphql/tests/aggregation.test.ts +1115 -0
  100. package/src/graphql/tests/e2e.test.ts +590 -0
  101. package/src/graphql/tests/scalars.test.ts +250 -0
  102. package/src/graphql/types.ts +227 -0
  103. package/src/hooks/HookObject.test.ts +122 -0
  104. package/src/hooks/HookObject.ts +165 -0
  105. package/src/hooks/authentication.ts +67 -0
  106. package/src/hooks/createUser.test.ts +77 -0
  107. package/src/hooks/createUser.ts +10 -0
  108. package/src/hooks/defaultFields.test.ts +176 -0
  109. package/src/hooks/defaultFields.ts +32 -0
  110. package/src/hooks/deleteSession.test.ts +181 -0
  111. package/src/hooks/deleteSession.ts +20 -0
  112. package/src/hooks/hashFieldHook.test.ts +152 -0
  113. package/src/hooks/hashFieldHook.ts +89 -0
  114. package/src/hooks/index.test.ts +258 -0
  115. package/src/hooks/index.ts +414 -0
  116. package/src/hooks/permissions.test.ts +412 -0
  117. package/src/hooks/permissions.ts +93 -0
  118. package/src/hooks/protected.test.ts +551 -0
  119. package/src/hooks/protected.ts +60 -0
  120. package/src/hooks/searchableFields.test.ts +147 -0
  121. package/src/hooks/searchableFields.ts +86 -0
  122. package/src/hooks/session.test.ts +134 -0
  123. package/src/hooks/session.ts +76 -0
  124. package/src/hooks/setEmail.test.ts +216 -0
  125. package/src/hooks/setEmail.ts +33 -0
  126. package/src/hooks/setupAcl.test.ts +618 -0
  127. package/src/hooks/setupAcl.ts +25 -0
  128. package/src/index.ts +9 -0
  129. package/src/schema/Schema.test.ts +482 -0
  130. package/src/schema/Schema.ts +757 -0
  131. package/src/schema/defaultResolvers.ts +93 -0
  132. package/src/schema/index.ts +1 -0
  133. package/src/schema/resolvers/meResolver.test.ts +62 -0
  134. package/src/schema/resolvers/meResolver.ts +10 -0
  135. package/src/schema/resolvers/resetPassword.test.ts +341 -0
  136. package/src/schema/resolvers/resetPassword.ts +63 -0
  137. package/src/schema/resolvers/sendEmail.test.ts +118 -0
  138. package/src/schema/resolvers/sendEmail.ts +21 -0
  139. package/src/schema/resolvers/sendOtpCode.test.ts +141 -0
  140. package/src/schema/resolvers/sendOtpCode.ts +52 -0
  141. package/src/security.test.ts +3434 -0
  142. package/src/server/defaultSessionHandler.test.ts +62 -0
  143. package/src/server/defaultSessionHandler.ts +105 -0
  144. package/src/server/generateCodegen.ts +433 -0
  145. package/src/server/index.test.ts +532 -0
  146. package/src/server/index.ts +334 -0
  147. package/src/server/interface.ts +11 -0
  148. package/src/server/routes/authHandler.ts +169 -0
  149. package/src/server/routes/index.ts +39 -0
  150. package/src/utils/crypto.test.ts +41 -0
  151. package/src/utils/crypto.ts +105 -0
  152. package/src/utils/export.ts +11 -0
  153. package/src/utils/helper.ts +204 -0
  154. package/src/utils/index.test.ts +11 -0
  155. package/src/utils/index.ts +189 -0
  156. package/src/utils/preload.ts +8 -0
  157. package/src/utils/testHelper.ts +116 -0
  158. package/tsconfig.json +32 -0
  159. package/bunfig.toml +0 -4
  160. package/dist/ai/index.d.ts +0 -1
  161. package/dist/ai/interface.d.ts +0 -9
  162. /package/dist/server/{defaultHandlers.d.ts → defaultSessionHandler.d.ts} +0 -0
@@ -0,0 +1,1007 @@
1
+ import { selectFieldsWithoutPrivateFields } from 'src/utils/helper'
2
+ import type { WabeTypes } from '../..'
3
+ import { initializeHook, OperationType } from '../hooks'
4
+ import type { SchemaInterface } from '../schema'
5
+ import type { WabeContext } from '../server/interface'
6
+ import { contextWithRoot, notEmpty } from '../utils/export'
7
+ import type { DevWabeTypes } from '../utils/helper'
8
+ import type {
9
+ CountOptions,
10
+ CreateObjectOptions,
11
+ CreateObjectsOptions,
12
+ DatabaseAdapter,
13
+ DeleteObjectOptions,
14
+ DeleteObjectsOptions,
15
+ GetObjectOptions,
16
+ GetObjectsOptions,
17
+ OutputType,
18
+ UpdateObjectOptions,
19
+ UpdateObjectsOptions,
20
+ WhereType,
21
+ } from './interface'
22
+
23
+ export type Select = Record<string, boolean>
24
+ type SelectWithObject = Record<string, object | boolean>
25
+
26
+ export class DatabaseController<T extends WabeTypes> {
27
+ public adapter: DatabaseAdapter<T>
28
+
29
+ constructor(adapter: DatabaseAdapter<T>) {
30
+ this.adapter = adapter
31
+ }
32
+
33
+ /**
34
+ * Get a class definition from the schema by name (case-insensitive)
35
+ */
36
+ _getClass(className: string | keyof T['types'], context: WabeContext<T>) {
37
+ return context.wabe.config.schema?.classes?.find(
38
+ (c) => c.name.toLowerCase() === String(className).toLowerCase(),
39
+ )
40
+ }
41
+
42
+ /**
43
+ * Get field type and target class information
44
+ */
45
+ _getFieldType(originClassName: string, fieldName: string, context: WabeContext<T>) {
46
+ const realClass = this._getClass(originClassName, context)
47
+ return realClass?.fields[fieldName] as { type: string; class?: string } | undefined
48
+ }
49
+
50
+ _getSelectMinusPointersAndRelations({
51
+ className,
52
+ context,
53
+ select,
54
+ }: {
55
+ className: keyof T['types']
56
+ context: WabeContext<T>
57
+ select?: SelectWithObject
58
+ }): {
59
+ pointers: Record<string, { className: string; select: Select }>
60
+ selectWithoutPointers: Select
61
+ } {
62
+ const realClass = this._getClass(className, context)
63
+
64
+ if (!realClass) throw new Error('Class not found in schema')
65
+
66
+ if (!select) return { pointers: {}, selectWithoutPointers: {} }
67
+
68
+ const pointers: Record<string, { className: string; select: Select }> = {}
69
+ const selectWithoutPointers: Select = {}
70
+
71
+ const selectEntries = Object.entries(
72
+ context.isRoot ? select : selectFieldsWithoutPrivateFields(select),
73
+ )
74
+
75
+ for (const [fieldName, value] of selectEntries) {
76
+ const field = realClass.fields[fieldName]
77
+ const isPointerOrRelation = field?.type === 'Pointer' || field?.type === 'Relation'
78
+
79
+ if (!isPointerOrRelation) {
80
+ selectWithoutPointers[fieldName] = true
81
+ } else {
82
+ pointers[fieldName] = {
83
+ className: (field as { class: string }).class,
84
+ select: (value === true ? undefined : value) as Select,
85
+ }
86
+ }
87
+ }
88
+
89
+ return { pointers, selectWithoutPointers }
90
+ }
91
+
92
+ _isFieldOfType(
93
+ originClassName: string,
94
+ pointerField: string,
95
+ expectedType: 'Pointer' | 'Relation',
96
+ context: WabeContext<T>,
97
+ currentClassName?: string,
98
+ ): boolean {
99
+ if (!currentClassName) return false
100
+
101
+ const field = this._getFieldType(originClassName, pointerField, context)
102
+ return (
103
+ field?.type === expectedType && field.class?.toLowerCase() === currentClassName.toLowerCase()
104
+ )
105
+ }
106
+
107
+ async _getWhereObjectWithPointerOrRelation<U extends keyof T['types']>(
108
+ className: U,
109
+ where: WhereType<T, U>,
110
+ context: WabeContext<T>,
111
+ ) {
112
+ const whereKeys = Object.keys(where) as Array<keyof WhereType<T, U>>
113
+
114
+ const realClass = this._getClass(className, context)
115
+
116
+ const newWhereObject = await whereKeys.reduce(async (acc, whereKey) => {
117
+ const currentAcc = await acc
118
+
119
+ const typedWhereKey = whereKey as string
120
+
121
+ const field = realClass?.fields[typedWhereKey]
122
+
123
+ if (typedWhereKey === 'AND' || typedWhereKey === 'OR') {
124
+ const newWhere = await Promise.all(
125
+ (where[typedWhereKey] as any).map((whereObject: any) =>
126
+ this._getWhereObjectWithPointerOrRelation(className, whereObject, context),
127
+ ),
128
+ )
129
+
130
+ return {
131
+ ...currentAcc,
132
+ [typedWhereKey]: newWhere,
133
+ }
134
+ }
135
+
136
+ if (field?.type !== 'Pointer' && field?.type !== 'Relation') return acc
137
+
138
+ // @ts-expect-error
139
+ const fieldTargetClass = field.class
140
+
141
+ const defaultWhere = where[typedWhereKey]
142
+
143
+ const objects = await this.getObjects({
144
+ className: fieldTargetClass,
145
+ // @ts-expect-error
146
+ select: { id: true },
147
+ // @ts-expect-error
148
+ where: defaultWhere,
149
+ context,
150
+ })
151
+
152
+ return {
153
+ ...acc,
154
+ // If we don't found any object we just execute the query with the default where
155
+ // Without any transformation for pointer or relation
156
+ // Ensure the 'in' condition is not empty to avoid unauthorized access
157
+ ...(objects.length > 0
158
+ ? {
159
+ [typedWhereKey]: {
160
+ in: objects.map((object) => object?.id).filter(notEmpty),
161
+ },
162
+ }
163
+ : {}),
164
+ }
165
+ }, Promise.resolve({}))
166
+
167
+ return {
168
+ ...where,
169
+ ...newWhereObject,
170
+ }
171
+ }
172
+
173
+ _buildWhereWithACL<K extends keyof T['types']>(
174
+ where: WhereType<T, K>,
175
+ context: WabeContext<T>,
176
+ operation: 'write' | 'read',
177
+ ): WhereType<T, K> {
178
+ if (context.isRoot) return where
179
+
180
+ const roleId = context.user?.role?.id
181
+ const userId = context.user?.id
182
+
183
+ // If we have an user we good right we return
184
+ // If we don't have user we check role
185
+ // If the role is good we return
186
+
187
+ // @ts-expect-error
188
+ return {
189
+ AND: [
190
+ { ...where },
191
+ // If the user is not connected we need to have a null acl
192
+ !userId
193
+ ? {
194
+ acl: { equalTo: null },
195
+ }
196
+ : undefined,
197
+ // If we have user or role we need to check the acl
198
+ userId || roleId
199
+ ? {
200
+ OR: [
201
+ {
202
+ acl: { equalTo: null },
203
+ },
204
+ userId
205
+ ? {
206
+ acl: {
207
+ users: {
208
+ contains: {
209
+ userId,
210
+ [operation]: true,
211
+ },
212
+ },
213
+ },
214
+ }
215
+ : undefined,
216
+ roleId
217
+ ? {
218
+ AND: [
219
+ {
220
+ acl: {
221
+ users: {
222
+ notContains: {
223
+ userId,
224
+ },
225
+ },
226
+ },
227
+ },
228
+ {
229
+ acl: {
230
+ roles: {
231
+ contains: {
232
+ roleId,
233
+ [operation]: true,
234
+ },
235
+ },
236
+ },
237
+ },
238
+ ],
239
+ }
240
+ : undefined,
241
+ ].filter(notEmpty),
242
+ }
243
+ : undefined,
244
+ ].filter(notEmpty),
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Private helper to load a single object for hooks (skips hooks to avoid recursion)
250
+ */
251
+ _loadObjectForHooks(className: keyof T['types'], context: WabeContext<T>) {
252
+ return (id: string) =>
253
+ this.getObject({
254
+ className,
255
+ context: contextWithRoot(context),
256
+ id,
257
+ _skipHooks: true,
258
+ })
259
+ }
260
+
261
+ /**
262
+ * Private helper to load multiple objects for hooks (skips hooks to avoid recursion)
263
+ */
264
+ _loadObjectsForHooks(className: keyof T['types'], context: WabeContext<T>) {
265
+ return ({ where, ids }: { where?: WhereType<DevWabeTypes, any>; ids: string[] }) =>
266
+ this.getObjects({
267
+ className,
268
+ context: contextWithRoot(context),
269
+ // @ts-expect-error
270
+ where: where ? where : { id: { in: ids } },
271
+ _skipHooks: true,
272
+ })
273
+ }
274
+
275
+ /**
276
+ * Generic executor for single object operations (create, update, delete, read)
277
+ * Encapsulates the hook lifecycle: Init -> Before -> Adapter -> After
278
+ */
279
+ async _executeSingleOperationWithHooks<
280
+ K extends keyof T['types'],
281
+ U extends keyof T['types'][K],
282
+ >({
283
+ operationTypeBefore,
284
+ operationTypeAfter,
285
+ className,
286
+ context,
287
+ data,
288
+ select,
289
+ id,
290
+ adapterCallback,
291
+ inputObject,
292
+ }: {
293
+ operationTypeBefore: OperationType
294
+ operationTypeAfter: OperationType
295
+ className: K
296
+ context: WabeContext<T>
297
+ data?: any
298
+ select?: Select
299
+ id?: string
300
+ inputObject?: OutputType<T, K, U>
301
+ adapterCallback: (newData: any) => Promise<OutputType<T, K, U> | { id: string } | null>
302
+ }) {
303
+ const hook = initializeHook({
304
+ className,
305
+ context,
306
+ newData: data,
307
+ // @ts-expect-error
308
+ select,
309
+ objectLoader: this._loadObjectForHooks(className, context),
310
+ objectsLoader: this._loadObjectsForHooks(className, context),
311
+ })
312
+
313
+ const { newData, object: objectFromHook } = await hook.runOnSingleObject({
314
+ operationType: operationTypeBefore,
315
+ id,
316
+
317
+ object: inputObject,
318
+ })
319
+
320
+ const res = await adapterCallback(newData || data)
321
+
322
+ if (!res) return null
323
+
324
+ // If the operation is delete, we use the objectFromHook (snapshot before delete)
325
+ // Otherwise we use the result from adapter (which might be just { id })
326
+ // loading the full object if needed happens inside runOnSingleObject if 'id' is passed
327
+ await hook.runOnSingleObject({
328
+ operationType: operationTypeAfter,
329
+ id: res.id,
330
+
331
+ originalObject: objectFromHook,
332
+ })
333
+
334
+ return res
335
+ }
336
+
337
+ _getFinalObjectWithPointerAndRelation({
338
+ pointers,
339
+ context,
340
+ originClassName,
341
+ object,
342
+ _skipHooks,
343
+ }: {
344
+ originClassName: string
345
+ pointers: Record<string, { className: string; select: Select }>
346
+ context: WabeContext<any>
347
+ object: Record<string, any>
348
+ _skipHooks?: boolean
349
+ }) {
350
+ return Object.entries(pointers).reduce(
351
+ async (acc, [pointerField, { className: currentClassName, select: currentSelect }]) => {
352
+ const accObject = await acc
353
+
354
+ const isPointer = this._isFieldOfType(
355
+ originClassName,
356
+ pointerField,
357
+ 'Pointer',
358
+ context,
359
+ currentClassName,
360
+ )
361
+
362
+ if (isPointer) {
363
+ if (!object[pointerField])
364
+ return {
365
+ ...accObject,
366
+ [pointerField]: null,
367
+ }
368
+
369
+ const objectOfPointerClass = await this.getObject({
370
+ className: currentClassName,
371
+ id: object[pointerField],
372
+ context,
373
+ // @ts-expect-error
374
+ select: currentSelect,
375
+ _skipHooks,
376
+ })
377
+
378
+ return {
379
+ ...accObject,
380
+ [pointerField]: objectOfPointerClass,
381
+ }
382
+ }
383
+
384
+ const isRelation = this._isFieldOfType(
385
+ originClassName,
386
+ pointerField,
387
+ 'Relation',
388
+ context,
389
+ currentClassName,
390
+ )
391
+
392
+ if (isRelation && object[pointerField]) {
393
+ const selectWithoutTotalCount = Object.entries(currentSelect || {}).reduce(
394
+ (acc2, [key, value]) => {
395
+ if (key === 'totalCount') return acc2
396
+
397
+ return {
398
+ ...acc2,
399
+ [key]: value,
400
+ }
401
+ },
402
+ {} as Record<string, any>,
403
+ )
404
+
405
+ const relationObjects = await this.getObjects({
406
+ className: currentClassName,
407
+ select: selectWithoutTotalCount,
408
+ // @ts-expect-error
409
+ where: { id: { in: object[pointerField] } },
410
+ context,
411
+ _skipHooks,
412
+ })
413
+
414
+ return {
415
+ ...accObject,
416
+ [pointerField]: context.isGraphQLCall
417
+ ? {
418
+ totalCount: relationObjects.length,
419
+ edges: relationObjects.map((object: any) => ({
420
+ node: object,
421
+ })),
422
+ }
423
+ : relationObjects,
424
+ }
425
+ }
426
+
427
+ return accObject
428
+ },
429
+ Promise.resolve({} as Record<string, any>),
430
+ )
431
+ }
432
+
433
+ async close() {
434
+ await this.adapter.close()
435
+ }
436
+
437
+ createClassIfNotExist(className: string, schema: SchemaInterface<T>): Promise<any> {
438
+ return this.adapter.createClassIfNotExist(className, schema)
439
+ }
440
+
441
+ initializeDatabase(schema: SchemaInterface<T>): Promise<void> {
442
+ return this.adapter.initializeDatabase(schema)
443
+ }
444
+
445
+ async count<K extends keyof T['types']>({
446
+ className,
447
+ context,
448
+ where,
449
+ }: CountOptions<T, K>): Promise<number> {
450
+ const whereWithACLCondition = this._buildWhereWithACL(where || {}, context, 'read')
451
+
452
+ const hook = initializeHook({
453
+ className,
454
+ context,
455
+ select: {},
456
+ })
457
+
458
+ await hook?.runOnSingleObject({
459
+ operationType: OperationType.BeforeRead,
460
+ })
461
+
462
+ const count = await this.adapter.count({
463
+ className,
464
+ context,
465
+ where: whereWithACLCondition,
466
+ })
467
+
468
+ await hook?.runOnSingleObject({
469
+ operationType: OperationType.AfterRead,
470
+ })
471
+
472
+ return count
473
+ }
474
+
475
+ async clearDatabase(): Promise<void> {
476
+ await this.adapter.clearDatabase()
477
+ }
478
+
479
+ async getObject<K extends keyof T['types'], U extends keyof T['types'][K]>({
480
+ select,
481
+ className,
482
+ context,
483
+ _skipHooks,
484
+ id,
485
+ where,
486
+ }: GetObjectOptions<T, K, U>): Promise<OutputType<T, K, U>> {
487
+ const { pointers, selectWithoutPointers } = this._getSelectMinusPointersAndRelations({
488
+ className,
489
+ context,
490
+ select: select as SelectWithObject,
491
+ })
492
+
493
+ const hook = !_skipHooks
494
+ ? initializeHook({
495
+ className,
496
+ context,
497
+ select: selectWithoutPointers,
498
+ objectLoader: this._loadObjectForHooks(className, context),
499
+ objectsLoader: this._loadObjectsForHooks(className, context),
500
+ })
501
+ : undefined
502
+
503
+ await hook?.runOnSingleObject({
504
+ operationType: OperationType.BeforeRead,
505
+ id,
506
+ })
507
+
508
+ const whereWithACLCondition = this._buildWhereWithACL(where || {}, context, 'read')
509
+
510
+ const selectWithPointersAndRelationsToGetId = Object.keys(pointers).reduce((acc, fieldName) => {
511
+ acc[fieldName] = true
512
+
513
+ return acc
514
+ }, selectWithoutPointers)
515
+
516
+ const objectToReturn = await this.adapter.getObject({
517
+ className,
518
+ id,
519
+ context: contextWithRoot(context),
520
+ // @ts-expect-error
521
+ select: !select ? undefined : selectWithPointersAndRelationsToGetId,
522
+ where: whereWithACLCondition,
523
+ })
524
+
525
+ const finalObject = {
526
+ ...objectToReturn,
527
+ ...(await this._getFinalObjectWithPointerAndRelation({
528
+ context,
529
+ // @ts-expect-error
530
+ originClassName: className,
531
+ pointers,
532
+ // @ts-expect-error
533
+ object: objectToReturn,
534
+ _skipHooks,
535
+ })),
536
+ }
537
+
538
+ const afterReadResult = await hook?.runOnSingleObject({
539
+ operationType: OperationType.AfterRead,
540
+ id,
541
+ // @ts-expect-error
542
+ object: finalObject,
543
+ })
544
+
545
+ return afterReadResult?.object || finalObject
546
+ }
547
+
548
+ async getObjects<
549
+ K extends keyof T['types'],
550
+ U extends keyof T['types'][K],
551
+ W extends keyof T['types'][K],
552
+ >({
553
+ className,
554
+ select,
555
+ context,
556
+ where,
557
+ _skipHooks,
558
+ first,
559
+ offset,
560
+ order,
561
+ }: GetObjectsOptions<T, K, U, W>): Promise<OutputType<T, K, W>[]> {
562
+ const { pointers, selectWithoutPointers } = this._getSelectMinusPointersAndRelations({
563
+ className,
564
+ context,
565
+ select: select as SelectWithObject,
566
+ })
567
+
568
+ const whereWithPointer = await this._getWhereObjectWithPointerOrRelation(
569
+ className,
570
+ where || {},
571
+ context,
572
+ )
573
+
574
+ const whereWithACLCondition = this._buildWhereWithACL(whereWithPointer || {}, context, 'read')
575
+
576
+ const selectWithPointersAndRelationsToGetId = Object.keys(pointers).reduce((acc, fieldName) => {
577
+ acc[fieldName] = true
578
+
579
+ return acc
580
+ }, selectWithoutPointers)
581
+
582
+ const hook = !_skipHooks
583
+ ? initializeHook({
584
+ className,
585
+ select: selectWithoutPointers,
586
+ context,
587
+ objectLoader: this._loadObjectForHooks(className, context),
588
+ objectsLoader: this._loadObjectsForHooks(className, context),
589
+ })
590
+ : undefined
591
+
592
+ await hook?.runOnMultipleObjects({
593
+ operationType: OperationType.BeforeRead,
594
+ where: whereWithACLCondition,
595
+ })
596
+
597
+ const objectsToReturn = await this.adapter.getObjects({
598
+ className,
599
+ context: contextWithRoot(context),
600
+ first,
601
+ offset,
602
+ where: whereWithACLCondition,
603
+ // @ts-expect-error
604
+ select: !select ? undefined : selectWithPointersAndRelationsToGetId,
605
+ order,
606
+ })
607
+
608
+ const objectsWithPointers = await Promise.all(
609
+ objectsToReturn.map(async (object) => {
610
+ return {
611
+ ...object,
612
+ ...(await this._getFinalObjectWithPointerAndRelation({
613
+ // @ts-expect-error
614
+ object,
615
+ context,
616
+ // @ts-expect-error
617
+ originClassName: className,
618
+ pointers,
619
+ _skipHooks,
620
+ })),
621
+ }
622
+ }),
623
+ )
624
+
625
+ const afterReadResults = await hook?.runOnMultipleObjects({
626
+ operationType: OperationType.AfterRead,
627
+ // @ts-expect-error
628
+ objects: objectsWithPointers,
629
+ })
630
+
631
+ return (afterReadResults?.objects || objectsWithPointers) as unknown as Promise<
632
+ OutputType<T, K, W>[]
633
+ >
634
+ }
635
+
636
+ async createObject<
637
+ K extends keyof T['types'],
638
+ U extends keyof T['types'][K],
639
+ W extends keyof T['types'][K],
640
+ >({
641
+ className,
642
+ context,
643
+ data,
644
+ select,
645
+ }: CreateObjectOptions<T, K, U, W>): Promise<OutputType<T, K, W>> {
646
+ // Here data.file is null but should not be
647
+
648
+ const result = await this._executeSingleOperationWithHooks<K, W>({
649
+ operationTypeBefore: OperationType.BeforeCreate,
650
+ operationTypeAfter: OperationType.AfterCreate,
651
+ className,
652
+ context,
653
+ data,
654
+ select: select as Select,
655
+ adapterCallback: async (newData) => {
656
+ const res = await this.adapter.createObject({
657
+ className,
658
+ context,
659
+ select,
660
+ data: newData || data,
661
+ })
662
+
663
+ return { id: res.id }
664
+ },
665
+ })
666
+
667
+ const res = result as { id: string }
668
+
669
+ if (select && Object.keys(select).length === 0) return null
670
+
671
+ return this.getObject({
672
+ className,
673
+ context: contextWithRoot(context),
674
+ select: selectFieldsWithoutPrivateFields(select),
675
+ id: res.id,
676
+ })
677
+ }
678
+
679
+ async createObjects<
680
+ K extends keyof T['types'],
681
+ U extends keyof T['types'][K],
682
+ W extends keyof T['types'][K],
683
+ X extends keyof T['types'][K],
684
+ >({
685
+ data,
686
+ select,
687
+ className,
688
+ context,
689
+ first,
690
+ offset,
691
+ order,
692
+ }: CreateObjectsOptions<T, K, U, W, X>): Promise<OutputType<T, K, W>[]> {
693
+ if (data.length === 0) return []
694
+
695
+ const hooks = await Promise.all(
696
+ data.map((newData) =>
697
+ initializeHook({
698
+ className,
699
+ context,
700
+ newData,
701
+ // @ts-expect-error
702
+ select,
703
+ objectLoader: this._loadObjectForHooks(className, context),
704
+ objectsLoader: this._loadObjectsForHooks(className, context),
705
+ }),
706
+ ),
707
+ )
708
+
709
+ const arrayOfComputedData = (
710
+ await Promise.all(
711
+ hooks.map(
712
+ async (hook) =>
713
+ (
714
+ await hook.runOnMultipleObjects({
715
+ operationType: OperationType.BeforeCreate,
716
+ })
717
+ )?.newData[0],
718
+ ),
719
+ )
720
+ ).filter(notEmpty)
721
+
722
+ const listOfIds = await this.adapter.createObjects({
723
+ className,
724
+ select,
725
+ context,
726
+ data: arrayOfComputedData,
727
+ first,
728
+ offset,
729
+ order,
730
+ })
731
+
732
+ const ids = listOfIds.map(({ id }) => id)
733
+
734
+ await Promise.all(
735
+ hooks.map((hook) =>
736
+ hook.runOnMultipleObjects({
737
+ operationType: OperationType.AfterCreate,
738
+ ids,
739
+ }),
740
+ ),
741
+ )
742
+
743
+ if (select && Object.keys(select).length === 0) return []
744
+
745
+ return this.getObjects({
746
+ className,
747
+ context: contextWithRoot(context),
748
+ select,
749
+ // @ts-expect-error
750
+ where: { id: { in: ids } },
751
+ first,
752
+ offset,
753
+ order,
754
+ })
755
+ }
756
+
757
+ async updateObject<
758
+ K extends keyof T['types'],
759
+ U extends keyof T['types'][K],
760
+ W extends keyof T['types'][K],
761
+ >({
762
+ id,
763
+ className,
764
+ context,
765
+ data,
766
+ select,
767
+ _skipHooks,
768
+ }: UpdateObjectOptions<T, K, U, W>): Promise<OutputType<T, K, W>> {
769
+ if (_skipHooks) {
770
+ const whereWithACLCondition = this._buildWhereWithACL({}, context, 'write')
771
+
772
+ return this.adapter.updateObject({
773
+ className,
774
+ select,
775
+ id,
776
+ context,
777
+ data,
778
+ where: whereWithACLCondition,
779
+ }) as Promise<OutputType<T, K, W>>
780
+ }
781
+
782
+ await this._executeSingleOperationWithHooks<K, W>({
783
+ operationTypeBefore: OperationType.BeforeUpdate,
784
+ operationTypeAfter: OperationType.AfterUpdate,
785
+ className,
786
+ context,
787
+ data,
788
+ id,
789
+ select: select as Select,
790
+ adapterCallback: async (newData) => {
791
+ const whereWithACLCondition = this._buildWhereWithACL({}, context, 'write')
792
+
793
+ await this.adapter.updateObject({
794
+ className,
795
+ select,
796
+ id,
797
+ context,
798
+ data: newData || data,
799
+ where: whereWithACLCondition,
800
+ })
801
+
802
+ return { id }
803
+ },
804
+ })
805
+
806
+ if (select && Object.keys(select).length === 0) return null
807
+
808
+ return this.getObject({
809
+ className,
810
+ context,
811
+ select,
812
+ id,
813
+ })
814
+ }
815
+
816
+ async updateObjects<
817
+ K extends keyof T['types'],
818
+ U extends keyof T['types'][K],
819
+ W extends keyof T['types'][K],
820
+ X extends keyof T['types'][K],
821
+ >({
822
+ className,
823
+ where,
824
+ context,
825
+ select,
826
+ data,
827
+ first,
828
+ offset,
829
+ order,
830
+ _skipHooks,
831
+ }: UpdateObjectsOptions<T, K, U, W, X>): Promise<OutputType<T, K, W>[]> {
832
+ const whereObject = await this._getWhereObjectWithPointerOrRelation(
833
+ className,
834
+ where || {},
835
+ context,
836
+ )
837
+
838
+ const hook = !_skipHooks
839
+ ? initializeHook({
840
+ className,
841
+ context,
842
+ newData: data,
843
+ // @ts-expect-error
844
+ select,
845
+ objectLoader: this._loadObjectForHooks(className, context),
846
+ objectsLoader: this._loadObjectsForHooks(className, context),
847
+ })
848
+ : undefined
849
+
850
+ const whereWithACLCondition = this._buildWhereWithACL(whereObject, context, 'write')
851
+
852
+ const resultsAfterBeforeUpdate = await hook?.runOnMultipleObjects({
853
+ operationType: OperationType.BeforeUpdate,
854
+ where: whereWithACLCondition,
855
+ })
856
+
857
+ const objects = await this.adapter.updateObjects({
858
+ className,
859
+ context,
860
+ select,
861
+ data: resultsAfterBeforeUpdate?.newData[0] || data,
862
+ where: whereWithACLCondition,
863
+ first,
864
+ offset,
865
+ order,
866
+ })
867
+
868
+ const objectsId = objects.map((object) => object?.id).filter(notEmpty)
869
+
870
+ await hook?.runOnMultipleObjects({
871
+ operationType: OperationType.AfterUpdate,
872
+ ids: objectsId,
873
+ originalObjects: resultsAfterBeforeUpdate?.objects || [],
874
+ })
875
+
876
+ if (select && Object.keys(select).length === 0) return []
877
+
878
+ return this.getObjects({
879
+ className,
880
+ context,
881
+ select,
882
+ // @ts-expect-error
883
+ where: { id: { in: objectsId } },
884
+ first,
885
+ offset,
886
+ order,
887
+ })
888
+ }
889
+
890
+ async deleteObject<K extends keyof T['types'], U extends keyof T['types'][K]>({
891
+ context,
892
+ className,
893
+ id,
894
+ select,
895
+ }: DeleteObjectOptions<T, K, U>): Promise<OutputType<T, K, U>> {
896
+ const result = (await this._executeSingleOperationWithHooks<K, U>({
897
+ operationTypeBefore: OperationType.BeforeDelete,
898
+ operationTypeAfter: OperationType.AfterDelete,
899
+ className,
900
+ context,
901
+ id,
902
+ select: select as Select,
903
+ adapterCallback: async (_newData) => {
904
+ const whereWithACLCondition = this._buildWhereWithACL({}, context, 'write')
905
+
906
+ // We need to fetch the object before deleting it if we want to return it
907
+ // But executeSingleOperationWithHooks already fetched it in runOnSingleObject if an id is present
908
+ // Wait, runOnSingleObject fetches it for the hook context 'object', but does not return it unless we use it.
909
+ // However, if we utilize _executeSingleOperationWithHooks, the 'object' from before-hook is kept.
910
+ // But _executeSingleOperationWithHooks logic for delete is slightly different regarding return value:
911
+ // Delete usually returns the deleted object.
912
+ // Update: My abstraction returns 'res' from adapterCallback.
913
+ // So I need to return the object here.
914
+
915
+ let objectBeforeDelete = null
916
+
917
+ if (select && Object.keys(select).length > 0)
918
+ objectBeforeDelete = await this.getObject({
919
+ className,
920
+ context,
921
+ select,
922
+ id,
923
+ })
924
+
925
+ await this.adapter.deleteObject({
926
+ className,
927
+ context,
928
+ id,
929
+
930
+ where: whereWithACLCondition,
931
+ })
932
+
933
+ return objectBeforeDelete || { id }
934
+ },
935
+ })) as unknown as OutputType<T, K, U>
936
+
937
+ if (select && Object.keys(select).length === 0) return null as any
938
+
939
+ return result
940
+ }
941
+
942
+ async deleteObjects<
943
+ K extends keyof T['types'],
944
+ U extends keyof T['types'][K],
945
+ W extends keyof T['types'][K],
946
+ >({
947
+ className,
948
+ context,
949
+ select,
950
+ where,
951
+ first,
952
+ offset,
953
+ order,
954
+ }: DeleteObjectsOptions<T, K, U, W>): Promise<OutputType<T, K, W>[]> {
955
+ const whereObject = await this._getWhereObjectWithPointerOrRelation(
956
+ className,
957
+ where || {},
958
+ context,
959
+ )
960
+
961
+ const hook = initializeHook({
962
+ className,
963
+ context,
964
+ // @ts-expect-error
965
+ select,
966
+ objectLoader: this._loadObjectForHooks(className, context),
967
+ objectsLoader: this._loadObjectsForHooks(className, context),
968
+ })
969
+
970
+ const whereWithACLCondition = this._buildWhereWithACL(whereObject, context, 'write')
971
+
972
+ let objectsBeforeDelete: OutputType<T, K, W>[] = []
973
+
974
+ if (select && Object.keys(select).length > 0)
975
+ objectsBeforeDelete = await this.getObjects({
976
+ className,
977
+ where,
978
+ select,
979
+ context,
980
+ first,
981
+ offset,
982
+ order,
983
+ })
984
+
985
+ const resultOfBeforeDelete = await hook.runOnMultipleObjects({
986
+ operationType: OperationType.BeforeDelete,
987
+ where: whereWithACLCondition,
988
+ })
989
+
990
+ await this.adapter.deleteObjects({
991
+ className,
992
+ context,
993
+ select,
994
+ first,
995
+ offset,
996
+ where: whereWithACLCondition,
997
+ order,
998
+ })
999
+
1000
+ await hook.runOnMultipleObjects({
1001
+ operationType: OperationType.AfterDelete,
1002
+ originalObjects: resultOfBeforeDelete.objects,
1003
+ })
1004
+
1005
+ return objectsBeforeDelete
1006
+ }
1007
+ }