@budibase/backend-core 2.21.3 → 2.21.5
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.
- package/dist/index.js +301 -68
- package/dist/index.js.map +4 -4
- package/dist/index.js.meta.json +1 -1
- package/dist/package.json +6 -5
- package/dist/plugins.js.meta.json +1 -1
- package/dist/src/cache/base/index.d.ts +33 -3
- package/dist/src/cache/base/index.js +60 -1
- package/dist/src/cache/base/index.js.map +1 -1
- package/dist/src/cache/docWritethrough.d.ts +21 -0
- package/dist/src/cache/docWritethrough.js +107 -0
- package/dist/src/cache/docWritethrough.js.map +1 -0
- package/dist/src/cache/generic.d.ts +3 -3
- package/dist/src/cache/generic.js.map +1 -1
- package/dist/src/cache/index.d.ts +1 -0
- package/dist/src/cache/index.js +2 -1
- package/dist/src/cache/index.js.map +1 -1
- package/dist/src/cache/user.js.map +1 -1
- package/dist/src/configs/configs.d.ts +1 -1
- package/dist/src/constants/db.d.ts +3 -0
- package/dist/src/constants/db.js +3 -0
- package/dist/src/constants/db.js.map +1 -1
- package/dist/src/context/mainContext.d.ts +1 -0
- package/dist/src/context/mainContext.js +13 -1
- package/dist/src/context/mainContext.js.map +1 -1
- package/dist/src/db/Replication.d.ts +13 -25
- package/dist/src/db/Replication.js +18 -33
- package/dist/src/db/Replication.js.map +1 -1
- package/dist/src/db/couch/DatabaseImpl.d.ts +3 -1
- package/dist/src/db/couch/DatabaseImpl.js +18 -1
- package/dist/src/db/couch/DatabaseImpl.js.map +1 -1
- package/dist/src/db/instrumentation.d.ts +1 -1
- package/dist/src/db/instrumentation.js +5 -2
- package/dist/src/db/instrumentation.js.map +1 -1
- package/dist/src/environment.d.ts +1 -0
- package/dist/src/environment.js +1 -1
- package/dist/src/environment.js.map +1 -1
- package/dist/src/events/analytics.d.ts +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/queue/constants.d.ts +2 -1
- package/dist/src/queue/constants.js +1 -0
- package/dist/src/queue/constants.js.map +1 -1
- package/dist/src/queue/inMemoryQueue.d.ts +23 -13
- package/dist/src/queue/inMemoryQueue.js +83 -30
- package/dist/src/queue/inMemoryQueue.js.map +1 -1
- package/dist/src/queue/listeners.js +2 -0
- package/dist/src/queue/listeners.js.map +1 -1
- package/dist/src/queue/queue.d.ts +1 -0
- package/dist/src/queue/queue.js.map +1 -1
- package/dist/src/redis/init.d.ts +1 -0
- package/dist/src/redis/init.js +12 -2
- package/dist/src/redis/init.js.map +1 -1
- package/dist/src/redis/redis.d.ts +10 -5
- package/dist/src/redis/redis.js +52 -3
- package/dist/src/redis/redis.js.map +1 -1
- package/dist/src/redis/redlockImpl.js.map +1 -1
- package/dist/src/redis/utils.d.ts +2 -1
- package/dist/src/redis/utils.js +1 -0
- package/dist/src/redis/utils.js.map +1 -1
- package/dist/src/security/roles.d.ts +1 -1
- package/dist/src/security/roles.js +0 -3
- package/dist/src/security/roles.js.map +1 -1
- package/dist/tests/core/utilities/structures/accounts.js +1 -1
- package/dist/tests/core/utilities/structures/accounts.js.map +1 -1
- package/dist/tests/core/utilities/structures/scim.js +1 -1
- package/dist/tests/core/utilities/structures/scim.js.map +1 -1
- package/package.json +6 -5
- package/src/cache/base/index.ts +62 -4
- package/src/cache/docWritethrough.ts +97 -0
- package/src/cache/generic.ts +3 -2
- package/src/cache/index.ts +1 -0
- package/src/cache/tests/docWritethrough.spec.ts +293 -0
- package/src/cache/user.ts +2 -2
- package/src/constants/db.ts +3 -0
- package/src/context/mainContext.ts +11 -0
- package/src/db/Replication.ts +27 -40
- package/src/db/couch/DatabaseImpl.ts +18 -1
- package/src/db/instrumentation.ts +5 -2
- package/src/db/tests/DatabaseImpl.spec.ts +55 -0
- package/src/environment.ts +1 -0
- package/src/queue/constants.ts +1 -0
- package/src/queue/inMemoryQueue.ts +79 -24
- package/src/queue/listeners.ts +2 -0
- package/src/queue/queue.ts +2 -0
- package/src/redis/init.ts +12 -1
- package/src/redis/redis.ts +63 -9
- package/src/redis/redlockImpl.ts +1 -1
- package/src/redis/tests/redis.spec.ts +214 -0
- package/src/redis/utils.ts +1 -0
- package/src/security/roles.ts +1 -4
- package/tests/core/utilities/structures/accounts.ts +1 -1
- package/tests/core/utilities/structures/scim.ts +1 -1
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import tk from "timekeeper"
|
|
2
|
+
|
|
3
|
+
import _ from "lodash"
|
|
4
|
+
import { DBTestConfiguration, generator, structures } from "../../../tests"
|
|
5
|
+
import { getDB } from "../../db"
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
DocWritethrough,
|
|
9
|
+
docWritethroughProcessorQueue,
|
|
10
|
+
init,
|
|
11
|
+
} from "../docWritethrough"
|
|
12
|
+
|
|
13
|
+
import InMemoryQueue from "../../queue/inMemoryQueue"
|
|
14
|
+
|
|
15
|
+
const initialTime = Date.now()
|
|
16
|
+
|
|
17
|
+
async function waitForQueueCompletion() {
|
|
18
|
+
const queue: InMemoryQueue = docWritethroughProcessorQueue as never
|
|
19
|
+
await queue.waitForCompletion()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("docWritethrough", () => {
|
|
23
|
+
beforeAll(() => {
|
|
24
|
+
init()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const config = new DBTestConfiguration()
|
|
28
|
+
|
|
29
|
+
const db = getDB(structures.db.id())
|
|
30
|
+
let documentId: string
|
|
31
|
+
let docWritethrough: DocWritethrough
|
|
32
|
+
|
|
33
|
+
describe("patch", () => {
|
|
34
|
+
function generatePatchObject(fieldCount: number) {
|
|
35
|
+
const keys = generator.unique(() => generator.word(), fieldCount)
|
|
36
|
+
return keys.reduce((acc, c) => {
|
|
37
|
+
acc[c] = generator.word()
|
|
38
|
+
return acc
|
|
39
|
+
}, {} as Record<string, any>)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
beforeEach(async () => {
|
|
43
|
+
jest.clearAllMocks()
|
|
44
|
+
documentId = structures.uuid()
|
|
45
|
+
docWritethrough = new DocWritethrough(db, documentId)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it("patching will not persist until the messages are persisted", async () => {
|
|
49
|
+
await config.doInTenant(async () => {
|
|
50
|
+
await docWritethrough.patch(generatePatchObject(2))
|
|
51
|
+
await docWritethrough.patch(generatePatchObject(2))
|
|
52
|
+
|
|
53
|
+
expect(await db.exists(documentId)).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it("patching will persist when the messages are persisted", async () => {
|
|
58
|
+
await config.doInTenant(async () => {
|
|
59
|
+
const patch1 = generatePatchObject(2)
|
|
60
|
+
const patch2 = generatePatchObject(2)
|
|
61
|
+
await docWritethrough.patch(patch1)
|
|
62
|
+
await docWritethrough.patch(patch2)
|
|
63
|
+
|
|
64
|
+
await waitForQueueCompletion()
|
|
65
|
+
|
|
66
|
+
// This will not be persisted
|
|
67
|
+
const patch3 = generatePatchObject(3)
|
|
68
|
+
await docWritethrough.patch(patch3)
|
|
69
|
+
|
|
70
|
+
expect(await db.get(documentId)).toEqual({
|
|
71
|
+
_id: documentId,
|
|
72
|
+
...patch1,
|
|
73
|
+
...patch2,
|
|
74
|
+
_rev: expect.stringMatching(/2-.+/),
|
|
75
|
+
createdAt: new Date(initialTime).toISOString(),
|
|
76
|
+
updatedAt: new Date(initialTime).toISOString(),
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it("patching will persist keeping the previous data", async () => {
|
|
82
|
+
await config.doInTenant(async () => {
|
|
83
|
+
const patch1 = generatePatchObject(2)
|
|
84
|
+
const patch2 = generatePatchObject(2)
|
|
85
|
+
await docWritethrough.patch(patch1)
|
|
86
|
+
await docWritethrough.patch(patch2)
|
|
87
|
+
|
|
88
|
+
await waitForQueueCompletion()
|
|
89
|
+
|
|
90
|
+
const patch3 = generatePatchObject(3)
|
|
91
|
+
await docWritethrough.patch(patch3)
|
|
92
|
+
|
|
93
|
+
await waitForQueueCompletion()
|
|
94
|
+
|
|
95
|
+
expect(await db.get(documentId)).toEqual(
|
|
96
|
+
expect.objectContaining({
|
|
97
|
+
_id: documentId,
|
|
98
|
+
...patch1,
|
|
99
|
+
...patch2,
|
|
100
|
+
...patch3,
|
|
101
|
+
})
|
|
102
|
+
)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it("date audit fields are set correctly when persisting", async () => {
|
|
107
|
+
await config.doInTenant(async () => {
|
|
108
|
+
const patch1 = generatePatchObject(2)
|
|
109
|
+
const patch2 = generatePatchObject(2)
|
|
110
|
+
await docWritethrough.patch(patch1)
|
|
111
|
+
const date1 = new Date()
|
|
112
|
+
await waitForQueueCompletion()
|
|
113
|
+
await docWritethrough.patch(patch2)
|
|
114
|
+
|
|
115
|
+
tk.travel(Date.now() + 100)
|
|
116
|
+
const date2 = new Date()
|
|
117
|
+
await waitForQueueCompletion()
|
|
118
|
+
|
|
119
|
+
expect(date1).not.toEqual(date2)
|
|
120
|
+
expect(await db.get(documentId)).toEqual(
|
|
121
|
+
expect.objectContaining({
|
|
122
|
+
createdAt: date1.toISOString(),
|
|
123
|
+
updatedAt: date2.toISOString(),
|
|
124
|
+
})
|
|
125
|
+
)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it("concurrent patches will override keys", async () => {
|
|
130
|
+
await config.doInTenant(async () => {
|
|
131
|
+
const patch1 = generatePatchObject(2)
|
|
132
|
+
await docWritethrough.patch(patch1)
|
|
133
|
+
await waitForQueueCompletion()
|
|
134
|
+
const patch2 = generatePatchObject(1)
|
|
135
|
+
await docWritethrough.patch(patch2)
|
|
136
|
+
|
|
137
|
+
const keyToOverride = _.sample(Object.keys(patch1))!
|
|
138
|
+
expect(await db.get(documentId)).toEqual(
|
|
139
|
+
expect.objectContaining({
|
|
140
|
+
[keyToOverride]: patch1[keyToOverride],
|
|
141
|
+
})
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
await waitForQueueCompletion()
|
|
145
|
+
|
|
146
|
+
const patch3 = {
|
|
147
|
+
...generatePatchObject(3),
|
|
148
|
+
[keyToOverride]: generator.word(),
|
|
149
|
+
}
|
|
150
|
+
await docWritethrough.patch(patch3)
|
|
151
|
+
await waitForQueueCompletion()
|
|
152
|
+
|
|
153
|
+
expect(await db.get(documentId)).toEqual(
|
|
154
|
+
expect.objectContaining({
|
|
155
|
+
...patch1,
|
|
156
|
+
...patch2,
|
|
157
|
+
...patch3,
|
|
158
|
+
})
|
|
159
|
+
)
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it("concurrent patches to different docWritethrough will not pollute each other", async () => {
|
|
164
|
+
await config.doInTenant(async () => {
|
|
165
|
+
const secondDocWritethrough = new DocWritethrough(
|
|
166
|
+
db,
|
|
167
|
+
structures.db.id()
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
const doc1Patch = generatePatchObject(2)
|
|
171
|
+
await docWritethrough.patch(doc1Patch)
|
|
172
|
+
const doc2Patch = generatePatchObject(1)
|
|
173
|
+
await secondDocWritethrough.patch(doc2Patch)
|
|
174
|
+
|
|
175
|
+
await waitForQueueCompletion()
|
|
176
|
+
|
|
177
|
+
const doc1Patch2 = generatePatchObject(3)
|
|
178
|
+
await docWritethrough.patch(doc1Patch2)
|
|
179
|
+
const doc2Patch2 = generatePatchObject(3)
|
|
180
|
+
await secondDocWritethrough.patch(doc2Patch2)
|
|
181
|
+
await waitForQueueCompletion()
|
|
182
|
+
|
|
183
|
+
expect(await db.get(docWritethrough.docId)).toEqual(
|
|
184
|
+
expect.objectContaining({
|
|
185
|
+
...doc1Patch,
|
|
186
|
+
...doc1Patch2,
|
|
187
|
+
})
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
expect(await db.get(secondDocWritethrough.docId)).toEqual(
|
|
191
|
+
expect.objectContaining({
|
|
192
|
+
...doc2Patch,
|
|
193
|
+
...doc2Patch2,
|
|
194
|
+
})
|
|
195
|
+
)
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it("cached values are persisted only once", async () => {
|
|
200
|
+
await config.doInTenant(async () => {
|
|
201
|
+
const initialPatch = generatePatchObject(5)
|
|
202
|
+
|
|
203
|
+
await docWritethrough.patch(initialPatch)
|
|
204
|
+
await waitForQueueCompletion()
|
|
205
|
+
|
|
206
|
+
expect(await db.get(documentId)).toEqual(
|
|
207
|
+
expect.objectContaining(initialPatch)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
await db.remove(await db.get(documentId))
|
|
211
|
+
|
|
212
|
+
await waitForQueueCompletion()
|
|
213
|
+
const extraPatch = generatePatchObject(5)
|
|
214
|
+
await docWritethrough.patch(extraPatch)
|
|
215
|
+
await waitForQueueCompletion()
|
|
216
|
+
|
|
217
|
+
expect(await db.get(documentId)).toEqual(
|
|
218
|
+
expect.objectContaining(extraPatch)
|
|
219
|
+
)
|
|
220
|
+
expect(await db.get(documentId)).not.toEqual(
|
|
221
|
+
expect.objectContaining(initialPatch)
|
|
222
|
+
)
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it("concurrent calls will not cause conflicts", async () => {
|
|
227
|
+
async function parallelPatch(count: number) {
|
|
228
|
+
const patches = Array.from({ length: count }).map(() =>
|
|
229
|
+
generatePatchObject(1)
|
|
230
|
+
)
|
|
231
|
+
await Promise.all(patches.map(p => docWritethrough.patch(p)))
|
|
232
|
+
|
|
233
|
+
return patches.reduce((acc, c) => {
|
|
234
|
+
acc = { ...acc, ...c }
|
|
235
|
+
return acc
|
|
236
|
+
}, {})
|
|
237
|
+
}
|
|
238
|
+
const queueMessageSpy = jest.spyOn(docWritethroughProcessorQueue, "add")
|
|
239
|
+
|
|
240
|
+
await config.doInTenant(async () => {
|
|
241
|
+
let patches = await parallelPatch(5)
|
|
242
|
+
expect(queueMessageSpy).toBeCalledTimes(5)
|
|
243
|
+
|
|
244
|
+
await waitForQueueCompletion()
|
|
245
|
+
expect(await db.get(documentId)).toEqual(
|
|
246
|
+
expect.objectContaining(patches)
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
patches = { ...patches, ...(await parallelPatch(40)) }
|
|
250
|
+
expect(queueMessageSpy).toBeCalledTimes(45)
|
|
251
|
+
|
|
252
|
+
await waitForQueueCompletion()
|
|
253
|
+
expect(await db.get(documentId)).toEqual(
|
|
254
|
+
expect.objectContaining(patches)
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
patches = { ...patches, ...(await parallelPatch(10)) }
|
|
258
|
+
expect(queueMessageSpy).toBeCalledTimes(55)
|
|
259
|
+
|
|
260
|
+
await waitForQueueCompletion()
|
|
261
|
+
expect(await db.get(documentId)).toEqual(
|
|
262
|
+
expect.objectContaining(patches)
|
|
263
|
+
)
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
// This is not yet supported
|
|
268
|
+
it.skip("patches will execute in order", async () => {
|
|
269
|
+
let incrementalValue = 0
|
|
270
|
+
const keyToOverride = generator.word()
|
|
271
|
+
async function incrementalPatches(count: number) {
|
|
272
|
+
for (let i = 0; i < count; i++) {
|
|
273
|
+
await docWritethrough.patch({ [keyToOverride]: incrementalValue++ })
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
await config.doInTenant(async () => {
|
|
278
|
+
await incrementalPatches(5)
|
|
279
|
+
|
|
280
|
+
await waitForQueueCompletion()
|
|
281
|
+
expect(await db.get(documentId)).toEqual(
|
|
282
|
+
expect.objectContaining({ [keyToOverride]: 5 })
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
await incrementalPatches(40)
|
|
286
|
+
await waitForQueueCompletion()
|
|
287
|
+
expect(await db.get(documentId)).toEqual(
|
|
288
|
+
expect.objectContaining({ [keyToOverride]: 45 })
|
|
289
|
+
)
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
})
|
|
293
|
+
})
|
package/src/cache/user.ts
CHANGED
|
@@ -6,7 +6,7 @@ import env from "../environment"
|
|
|
6
6
|
import * as accounts from "../accounts"
|
|
7
7
|
import { UserDB } from "../users"
|
|
8
8
|
import { sdk } from "@budibase/shared-core"
|
|
9
|
-
import { User } from "@budibase/types"
|
|
9
|
+
import { User, UserMetadata } from "@budibase/types"
|
|
10
10
|
|
|
11
11
|
const EXPIRY_SECONDS = 3600
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ const EXPIRY_SECONDS = 3600
|
|
|
15
15
|
*/
|
|
16
16
|
async function populateFromDB(userId: string, tenantId: string) {
|
|
17
17
|
const db = tenancy.getTenantDB(tenantId)
|
|
18
|
-
const user = await db.get<
|
|
18
|
+
const user = await db.get<UserMetadata>(userId)
|
|
19
19
|
user.budibaseAccess = true
|
|
20
20
|
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
|
21
21
|
const account = await accounts.getAccount(user.email)
|
package/src/constants/db.ts
CHANGED
|
@@ -35,6 +35,17 @@ export function getAuditLogDBName(tenantId?: string) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export function getScimDBName(tenantId?: string) {
|
|
39
|
+
if (!tenantId) {
|
|
40
|
+
tenantId = getTenantId()
|
|
41
|
+
}
|
|
42
|
+
if (tenantId === DEFAULT_TENANT_ID) {
|
|
43
|
+
return StaticDatabases.SCIM_LOGS.name
|
|
44
|
+
} else {
|
|
45
|
+
return `${tenantId}${SEPARATOR}${StaticDatabases.SCIM_LOGS.name}`
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
export function baseGlobalDBName(tenantId: string | undefined | null) {
|
|
39
50
|
if (!tenantId || tenantId === DEFAULT_TENANT_ID) {
|
|
40
51
|
return StaticDatabases.GLOBAL.name
|
package/src/db/Replication.ts
CHANGED
|
@@ -1,66 +1,57 @@
|
|
|
1
|
+
import PouchDB from "pouchdb"
|
|
1
2
|
import { getPouchDB, closePouchDB } from "./couch"
|
|
2
3
|
import { DocumentType } from "../constants"
|
|
3
4
|
|
|
4
5
|
class Replication {
|
|
5
|
-
source:
|
|
6
|
-
target:
|
|
7
|
-
replication: any
|
|
6
|
+
source: PouchDB.Database
|
|
7
|
+
target: PouchDB.Database
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
* @param source - the DB you want to replicate or rollback to
|
|
12
|
-
* @param target - the DB you want to replicate to, or rollback from
|
|
13
|
-
*/
|
|
14
|
-
constructor({ source, target }: any) {
|
|
9
|
+
constructor({ source, target }: { source: string; target: string }) {
|
|
15
10
|
this.source = getPouchDB(source)
|
|
16
11
|
this.target = getPouchDB(target)
|
|
17
12
|
}
|
|
18
13
|
|
|
19
|
-
close() {
|
|
20
|
-
|
|
14
|
+
async close() {
|
|
15
|
+
await Promise.all([closePouchDB(this.source), closePouchDB(this.target)])
|
|
21
16
|
}
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
return new Promise(resolve => {
|
|
25
|
-
|
|
26
|
-
.
|
|
18
|
+
replicate(opts: PouchDB.Replication.ReplicateOptions = {}) {
|
|
19
|
+
return new Promise<PouchDB.Replication.ReplicationResult<{}>>(resolve => {
|
|
20
|
+
this.source.replicate
|
|
21
|
+
.to(this.target, opts)
|
|
22
|
+
.on("denied", function (err) {
|
|
27
23
|
// a document failed to replicate (e.g. due to permissions)
|
|
28
24
|
throw new Error(`Denied: Document failed to replicate ${err}`)
|
|
29
25
|
})
|
|
30
|
-
.on("complete", function (info
|
|
26
|
+
.on("complete", function (info) {
|
|
31
27
|
return resolve(info)
|
|
32
28
|
})
|
|
33
|
-
.on("error", function (err
|
|
29
|
+
.on("error", function (err) {
|
|
34
30
|
throw new Error(`Replication Error: ${err}`)
|
|
35
31
|
})
|
|
36
32
|
})
|
|
37
33
|
}
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return this.replication
|
|
46
|
-
}
|
|
35
|
+
appReplicateOpts(
|
|
36
|
+
opts: PouchDB.Replication.ReplicateOptions = {}
|
|
37
|
+
): PouchDB.Replication.ReplicateOptions {
|
|
38
|
+
if (typeof opts.filter === "string") {
|
|
39
|
+
return opts
|
|
40
|
+
}
|
|
47
41
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
* @param opts - PouchDB replication options
|
|
51
|
-
*/
|
|
52
|
-
replicate(opts = {}) {
|
|
53
|
-
this.replication = this.promisify(this.source.replicate.to, opts)
|
|
54
|
-
return this.replication
|
|
55
|
-
}
|
|
42
|
+
const filter = opts.filter
|
|
43
|
+
delete opts.filter
|
|
56
44
|
|
|
57
|
-
appReplicateOpts() {
|
|
58
45
|
return {
|
|
59
|
-
|
|
46
|
+
...opts,
|
|
47
|
+
filter: (doc: any, params: any) => {
|
|
60
48
|
if (doc._id && doc._id.startsWith(DocumentType.AUTOMATION_LOG)) {
|
|
61
49
|
return false
|
|
62
50
|
}
|
|
63
|
-
|
|
51
|
+
if (doc._id === DocumentType.APP_METADATA) {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
return filter ? filter(doc, params) : true
|
|
64
55
|
},
|
|
65
56
|
}
|
|
66
57
|
}
|
|
@@ -75,10 +66,6 @@ class Replication {
|
|
|
75
66
|
// take the opportunity to remove deleted tombstones
|
|
76
67
|
await this.replicate()
|
|
77
68
|
}
|
|
78
|
-
|
|
79
|
-
cancel() {
|
|
80
|
-
this.replication.cancel()
|
|
81
|
-
}
|
|
82
69
|
}
|
|
83
70
|
|
|
84
71
|
export default Replication
|
|
@@ -70,7 +70,15 @@ export class DatabaseImpl implements Database {
|
|
|
70
70
|
DatabaseImpl.nano = buildNano(couchInfo)
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
exists(docId?: string) {
|
|
74
|
+
if (docId === undefined) {
|
|
75
|
+
return this.dbExists()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return this.docExists(docId)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private async dbExists() {
|
|
74
82
|
const response = await directCouchUrlCall({
|
|
75
83
|
url: `${this.couchInfo.url}/${this.name}`,
|
|
76
84
|
method: "HEAD",
|
|
@@ -79,6 +87,15 @@ export class DatabaseImpl implements Database {
|
|
|
79
87
|
return response.status === 200
|
|
80
88
|
}
|
|
81
89
|
|
|
90
|
+
private async docExists(id: string): Promise<boolean> {
|
|
91
|
+
try {
|
|
92
|
+
await this.performCall(db => () => db.head(id))
|
|
93
|
+
return true
|
|
94
|
+
} catch {
|
|
95
|
+
return false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
82
99
|
private nano() {
|
|
83
100
|
return this.instanceNano || DatabaseImpl.nano
|
|
84
101
|
}
|
|
@@ -24,9 +24,12 @@ export class DDInstrumentedDatabase implements Database {
|
|
|
24
24
|
return this.db.name
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
exists(): Promise<boolean> {
|
|
27
|
+
exists(docId?: string): Promise<boolean> {
|
|
28
28
|
return tracer.trace("db.exists", span => {
|
|
29
|
-
span?.addTags({ db_name: this.name })
|
|
29
|
+
span?.addTags({ db_name: this.name, doc_id: docId })
|
|
30
|
+
if (docId) {
|
|
31
|
+
return this.db.exists(docId)
|
|
32
|
+
}
|
|
30
33
|
return this.db.exists()
|
|
31
34
|
})
|
|
32
35
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import _ from "lodash"
|
|
2
|
+
import { AnyDocument } from "@budibase/types"
|
|
3
|
+
import { generator } from "../../../tests"
|
|
4
|
+
import { DatabaseImpl } from "../couch"
|
|
5
|
+
import { newid } from "../../utils"
|
|
6
|
+
|
|
7
|
+
describe("DatabaseImpl", () => {
|
|
8
|
+
const database = new DatabaseImpl(generator.word())
|
|
9
|
+
const documents: AnyDocument[] = []
|
|
10
|
+
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
const docsToCreate = Array.from({ length: 10 }).map(() => ({
|
|
13
|
+
_id: newid(),
|
|
14
|
+
}))
|
|
15
|
+
const createdDocs = await database.bulkDocs(docsToCreate)
|
|
16
|
+
|
|
17
|
+
documents.push(...createdDocs.map((x: any) => ({ _id: x.id, _rev: x.rev })))
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe("document exists", () => {
|
|
21
|
+
it("can check existing docs by id", async () => {
|
|
22
|
+
const existingDoc = _.sample(documents)
|
|
23
|
+
const result = await database.exists(existingDoc!._id!)
|
|
24
|
+
|
|
25
|
+
expect(result).toBe(true)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("can check non existing docs by id", async () => {
|
|
29
|
+
const result = await database.exists(newid())
|
|
30
|
+
|
|
31
|
+
expect(result).toBe(false)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("can check an existing doc by id multiple times", async () => {
|
|
35
|
+
const existingDoc = _.sample(documents)
|
|
36
|
+
const id = existingDoc!._id!
|
|
37
|
+
|
|
38
|
+
const results = []
|
|
39
|
+
results.push(await database.exists(id))
|
|
40
|
+
results.push(await database.exists(id))
|
|
41
|
+
results.push(await database.exists(id))
|
|
42
|
+
|
|
43
|
+
expect(results).toEqual([true, true, true])
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it("returns false after the doc is deleted", async () => {
|
|
47
|
+
const existingDoc = _.sample(documents)
|
|
48
|
+
const id = existingDoc!._id!
|
|
49
|
+
expect(await database.exists(id)).toBe(true)
|
|
50
|
+
|
|
51
|
+
await database.remove(existingDoc!)
|
|
52
|
+
expect(await database.exists(id)).toBe(false)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
})
|
package/src/environment.ts
CHANGED