wabe 0.6.9 → 0.6.10

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 (158) hide show
  1. package/README.md +138 -32
  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/email/interface.d.ts +1 -1
  7. package/dist/graphql/resolvers.d.ts +4 -2
  8. package/dist/hooks/index.d.ts +1 -0
  9. package/dist/index.d.ts +0 -1
  10. package/dist/index.js +8713 -8867
  11. package/dist/server/index.d.ts +4 -2
  12. package/dist/utils/crypto.d.ts +7 -0
  13. package/dist/utils/helper.d.ts +4 -1
  14. package/generated/schema.graphql +16 -14
  15. package/generated/wabe.ts +4 -4
  16. package/package.json +15 -15
  17. package/src/authentication/OTP.test.ts +69 -0
  18. package/src/authentication/OTP.ts +66 -0
  19. package/src/authentication/Session.test.ts +665 -0
  20. package/src/authentication/Session.ts +529 -0
  21. package/src/authentication/defaultAuthentication.ts +214 -0
  22. package/src/authentication/index.ts +3 -0
  23. package/src/authentication/interface.ts +157 -0
  24. package/src/authentication/oauth/GitHub.test.ts +105 -0
  25. package/src/authentication/oauth/GitHub.ts +133 -0
  26. package/src/authentication/oauth/Google.test.ts +105 -0
  27. package/src/authentication/oauth/Google.ts +110 -0
  28. package/src/authentication/oauth/Oauth2Client.test.ts +225 -0
  29. package/src/authentication/oauth/Oauth2Client.ts +140 -0
  30. package/src/authentication/oauth/index.ts +2 -0
  31. package/src/authentication/oauth/utils.test.ts +35 -0
  32. package/src/authentication/oauth/utils.ts +28 -0
  33. package/src/authentication/providers/EmailOTP.test.ts +138 -0
  34. package/src/authentication/providers/EmailOTP.ts +93 -0
  35. package/src/authentication/providers/EmailPassword.test.ts +187 -0
  36. package/src/authentication/providers/EmailPassword.ts +130 -0
  37. package/src/authentication/providers/EmailPasswordSRP.test.ts +206 -0
  38. package/src/authentication/providers/EmailPasswordSRP.ts +184 -0
  39. package/src/authentication/providers/GitHub.ts +30 -0
  40. package/src/authentication/providers/Google.ts +30 -0
  41. package/src/authentication/providers/OAuth.test.ts +185 -0
  42. package/src/authentication/providers/OAuth.ts +112 -0
  43. package/src/authentication/providers/PhonePassword.test.ts +187 -0
  44. package/src/authentication/providers/PhonePassword.ts +129 -0
  45. package/src/authentication/providers/QRCodeOTP.test.ts +79 -0
  46. package/src/authentication/providers/QRCodeOTP.ts +65 -0
  47. package/src/authentication/providers/index.ts +6 -0
  48. package/src/authentication/resolvers/refreshResolver.test.ts +37 -0
  49. package/src/authentication/resolvers/refreshResolver.ts +20 -0
  50. package/src/authentication/resolvers/signInWithResolver.inte.test.ts +59 -0
  51. package/src/authentication/resolvers/signInWithResolver.test.ts +307 -0
  52. package/src/authentication/resolvers/signInWithResolver.ts +102 -0
  53. package/src/authentication/resolvers/signOutResolver.test.ts +41 -0
  54. package/src/authentication/resolvers/signOutResolver.ts +22 -0
  55. package/src/authentication/resolvers/signUpWithResolver.test.ts +186 -0
  56. package/src/authentication/resolvers/signUpWithResolver.ts +69 -0
  57. package/src/authentication/resolvers/verifyChallenge.test.ts +136 -0
  58. package/src/authentication/resolvers/verifyChallenge.ts +69 -0
  59. package/src/authentication/roles.test.ts +59 -0
  60. package/src/authentication/roles.ts +40 -0
  61. package/src/authentication/utils.test.ts +99 -0
  62. package/src/authentication/utils.ts +43 -0
  63. package/src/cache/InMemoryCache.test.ts +62 -0
  64. package/src/cache/InMemoryCache.ts +45 -0
  65. package/src/cron/index.test.ts +17 -0
  66. package/src/cron/index.ts +46 -0
  67. package/src/database/DatabaseController.test.ts +625 -0
  68. package/src/database/DatabaseController.ts +983 -0
  69. package/src/database/index.test.ts +1230 -0
  70. package/src/database/index.ts +9 -0
  71. package/src/database/interface.ts +312 -0
  72. package/src/email/DevAdapter.ts +8 -0
  73. package/src/email/EmailController.test.ts +29 -0
  74. package/src/email/EmailController.ts +13 -0
  75. package/src/email/index.ts +2 -0
  76. package/src/email/interface.ts +36 -0
  77. package/src/email/templates/sendOtpCode.ts +120 -0
  78. package/src/file/FileController.ts +28 -0
  79. package/src/file/FileDevAdapter.ts +54 -0
  80. package/src/file/hookDeleteFile.ts +27 -0
  81. package/src/file/hookReadFile.ts +70 -0
  82. package/src/file/hookUploadFile.ts +53 -0
  83. package/src/file/index.test.ts +979 -0
  84. package/src/file/index.ts +2 -0
  85. package/src/file/interface.ts +42 -0
  86. package/src/graphql/GraphQLSchema.test.ts +4399 -0
  87. package/src/graphql/GraphQLSchema.ts +928 -0
  88. package/src/graphql/index.ts +2 -0
  89. package/src/graphql/parseGraphqlSchema.ts +94 -0
  90. package/src/graphql/parser.test.ts +217 -0
  91. package/src/graphql/parser.ts +566 -0
  92. package/src/graphql/pointerAndRelationFunction.ts +200 -0
  93. package/src/graphql/resolvers.ts +467 -0
  94. package/src/graphql/tests/aggregation.test.ts +1123 -0
  95. package/src/graphql/tests/e2e.test.ts +596 -0
  96. package/src/graphql/tests/scalars.test.ts +250 -0
  97. package/src/graphql/types.ts +219 -0
  98. package/src/hooks/HookObject.test.ts +122 -0
  99. package/src/hooks/HookObject.ts +168 -0
  100. package/src/hooks/authentication.ts +76 -0
  101. package/src/hooks/createUser.test.ts +77 -0
  102. package/src/hooks/createUser.ts +10 -0
  103. package/src/hooks/defaultFields.test.ts +187 -0
  104. package/src/hooks/defaultFields.ts +40 -0
  105. package/src/hooks/deleteSession.test.ts +181 -0
  106. package/src/hooks/deleteSession.ts +20 -0
  107. package/src/hooks/hashFieldHook.test.ts +163 -0
  108. package/src/hooks/hashFieldHook.ts +97 -0
  109. package/src/hooks/index.test.ts +207 -0
  110. package/src/hooks/index.ts +430 -0
  111. package/src/hooks/permissions.test.ts +424 -0
  112. package/src/hooks/permissions.ts +113 -0
  113. package/src/hooks/protected.test.ts +551 -0
  114. package/src/hooks/protected.ts +72 -0
  115. package/src/hooks/searchableFields.test.ts +166 -0
  116. package/src/hooks/searchableFields.ts +98 -0
  117. package/src/hooks/session.test.ts +138 -0
  118. package/src/hooks/session.ts +78 -0
  119. package/src/hooks/setEmail.test.ts +216 -0
  120. package/src/hooks/setEmail.ts +35 -0
  121. package/src/hooks/setupAcl.test.ts +589 -0
  122. package/src/hooks/setupAcl.ts +29 -0
  123. package/src/index.ts +9 -0
  124. package/src/schema/Schema.test.ts +484 -0
  125. package/src/schema/Schema.ts +795 -0
  126. package/src/schema/defaultResolvers.ts +94 -0
  127. package/src/schema/index.ts +1 -0
  128. package/src/schema/resolvers/meResolver.test.ts +62 -0
  129. package/src/schema/resolvers/meResolver.ts +14 -0
  130. package/src/schema/resolvers/newFile.ts +0 -0
  131. package/src/schema/resolvers/resetPassword.test.ts +345 -0
  132. package/src/schema/resolvers/resetPassword.ts +64 -0
  133. package/src/schema/resolvers/sendEmail.test.ts +118 -0
  134. package/src/schema/resolvers/sendEmail.ts +21 -0
  135. package/src/schema/resolvers/sendOtpCode.test.ts +153 -0
  136. package/src/schema/resolvers/sendOtpCode.ts +52 -0
  137. package/src/security.test.ts +3461 -0
  138. package/src/server/defaultSessionHandler.test.ts +66 -0
  139. package/src/server/defaultSessionHandler.ts +115 -0
  140. package/src/server/generateCodegen.ts +476 -0
  141. package/src/server/index.test.ts +552 -0
  142. package/src/server/index.ts +354 -0
  143. package/src/server/interface.ts +11 -0
  144. package/src/server/routes/authHandler.ts +187 -0
  145. package/src/server/routes/index.ts +40 -0
  146. package/src/utils/crypto.test.ts +41 -0
  147. package/src/utils/crypto.ts +121 -0
  148. package/src/utils/export.ts +13 -0
  149. package/src/utils/helper.ts +195 -0
  150. package/src/utils/index.test.ts +11 -0
  151. package/src/utils/index.ts +201 -0
  152. package/src/utils/preload.ts +8 -0
  153. package/src/utils/testHelper.ts +117 -0
  154. package/tsconfig.json +32 -0
  155. package/bunfig.toml +0 -4
  156. package/dist/ai/index.d.ts +0 -1
  157. package/dist/ai/interface.d.ts +0 -9
  158. /package/dist/server/{defaultHandlers.d.ts → defaultSessionHandler.d.ts} +0 -0
@@ -0,0 +1,979 @@
1
+ import {
2
+ afterAll,
3
+ afterEach,
4
+ beforeAll,
5
+ describe,
6
+ expect,
7
+ it,
8
+ mock,
9
+ spyOn,
10
+ } from 'bun:test'
11
+ import { FileDevAdapter, type Wabe } from '..'
12
+ import { type DevWabeTypes, getAnonymousClient } from '../utils/helper'
13
+ import { setupTests, closeTests } from '../utils/testHelper'
14
+ import { gql } from 'graphql-request'
15
+
16
+ describe('File upload', () => {
17
+ let wabe: Wabe<DevWabeTypes>
18
+ let port: number
19
+
20
+ const spyFileDevAdapterUploadFile = spyOn(
21
+ FileDevAdapter.prototype,
22
+ 'uploadFile',
23
+ )
24
+ const spyFileDevAdapterReadFile = spyOn(FileDevAdapter.prototype, 'readFile')
25
+
26
+ const mockBeforeUpload = mock()
27
+
28
+ beforeAll(async () => {
29
+ const setup = await setupTests([
30
+ {
31
+ name: 'Test3',
32
+ fields: {
33
+ file: { type: 'File' },
34
+ },
35
+ permissions: {
36
+ read: {
37
+ requireAuthentication: false,
38
+ },
39
+ create: {
40
+ requireAuthentication: false,
41
+ },
42
+ update: {
43
+ requireAuthentication: false,
44
+ },
45
+ delete: {
46
+ requireAuthentication: false,
47
+ },
48
+ },
49
+ },
50
+ ])
51
+ wabe = setup.wabe
52
+ port = setup.port
53
+
54
+ spyFileDevAdapterReadFile.mockClear()
55
+ spyFileDevAdapterUploadFile.mockClear()
56
+
57
+ const fileConfig = wabe.config.file
58
+
59
+ if (fileConfig) fileConfig.beforeUpload = mockBeforeUpload
60
+ })
61
+
62
+ afterAll(async () => {
63
+ await closeTests(wabe)
64
+ })
65
+
66
+ afterEach(async () => {
67
+ spyFileDevAdapterUploadFile.mockClear()
68
+ spyFileDevAdapterReadFile.mockClear()
69
+ mockBeforeUpload.mockClear()
70
+
71
+ await wabe.controllers.database.deleteObjects({
72
+ // @ts-expect-error
73
+ className: 'Test3',
74
+ context: {
75
+ isRoot: true,
76
+ wabe,
77
+ },
78
+ where: {},
79
+ select: {},
80
+ })
81
+ })
82
+
83
+ it('should call beforeUpload if specified in the file config', async () => {
84
+ await wabe.controllers.database.createObject({
85
+ // @ts-expect-error
86
+ className: 'Test3',
87
+ context: {
88
+ isRoot: true,
89
+ wabe,
90
+ },
91
+ data: {
92
+ // @ts-expect-error
93
+ file: {
94
+ file: new File(['a'], 'a', { type: 'text/plain' }),
95
+ },
96
+ },
97
+ select: {},
98
+ })
99
+
100
+ expect(mockBeforeUpload).toHaveBeenCalledTimes(1)
101
+ const fileArg = mockBeforeUpload.mock.calls[0]?.[0]
102
+ expect(fileArg?.name).toEqual('a')
103
+ expect(await fileArg?.text()).toEqual('a')
104
+
105
+ // should return the same file if no file is returned by beforeUpload
106
+ expect(spyFileDevAdapterUploadFile).toHaveBeenCalledTimes(1)
107
+ const fileArg2 = spyFileDevAdapterUploadFile.mock.calls[0]?.[0]
108
+ expect(fileArg2?.name).toEqual('a')
109
+ expect(await fileArg2?.text()).toEqual('a')
110
+ })
111
+
112
+ it('should call beforeUpload and return the file returned by beforeUpload', async () => {
113
+ mockBeforeUpload.mockImplementationOnce(
114
+ () => new File(['b'], 'b.txt', { type: 'text/plain' }),
115
+ )
116
+
117
+ await wabe.controllers.database.createObject({
118
+ // @ts-expect-error
119
+ className: 'Test3',
120
+ context: {
121
+ isRoot: true,
122
+ wabe,
123
+ },
124
+ data: {
125
+ // @ts-expect-error
126
+ file: {
127
+ file: new File(['a'], 'a', { type: 'text/plain' }),
128
+ },
129
+ },
130
+ select: {},
131
+ })
132
+
133
+ expect(mockBeforeUpload).toHaveBeenCalledTimes(1)
134
+ const fileArg = mockBeforeUpload.mock.calls[0]?.[0]
135
+ expect(fileArg?.name).toEqual('a')
136
+ expect(await fileArg?.text()).toEqual('a')
137
+
138
+ // should return the same file if no file is returned by beforeUpload
139
+ expect(spyFileDevAdapterUploadFile).toHaveBeenCalledTimes(1)
140
+ const fileArg2 = spyFileDevAdapterUploadFile.mock.calls[0]?.[0]
141
+ expect(fileArg2?.name).toEqual('b.txt')
142
+ expect(await fileArg2?.text()).toEqual('b')
143
+ })
144
+
145
+ it('should not crash when there is no extension for the uploaded file', async () => {
146
+ await wabe.controllers.database.createObject({
147
+ // @ts-expect-error
148
+ className: 'Test3',
149
+ context: {
150
+ isRoot: true,
151
+ wabe,
152
+ },
153
+ data: {
154
+ // @ts-expect-error
155
+ file: {
156
+ file: new File(['a'], 'a', { type: 'text/plain' }),
157
+ },
158
+ },
159
+ select: {},
160
+ })
161
+
162
+ const result = await wabe.controllers.database.getObjects({
163
+ // @ts-expect-error
164
+ className: 'Test3',
165
+ context: {
166
+ isRoot: true,
167
+ wabe,
168
+ },
169
+ where: {},
170
+ // @ts-expect-error
171
+ select: { file: true, id: true },
172
+ })
173
+
174
+ // @ts-expect-error
175
+ expect(result[0].file.name).toEqual('a')
176
+ // @ts-expect-error
177
+ expect(result[0].file.url).toEqual(`http://127.0.0.1:${port}/bucket/a`)
178
+ })
179
+
180
+ it('should throw an error if no file adapter is provided', async () => {
181
+ const previousFileController = wabe.controllers.file
182
+ // @ts-expect-error
183
+ wabe.controllers.file = null
184
+
185
+ const formData = new FormData()
186
+
187
+ formData.append(
188
+ 'operations',
189
+ JSON.stringify({
190
+ query:
191
+ 'mutation ($file: File!) {createTest3(input: {fields: {file: {file:$file}}}){test3{id, file {name, isPresignedUrl}}}}',
192
+ variables: { file: null },
193
+ }),
194
+ )
195
+
196
+ formData.append('map', JSON.stringify({ 0: ['variables.file'] }))
197
+
198
+ formData.append('0', new File(['a'], 'a.text', { type: 'text/plain' }))
199
+
200
+ const res = await fetch(`http://127.0.0.1:${port}/graphql`, {
201
+ method: 'POST',
202
+ body: formData,
203
+ })
204
+
205
+ expect(await res.text()).toContain('No file adapter found')
206
+
207
+ wabe.controllers.file = previousFileController
208
+ })
209
+
210
+ it("should upload a file with the database controller's method", async () => {
211
+ await wabe.controllers.database.createObject({
212
+ // @ts-expect-error
213
+ className: 'Test3',
214
+ context: {
215
+ isRoot: true,
216
+ wabe,
217
+ },
218
+ data: {
219
+ // @ts-expect-error
220
+ file: {
221
+ file: new File(['a'], 'a.text', { type: 'text/plain' }),
222
+ },
223
+ },
224
+ select: {},
225
+ })
226
+
227
+ const result = await wabe.controllers.database.getObjects({
228
+ // @ts-expect-error
229
+ className: 'Test3',
230
+ context: {
231
+ isRoot: true,
232
+ wabe,
233
+ },
234
+ where: {},
235
+ // @ts-expect-error
236
+ select: { file: true, id: true },
237
+ })
238
+
239
+ // @ts-expect-error
240
+ expect(result[0].file.name).toEqual('a.text')
241
+ // @ts-expect-error
242
+ expect(result[0].file.url).toEqual(`http://127.0.0.1:${port}/bucket/a.text`)
243
+
244
+ const res = await wabe.controllers.database.updateObject({
245
+ // @ts-expect-error
246
+ className: 'Test3',
247
+ context: {
248
+ isRoot: true,
249
+ wabe,
250
+ },
251
+ where: {},
252
+ // @ts-expect-error
253
+ select: { file: true, id: true },
254
+ data: {
255
+ // @ts-expect-error
256
+ file: {
257
+ url: 'https://palixir.github.io/wabe//assets/logo.png',
258
+ },
259
+ },
260
+ id: result?.[0]?.id || '',
261
+ })
262
+
263
+ // @ts-expect-error
264
+ expect(res.file.url).toEqual(
265
+ 'https://palixir.github.io/wabe//assets/logo.png',
266
+ )
267
+ // @ts-expect-error
268
+ expect(res.file.isPresignedUrl).toEqual(false)
269
+ })
270
+
271
+ it('should upload multiple objects with the same file', async () => {
272
+ const formData = new FormData()
273
+
274
+ formData.append(
275
+ 'operations',
276
+ JSON.stringify({
277
+ query: gql`
278
+ mutation ($file: File!, $file2: File!) {
279
+ createTest3s(
280
+ input: {
281
+ fields: [
282
+ { file: { file: $file } }
283
+ { file: { file: $file2 } }
284
+ ]
285
+ }
286
+ ) {
287
+ edges {
288
+ node {
289
+ id
290
+ file {
291
+ name
292
+ }
293
+ }
294
+ }
295
+ }
296
+ }
297
+ `,
298
+ variables: { file: null },
299
+ }),
300
+ )
301
+
302
+ formData.append(
303
+ 'map',
304
+ JSON.stringify({ 0: ['variables.file'], 1: ['variables.file2'] }),
305
+ )
306
+ formData.append('0', new File(['a'], 'a.text', { type: 'text/plain' }))
307
+ formData.append('1', new File(['b'], 'b.text', { type: 'text/plain' }))
308
+
309
+ const res = await fetch(`http://127.0.0.1:${port}/graphql`, {
310
+ method: 'POST',
311
+ body: formData,
312
+ })
313
+
314
+ const jsonRes = await res.json()
315
+
316
+ // Return the url in dev adapter it's the file name
317
+ expect(jsonRes.data.createTest3s.edges[0].node.file.name).toEqual('a.text')
318
+ expect(jsonRes.data.createTest3s.edges[1].node.file.name).toEqual('b.text')
319
+
320
+ expect(spyFileDevAdapterUploadFile).toHaveBeenCalledTimes(2)
321
+ const fileArg = spyFileDevAdapterUploadFile.mock.calls[0]?.[0]
322
+ expect(fileArg?.name).toEqual('a.text')
323
+ expect(await fileArg?.text()).toEqual('a')
324
+
325
+ const fileArg2 = spyFileDevAdapterUploadFile.mock.calls[1]?.[0]
326
+ expect(fileArg2?.name).toEqual('b.text')
327
+ expect(await fileArg2?.text()).toEqual('b')
328
+ })
329
+
330
+ it('should upload a file on request on type File on create request', async () => {
331
+ const formData = new FormData()
332
+
333
+ formData.append(
334
+ 'operations',
335
+ JSON.stringify({
336
+ query:
337
+ 'mutation ($file: File!) {createTest3(input: {fields: {file: {file:$file}}}){test3{id, file {name, isPresignedUrl}}}}',
338
+ variables: { file: null },
339
+ }),
340
+ )
341
+
342
+ formData.append('map', JSON.stringify({ 0: ['variables.file'] }))
343
+
344
+ formData.append('0', new File(['a'], 'a.text', { type: 'text/plain' }))
345
+
346
+ const res = await fetch(`http://127.0.0.1:${port}/graphql`, {
347
+ method: 'POST',
348
+ body: formData,
349
+ })
350
+
351
+ const jsonRes = await res.json()
352
+
353
+ // Return the url in dev adapter it's the file name
354
+ expect(jsonRes.data.createTest3.test3.file.name).toEqual('a.text')
355
+ expect(jsonRes.data.createTest3.test3.file.isPresignedUrl).toEqual(true)
356
+
357
+ expect(spyFileDevAdapterUploadFile).toHaveBeenCalledTimes(1)
358
+ const fileArg = spyFileDevAdapterUploadFile.mock.calls[0]?.[0]
359
+ expect(fileArg?.name).toEqual('a.text')
360
+ expect(await fileArg?.text()).toEqual('a')
361
+ })
362
+
363
+ it('should upload a file on request on type File on update request', async () => {
364
+ const formData = new FormData()
365
+
366
+ formData.append(
367
+ 'operations',
368
+ JSON.stringify({
369
+ query:
370
+ 'mutation ($file: File!) {createTest3(input: {fields: {file: {file:$file}}}){test3{id, file { name }}}}',
371
+ variables: { file: null },
372
+ }),
373
+ )
374
+
375
+ formData.append('map', JSON.stringify({ 0: ['variables.file'] }))
376
+
377
+ formData.append('0', new File(['a'], 'a.text', { type: 'text/plain' }))
378
+
379
+ const res = await fetch(`http://127.0.0.1:${port}/graphql`, {
380
+ method: 'POST',
381
+ body: formData,
382
+ })
383
+
384
+ const jsonRes = await res.json()
385
+
386
+ const idOfCreatedObject = jsonRes.data.createTest3.test3.id
387
+
388
+ const formData2 = new FormData()
389
+
390
+ formData2.append(
391
+ 'operations',
392
+ JSON.stringify({
393
+ query: `mutation ($file: File!) {updateTest3(input: {id: "${idOfCreatedObject}",fields: {file: {file:$file}}}){test3{id, file { name }}}}`,
394
+ variables: { file: null },
395
+ }),
396
+ )
397
+
398
+ formData2.append('map', JSON.stringify({ 0: ['variables.file'] }))
399
+
400
+ formData2.append('0', new File(['b'], 'b.text', { type: 'text/plain' }))
401
+
402
+ const updatedRes = await fetch(`http://127.0.0.1:${port}/graphql`, {
403
+ method: 'POST',
404
+ body: formData2,
405
+ })
406
+
407
+ const jsonUpdatedRes = await updatedRes.json()
408
+
409
+ // Return the url in dev adapter it's the file name
410
+ expect(jsonUpdatedRes.data.updateTest3.test3.file.name).toEqual('b.text')
411
+
412
+ // 2 for create and update
413
+ expect(spyFileDevAdapterUploadFile).toHaveBeenCalledTimes(2)
414
+ const fileArg = spyFileDevAdapterUploadFile.mock.calls[1]?.[0]
415
+ expect(fileArg?.name).toEqual('b.text')
416
+ expect(await fileArg?.text()).toEqual('b')
417
+ })
418
+
419
+ it('should return the url of the file on after read request', async () => {
420
+ const formData = new FormData()
421
+
422
+ formData.append(
423
+ 'operations',
424
+ JSON.stringify({
425
+ query:
426
+ 'mutation ($file: File!) {createTest3(input: {fields: {file: {file:$file}}}){test3{id, file { name}}}}',
427
+ variables: { file: null },
428
+ }),
429
+ )
430
+
431
+ formData.append('map', JSON.stringify({ 0: ['variables.file'] }))
432
+
433
+ formData.append('0', new File(['a'], 'a.text', { type: 'text/plain' }))
434
+
435
+ await fetch(`http://127.0.0.1:${port}/graphql`, {
436
+ method: 'POST',
437
+ body: formData,
438
+ })
439
+
440
+ const anonymousClient = getAnonymousClient(port)
441
+
442
+ const { test3s } = await anonymousClient.request<any>(gql`
443
+ query {
444
+ test3s {
445
+ edges {
446
+ node {
447
+ id
448
+ file {
449
+ name
450
+ url
451
+ urlGeneratedAt
452
+ }
453
+ }
454
+ }
455
+ }
456
+ }
457
+ `)
458
+
459
+ expect(test3s.edges[0].node.file.name).toEqual('a.text')
460
+ expect(test3s.edges[0].node.file.url).toEqual(
461
+ `http://127.0.0.1:${port}/bucket/a.text`,
462
+ )
463
+ expect(new Date(test3s.edges[0].node.file.urlGeneratedAt)).toBeDate()
464
+ })
465
+
466
+ it('should return the url of the file on after read request for multiple objects', async () => {
467
+ const upload = async (fileName: string, content: string) => {
468
+ const formData = new FormData()
469
+
470
+ formData.append(
471
+ 'operations',
472
+ JSON.stringify({
473
+ query:
474
+ 'mutation ($file: File!) {createTest3(input: {fields: {file: {file:$file}}}){test3{id, file { name}}}}',
475
+ variables: { file: null },
476
+ }),
477
+ )
478
+
479
+ formData.append('map', JSON.stringify({ 0: ['variables.file'] }))
480
+
481
+ formData.append(
482
+ '0',
483
+ new File([content], fileName, { type: 'text/plain' }),
484
+ )
485
+
486
+ await fetch(`http://127.0.0.1:${port}/graphql`, {
487
+ method: 'POST',
488
+ body: formData,
489
+ })
490
+ }
491
+
492
+ await upload('a.text', 'a')
493
+ await upload('b.text', 'b')
494
+
495
+ const anonymousClient = getAnonymousClient(port)
496
+
497
+ const { test3s } = await anonymousClient.request<any>(gql`
498
+ query {
499
+ test3s {
500
+ edges {
501
+ node {
502
+ id
503
+ file {
504
+ name
505
+ url
506
+ urlGeneratedAt
507
+ }
508
+ }
509
+ }
510
+ }
511
+ }
512
+ `)
513
+
514
+ const files = test3s.edges.map((edge: any) => edge.node.file)
515
+
516
+ expect(files).toHaveLength(2)
517
+ expect(files.map((f: any) => f.name)).toEqual(
518
+ expect.arrayContaining(['a.text', 'b.text']),
519
+ )
520
+
521
+ files.forEach((file: any) => {
522
+ expect(file.url).toEqual(`http://127.0.0.1:${port}/bucket/${file.name}`)
523
+ expect(new Date(file.urlGeneratedAt)).toBeDate()
524
+ })
525
+ })
526
+
527
+ it('should not read the file again in the bucket if the cache is not expired', async () => {
528
+ const formData = new FormData()
529
+
530
+ formData.append(
531
+ 'operations',
532
+ JSON.stringify({
533
+ query:
534
+ 'mutation ($file: File!) {createTest3(input: {fields: {file: {file:$file}}}){test3{id, file { name}}}}',
535
+ variables: { file: null },
536
+ }),
537
+ )
538
+
539
+ formData.append('map', JSON.stringify({ 0: ['variables.file'] }))
540
+
541
+ formData.append('0', new File(['a'], 'a.text', { type: 'text/plain' }))
542
+
543
+ await fetch(`http://127.0.0.1:${port}/graphql`, {
544
+ method: 'POST',
545
+ body: formData,
546
+ })
547
+
548
+ const anonymousClient = getAnonymousClient(port)
549
+
550
+ const { test3s } = await anonymousClient.request<any>(gql`
551
+ query {
552
+ test3s {
553
+ edges {
554
+ node {
555
+ id
556
+ file {
557
+ name
558
+ url
559
+ urlGeneratedAt
560
+ }
561
+ }
562
+ }
563
+ }
564
+ }
565
+ `)
566
+
567
+ expect(test3s.edges[0].node.file.name).toEqual('a.text')
568
+ expect(test3s.edges[0].node.file.url).toEqual(
569
+ `http://127.0.0.1:${port}/bucket/a.text`,
570
+ )
571
+ expect(new Date(test3s.edges[0].node.file.urlGeneratedAt)).toBeDate()
572
+
573
+ expect(spyFileDevAdapterReadFile).toHaveBeenCalledTimes(1)
574
+
575
+ await anonymousClient.request<any>(gql`
576
+ query {
577
+ test3s {
578
+ edges {
579
+ node {
580
+ id
581
+ file {
582
+ name
583
+ url
584
+ urlGeneratedAt
585
+ }
586
+ }
587
+ }
588
+ }
589
+ }
590
+ `)
591
+
592
+ // Again once because the cache is not expired
593
+ expect(spyFileDevAdapterReadFile).toHaveBeenCalledTimes(1)
594
+ })
595
+
596
+ it('should reset the cache if the file is updated', async () => {
597
+ const formData = new FormData()
598
+
599
+ formData.append(
600
+ 'operations',
601
+ JSON.stringify({
602
+ query:
603
+ 'mutation ($file: File!) {createTest3(input: {fields: {file: {file:$file}}}){test3{id, file { name}}}}',
604
+ variables: { file: null },
605
+ }),
606
+ )
607
+
608
+ formData.append('map', JSON.stringify({ 0: ['variables.file'] }))
609
+
610
+ formData.append('0', new File(['a'], 'a.text', { type: 'text/plain' }))
611
+
612
+ await fetch(`http://127.0.0.1:${port}/graphql`, {
613
+ method: 'POST',
614
+ body: formData,
615
+ })
616
+
617
+ const anonymousClient = getAnonymousClient(port)
618
+
619
+ const { test3s } = await anonymousClient.request<any>(gql`
620
+ query {
621
+ test3s {
622
+ edges {
623
+ node {
624
+ id
625
+ file {
626
+ name
627
+ url
628
+ urlGeneratedAt
629
+ }
630
+ }
631
+ }
632
+ }
633
+ }
634
+ `)
635
+
636
+ expect(test3s.edges[0].node.file.name).toEqual('a.text')
637
+ expect(test3s.edges[0].node.file.url).toEqual(
638
+ `http://127.0.0.1:${port}/bucket/a.text`,
639
+ )
640
+ expect(new Date(test3s.edges[0].node.file.urlGeneratedAt)).toBeDate()
641
+
642
+ expect(spyFileDevAdapterReadFile).toHaveBeenCalledTimes(1)
643
+
644
+ const idOfCreatedObject = test3s.edges[0].node.id
645
+
646
+ const formData2 = new FormData()
647
+
648
+ formData2.append(
649
+ 'operations',
650
+ JSON.stringify({
651
+ query: `mutation ($file: File!) {updateTest3(input: {id: "${idOfCreatedObject}",fields: {file: {file:$file}}}){test3{id, file { name }}}}`,
652
+ variables: { file: null },
653
+ }),
654
+ )
655
+
656
+ formData2.append('map', JSON.stringify({ 0: ['variables.file'] }))
657
+
658
+ formData2.append('0', new File(['b'], 'b.text', { type: 'text/plain' }))
659
+
660
+ // We update the file
661
+ await fetch(`http://127.0.0.1:${port}/graphql`, {
662
+ method: 'POST',
663
+ body: formData2,
664
+ })
665
+
666
+ await anonymousClient.request<any>(gql`
667
+ query {
668
+ test3s {
669
+ edges {
670
+ node {
671
+ id
672
+ file {
673
+ name
674
+ url
675
+ urlGeneratedAt
676
+ }
677
+ }
678
+ }
679
+ }
680
+ }
681
+ `)
682
+
683
+ // Again once because the file was updated
684
+ expect(spyFileDevAdapterReadFile).toHaveBeenCalledTimes(2)
685
+ })
686
+
687
+ it('should reset the cache if the url is updated', async () => {
688
+ const formData = new FormData()
689
+
690
+ formData.append(
691
+ 'operations',
692
+ JSON.stringify({
693
+ query:
694
+ 'mutation ($file: File!) {createTest3(input: {fields: {file: {file:$file}}}){test3{id, file { name}}}}',
695
+ variables: { file: null },
696
+ }),
697
+ )
698
+
699
+ formData.append('map', JSON.stringify({ 0: ['variables.file'] }))
700
+
701
+ formData.append('0', new File(['a'], 'a.text', { type: 'text/plain' }))
702
+
703
+ await fetch(`http://127.0.0.1:${port}/graphql`, {
704
+ method: 'POST',
705
+ body: formData,
706
+ })
707
+
708
+ const anonymousClient = getAnonymousClient(port)
709
+
710
+ const { test3s } = await anonymousClient.request<any>(gql`
711
+ query {
712
+ test3s {
713
+ edges {
714
+ node {
715
+ id
716
+ file {
717
+ name
718
+ url
719
+ urlGeneratedAt
720
+ }
721
+ }
722
+ }
723
+ }
724
+ }
725
+ `)
726
+
727
+ expect(test3s.edges[0].node.file.name).toEqual('a.text')
728
+ expect(test3s.edges[0].node.file.url).toEqual(
729
+ `http://127.0.0.1:${port}/bucket/a.text`,
730
+ )
731
+ expect(new Date(test3s.edges[0].node.file.urlGeneratedAt)).toBeDate()
732
+
733
+ expect(spyFileDevAdapterReadFile).toHaveBeenCalledTimes(1)
734
+
735
+ const idOfCreatedObject = test3s.edges[0].node.id
736
+
737
+ await wabe.controllers.database.updateObject({
738
+ // @ts-expect-error
739
+ className: 'Test3',
740
+ context: {
741
+ isRoot: true,
742
+ wabe,
743
+ },
744
+ where: {},
745
+ // @ts-expect-error
746
+ select: { file: true, id: true },
747
+ data: {
748
+ // @ts-expect-error
749
+ file: {
750
+ url: 'https://palixir.github.io/wabe//assets/logo.png',
751
+ },
752
+ },
753
+ id: idOfCreatedObject,
754
+ })
755
+
756
+ await anonymousClient.request<any>(gql`
757
+ query {
758
+ test3s {
759
+ edges {
760
+ node {
761
+ id
762
+ file {
763
+ name
764
+ url
765
+ urlGeneratedAt
766
+ }
767
+ }
768
+ }
769
+ }
770
+ }
771
+ `)
772
+
773
+ expect(spyFileDevAdapterReadFile).toHaveBeenCalledTimes(1)
774
+ })
775
+
776
+ it('should delete the file on the bucket after delete the object', async () => {
777
+ const formData = new FormData()
778
+
779
+ formData.append(
780
+ 'operations',
781
+ JSON.stringify({
782
+ query:
783
+ 'mutation ($file: File!) {createTest3(input: {fields: {file: {file:$file}}}){test3{id, file { name}}}}',
784
+ variables: { file: null },
785
+ }),
786
+ )
787
+
788
+ formData.append('map', JSON.stringify({ 0: ['variables.file'] }))
789
+
790
+ formData.append('0', new File(['a'], 'a.text', { type: 'text/plain' }))
791
+
792
+ const res = await fetch(`http://127.0.0.1:${port}/graphql`, {
793
+ method: 'POST',
794
+ body: formData,
795
+ })
796
+
797
+ const jsonRes = await res.json()
798
+
799
+ const id = jsonRes.data.createTest3.test3.id
800
+
801
+ const url = await wabe.config.file?.adapter.readFile('a.text')
802
+ expect(url).not.toBeNull()
803
+
804
+ const anonymousClient = getAnonymousClient(port)
805
+
806
+ await anonymousClient.request<any>(
807
+ gql`
808
+ mutation {
809
+ deleteTest3(input: {id: "${id}"}) {
810
+ test3 {
811
+ id
812
+ }
813
+ }
814
+ }
815
+ `,
816
+ )
817
+
818
+ const { test3s } = await anonymousClient.request<any>(gql`
819
+ query {
820
+ test3s {
821
+ edges {
822
+ node {
823
+ id
824
+ file {
825
+ name
826
+ url
827
+ urlGeneratedAt
828
+ }
829
+ }
830
+ }
831
+ }
832
+ }
833
+ `)
834
+
835
+ expect(test3s.edges.length).toEqual(0)
836
+
837
+ const url2 = await wabe.config.file?.adapter.readFile('a.text')
838
+ expect(url2).toBeNull()
839
+ })
840
+
841
+ it('should not delete a file if the file not exists', async () => {
842
+ const formData = new FormData()
843
+
844
+ formData.append(
845
+ 'operations',
846
+ JSON.stringify({
847
+ query:
848
+ 'mutation ($file: File!) {createTest3(input: {fields: {file: {file:$file}}}){test3{id, file { name}}}}',
849
+ variables: { file: null },
850
+ }),
851
+ )
852
+
853
+ formData.append('map', JSON.stringify({ 0: ['variables.file'] }))
854
+
855
+ formData.append('0', new File(['a'], 'a.text', { type: 'text/plain' }))
856
+
857
+ const res = await fetch(`http://127.0.0.1:${port}/graphql`, {
858
+ method: 'POST',
859
+ body: formData,
860
+ })
861
+
862
+ const jsonRes = await res.json()
863
+
864
+ const id = jsonRes.data.createTest3.test3.id
865
+
866
+ const url = await wabe.config.file?.adapter.readFile('a.text')
867
+ expect(url).not.toBeNull()
868
+
869
+ await wabe.config.file?.adapter.deleteFile('a.text')
870
+
871
+ const anonymousClient = getAnonymousClient(port)
872
+
873
+ expect(
874
+ anonymousClient.request<any>(
875
+ gql`
876
+ mutation {
877
+ deleteTest3(input: {id: "${id}"}) {
878
+ test3 {
879
+ id
880
+ }
881
+ }
882
+ }
883
+ `,
884
+ ),
885
+ ).resolves.toEqual(expect.anything())
886
+ })
887
+
888
+ it('should upload a file providing an url without File scalar', async () => {
889
+ const anonymousClient = getAnonymousClient(port)
890
+
891
+ await anonymousClient.request<any>(gql`
892
+ mutation {
893
+ createTest3(
894
+ input: {
895
+ fields: {
896
+ file: {
897
+ url: "https://palixir.github.io/wabe//assets/logo.png"
898
+ }
899
+ }
900
+ }
901
+ ) {
902
+ test3 {
903
+ id
904
+ file {
905
+ name
906
+ url
907
+ urlGeneratedAt
908
+ }
909
+ }
910
+ }
911
+ }
912
+ `)
913
+
914
+ const { test3s } = await anonymousClient.request<any>(gql`
915
+ query {
916
+ test3s {
917
+ edges {
918
+ node {
919
+ id
920
+ file {
921
+ name
922
+ url
923
+ urlGeneratedAt
924
+ }
925
+ }
926
+ }
927
+ }
928
+ }
929
+ `)
930
+
931
+ expect(test3s.edges[0].node.file.url).toEqual(
932
+ 'https://palixir.github.io/wabe//assets/logo.png',
933
+ )
934
+ })
935
+
936
+ it('should upload a file and access to it with the local url provided by upload directory', async () => {
937
+ await wabe.controllers.database.createObject({
938
+ // @ts-expect-error
939
+ className: 'Test3',
940
+ context: {
941
+ isRoot: true,
942
+ wabe,
943
+ },
944
+ data: {
945
+ // @ts-expect-error
946
+ file: {
947
+ file: new File(['this is the content'], 'a.txt', {
948
+ type: 'text/plain',
949
+ }),
950
+ },
951
+ },
952
+ select: {},
953
+ })
954
+
955
+ const result = await wabe.controllers.database.getObjects({
956
+ // @ts-expect-error
957
+ className: 'Test3',
958
+ context: {
959
+ isRoot: true,
960
+ wabe,
961
+ },
962
+ where: {},
963
+ // @ts-expect-error
964
+ select: { file: true, id: true },
965
+ })
966
+
967
+ // @ts-expect-error
968
+ expect(result[0].file.name).toEqual('a.txt')
969
+ // @ts-expect-error
970
+ expect(result[0].file.url).toEqual(`http://127.0.0.1:${port}/bucket/a.txt`)
971
+
972
+ // @ts-expect-error
973
+ const url = result?.[0]?.file?.url
974
+
975
+ const res = await fetch(url)
976
+
977
+ expect(await res.text()).toEqual('this is the content')
978
+ })
979
+ })