wabe 0.6.12 → 0.6.14

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 (156) hide show
  1. package/dist/database/DatabaseController.d.ts +2 -0
  2. package/dist/file/FileDevAdapter.d.ts +1 -0
  3. package/dist/graphql/pointerAndRelationFunction.d.ts +6 -0
  4. package/dist/index.js +3827 -3541
  5. package/dist/schema/Schema.d.ts +2 -2
  6. package/dist/server/generateCodegen.d.ts +10 -0
  7. package/dist/server/index.d.ts +2 -1
  8. package/dist/utils/objectKeys.d.ts +1 -0
  9. package/package.json +7 -4
  10. package/dev/index.ts +0 -215
  11. package/dist/schema/resolvers/sendEmail.d.ts +0 -1
  12. package/generated/schema.graphql +0 -1945
  13. package/generated/wabe.ts +0 -448
  14. package/src/authentication/OTP.test.ts +0 -69
  15. package/src/authentication/OTP.ts +0 -64
  16. package/src/authentication/Session.test.ts +0 -629
  17. package/src/authentication/Session.ts +0 -517
  18. package/src/authentication/cookies.ts +0 -10
  19. package/src/authentication/defaultAuthentication.ts +0 -209
  20. package/src/authentication/index.ts +0 -4
  21. package/src/authentication/interface.ts +0 -177
  22. package/src/authentication/oauth/GitHub.test.ts +0 -91
  23. package/src/authentication/oauth/GitHub.ts +0 -121
  24. package/src/authentication/oauth/Google.test.ts +0 -91
  25. package/src/authentication/oauth/Google.ts +0 -101
  26. package/src/authentication/oauth/Oauth2Client.test.ts +0 -219
  27. package/src/authentication/oauth/Oauth2Client.ts +0 -135
  28. package/src/authentication/oauth/index.ts +0 -2
  29. package/src/authentication/oauth/utils.test.ts +0 -33
  30. package/src/authentication/oauth/utils.ts +0 -27
  31. package/src/authentication/providers/EmailOTP.test.ts +0 -127
  32. package/src/authentication/providers/EmailOTP.ts +0 -95
  33. package/src/authentication/providers/EmailPassword.test.ts +0 -263
  34. package/src/authentication/providers/EmailPassword.ts +0 -138
  35. package/src/authentication/providers/EmailPasswordSRP.test.ts +0 -208
  36. package/src/authentication/providers/EmailPasswordSRP.ts +0 -191
  37. package/src/authentication/providers/GitHub.ts +0 -24
  38. package/src/authentication/providers/Google.ts +0 -24
  39. package/src/authentication/providers/OAuth.test.ts +0 -185
  40. package/src/authentication/providers/OAuth.ts +0 -106
  41. package/src/authentication/providers/PhonePassword.test.ts +0 -221
  42. package/src/authentication/providers/PhonePassword.ts +0 -136
  43. package/src/authentication/providers/QRCodeOTP.test.ts +0 -77
  44. package/src/authentication/providers/QRCodeOTP.ts +0 -69
  45. package/src/authentication/providers/index.ts +0 -6
  46. package/src/authentication/resolvers/refreshResolver.test.ts +0 -30
  47. package/src/authentication/resolvers/refreshResolver.ts +0 -19
  48. package/src/authentication/resolvers/signInWithResolver.inte.test.ts +0 -59
  49. package/src/authentication/resolvers/signInWithResolver.test.ts +0 -306
  50. package/src/authentication/resolvers/signInWithResolver.ts +0 -106
  51. package/src/authentication/resolvers/signOutResolver.test.ts +0 -38
  52. package/src/authentication/resolvers/signOutResolver.ts +0 -18
  53. package/src/authentication/resolvers/signUpWithResolver.test.ts +0 -180
  54. package/src/authentication/resolvers/signUpWithResolver.ts +0 -68
  55. package/src/authentication/resolvers/verifyChallenge.test.ts +0 -230
  56. package/src/authentication/resolvers/verifyChallenge.ts +0 -78
  57. package/src/authentication/roles.test.ts +0 -49
  58. package/src/authentication/roles.ts +0 -40
  59. package/src/authentication/security.ts +0 -278
  60. package/src/authentication/utils.test.ts +0 -97
  61. package/src/authentication/utils.ts +0 -39
  62. package/src/cache/InMemoryCache.test.ts +0 -62
  63. package/src/cache/InMemoryCache.ts +0 -45
  64. package/src/cron/index.test.ts +0 -17
  65. package/src/cron/index.ts +0 -43
  66. package/src/database/DatabaseController.test.ts +0 -613
  67. package/src/database/DatabaseController.ts +0 -1415
  68. package/src/database/index.test.ts +0 -1551
  69. package/src/database/index.ts +0 -9
  70. package/src/database/interface.ts +0 -308
  71. package/src/email/DevAdapter.ts +0 -7
  72. package/src/email/EmailController.test.ts +0 -29
  73. package/src/email/EmailController.ts +0 -13
  74. package/src/email/index.ts +0 -2
  75. package/src/email/interface.ts +0 -36
  76. package/src/email/templates/sendOtpCode.ts +0 -120
  77. package/src/file/FileController.ts +0 -28
  78. package/src/file/FileDevAdapter.ts +0 -51
  79. package/src/file/hookDeleteFile.ts +0 -25
  80. package/src/file/hookReadFile.ts +0 -66
  81. package/src/file/hookUploadFile.ts +0 -52
  82. package/src/file/index.test.ts +0 -1031
  83. package/src/file/index.ts +0 -2
  84. package/src/file/interface.ts +0 -63
  85. package/src/file/security.ts +0 -156
  86. package/src/graphql/GraphQLSchema.test.ts +0 -5099
  87. package/src/graphql/GraphQLSchema.ts +0 -886
  88. package/src/graphql/index.ts +0 -2
  89. package/src/graphql/parseGraphqlSchema.ts +0 -85
  90. package/src/graphql/parser.test.ts +0 -203
  91. package/src/graphql/parser.ts +0 -707
  92. package/src/graphql/pointerAndRelationFunction.ts +0 -191
  93. package/src/graphql/resolvers.ts +0 -464
  94. package/src/graphql/tests/aggregation.test.ts +0 -1115
  95. package/src/graphql/tests/e2e.test.ts +0 -590
  96. package/src/graphql/tests/scalars.test.ts +0 -250
  97. package/src/graphql/types.ts +0 -227
  98. package/src/hooks/HookObject.test.ts +0 -122
  99. package/src/hooks/HookObject.ts +0 -165
  100. package/src/hooks/authentication.ts +0 -67
  101. package/src/hooks/createUser.test.ts +0 -77
  102. package/src/hooks/createUser.ts +0 -10
  103. package/src/hooks/defaultFields.test.ts +0 -176
  104. package/src/hooks/defaultFields.ts +0 -32
  105. package/src/hooks/deleteSession.test.ts +0 -181
  106. package/src/hooks/deleteSession.ts +0 -20
  107. package/src/hooks/hashFieldHook.test.ts +0 -152
  108. package/src/hooks/hashFieldHook.ts +0 -89
  109. package/src/hooks/index.test.ts +0 -258
  110. package/src/hooks/index.ts +0 -420
  111. package/src/hooks/permissions.test.ts +0 -412
  112. package/src/hooks/permissions.ts +0 -93
  113. package/src/hooks/protected.test.ts +0 -551
  114. package/src/hooks/protected.ts +0 -74
  115. package/src/hooks/searchableFields.test.ts +0 -147
  116. package/src/hooks/searchableFields.ts +0 -86
  117. package/src/hooks/session.test.ts +0 -134
  118. package/src/hooks/session.ts +0 -76
  119. package/src/hooks/setEmail.test.ts +0 -216
  120. package/src/hooks/setEmail.ts +0 -33
  121. package/src/hooks/setupAcl.test.ts +0 -618
  122. package/src/hooks/setupAcl.ts +0 -25
  123. package/src/hooks/virtualFields.test.ts +0 -228
  124. package/src/hooks/virtualFields.ts +0 -48
  125. package/src/index.ts +0 -9
  126. package/src/schema/Schema.test.ts +0 -482
  127. package/src/schema/Schema.ts +0 -839
  128. package/src/schema/defaultResolvers.ts +0 -93
  129. package/src/schema/index.ts +0 -1
  130. package/src/schema/resolvers/meResolver.test.ts +0 -62
  131. package/src/schema/resolvers/meResolver.ts +0 -10
  132. package/src/schema/resolvers/resetPassword.test.ts +0 -341
  133. package/src/schema/resolvers/resetPassword.ts +0 -63
  134. package/src/schema/resolvers/sendEmail.test.ts +0 -118
  135. package/src/schema/resolvers/sendEmail.ts +0 -21
  136. package/src/schema/resolvers/sendOtpCode.test.ts +0 -141
  137. package/src/schema/resolvers/sendOtpCode.ts +0 -52
  138. package/src/security.test.ts +0 -4136
  139. package/src/server/defaultSessionHandler.test.ts +0 -62
  140. package/src/server/defaultSessionHandler.ts +0 -104
  141. package/src/server/generateCodegen.ts +0 -433
  142. package/src/server/index.test.ts +0 -843
  143. package/src/server/index.ts +0 -336
  144. package/src/server/interface.ts +0 -11
  145. package/src/server/routes/authHandler.ts +0 -171
  146. package/src/server/routes/index.ts +0 -48
  147. package/src/utils/crypto.test.ts +0 -41
  148. package/src/utils/crypto.ts +0 -105
  149. package/src/utils/database.ts +0 -8
  150. package/src/utils/export.ts +0 -12
  151. package/src/utils/helper.ts +0 -204
  152. package/src/utils/index.test.ts +0 -11
  153. package/src/utils/index.ts +0 -196
  154. package/src/utils/preload.ts +0 -8
  155. package/src/utils/testHelper.ts +0 -124
  156. package/tsconfig.json +0 -32
@@ -1,1415 +0,0 @@
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 {
9
- type CountOptions,
10
- type CreateObjectOptions,
11
- type CreateObjectsOptions,
12
- type DatabaseAdapter,
13
- type DeleteObjectOptions,
14
- type DeleteObjectsOptions,
15
- type GetObjectOptions,
16
- type GetObjectsOptions,
17
- type OutputType,
18
- type UpdateObjectOptions,
19
- type UpdateObjectsOptions,
20
- type WhereType,
21
- } from './interface'
22
-
23
- export type Select = Record<string, boolean>
24
- type SelectWithObject = Record<string, object | boolean>
25
-
26
- const scalarWhereOperators = new Set([
27
- 'equalTo',
28
- 'notEqualTo',
29
- 'greaterThan',
30
- 'lessThan',
31
- 'greaterThanOrEqualTo',
32
- 'lessThanOrEqualTo',
33
- 'in',
34
- 'notIn',
35
- 'contains',
36
- 'notContains',
37
- 'exists',
38
- ])
39
-
40
- const isScalarWhereFilter = (value: unknown): boolean =>
41
- value !== null &&
42
- typeof value === 'object' &&
43
- !Array.isArray(value) &&
44
- Object.keys(value).some((key) => scalarWhereOperators.has(key))
45
-
46
- type RuntimeVirtualField = {
47
- type: 'Virtual'
48
- dependsOn: string[]
49
- callback: (object: Record<string, unknown>) => unknown
50
- }
51
-
52
- const isVirtualField = (field: unknown): field is RuntimeVirtualField => {
53
- if (!field || typeof field !== 'object') return false
54
-
55
- if (!('type' in field) || field.type !== 'Virtual') return false
56
-
57
- return true
58
- }
59
-
60
- export class DatabaseController<T extends WabeTypes> {
61
- public adapter: DatabaseAdapter<T>
62
-
63
- constructor(adapter: DatabaseAdapter<T>) {
64
- this.adapter = adapter
65
- }
66
-
67
- /**
68
- * Get a class definition from the schema by name (case-insensitive)
69
- */
70
- _getClass(className: string | keyof T['types'], context: WabeContext<T>) {
71
- return context.wabe.config.schema?.classes?.find(
72
- (c) => c.name.toLowerCase() === String(className).toLowerCase(),
73
- )
74
- }
75
-
76
- /**
77
- * Get field type and target class information
78
- */
79
- _getFieldType(originClassName: string, fieldName: string, context: WabeContext<T>) {
80
- const realClass = this._getClass(originClassName, context)
81
- return realClass?.fields[fieldName] as { type: string; class?: string } | undefined
82
- }
83
-
84
- _getVirtualFieldsForClass(className: keyof T['types'], context: WabeContext<T>) {
85
- const currentClass = this._getClass(className, context)
86
-
87
- if (!currentClass) return {}
88
-
89
- const virtualFields: Record<string, RuntimeVirtualField> = {}
90
-
91
- for (const [fieldName, fieldDefinition] of Object.entries(currentClass.fields)) {
92
- if (!isVirtualField(fieldDefinition)) continue
93
-
94
- virtualFields[fieldName] = fieldDefinition
95
- }
96
-
97
- return virtualFields
98
- }
99
-
100
- _buildReadSelects({
101
- className,
102
- context,
103
- selectWithoutPointers,
104
- }: {
105
- className: keyof T['types']
106
- context: WabeContext<T>
107
- selectWithoutPointers: Select
108
- }) {
109
- const virtualFieldsByName = this._getVirtualFieldsForClass(className, context)
110
- const requestedVirtualFields: string[] = []
111
-
112
- const userSelect: Select = {}
113
-
114
- for (const [fieldName, selected] of Object.entries(selectWithoutPointers)) {
115
- if (!selected) continue
116
-
117
- const virtualField = virtualFieldsByName[fieldName]
118
-
119
- if (virtualField) {
120
- requestedVirtualFields.push(fieldName)
121
- continue
122
- }
123
-
124
- userSelect[fieldName] = true
125
- }
126
-
127
- const adapterSelect: Select = { ...userSelect }
128
-
129
- for (const virtualFieldName of requestedVirtualFields) {
130
- const virtualField = virtualFieldsByName[virtualFieldName]
131
-
132
- if (!virtualField) continue
133
-
134
- for (const dependencyField of virtualField.dependsOn) {
135
- const dependencyName = String(dependencyField)
136
-
137
- // Virtual dependencies are only useful for computation and must never reach adapters.
138
- if (virtualFieldsByName[dependencyName]) continue
139
-
140
- adapterSelect[dependencyName] = true
141
- }
142
- }
143
-
144
- return {
145
- userSelect,
146
- adapterSelect,
147
- }
148
- }
149
-
150
- _buildHookReadSelect({
151
- className,
152
- context,
153
- userSelect,
154
- selectWithoutPointers,
155
- }: {
156
- className: keyof T['types']
157
- context: WabeContext<T>
158
- userSelect: Select
159
- selectWithoutPointers: Select
160
- }): Select {
161
- const selectedVirtualFields = Object.keys(this._getVirtualFieldsForClass(className, context))
162
- .filter((fieldName) => !!selectWithoutPointers[fieldName])
163
- .map((fieldName) => [fieldName, true])
164
-
165
- return {
166
- ...userSelect,
167
- ...Object.fromEntries(selectedVirtualFields),
168
- }
169
- }
170
-
171
- _initializeReadHook<K extends keyof T['types']>({
172
- className,
173
- context,
174
- userSelect,
175
- selectWithoutPointers,
176
- _skipHooks,
177
- }: {
178
- className: K
179
- context: WabeContext<T>
180
- userSelect: Select
181
- selectWithoutPointers: Select
182
- _skipHooks?: boolean
183
- }) {
184
- if (_skipHooks) return undefined
185
-
186
- return initializeHook({
187
- className,
188
- context,
189
- select: this._buildHookReadSelect({
190
- className,
191
- context,
192
- userSelect,
193
- selectWithoutPointers,
194
- }),
195
- objectLoader: this._loadObjectForHooks(className, context),
196
- objectsLoader: this._loadObjectsForHooks(className, context),
197
- })
198
- }
199
-
200
- _buildSelectWithPointers({
201
- adapterSelect,
202
- pointers,
203
- }: {
204
- adapterSelect: Select
205
- pointers: Record<string, { className: string; select: Select }>
206
- }) {
207
- return Object.keys(pointers).reduce(
208
- (acc, fieldName) => {
209
- acc[fieldName] = true
210
- return acc
211
- },
212
- { ...adapterSelect },
213
- )
214
- }
215
-
216
- _isEmptySelect(select?: Record<string, unknown>): boolean {
217
- return !!select && Object.keys(select).length === 0
218
- }
219
-
220
- _projectObjectForUserSelect({
221
- object,
222
- select,
223
- }: {
224
- object: Record<string, any> | null | undefined
225
- select?: SelectWithObject
226
- }): any {
227
- if (!object) return object
228
- if (!select) return object
229
-
230
- const projectedObject: Record<string, any> = {}
231
-
232
- for (const [fieldName, selected] of Object.entries(select)) {
233
- if (!selected) continue
234
- if (!(fieldName in object)) continue
235
-
236
- projectedObject[fieldName] = object[fieldName]
237
- }
238
-
239
- return projectedObject
240
- }
241
-
242
- _stripVirtualFieldsFromPayload({
243
- className,
244
- context,
245
- payload,
246
- }: {
247
- className: keyof T['types']
248
- context: WabeContext<T>
249
- payload: unknown
250
- }): any {
251
- if (!payload || typeof payload !== 'object') return {}
252
-
253
- const virtualFields = this._getVirtualFieldsForClass(className, context)
254
-
255
- if (Object.keys(virtualFields).length === 0) return payload
256
-
257
- const filteredPayload: Record<string, unknown> = {}
258
-
259
- for (const [fieldName, value] of Object.entries(payload)) {
260
- if (virtualFields[fieldName]) continue
261
-
262
- filteredPayload[fieldName] = value
263
- }
264
-
265
- return filteredPayload
266
- }
267
-
268
- _stripVirtualFieldsFromSchema(schema: SchemaInterface<T>): SchemaInterface<T> {
269
- const classes = schema.classes?.map((classDefinition) => {
270
- const filteredFieldEntries = Object.entries(classDefinition.fields).filter(
271
- ([_fieldName, fieldDefinition]) => !isVirtualField(fieldDefinition),
272
- )
273
- const filteredFields = Object.fromEntries(filteredFieldEntries)
274
-
275
- const allowedFieldNames = new Set(Object.keys(filteredFields))
276
-
277
- const filteredIndexes = classDefinition.indexes?.filter((index) =>
278
- allowedFieldNames.has(index.field),
279
- )
280
-
281
- return {
282
- ...classDefinition,
283
- fields: filteredFields,
284
- indexes: filteredIndexes,
285
- }
286
- })
287
-
288
- return {
289
- ...schema,
290
- classes,
291
- }
292
- }
293
-
294
- _getSelectMinusPointersAndRelations({
295
- className,
296
- context,
297
- select,
298
- }: {
299
- className: keyof T['types']
300
- context: WabeContext<T>
301
- select?: SelectWithObject
302
- }): {
303
- pointers: Record<string, { className: string; select: Select }>
304
- selectWithoutPointers: Select
305
- } {
306
- const realClass = this._getClass(className, context)
307
-
308
- if (!realClass) throw new Error('Class not found in schema')
309
-
310
- if (!select) return { pointers: {}, selectWithoutPointers: {} }
311
-
312
- const pointers: Record<string, { className: string; select: Select }> = {}
313
- const selectWithoutPointers: Select = {}
314
-
315
- const selectEntries = Object.entries(
316
- context.isRoot ? select : selectFieldsWithoutPrivateFields(select),
317
- )
318
-
319
- for (const [fieldName, value] of selectEntries) {
320
- const field = realClass.fields[fieldName]
321
- const isPointerOrRelation = field?.type === 'Pointer' || field?.type === 'Relation'
322
-
323
- if (!isPointerOrRelation) {
324
- selectWithoutPointers[fieldName] = true
325
- } else {
326
- pointers[fieldName] = {
327
- className: (field as { class: string }).class,
328
- select: (value === true ? undefined : value) as Select,
329
- }
330
- }
331
- }
332
-
333
- return { pointers, selectWithoutPointers }
334
- }
335
-
336
- _isFieldOfType(
337
- originClassName: string,
338
- pointerField: string,
339
- expectedType: 'Pointer' | 'Relation',
340
- context: WabeContext<T>,
341
- currentClassName?: string,
342
- ): boolean {
343
- if (!currentClassName) return false
344
-
345
- const field = this._getFieldType(originClassName, pointerField, context)
346
- return (
347
- field?.type === expectedType && field.class?.toLowerCase() === currentClassName.toLowerCase()
348
- )
349
- }
350
-
351
- async _getWhereObjectWithPointerOrRelation<U extends keyof T['types']>(
352
- className: U,
353
- where: WhereType<T, U>,
354
- context: WabeContext<T>,
355
- ) {
356
- const whereKeys = Object.keys(where) as Array<keyof WhereType<T, U>>
357
-
358
- const realClass = this._getClass(className, context)
359
-
360
- const newWhereObject = await whereKeys.reduce(async (acc, whereKey) => {
361
- const currentAcc = await acc
362
-
363
- const typedWhereKey = whereKey as string
364
-
365
- const field = realClass?.fields[typedWhereKey]
366
-
367
- if (typedWhereKey === 'AND' || typedWhereKey === 'OR') {
368
- const newWhere = await Promise.all(
369
- (where[typedWhereKey] as any).map((whereObject: any) =>
370
- this._getWhereObjectWithPointerOrRelation(className, whereObject, context),
371
- ),
372
- )
373
-
374
- return {
375
- ...currentAcc,
376
- [typedWhereKey]: newWhere,
377
- }
378
- }
379
-
380
- if (field?.type !== 'Pointer' && field?.type !== 'Relation') return acc
381
-
382
- // @ts-expect-error
383
- const fieldTargetClass = field.class
384
-
385
- const relationValue = where[typedWhereKey]
386
-
387
- // Relation where can already be transformed (e.g. { in: [...] })
388
- // when reused across count/getObjects; keep scalar filters unchanged.
389
- if (field?.type === 'Relation' && isScalarWhereFilter(relationValue)) {
390
- return {
391
- ...currentAcc,
392
- [typedWhereKey]: relationValue,
393
- }
394
- }
395
-
396
- // For Relation: unwrap have/isEmpty structure
397
- let defaultWhere = relationValue
398
- if (field?.type === 'Relation' && relationValue) {
399
- // @ts-expect-error
400
- if (relationValue.isEmpty !== undefined) {
401
- // In storage, an empty relation can be either [] or an absent field.
402
- // Model both cases explicitly so the filter behaves consistently.
403
- // @ts-expect-error
404
- return relationValue.isEmpty === true
405
- ? {
406
- ...currentAcc,
407
- OR: [{ [typedWhereKey]: { equalTo: [] } }, { [typedWhereKey]: { exists: false } }],
408
- }
409
- : {
410
- ...currentAcc,
411
- AND: [
412
- { [typedWhereKey]: { exists: true } },
413
- { [typedWhereKey]: { notEqualTo: [] } },
414
- ],
415
- }
416
- }
417
-
418
- // @ts-expect-error
419
- if (relationValue.have)
420
- // @ts-expect-error
421
- defaultWhere = relationValue.have as typeof defaultWhere
422
- }
423
-
424
- const objects = await this.getObjects({
425
- className: fieldTargetClass,
426
- // @ts-expect-error
427
- select: { id: true },
428
- // @ts-expect-error
429
- where: defaultWhere,
430
- context,
431
- })
432
- // When no objects match, use impossible condition to return no results
433
- const relationWhere =
434
- objects.length > 0
435
- ? {
436
- in: objects.map((object) => object?.id).filter(notEmpty),
437
- }
438
- : { equalTo: '__no_match__' }
439
- return {
440
- ...currentAcc,
441
- [typedWhereKey]: relationWhere,
442
- }
443
- }, Promise.resolve({}))
444
-
445
- return {
446
- ...where,
447
- ...newWhereObject,
448
- }
449
- }
450
-
451
- _buildWhereWithACL<K extends keyof T['types']>(
452
- where: WhereType<T, K>,
453
- context: WabeContext<T>,
454
- operation: 'write' | 'read',
455
- ): WhereType<T, K> {
456
- if (context.isRoot) return where
457
-
458
- const roleId = context.user?.role?.id
459
- const userId = context.user?.id
460
-
461
- const aclNullCondition = {
462
- acl: { equalTo: null },
463
- }
464
- const aclUserCondition = userId
465
- ? {
466
- acl: {
467
- users: {
468
- contains: {
469
- userId,
470
- [operation]: true,
471
- },
472
- },
473
- },
474
- }
475
- : undefined
476
- const aclRoleCondition = roleId
477
- ? {
478
- AND: [
479
- {
480
- acl: {
481
- users: {
482
- notContains: {
483
- userId,
484
- },
485
- },
486
- },
487
- },
488
- {
489
- acl: {
490
- roles: {
491
- contains: {
492
- roleId,
493
- [operation]: true,
494
- },
495
- },
496
- },
497
- },
498
- ],
499
- }
500
- : undefined
501
-
502
- const aclForUnauthenticated = !userId ? aclNullCondition : undefined
503
- const aclForUserOrRole =
504
- userId || roleId
505
- ? {
506
- OR: [aclNullCondition, aclUserCondition, aclRoleCondition].filter(notEmpty),
507
- }
508
- : undefined
509
-
510
- return {
511
- AND: [{ ...where }, aclForUnauthenticated, aclForUserOrRole].filter(notEmpty),
512
- } as WhereType<T, K>
513
- }
514
-
515
- /**
516
- * Private helper to load a single object for hooks (skips hooks to avoid recursion)
517
- */
518
- _loadObjectForHooks(className: keyof T['types'], context: WabeContext<T>) {
519
- return (id: string) =>
520
- this.getObject({
521
- className,
522
- context: contextWithRoot(context),
523
- id,
524
- _skipHooks: true,
525
- })
526
- }
527
-
528
- /**
529
- * Private helper to load multiple objects for hooks (skips hooks to avoid recursion)
530
- */
531
- _loadObjectsForHooks(className: keyof T['types'], context: WabeContext<T>) {
532
- return ({ where, ids }: { where?: WhereType<DevWabeTypes, any>; ids: string[] }) =>
533
- this.getObjects({
534
- className,
535
- context: contextWithRoot(context),
536
- // @ts-expect-error
537
- where: where ? where : { id: { in: ids } },
538
- _skipHooks: true,
539
- })
540
- }
541
-
542
- /**
543
- * Generic executor for single object operations (create, update, delete, read)
544
- * Encapsulates the hook lifecycle: Init -> Before -> Adapter -> After
545
- */
546
- async _executeSingleOperationWithHooks<
547
- K extends keyof T['types'],
548
- U extends keyof T['types'][K],
549
- >({
550
- operationTypeBefore,
551
- operationTypeAfter,
552
- className,
553
- context,
554
- data,
555
- select,
556
- id,
557
- adapterCallback,
558
- inputObject,
559
- }: {
560
- operationTypeBefore: OperationType
561
- operationTypeAfter: OperationType
562
- className: K
563
- context: WabeContext<T>
564
- data?: any
565
- select?: Select
566
- id?: string
567
- inputObject?: OutputType<T, K, U>
568
- adapterCallback: (newData: any) => Promise<OutputType<T, K, U> | { id: string } | null>
569
- }) {
570
- const hook = initializeHook({
571
- className,
572
- context,
573
- newData: data,
574
- // @ts-expect-error
575
- select,
576
- objectLoader: this._loadObjectForHooks(className, context),
577
- objectsLoader: this._loadObjectsForHooks(className, context),
578
- })
579
-
580
- const { newData, object: objectFromHook } = await hook.runOnSingleObject({
581
- operationType: operationTypeBefore,
582
- id,
583
-
584
- object: inputObject,
585
- })
586
-
587
- const res = await adapterCallback(newData || data)
588
-
589
- if (!res) return null
590
-
591
- // If the operation is delete, we use the objectFromHook (snapshot before delete)
592
- // Otherwise we use the result from adapter (which might be just { id })
593
- // loading the full object if needed happens inside runOnSingleObject if 'id' is passed
594
- await hook.runOnSingleObject({
595
- operationType: operationTypeAfter,
596
- id: res.id,
597
-
598
- originalObject: objectFromHook,
599
- })
600
-
601
- return res
602
- }
603
-
604
- _getRelationSelectWithoutTotalCount(currentSelect?: Select): Select {
605
- const selectWithoutTotalCount = currentSelect
606
- ? Object.entries(currentSelect).reduce((acc, [key, value]) => {
607
- if (key === 'totalCount' || key === '_args') return acc
608
- return {
609
- ...acc,
610
- [key]: value,
611
- }
612
- }, {})
613
- : undefined
614
-
615
- return selectWithoutTotalCount && Object.keys(selectWithoutTotalCount).length > 0
616
- ? (selectWithoutTotalCount as Select)
617
- : { id: true }
618
- }
619
-
620
- async _resolvePointerField({
621
- currentClassName,
622
- object,
623
- pointerField,
624
- currentSelect,
625
- context,
626
- _skipHooks,
627
- }: {
628
- currentClassName: string
629
- object: Record<string, any>
630
- pointerField: string
631
- currentSelect?: Select
632
- context: WabeContext<any>
633
- _skipHooks?: boolean
634
- }) {
635
- if (!object[pointerField]) return null
636
-
637
- return this.getObject({
638
- className: currentClassName,
639
- id: object[pointerField],
640
- context,
641
- // @ts-expect-error
642
- select: currentSelect,
643
- _skipHooks,
644
- })
645
- }
646
-
647
- async _resolveRelationField({
648
- currentClassName,
649
- object,
650
- pointerField,
651
- currentSelect,
652
- context,
653
- _skipHooks,
654
- }: {
655
- currentClassName: string
656
- object: Record<string, any>
657
- pointerField: string
658
- currentSelect?: Select
659
- context: WabeContext<any>
660
- _skipHooks?: boolean
661
- }) {
662
- const relationIds = object[pointerField]
663
- if (!relationIds) return undefined
664
-
665
- const selectWithoutTotalCount = this._getRelationSelectWithoutTotalCount(currentSelect)
666
- const args = (currentSelect as any)?._args || {}
667
-
668
- const where: any = args.where
669
- ? { AND: [{ id: { in: relationIds } }, args.where] }
670
- : { id: { in: relationIds } }
671
-
672
- const order: any = args.order?.reduce(
673
- (acc: any, currentOrder: any) => {
674
- // In some AST parsing, enums may come as strings like "age_DESC"
675
- // or as objects depending on how valueFromASTUntyped processed it.
676
- if (typeof currentOrder === 'string') {
677
- const lastUnderscore = currentOrder.lastIndexOf('_')
678
- if (lastUnderscore !== -1) {
679
- const field = currentOrder.slice(0, lastUnderscore)
680
- const direction = currentOrder.slice(lastUnderscore + 1)
681
- return { ...acc, [field]: direction }
682
- }
683
- } else {
684
- const result = Object.entries(currentOrder)[0]
685
- if (result && result[0] && result[1]) {
686
- return { ...acc, [result[0]]: result[1] }
687
- }
688
- }
689
- return acc
690
- },
691
- {} as Record<string, 'ASC' | 'DESC'>,
692
- )
693
-
694
- const relationObjects = await this.getObjects({
695
- className: currentClassName,
696
- select: selectWithoutTotalCount as any,
697
- where,
698
- offset: args.offset,
699
- first: args.first,
700
- order,
701
- context,
702
- _skipHooks,
703
- })
704
-
705
- if (!context.isGraphQLCall) return relationObjects
706
-
707
- const shouldCount =
708
- args.offset !== undefined || args.first !== undefined || args.where !== undefined
709
- const totalCount = shouldCount
710
- ? await this.count({
711
- className: currentClassName,
712
- where,
713
- context,
714
- })
715
- : relationObjects.length
716
-
717
- return {
718
- totalCount,
719
- edges: relationObjects.map((object: any) => ({
720
- node: object,
721
- })),
722
- }
723
- }
724
-
725
- _getFinalObjectWithPointerAndRelation({
726
- pointers,
727
- context,
728
- originClassName,
729
- object,
730
- _skipHooks,
731
- }: {
732
- originClassName: keyof T['types']
733
- pointers: Record<string, { className: string; select: Select }>
734
- context: WabeContext<any>
735
- object: Record<string, any> | null | undefined
736
- _skipHooks?: boolean
737
- }) {
738
- if (!object) return Promise.resolve({})
739
-
740
- return Object.entries(pointers).reduce(
741
- async (acc, [pointerField, { className: currentClassName, select: currentSelect }]) => {
742
- const accObject = await acc
743
-
744
- const isPointer = this._isFieldOfType(
745
- String(originClassName),
746
- pointerField,
747
- 'Pointer',
748
- context,
749
- currentClassName,
750
- )
751
-
752
- if (isPointer) {
753
- return {
754
- ...accObject,
755
- [pointerField]: await this._resolvePointerField({
756
- currentClassName,
757
- object,
758
- pointerField,
759
- currentSelect,
760
- context,
761
- _skipHooks,
762
- }),
763
- }
764
- }
765
-
766
- const isRelation = this._isFieldOfType(
767
- String(originClassName),
768
- pointerField,
769
- 'Relation',
770
- context,
771
- currentClassName,
772
- )
773
-
774
- if (!isRelation) return accObject
775
-
776
- const relationValue = await this._resolveRelationField({
777
- currentClassName,
778
- object,
779
- pointerField,
780
- currentSelect,
781
- context,
782
- _skipHooks,
783
- })
784
-
785
- if (relationValue === undefined) return accObject
786
-
787
- return {
788
- ...accObject,
789
- [pointerField]: relationValue,
790
- }
791
- },
792
- Promise.resolve({} as Record<string, any>),
793
- )
794
- }
795
-
796
- async close() {
797
- await this.adapter.close()
798
- }
799
-
800
- createClassIfNotExist(className: string, schema: SchemaInterface<T>): Promise<any> {
801
- return this.adapter.createClassIfNotExist(className, this._stripVirtualFieldsFromSchema(schema))
802
- }
803
-
804
- initializeDatabase(schema: SchemaInterface<T>): Promise<void> {
805
- return this.adapter.initializeDatabase(this._stripVirtualFieldsFromSchema(schema))
806
- }
807
-
808
- async count<K extends keyof T['types']>({
809
- className,
810
- context,
811
- where,
812
- }: CountOptions<T, K>): Promise<number> {
813
- const whereWithPointer = await this._getWhereObjectWithPointerOrRelation(
814
- className,
815
- where || {},
816
- context,
817
- )
818
-
819
- const whereWithACLCondition = this._buildWhereWithACL(whereWithPointer, context, 'read')
820
-
821
- const hook = initializeHook({
822
- className,
823
- context,
824
- select: {},
825
- })
826
-
827
- await hook?.runOnSingleObject({
828
- operationType: OperationType.BeforeRead,
829
- })
830
-
831
- const count = await this.adapter.count({
832
- className,
833
- context,
834
- where: whereWithACLCondition,
835
- })
836
-
837
- await hook?.runOnSingleObject({
838
- operationType: OperationType.AfterRead,
839
- })
840
-
841
- return count
842
- }
843
-
844
- async clearDatabase(): Promise<void> {
845
- await this.adapter.clearDatabase()
846
- }
847
-
848
- async getObject<K extends keyof T['types'], U extends keyof T['types'][K]>({
849
- select,
850
- className,
851
- context,
852
- _skipHooks,
853
- id,
854
- where,
855
- }: GetObjectOptions<T, K, U>): Promise<OutputType<T, K, U>> {
856
- const { pointers, selectWithoutPointers } = this._getSelectMinusPointersAndRelations({
857
- className,
858
- context,
859
- select: select as SelectWithObject,
860
- })
861
- const { userSelect, adapterSelect } = this._buildReadSelects({
862
- className,
863
- context,
864
- selectWithoutPointers,
865
- })
866
-
867
- const hook = this._initializeReadHook({
868
- className,
869
- context,
870
- userSelect,
871
- selectWithoutPointers,
872
- _skipHooks,
873
- })
874
-
875
- await hook?.runOnSingleObject({
876
- operationType: OperationType.BeforeRead,
877
- id,
878
- })
879
-
880
- const whereWithACLCondition = this._buildWhereWithACL(where || {}, context, 'read')
881
-
882
- const selectWithPointersAndRelationsToGetId = this._buildSelectWithPointers({
883
- adapterSelect,
884
- pointers,
885
- })
886
-
887
- const objectToReturn = await this.adapter.getObject({
888
- className,
889
- id,
890
- context: contextWithRoot(context),
891
- // @ts-expect-error
892
- select: !select ? undefined : selectWithPointersAndRelationsToGetId,
893
- where: whereWithACLCondition,
894
- })
895
-
896
- const finalObject = {
897
- ...objectToReturn,
898
- ...(await this._getFinalObjectWithPointerAndRelation({
899
- context,
900
- originClassName: className,
901
- pointers,
902
- object: objectToReturn,
903
- _skipHooks,
904
- })),
905
- }
906
-
907
- const afterReadResult = await hook?.runOnSingleObject({
908
- operationType: OperationType.AfterRead,
909
- id,
910
- // @ts-expect-error
911
- object: finalObject,
912
- })
913
- const objectAfterHooks = afterReadResult?.object || finalObject
914
- const objectProjectedForUser = this._projectObjectForUserSelect({
915
- object: objectAfterHooks,
916
- select: select as SelectWithObject,
917
- })
918
-
919
- return objectProjectedForUser
920
- }
921
-
922
- async getObjects<
923
- K extends keyof T['types'],
924
- U extends keyof T['types'][K],
925
- W extends keyof T['types'][K],
926
- >({
927
- className,
928
- select,
929
- context,
930
- where,
931
- _skipHooks,
932
- first,
933
- offset,
934
- order,
935
- }: GetObjectsOptions<T, K, U, W>): Promise<OutputType<T, K, W>[]> {
936
- const { pointers, selectWithoutPointers } = this._getSelectMinusPointersAndRelations({
937
- className,
938
- context,
939
- select: select as SelectWithObject,
940
- })
941
- const { userSelect, adapterSelect } = this._buildReadSelects({
942
- className,
943
- context,
944
- selectWithoutPointers,
945
- })
946
-
947
- const whereWithPointer = await this._getWhereObjectWithPointerOrRelation(
948
- className,
949
- where || {},
950
- context,
951
- )
952
-
953
- const whereWithACLCondition = this._buildWhereWithACL(whereWithPointer || {}, context, 'read')
954
-
955
- const selectWithPointersAndRelationsToGetId = this._buildSelectWithPointers({
956
- adapterSelect,
957
- pointers,
958
- })
959
-
960
- const hook = this._initializeReadHook({
961
- className,
962
- context,
963
- userSelect,
964
- selectWithoutPointers,
965
- _skipHooks,
966
- })
967
-
968
- await hook?.runOnMultipleObjects({
969
- operationType: OperationType.BeforeRead,
970
- where: whereWithACLCondition,
971
- })
972
-
973
- const objectsToReturn = await this.adapter.getObjects({
974
- className,
975
- context: contextWithRoot(context),
976
- first,
977
- offset,
978
- where: whereWithACLCondition,
979
- // @ts-expect-error
980
- select: !select ? undefined : selectWithPointersAndRelationsToGetId,
981
- order,
982
- })
983
-
984
- const objectsWithPointers = await Promise.all(
985
- objectsToReturn.map(async (object) => {
986
- return {
987
- ...object,
988
- ...(await this._getFinalObjectWithPointerAndRelation({
989
- object,
990
- context,
991
- originClassName: className,
992
- pointers,
993
- _skipHooks,
994
- })),
995
- }
996
- }),
997
- )
998
-
999
- const afterReadResults = await hook?.runOnMultipleObjects({
1000
- operationType: OperationType.AfterRead,
1001
- // @ts-expect-error
1002
- objects: objectsWithPointers,
1003
- })
1004
- const objectsAfterHooks = afterReadResults?.objects || objectsWithPointers
1005
- const projectedObjects = objectsAfterHooks.map((object) =>
1006
- this._projectObjectForUserSelect({
1007
- object,
1008
- select: select as SelectWithObject,
1009
- }),
1010
- )
1011
-
1012
- // Projection keeps only user-requested top-level fields, including virtual fields.
1013
- return projectedObjects
1014
- }
1015
-
1016
- async createObject<
1017
- K extends keyof T['types'],
1018
- U extends keyof T['types'][K],
1019
- W extends keyof T['types'][K],
1020
- >({
1021
- className,
1022
- context,
1023
- data,
1024
- select,
1025
- }: CreateObjectOptions<T, K, U, W>): Promise<OutputType<T, K, W>> {
1026
- // Here data.file is null but should not be
1027
-
1028
- const result = await this._executeSingleOperationWithHooks<K, W>({
1029
- operationTypeBefore: OperationType.BeforeCreate,
1030
- operationTypeAfter: OperationType.AfterCreate,
1031
- className,
1032
- context,
1033
- data,
1034
- select: select as Select,
1035
- adapterCallback: async (newData) => {
1036
- const payload = this._stripVirtualFieldsFromPayload({
1037
- className,
1038
- context,
1039
- payload: newData || data,
1040
- })
1041
-
1042
- const res = await this.adapter.createObject({
1043
- className,
1044
- context,
1045
- select,
1046
- data: payload,
1047
- })
1048
-
1049
- return { id: res.id }
1050
- },
1051
- })
1052
-
1053
- const res = result as { id: string }
1054
-
1055
- if (this._isEmptySelect(select as Record<string, unknown>)) return null
1056
-
1057
- const selectWithoutPrivateFields = select ? selectFieldsWithoutPrivateFields(select) : undefined
1058
-
1059
- return this.getObject({
1060
- className,
1061
- context: contextWithRoot(context),
1062
- select: selectWithoutPrivateFields,
1063
- id: res.id,
1064
- })
1065
- }
1066
-
1067
- async createObjects<
1068
- K extends keyof T['types'],
1069
- U extends keyof T['types'][K],
1070
- W extends keyof T['types'][K],
1071
- X extends keyof T['types'][K],
1072
- >({
1073
- data,
1074
- select,
1075
- className,
1076
- context,
1077
- first,
1078
- offset,
1079
- order,
1080
- }: CreateObjectsOptions<T, K, U, W, X>): Promise<OutputType<T, K, W>[]> {
1081
- if (data.length === 0) return []
1082
-
1083
- const hooks = await Promise.all(
1084
- data.map((newData) =>
1085
- initializeHook({
1086
- className,
1087
- context,
1088
- newData,
1089
- // @ts-expect-error
1090
- select,
1091
- objectLoader: this._loadObjectForHooks(className, context),
1092
- objectsLoader: this._loadObjectsForHooks(className, context),
1093
- }),
1094
- ),
1095
- )
1096
-
1097
- const arrayOfComputedData = (
1098
- await Promise.all(
1099
- hooks.map(
1100
- async (hook) =>
1101
- (
1102
- await hook.runOnMultipleObjects({
1103
- operationType: OperationType.BeforeCreate,
1104
- })
1105
- )?.newData[0],
1106
- ),
1107
- )
1108
- ).filter(notEmpty)
1109
-
1110
- const listOfIds = await this.adapter.createObjects({
1111
- className,
1112
- select,
1113
- context,
1114
- data: arrayOfComputedData.map((payload) =>
1115
- this._stripVirtualFieldsFromPayload({
1116
- className,
1117
- context,
1118
- payload,
1119
- }),
1120
- ),
1121
- first,
1122
- offset,
1123
- order,
1124
- })
1125
-
1126
- const ids = listOfIds.map(({ id }) => id)
1127
-
1128
- await Promise.all(
1129
- hooks.map((hook) =>
1130
- hook.runOnMultipleObjects({
1131
- operationType: OperationType.AfterCreate,
1132
- ids,
1133
- }),
1134
- ),
1135
- )
1136
-
1137
- if (this._isEmptySelect(select as Record<string, unknown>)) return []
1138
-
1139
- return this.getObjects({
1140
- className,
1141
- context: contextWithRoot(context),
1142
- select,
1143
- // @ts-expect-error
1144
- where: { id: { in: ids } },
1145
- first,
1146
- offset,
1147
- order,
1148
- })
1149
- }
1150
-
1151
- async updateObject<
1152
- K extends keyof T['types'],
1153
- U extends keyof T['types'][K],
1154
- W extends keyof T['types'][K],
1155
- >({
1156
- id,
1157
- className,
1158
- context,
1159
- data,
1160
- select,
1161
- _skipHooks,
1162
- }: UpdateObjectOptions<T, K, U, W>): Promise<OutputType<T, K, W>> {
1163
- if (_skipHooks) {
1164
- const whereWithACLCondition = this._buildWhereWithACL({}, context, 'write')
1165
- const payload = this._stripVirtualFieldsFromPayload({
1166
- className,
1167
- context,
1168
- payload: data,
1169
- })
1170
-
1171
- return this.adapter.updateObject({
1172
- className,
1173
- select,
1174
- id,
1175
- context,
1176
- data: payload,
1177
- where: whereWithACLCondition,
1178
- }) as Promise<OutputType<T, K, W>>
1179
- }
1180
-
1181
- await this._executeSingleOperationWithHooks<K, W>({
1182
- operationTypeBefore: OperationType.BeforeUpdate,
1183
- operationTypeAfter: OperationType.AfterUpdate,
1184
- className,
1185
- context,
1186
- data,
1187
- id,
1188
- select: select as Select,
1189
- adapterCallback: async (newData) => {
1190
- const whereWithACLCondition = this._buildWhereWithACL({}, context, 'write')
1191
- const payload = this._stripVirtualFieldsFromPayload({
1192
- className,
1193
- context,
1194
- payload: newData || data,
1195
- })
1196
-
1197
- await this.adapter.updateObject({
1198
- className,
1199
- select,
1200
- id,
1201
- context,
1202
- data: payload,
1203
- where: whereWithACLCondition,
1204
- })
1205
-
1206
- return { id }
1207
- },
1208
- })
1209
-
1210
- if (this._isEmptySelect(select as Record<string, unknown>)) return null
1211
-
1212
- return this.getObject({
1213
- className,
1214
- context,
1215
- select,
1216
- id,
1217
- })
1218
- }
1219
-
1220
- async updateObjects<
1221
- K extends keyof T['types'],
1222
- U extends keyof T['types'][K],
1223
- W extends keyof T['types'][K],
1224
- X extends keyof T['types'][K],
1225
- >({
1226
- className,
1227
- where,
1228
- context,
1229
- select,
1230
- data,
1231
- first,
1232
- offset,
1233
- order,
1234
- _skipHooks,
1235
- }: UpdateObjectsOptions<T, K, U, W, X>): Promise<OutputType<T, K, W>[]> {
1236
- const whereObject = await this._getWhereObjectWithPointerOrRelation(
1237
- className,
1238
- where || {},
1239
- context,
1240
- )
1241
-
1242
- const hook = !_skipHooks
1243
- ? initializeHook({
1244
- className,
1245
- context,
1246
- newData: data,
1247
- // @ts-expect-error
1248
- select,
1249
- objectLoader: this._loadObjectForHooks(className, context),
1250
- objectsLoader: this._loadObjectsForHooks(className, context),
1251
- })
1252
- : undefined
1253
-
1254
- const whereWithACLCondition = this._buildWhereWithACL(whereObject, context, 'write')
1255
-
1256
- const resultsAfterBeforeUpdate = await hook?.runOnMultipleObjects({
1257
- operationType: OperationType.BeforeUpdate,
1258
- where: whereWithACLCondition,
1259
- })
1260
-
1261
- const objects = await this.adapter.updateObjects({
1262
- className,
1263
- context,
1264
- select,
1265
- data: this._stripVirtualFieldsFromPayload({
1266
- className,
1267
- context,
1268
- payload: resultsAfterBeforeUpdate?.newData[0] || data || {},
1269
- }),
1270
- where: whereWithACLCondition,
1271
- first,
1272
- offset,
1273
- order,
1274
- })
1275
-
1276
- const objectsId = objects.map((object) => object?.id).filter(notEmpty)
1277
-
1278
- await hook?.runOnMultipleObjects({
1279
- operationType: OperationType.AfterUpdate,
1280
- ids: objectsId,
1281
- originalObjects: resultsAfterBeforeUpdate?.objects || [],
1282
- })
1283
-
1284
- if (this._isEmptySelect(select as Record<string, unknown>)) return []
1285
-
1286
- return this.getObjects({
1287
- className,
1288
- context,
1289
- select,
1290
- // @ts-expect-error
1291
- where: { id: { in: objectsId } },
1292
- first,
1293
- offset,
1294
- order,
1295
- })
1296
- }
1297
-
1298
- async deleteObject<K extends keyof T['types'], U extends keyof T['types'][K]>({
1299
- context,
1300
- className,
1301
- id,
1302
- select,
1303
- }: DeleteObjectOptions<T, K, U>): Promise<OutputType<T, K, U>> {
1304
- const result = (await this._executeSingleOperationWithHooks<K, U>({
1305
- operationTypeBefore: OperationType.BeforeDelete,
1306
- operationTypeAfter: OperationType.AfterDelete,
1307
- className,
1308
- context,
1309
- id,
1310
- select: select as Select,
1311
- adapterCallback: async (_newData) => {
1312
- const whereWithACLCondition = this._buildWhereWithACL({}, context, 'write')
1313
-
1314
- // We need to fetch the object before deleting it if we want to return it
1315
- // But executeSingleOperationWithHooks already fetched it in runOnSingleObject if an id is present
1316
- // Wait, runOnSingleObject fetches it for the hook context 'object', but does not return it unless we use it.
1317
- // However, if we utilize _executeSingleOperationWithHooks, the 'object' from before-hook is kept.
1318
- // But _executeSingleOperationWithHooks logic for delete is slightly different regarding return value:
1319
- // Delete usually returns the deleted object.
1320
- // Update: My abstraction returns 'res' from adapterCallback.
1321
- // So I need to return the object here.
1322
-
1323
- let objectBeforeDelete = null
1324
-
1325
- if (select && Object.keys(select).length > 0)
1326
- objectBeforeDelete = await this.getObject({
1327
- className,
1328
- context,
1329
- select,
1330
- id,
1331
- })
1332
-
1333
- await this.adapter.deleteObject({
1334
- className,
1335
- context,
1336
- id,
1337
-
1338
- where: whereWithACLCondition,
1339
- })
1340
-
1341
- return objectBeforeDelete || { id }
1342
- },
1343
- })) as unknown as OutputType<T, K, U>
1344
-
1345
- if (this._isEmptySelect(select as Record<string, unknown>)) return null as any
1346
-
1347
- return result
1348
- }
1349
-
1350
- async deleteObjects<
1351
- K extends keyof T['types'],
1352
- U extends keyof T['types'][K],
1353
- W extends keyof T['types'][K],
1354
- >({
1355
- className,
1356
- context,
1357
- select,
1358
- where,
1359
- first,
1360
- offset,
1361
- order,
1362
- }: DeleteObjectsOptions<T, K, U, W>): Promise<OutputType<T, K, W>[]> {
1363
- const whereObject = await this._getWhereObjectWithPointerOrRelation(
1364
- className,
1365
- where || {},
1366
- context,
1367
- )
1368
-
1369
- const hook = initializeHook({
1370
- className,
1371
- context,
1372
- // @ts-expect-error
1373
- select,
1374
- objectLoader: this._loadObjectForHooks(className, context),
1375
- objectsLoader: this._loadObjectsForHooks(className, context),
1376
- })
1377
-
1378
- const whereWithACLCondition = this._buildWhereWithACL(whereObject, context, 'write')
1379
-
1380
- let objectsBeforeDelete: OutputType<T, K, W>[] = []
1381
-
1382
- if (select && Object.keys(select).length > 0)
1383
- objectsBeforeDelete = await this.getObjects({
1384
- className,
1385
- where,
1386
- select,
1387
- context,
1388
- first,
1389
- offset,
1390
- order,
1391
- })
1392
-
1393
- const resultOfBeforeDelete = await hook.runOnMultipleObjects({
1394
- operationType: OperationType.BeforeDelete,
1395
- where: whereWithACLCondition,
1396
- })
1397
-
1398
- await this.adapter.deleteObjects({
1399
- className,
1400
- context,
1401
- select,
1402
- first,
1403
- offset,
1404
- where: whereWithACLCondition,
1405
- order,
1406
- })
1407
-
1408
- await hook.runOnMultipleObjects({
1409
- operationType: OperationType.AfterDelete,
1410
- originalObjects: resultOfBeforeDelete.objects,
1411
- })
1412
-
1413
- return objectsBeforeDelete
1414
- }
1415
- }