@cloudbase/database 1.4.3-alpha.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commonjs/ObjectId/index.d.ts +1 -1
- package/dist/commonjs/ObjectId/index.js +16 -9
- package/dist/commonjs/aggregate.js +126 -75
- package/dist/commonjs/collection.d.ts +3 -2
- package/dist/commonjs/collection.js +131 -58
- package/dist/commonjs/command.d.ts +5 -5
- package/dist/commonjs/command.js +181 -154
- package/dist/commonjs/commands/logic.js +30 -20
- package/dist/commonjs/commands/query.d.ts +1 -1
- package/dist/commonjs/commands/query.js +85 -65
- package/dist/commonjs/commands/update.js +9 -7
- package/dist/commonjs/config/error.config.js +1 -0
- package/dist/commonjs/const/code.js +1 -0
- package/dist/commonjs/constant.d.ts +7 -7
- package/dist/commonjs/constant.js +13 -11
- package/dist/commonjs/document.d.ts +10 -6
- package/dist/commonjs/document.js +305 -193
- package/dist/commonjs/geo/index.js +20 -9
- package/dist/commonjs/geo/lineString.d.ts +1 -1
- package/dist/commonjs/geo/lineString.js +62 -33
- package/dist/commonjs/geo/multiLineString.d.ts +1 -1
- package/dist/commonjs/geo/multiLineString.js +71 -32
- package/dist/commonjs/geo/multiPoint.d.ts +1 -1
- package/dist/commonjs/geo/multiPoint.js +58 -29
- package/dist/commonjs/geo/multiPolygon.d.ts +1 -1
- package/dist/commonjs/geo/multiPolygon.js +96 -36
- package/dist/commonjs/geo/point.d.ts +1 -1
- package/dist/commonjs/geo/point.js +30 -23
- package/dist/commonjs/geo/polygon.d.ts +1 -1
- package/dist/commonjs/geo/polygon.js +78 -39
- package/dist/commonjs/helper/symbol.js +17 -5
- package/dist/commonjs/index.d.ts +8 -2
- package/dist/commonjs/index.js +73 -20
- package/dist/commonjs/lib/util.js +4 -3
- package/dist/commonjs/operator-map.js +7 -6
- package/dist/commonjs/query.d.ts +8 -4
- package/dist/commonjs/query.js +308 -201
- package/dist/commonjs/regexp/index.d.ts +1 -1
- package/dist/commonjs/regexp/index.js +16 -9
- package/dist/commonjs/serializer/common.d.ts +2 -2
- package/dist/commonjs/serializer/common.js +59 -22
- package/dist/commonjs/serializer/datatype.d.ts +2 -2
- package/dist/commonjs/serializer/datatype.js +58 -21
- package/dist/commonjs/serializer/query.d.ts +1 -1
- package/dist/commonjs/serializer/query.js +137 -127
- package/dist/commonjs/serializer/update.d.ts +1 -1
- package/dist/commonjs/serializer/update.js +83 -78
- package/dist/commonjs/serverDate/index.d.ts +1 -1
- package/dist/commonjs/serverDate/index.js +16 -9
- package/dist/commonjs/transaction/collection.d.ts +1 -1
- package/dist/commonjs/transaction/collection.js +5 -2
- package/dist/commonjs/transaction/document.d.ts +1 -1
- package/dist/commonjs/transaction/document.js +5 -5
- package/dist/commonjs/transaction/index.js +196 -86
- package/dist/commonjs/transaction/query.d.ts +39 -1
- package/dist/commonjs/transaction/query.js +252 -1
- package/dist/commonjs/typings/index.d.ts +4 -4
- package/dist/commonjs/typings/realtime.d.ts +16 -16
- package/dist/commonjs/util.js +123 -92
- package/dist/commonjs/utils/error.d.ts +2 -1
- package/dist/commonjs/utils/error.js +74 -38
- package/dist/commonjs/utils/symbol.js +33 -14
- package/dist/commonjs/utils/type.d.ts +1 -1
- package/dist/commonjs/utils/type.js +25 -13
- package/dist/commonjs/utils/utils.d.ts +4 -1
- package/dist/commonjs/utils/utils.js +93 -22
- package/dist/commonjs/validate.js +79 -63
- package/dist/esm/ObjectId/index.d.ts +1 -1
- package/dist/esm/ObjectId/index.js +15 -8
- package/dist/esm/aggregate.js +118 -66
- package/dist/esm/collection.d.ts +3 -2
- package/dist/esm/collection.js +125 -52
- package/dist/esm/command.d.ts +5 -5
- package/dist/esm/command.js +175 -147
- package/dist/esm/commands/logic.js +30 -20
- package/dist/esm/commands/query.d.ts +1 -1
- package/dist/esm/commands/query.js +91 -71
- package/dist/esm/commands/update.js +8 -6
- package/dist/esm/config/error.config.js +1 -1
- package/dist/esm/const/code.js +1 -1
- package/dist/esm/constant.d.ts +7 -7
- package/dist/esm/constant.js +12 -11
- package/dist/esm/document.d.ts +10 -6
- package/dist/esm/document.js +297 -185
- package/dist/esm/geo/interface.js +1 -0
- package/dist/esm/geo/lineString.d.ts +1 -1
- package/dist/esm/geo/lineString.js +57 -28
- package/dist/esm/geo/multiLineString.d.ts +1 -1
- package/dist/esm/geo/multiLineString.js +66 -27
- package/dist/esm/geo/multiPoint.d.ts +1 -1
- package/dist/esm/geo/multiPoint.js +53 -24
- package/dist/esm/geo/multiPolygon.d.ts +1 -1
- package/dist/esm/geo/multiPolygon.js +91 -31
- package/dist/esm/geo/point.d.ts +1 -1
- package/dist/esm/geo/point.js +26 -19
- package/dist/esm/geo/polygon.d.ts +1 -1
- package/dist/esm/geo/polygon.js +73 -34
- package/dist/esm/helper/symbol.js +13 -13
- package/dist/esm/index.d.ts +8 -2
- package/dist/esm/index.js +62 -9
- package/dist/esm/lib/util.js +1 -2
- package/dist/esm/operator-map.js +4 -4
- package/dist/esm/query.d.ts +8 -4
- package/dist/esm/query.js +299 -192
- package/dist/esm/regexp/index.d.ts +1 -1
- package/dist/esm/regexp/index.js +15 -8
- package/dist/esm/serializer/common.d.ts +2 -2
- package/dist/esm/serializer/common.js +49 -13
- package/dist/esm/serializer/datatype.d.ts +2 -2
- package/dist/esm/serializer/datatype.js +44 -8
- package/dist/esm/serializer/query.d.ts +1 -1
- package/dist/esm/serializer/query.js +112 -102
- package/dist/esm/serializer/update.d.ts +1 -1
- package/dist/esm/serializer/update.js +69 -64
- package/dist/esm/serverDate/index.d.ts +1 -1
- package/dist/esm/serverDate/index.js +15 -8
- package/dist/esm/transaction/collection.d.ts +1 -1
- package/dist/esm/transaction/collection.js +4 -2
- package/dist/esm/transaction/document.d.ts +1 -1
- package/dist/esm/transaction/document.js +2 -3
- package/dist/esm/transaction/index.js +191 -81
- package/dist/esm/transaction/query.d.ts +39 -1
- package/dist/esm/transaction/query.js +251 -1
- package/dist/esm/typings/index.d.ts +4 -4
- package/dist/esm/typings/index.js +1 -0
- package/dist/esm/typings/realtime.d.ts +16 -16
- package/dist/esm/typings/realtime.js +1 -0
- package/dist/esm/util.js +121 -90
- package/dist/esm/utils/error.d.ts +2 -1
- package/dist/esm/utils/error.js +70 -35
- package/dist/esm/utils/symbol.js +33 -14
- package/dist/esm/utils/type.d.ts +1 -1
- package/dist/esm/utils/type.js +12 -12
- package/dist/esm/utils/utils.d.ts +4 -1
- package/dist/esm/utils/utils.js +78 -16
- package/dist/esm/validate.js +65 -49
- package/jest.config.js +14 -0
- package/package.json +14 -10
- package/src/aggregate.ts +1 -5
- package/src/collection.ts +15 -2
- package/src/document.ts +45 -34
- package/src/index.ts +51 -4
- package/src/query.ts +33 -29
- package/src/serializer/query.ts +18 -6
- package/src/transaction/index.ts +1 -1
- package/src/typings/index.ts +0 -4
- package/src/utils/utils.ts +46 -0
- package/src/validate.ts +3 -6
- package/tests/gateway-contract.test.ts +180 -0
- package/tests/index.test.ts +283 -0
- package/tests/transaction-query.test.ts +207 -0
- package/tsconfig.esm.json +4 -2
- package/tsconfig.json +16 -6
- package/tsconfig.test.json +15 -0
- package/dist/commonjs/realtime/error.d.ts +0 -7
- package/dist/commonjs/realtime/error.js +0 -11
- package/dist/commonjs/realtime/listener.d.ts +0 -14
- package/dist/commonjs/realtime/listener.js +0 -17
- package/dist/commonjs/realtime/message.d.ts +0 -3
- package/dist/commonjs/realtime/message.js +0 -10
- package/dist/commonjs/realtime/snapshot.d.ts +0 -16
- package/dist/commonjs/realtime/snapshot.js +0 -42
- package/dist/commonjs/realtime/virtual-websocket-client.d.ts +0 -63
- package/dist/commonjs/realtime/virtual-websocket-client.js +0 -736
- package/dist/commonjs/realtime/websocket-client.d.ts +0 -78
- package/dist/commonjs/realtime/websocket-client.js +0 -567
- package/dist/commonjs/realtime/ws-event.d.ts +0 -126
- package/dist/commonjs/realtime/ws-event.js +0 -139
- package/dist/commonjs/transaction.d.ts +0 -42
- package/dist/commonjs/transaction.js +0 -126
- package/dist/esm/realtime/error.d.ts +0 -7
- package/dist/esm/realtime/error.js +0 -8
- package/dist/esm/realtime/listener.d.ts +0 -14
- package/dist/esm/realtime/listener.js +0 -14
- package/dist/esm/realtime/message.d.ts +0 -3
- package/dist/esm/realtime/message.js +0 -6
- package/dist/esm/realtime/snapshot.d.ts +0 -16
- package/dist/esm/realtime/snapshot.js +0 -39
- package/dist/esm/realtime/virtual-websocket-client.d.ts +0 -63
- package/dist/esm/realtime/virtual-websocket-client.js +0 -733
- package/dist/esm/realtime/websocket-client.d.ts +0 -78
- package/dist/esm/realtime/websocket-client.js +0 -564
- package/dist/esm/realtime/ws-event.d.ts +0 -126
- package/dist/esm/realtime/ws-event.js +0 -137
- package/dist/esm/transaction.d.ts +0 -42
- package/dist/esm/transaction.js +0 -121
- package/src/realtime/error.ts +0 -19
- package/src/realtime/listener.ts +0 -35
- package/src/realtime/message.ts +0 -14
- package/src/realtime/snapshot.ts +0 -60
- package/src/realtime/virtual-websocket-client.ts +0 -1195
- package/src/realtime/websocket-client.ts +0 -995
- package/src/realtime/ws-event.ts +0 -163
- package/src/transaction.ts +0 -190
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { EJSON } from 'bson'
|
|
2
|
+
import { Db } from '../src/index'
|
|
3
|
+
import { Transaction } from '../src/transaction/index'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* GATEWAY 模式接口名契约测试
|
|
7
|
+
*
|
|
8
|
+
* js-sdk(packages/cloudbase/database/src/index.ts 的 gateWayFunc)在
|
|
9
|
+
* endPointMode === 'GATEWAY' 时按 action 名查表路由到 RESTful 网关,
|
|
10
|
+
* 表外的 action 会 fallback 到 super.send() 走老通道,导致网关被绕过。
|
|
11
|
+
*
|
|
12
|
+
* 本测试用与 js-sdk gateWayFunc 相同的接口名集合模拟 GATEWAY 分发,
|
|
13
|
+
* 验证 2.0 引擎发出的所有 action 都能命中映射表,
|
|
14
|
+
* 任何一侧改名/新增接口而另一侧未同步时,此测试会失败。
|
|
15
|
+
*/
|
|
16
|
+
describe('GATEWAY 模式接口名契约(与 js-sdk gateWayFunc 对齐)', () => {
|
|
17
|
+
// 与 js-sdk gateWayFunc 的 key 保持一致
|
|
18
|
+
const GATEWAY_ACTIONS = [
|
|
19
|
+
// 旧接口名(0.12.x 引擎)
|
|
20
|
+
'database.queryDocument',
|
|
21
|
+
'database.addDocument',
|
|
22
|
+
'database.updateDocument',
|
|
23
|
+
'database.deleteDocument',
|
|
24
|
+
'database.countDocument',
|
|
25
|
+
'database.addCollection',
|
|
26
|
+
'database.aggregate',
|
|
27
|
+
// 事务(新旧引擎同名)
|
|
28
|
+
'database.startTransaction',
|
|
29
|
+
'database.commitTransaction',
|
|
30
|
+
'database.abortTransaction',
|
|
31
|
+
'database.getInTransaction',
|
|
32
|
+
'database.updateDocInTransaction',
|
|
33
|
+
'database.deleteDocInTransaction',
|
|
34
|
+
'database.insertDocInTransaction',
|
|
35
|
+
'database.runCommands',
|
|
36
|
+
// 新接口名(2.0 引擎,js-sdk gateWayFunc 已添加映射)
|
|
37
|
+
'database.getDocument',
|
|
38
|
+
'database.insertDocument',
|
|
39
|
+
'database.modifyDocument',
|
|
40
|
+
'database.removeDocument',
|
|
41
|
+
'database.calculateDocument',
|
|
42
|
+
'database.aggregateDocuments'
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
let hitActions: string[]
|
|
46
|
+
|
|
47
|
+
class MockGatewayReq {
|
|
48
|
+
async send(action: string) {
|
|
49
|
+
if (GATEWAY_ACTIONS.indexOf(action) < 0) {
|
|
50
|
+
throw new Error(`GATEWAY 模式下无此接口映射: ${action}`)
|
|
51
|
+
}
|
|
52
|
+
hitActions.push(action)
|
|
53
|
+
|
|
54
|
+
// 按接口返回 2.0 引擎期望的最小响应结构
|
|
55
|
+
switch (action) {
|
|
56
|
+
case 'database.getDocument':
|
|
57
|
+
return {
|
|
58
|
+
data: { list: [EJSON.stringify({ _id: 'doc1', name: 'Alice' })] },
|
|
59
|
+
requestId: 'gw-req-get'
|
|
60
|
+
}
|
|
61
|
+
case 'database.insertDocument':
|
|
62
|
+
return {
|
|
63
|
+
data: { insertedIds: ['new-id'] },
|
|
64
|
+
requestId: 'gw-req-insert'
|
|
65
|
+
}
|
|
66
|
+
case 'database.modifyDocument':
|
|
67
|
+
return {
|
|
68
|
+
data: { updated: 1, upsert_id: 'doc1' },
|
|
69
|
+
requestId: 'gw-req-modify'
|
|
70
|
+
}
|
|
71
|
+
case 'database.removeDocument':
|
|
72
|
+
return {
|
|
73
|
+
data: { deleted: 1 },
|
|
74
|
+
requestId: 'gw-req-remove'
|
|
75
|
+
}
|
|
76
|
+
case 'database.calculateDocument':
|
|
77
|
+
return {
|
|
78
|
+
data: { total: 5 },
|
|
79
|
+
requestId: 'gw-req-count'
|
|
80
|
+
}
|
|
81
|
+
case 'database.aggregateDocuments':
|
|
82
|
+
return {
|
|
83
|
+
data: { list: [EJSON.stringify({ total: 1 })] },
|
|
84
|
+
requestId: 'gw-req-agg'
|
|
85
|
+
}
|
|
86
|
+
case 'database.startTransaction':
|
|
87
|
+
return { transactionId: 'gw-tx-001' }
|
|
88
|
+
case 'database.commitTransaction':
|
|
89
|
+
case 'database.abortTransaction':
|
|
90
|
+
return { requestId: 'gw-req-tx' }
|
|
91
|
+
default:
|
|
92
|
+
return { data: { ok: 1 }, requestId: 'gw-req-default' }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
hitActions = []
|
|
99
|
+
Db.reqClass = MockGatewayReq as any
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
function createDb() {
|
|
103
|
+
return new Db({ env: 'test-env' })
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function createInitedTransaction(db: Db) {
|
|
107
|
+
const tx = new Transaction(db)
|
|
108
|
+
await tx.init()
|
|
109
|
+
return tx
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
it('collection CRUD 应全部命中网关映射表', async () => {
|
|
113
|
+
const db = createDb()
|
|
114
|
+
const coll = db.collection('users')
|
|
115
|
+
|
|
116
|
+
await coll.where({ age: 18 }).get()
|
|
117
|
+
await coll.where({ age: 18 }).count()
|
|
118
|
+
await coll.where({ age: 18 }).update({ age: 19 })
|
|
119
|
+
await coll.where({ age: 18 }).remove()
|
|
120
|
+
await coll.add({ name: 'New' })
|
|
121
|
+
await coll
|
|
122
|
+
.aggregate()
|
|
123
|
+
.match({ age: { $gt: 10 } })
|
|
124
|
+
.end()
|
|
125
|
+
|
|
126
|
+
expect(hitActions).toEqual([
|
|
127
|
+
'database.getDocument',
|
|
128
|
+
'database.calculateDocument',
|
|
129
|
+
'database.modifyDocument',
|
|
130
|
+
'database.removeDocument',
|
|
131
|
+
'database.insertDocument',
|
|
132
|
+
'database.aggregateDocuments'
|
|
133
|
+
])
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('document CRUD 应全部命中网关映射表', async () => {
|
|
137
|
+
const db = createDb()
|
|
138
|
+
const doc = db.collection('users').doc('doc1')
|
|
139
|
+
|
|
140
|
+
await doc.get()
|
|
141
|
+
await doc.create({ name: 'Alice' })
|
|
142
|
+
await doc.set({ name: 'Alice2' })
|
|
143
|
+
await doc.update({ name: 'Alice3' })
|
|
144
|
+
await doc.remove()
|
|
145
|
+
|
|
146
|
+
expect(hitActions).toEqual([
|
|
147
|
+
'database.getDocument',
|
|
148
|
+
'database.insertDocument',
|
|
149
|
+
'database.modifyDocument',
|
|
150
|
+
'database.modifyDocument',
|
|
151
|
+
'database.removeDocument'
|
|
152
|
+
])
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('事务操作应全部命中网关映射表', async () => {
|
|
156
|
+
const db = createDb()
|
|
157
|
+
const tx = await createInitedTransaction(db)
|
|
158
|
+
|
|
159
|
+
await tx.collection('users').where({ age: 18 }).get()
|
|
160
|
+
await tx.collection('users').where({ age: 18 }).update({ age: 19 })
|
|
161
|
+
await tx.collection('users').add({ name: 'TxNew' })
|
|
162
|
+
await tx.collection('users').doc('doc1').set({ name: 'TxSet' })
|
|
163
|
+
await tx.commit()
|
|
164
|
+
|
|
165
|
+
expect(hitActions).toEqual([
|
|
166
|
+
'database.startTransaction',
|
|
167
|
+
'database.getDocument',
|
|
168
|
+
'database.modifyDocument',
|
|
169
|
+
'database.insertDocument',
|
|
170
|
+
'database.modifyDocument',
|
|
171
|
+
'database.commitTransaction'
|
|
172
|
+
])
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('createCollection 应命中网关映射表', async () => {
|
|
176
|
+
const db = createDb()
|
|
177
|
+
await db.createCollection('new-coll')
|
|
178
|
+
expect(hitActions).toEqual(['database.addCollection'])
|
|
179
|
+
})
|
|
180
|
+
})
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { EJSON } from 'bson'
|
|
2
|
+
import { Db } from '../src/index'
|
|
3
|
+
|
|
4
|
+
describe('数据库请求参数测试', function() {
|
|
5
|
+
// 模拟请求类,用于捕获和验证请求参数
|
|
6
|
+
class MockRequest {
|
|
7
|
+
private config: any;
|
|
8
|
+
|
|
9
|
+
constructor(config: any) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async send(action: string, data: any) {
|
|
14
|
+
// 验证 instance 和 database 参数是否正确传递
|
|
15
|
+
if (this.config.instance) {
|
|
16
|
+
expect(data).toHaveProperty('instance', this.config.instance);
|
|
17
|
+
}
|
|
18
|
+
if (this.config.database) {
|
|
19
|
+
expect(data).toHaveProperty('database', this.config.database);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 返回符合各操作期望的数据结构
|
|
23
|
+
switch (action) {
|
|
24
|
+
case 'database.getDocument':
|
|
25
|
+
return {
|
|
26
|
+
data: {
|
|
27
|
+
list: [
|
|
28
|
+
EJSON.stringify({ _id: 'doc1', name: '测试文档1', value: 100 }),
|
|
29
|
+
EJSON.stringify({ _id: 'doc2', name: '测试文档2', value: 200 })
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
requestId: 'mock-request-id-123456',
|
|
33
|
+
total: 2,
|
|
34
|
+
limit: 100,
|
|
35
|
+
offset: 5
|
|
36
|
+
};
|
|
37
|
+
case 'database.insertDocument':
|
|
38
|
+
return {
|
|
39
|
+
data: { insertedIds: ['doc-new'] },
|
|
40
|
+
requestId: 'mock-request-id-insert'
|
|
41
|
+
};
|
|
42
|
+
case 'database.modifyDocument':
|
|
43
|
+
return {
|
|
44
|
+
data: { updated: 1, upsert_id: 'doc1' },
|
|
45
|
+
requestId: 'mock-request-id-modify'
|
|
46
|
+
};
|
|
47
|
+
case 'database.aggregateDocuments':
|
|
48
|
+
return {
|
|
49
|
+
data: { list: [] },
|
|
50
|
+
requestId: 'mock-request-id-aggregate'
|
|
51
|
+
};
|
|
52
|
+
default:
|
|
53
|
+
return { data: { ok: 1 } };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 测试配置
|
|
59
|
+
const testConfig = {
|
|
60
|
+
env: 'test-env',
|
|
61
|
+
instance: 'test-instance',
|
|
62
|
+
database: 'test-database'
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// 在每个测试前配置
|
|
66
|
+
beforeEach(function() {
|
|
67
|
+
Db.reqClass = MockRequest;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('集合查询操作应包含 instance 和 database 参数', async function() {
|
|
71
|
+
const db = new Db(testConfig);
|
|
72
|
+
const coll = db.collection('test-collection');
|
|
73
|
+
await coll.where({}).get();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('集合添加操作应包含 instance 和 database 参数', async function() {
|
|
77
|
+
const db = new Db(testConfig);
|
|
78
|
+
const coll = db.collection('test-collection');
|
|
79
|
+
await coll.add({ name: '新文档', value: 300 });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('文档更新操作应包含 instance 和 database 参数', async function() {
|
|
83
|
+
const db = new Db(testConfig);
|
|
84
|
+
const coll = db.collection('test-collection');
|
|
85
|
+
const doc = coll.doc('doc1');
|
|
86
|
+
await doc.update({ value: 150 });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('聚合操作应包含 instance 和 database 参数', async function() {
|
|
90
|
+
const db = new Db(testConfig);
|
|
91
|
+
const coll = db.collection('test-collection');
|
|
92
|
+
const aggregate = coll
|
|
93
|
+
.aggregate()
|
|
94
|
+
.match({ value: { $gt: 50 } })
|
|
95
|
+
.group({ _id: null, total: { $sum: '$value' } });
|
|
96
|
+
await aggregate.end();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('创建集合操作应包含 instance 和 database 参数', async function() {
|
|
100
|
+
const db = new Db(testConfig);
|
|
101
|
+
await db.createCollection('new-collection');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('应支持 const res = await db.collection(\'xxx\').get() 方式调用', async function() {
|
|
105
|
+
const db = new Db(testConfig);
|
|
106
|
+
const coll = db.collection('test-collection');
|
|
107
|
+
|
|
108
|
+
// 测试直接调用 get() 而不传递 where 条件
|
|
109
|
+
const res = await coll.get();
|
|
110
|
+
|
|
111
|
+
// 验证返回结果的结构
|
|
112
|
+
expect(res).toHaveProperty('data');
|
|
113
|
+
expect(res).toHaveProperty('requestId');
|
|
114
|
+
expect(res).toHaveProperty('total');
|
|
115
|
+
expect(res).toHaveProperty('limit');
|
|
116
|
+
expect(res).toHaveProperty('offset');
|
|
117
|
+
|
|
118
|
+
// 验证返回的数据
|
|
119
|
+
expect(Array.isArray(res.data)).toBe(true);
|
|
120
|
+
expect(res.data.length).toBe(2);
|
|
121
|
+
expect(res.data[0]).toHaveProperty('_id', 'doc1');
|
|
122
|
+
expect(res.data[0]).toHaveProperty('name', '测试文档1');
|
|
123
|
+
expect(res.data[1]).toHaveProperty('_id', 'doc2');
|
|
124
|
+
expect(res.data[1]).toHaveProperty('name', '测试文档2');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ---------- timeout 透传(per-request,经 createRequest 第三参数) ----------
|
|
128
|
+
describe('timeout 透传', function() {
|
|
129
|
+
it('options({timeout}) 应把 timeout 作为 send 第三参透传', async function() {
|
|
130
|
+
let capturedArgs: any[] = [];
|
|
131
|
+
class TimeoutReq {
|
|
132
|
+
constructor(_config: any) {}
|
|
133
|
+
async send(...args: any[]) {
|
|
134
|
+
capturedArgs = args;
|
|
135
|
+
return { data: { list: [] }, requestId: 'req-timeout' };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
Db.reqClass = TimeoutReq as any;
|
|
139
|
+
|
|
140
|
+
const db = new Db({ env: 'test-env' });
|
|
141
|
+
await db.collection('users').options({ timeout: 500 }).get();
|
|
142
|
+
|
|
143
|
+
expect(capturedArgs[0]).toBe('database.getDocument');
|
|
144
|
+
expect(capturedArgs[2]).toEqual({ timeout: 500 });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('未设置 timeout 时第三参应为空对象', async function() {
|
|
148
|
+
let capturedArgs: any[] = [];
|
|
149
|
+
class TimeoutReq {
|
|
150
|
+
constructor(_config: any) {}
|
|
151
|
+
async send(...args: any[]) {
|
|
152
|
+
capturedArgs = args;
|
|
153
|
+
return { data: { list: [] }, requestId: 'req-no-timeout' };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
Db.reqClass = TimeoutReq as any;
|
|
157
|
+
|
|
158
|
+
const db = new Db({ env: 'test-env' });
|
|
159
|
+
await db.collection('users').get();
|
|
160
|
+
|
|
161
|
+
expect(capturedArgs[2]).toEqual({});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('timeout 与 instance/database 注入应共存', async function() {
|
|
165
|
+
let capturedArgs: any[] = [];
|
|
166
|
+
class TimeoutReq {
|
|
167
|
+
constructor(_config: any) {}
|
|
168
|
+
async send(...args: any[]) {
|
|
169
|
+
capturedArgs = args;
|
|
170
|
+
return { data: { list: [] }, requestId: 'req-mix' };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
Db.reqClass = TimeoutReq as any;
|
|
174
|
+
|
|
175
|
+
const db = new Db({ env: 'test-env', instance: 'inst-1', database: 'db-1' });
|
|
176
|
+
await db.collection('users').options({ timeout: 1000 }).get();
|
|
177
|
+
|
|
178
|
+
expect(capturedArgs[1]).toMatchObject({ instance: 'inst-1', database: 'db-1' });
|
|
179
|
+
expect(capturedArgs[2]).toEqual({ timeout: 1000 });
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// ---------- callback 兼容(对齐 0.12.x 旧调用风格) ----------
|
|
184
|
+
describe('callback 兼容', function() {
|
|
185
|
+
it('collection().get(callback) 应调用 callback(0, result) 且返回 undefined', async function() {
|
|
186
|
+
const db = new Db(testConfig);
|
|
187
|
+
const coll = db.collection('test-collection');
|
|
188
|
+
|
|
189
|
+
const callback = jest.fn();
|
|
190
|
+
const retVal = coll.get(callback);
|
|
191
|
+
|
|
192
|
+
// 对齐 master 行为:传 callback 时返回 undefined
|
|
193
|
+
expect(retVal).toBeUndefined();
|
|
194
|
+
|
|
195
|
+
// 等 callback 被调用
|
|
196
|
+
await new Promise<void>(resolve => {
|
|
197
|
+
const timer = setInterval(() => {
|
|
198
|
+
if (callback.mock.calls.length > 0) {
|
|
199
|
+
clearInterval(timer);
|
|
200
|
+
resolve();
|
|
201
|
+
}
|
|
202
|
+
}, 10);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
206
|
+
const [err, result] = callback.mock.calls[0];
|
|
207
|
+
expect(err).toBe(0);
|
|
208
|
+
expect(result).toHaveProperty('data');
|
|
209
|
+
expect(result.data.length).toBe(2);
|
|
210
|
+
expect(result).toHaveProperty('requestId');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('collection().add(data, callback) 应调用 callback(0, result)', async function() {
|
|
214
|
+
const db = new Db(testConfig);
|
|
215
|
+
const coll = db.collection('test-collection');
|
|
216
|
+
|
|
217
|
+
const callback = jest.fn();
|
|
218
|
+
coll.add({ name: 'cb-doc' }, callback);
|
|
219
|
+
|
|
220
|
+
await new Promise<void>(resolve => {
|
|
221
|
+
const timer = setInterval(() => {
|
|
222
|
+
if (callback.mock.calls.length > 0) {
|
|
223
|
+
clearInterval(timer);
|
|
224
|
+
resolve();
|
|
225
|
+
}
|
|
226
|
+
}, 10);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const [err, result] = callback.mock.calls[0];
|
|
230
|
+
expect(err).toBe(0);
|
|
231
|
+
expect(result).toHaveProperty('id', 'doc-new');
|
|
232
|
+
expect(result).toHaveProperty('requestId');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('doc().get(callback) 应调用 callback(0, result)', async function() {
|
|
236
|
+
const db = new Db(testConfig);
|
|
237
|
+
const doc = db.collection('test-collection').doc('doc1');
|
|
238
|
+
|
|
239
|
+
const callback = jest.fn();
|
|
240
|
+
doc.get(callback);
|
|
241
|
+
|
|
242
|
+
await new Promise<void>(resolve => {
|
|
243
|
+
const timer = setInterval(() => {
|
|
244
|
+
if (callback.mock.calls.length > 0) {
|
|
245
|
+
clearInterval(timer);
|
|
246
|
+
resolve();
|
|
247
|
+
}
|
|
248
|
+
}, 10);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const [err, result] = callback.mock.calls[0];
|
|
252
|
+
expect(err).toBe(0);
|
|
253
|
+
expect(result).toHaveProperty('data');
|
|
254
|
+
expect(result).toHaveProperty('requestId');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('请求异常时应调用 callback(err)', async function() {
|
|
258
|
+
class FailRequest {
|
|
259
|
+
async send() {
|
|
260
|
+
throw new Error('network down');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
Db.reqClass = FailRequest as any;
|
|
264
|
+
|
|
265
|
+
const db = new Db(testConfig);
|
|
266
|
+
const callback = jest.fn();
|
|
267
|
+
db.collection('test-collection').get(callback);
|
|
268
|
+
|
|
269
|
+
await new Promise<void>(resolve => {
|
|
270
|
+
const timer = setInterval(() => {
|
|
271
|
+
if (callback.mock.calls.length > 0) {
|
|
272
|
+
clearInterval(timer);
|
|
273
|
+
resolve();
|
|
274
|
+
}
|
|
275
|
+
}, 10);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const [err] = callback.mock.calls[0];
|
|
279
|
+
expect(err).toBeInstanceOf(Error);
|
|
280
|
+
expect(err.message).toBe('network down');
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
});
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { EJSON } from 'bson'
|
|
2
|
+
import { Db } from '../src/index'
|
|
3
|
+
import { Transaction } from '../src/transaction/index'
|
|
4
|
+
|
|
5
|
+
describe('Transaction(新事务 API)', () => {
|
|
6
|
+
let sendFn: jest.Mock
|
|
7
|
+
|
|
8
|
+
class MockRequest {
|
|
9
|
+
send(action: string, data: any) {
|
|
10
|
+
return sendFn(action, data)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
sendFn = jest.fn()
|
|
16
|
+
Db.reqClass = MockRequest as any
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
function createDb() {
|
|
20
|
+
return new Db({ env: 'test-env' })
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function createInitedTransaction(db: Db, txId = 'tx-001') {
|
|
24
|
+
const tx = new Transaction(db)
|
|
25
|
+
sendFn.mockResolvedValueOnce({ transactionId: txId })
|
|
26
|
+
await tx.init()
|
|
27
|
+
return tx
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function findCall(action: string) {
|
|
31
|
+
return sendFn.mock.calls.find(c => c[0] === action)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ---------- 初始化 ----------
|
|
35
|
+
describe('init()', () => {
|
|
36
|
+
it('应调用 database.startTransaction 并记录 transactionId', async () => {
|
|
37
|
+
const db = createDb()
|
|
38
|
+
const tx = await createInitedTransaction(db, 'tx-init')
|
|
39
|
+
|
|
40
|
+
expect(findCall('database.startTransaction')).toBeDefined()
|
|
41
|
+
expect(tx.getTransactionId()).toBe('tx-init')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('init 不带 data 参数时(send(START) 单参)在配置 instance/database 下不应崩溃', async () => {
|
|
45
|
+
class NoDataReq {
|
|
46
|
+
async send(...args: any[]) {
|
|
47
|
+
if (args[0] === 'database.startTransaction') {
|
|
48
|
+
return { transactionId: 'tx-nodata' }
|
|
49
|
+
}
|
|
50
|
+
return { data: { list: [] }, requestId: 'r' }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
Db.reqClass = NoDataReq as any
|
|
54
|
+
|
|
55
|
+
const db = new Db({ env: 'test-env', instance: 'inst-1', database: 'db-1' })
|
|
56
|
+
const tx = new Transaction(db)
|
|
57
|
+
await tx.init() // 不应抛 TypeError
|
|
58
|
+
|
|
59
|
+
expect(tx.getTransactionId()).toBe('tx-nodata')
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// ---------- 事务内查询 ----------
|
|
64
|
+
describe('collection() + where().get()', () => {
|
|
65
|
+
it('应把 transactionId 传给 database.getDocument', async () => {
|
|
66
|
+
const db = createDb()
|
|
67
|
+
const tx = await createInitedTransaction(db)
|
|
68
|
+
|
|
69
|
+
sendFn.mockResolvedValueOnce({ data: { list: [] }, requestId: 'req-1' })
|
|
70
|
+
|
|
71
|
+
await tx.collection('users').where({ age: 18 }).get()
|
|
72
|
+
|
|
73
|
+
const call = findCall('database.getDocument')
|
|
74
|
+
expect(call).toBeDefined()
|
|
75
|
+
expect(call![1]).toMatchObject({
|
|
76
|
+
collectionName: 'users',
|
|
77
|
+
transactionId: 'tx-001'
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('get() 应解析 EJSON list 并返回文档数组', async () => {
|
|
82
|
+
const db = createDb()
|
|
83
|
+
const tx = await createInitedTransaction(db)
|
|
84
|
+
|
|
85
|
+
const list = [
|
|
86
|
+
EJSON.stringify({ _id: 'doc1', name: 'Alice' }),
|
|
87
|
+
EJSON.stringify({ _id: 'doc2', name: 'Bob' })
|
|
88
|
+
]
|
|
89
|
+
sendFn.mockResolvedValueOnce({ data: { list }, requestId: 'req-2', total: 2 })
|
|
90
|
+
|
|
91
|
+
const result = await tx.collection('users').where({}).get()
|
|
92
|
+
|
|
93
|
+
expect(result.data.length).toBe(2)
|
|
94
|
+
expect(result.data[0]).toMatchObject({ _id: 'doc1', name: 'Alice' })
|
|
95
|
+
expect(result.total).toBe(2)
|
|
96
|
+
expect(result.requestId).toBe('req-2')
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// ---------- 事务内更新 ----------
|
|
101
|
+
describe('collection() + where().update()', () => {
|
|
102
|
+
it('应把 transactionId 传给 database.modifyDocument', async () => {
|
|
103
|
+
const db = createDb()
|
|
104
|
+
const tx = await createInitedTransaction(db)
|
|
105
|
+
|
|
106
|
+
sendFn.mockResolvedValueOnce({ data: { updated: 1 }, requestId: 'req-3' })
|
|
107
|
+
|
|
108
|
+
const result = await tx.collection('users').where({ name: 'Alice' }).update({ age: 25 })
|
|
109
|
+
|
|
110
|
+
const call = findCall('database.modifyDocument')
|
|
111
|
+
expect(call).toBeDefined()
|
|
112
|
+
expect(call![1]).toMatchObject({
|
|
113
|
+
collectionName: 'users',
|
|
114
|
+
transactionId: 'tx-001'
|
|
115
|
+
})
|
|
116
|
+
expect(result.updated).toBe(1)
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// ---------- 事务内删除 ----------
|
|
121
|
+
describe('collection() + where().remove()', () => {
|
|
122
|
+
it('应把 transactionId 传给 database.removeDocument', async () => {
|
|
123
|
+
const db = createDb()
|
|
124
|
+
const tx = await createInitedTransaction(db)
|
|
125
|
+
|
|
126
|
+
sendFn.mockResolvedValueOnce({ data: { deleted: 3 }, requestId: 'req-4' })
|
|
127
|
+
|
|
128
|
+
const result = await tx.collection('users').where({ status: 'inactive' }).remove()
|
|
129
|
+
|
|
130
|
+
const call = findCall('database.removeDocument')
|
|
131
|
+
expect(call).toBeDefined()
|
|
132
|
+
expect(call![1]).toMatchObject({
|
|
133
|
+
collectionName: 'users',
|
|
134
|
+
transactionId: 'tx-001'
|
|
135
|
+
})
|
|
136
|
+
expect(result.deleted).toBe(3)
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// ---------- 事务内计数 ----------
|
|
141
|
+
describe('collection() + where().count()', () => {
|
|
142
|
+
it('应调用 database.calculateDocument 并返回 total', async () => {
|
|
143
|
+
const db = createDb()
|
|
144
|
+
const tx = await createInitedTransaction(db)
|
|
145
|
+
|
|
146
|
+
sendFn.mockResolvedValueOnce({ data: { total: 5 }, requestId: 'req-5' })
|
|
147
|
+
|
|
148
|
+
const result = await tx.collection('users').where({ age: { $gt: 18 } }).count()
|
|
149
|
+
|
|
150
|
+
expect(findCall('database.calculateDocument')).toBeDefined()
|
|
151
|
+
expect(result.total).toBe(5)
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// ---------- 事务内插入 ----------
|
|
156
|
+
describe('collection().add()', () => {
|
|
157
|
+
it('应把 transactionId 传给 database.insertDocument,并返回事务兼容结果', async () => {
|
|
158
|
+
const db = createDb()
|
|
159
|
+
const tx = await createInitedTransaction(db)
|
|
160
|
+
|
|
161
|
+
sendFn.mockResolvedValueOnce({ data: { insertedIds: ['new-id'] }, requestId: 'req-6' })
|
|
162
|
+
|
|
163
|
+
const result = await tx.collection('users').add({ name: 'New' })
|
|
164
|
+
|
|
165
|
+
const call = findCall('database.insertDocument')
|
|
166
|
+
expect(call).toBeDefined()
|
|
167
|
+
expect(call![1]).toMatchObject({
|
|
168
|
+
collectionName: 'users',
|
|
169
|
+
transactionId: 'tx-001'
|
|
170
|
+
})
|
|
171
|
+
expect(result).toMatchObject({ inserted: 1, ok: 1, id: 'new-id' })
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// ---------- 事务内文档操作 ----------
|
|
176
|
+
describe('collection().doc().set()', () => {
|
|
177
|
+
it('应把 transactionId 传给 database.modifyDocument', async () => {
|
|
178
|
+
const db = createDb()
|
|
179
|
+
const tx = await createInitedTransaction(db)
|
|
180
|
+
|
|
181
|
+
sendFn.mockResolvedValueOnce({ data: { updated: 1, upsert_id: 'doc1' }, requestId: 'req-7' })
|
|
182
|
+
|
|
183
|
+
await tx.collection('users').doc('doc1').set({ name: 'Set' })
|
|
184
|
+
|
|
185
|
+
const call = findCall('database.modifyDocument')
|
|
186
|
+
expect(call).toBeDefined()
|
|
187
|
+
expect(call![1]).toMatchObject({
|
|
188
|
+
collectionName: 'users',
|
|
189
|
+
transactionId: 'tx-001'
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// ---------- 提交 ----------
|
|
195
|
+
describe('commit()', () => {
|
|
196
|
+
it('应调用 database.commitTransaction 并携带 transactionId', async () => {
|
|
197
|
+
const db = createDb()
|
|
198
|
+
const tx = await createInitedTransaction(db, 'tx-cmt')
|
|
199
|
+
|
|
200
|
+
sendFn.mockResolvedValueOnce({ requestId: 'req-cmt' })
|
|
201
|
+
await tx.commit()
|
|
202
|
+
|
|
203
|
+
const call = findCall('database.commitTransaction')
|
|
204
|
+
expect(call![1]).toEqual({ transactionId: 'tx-cmt' })
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
})
|
package/tsconfig.esm.json
CHANGED
|
@@ -20,10 +20,11 @@
|
|
|
20
20
|
"pretty": true,
|
|
21
21
|
"removeComments": true,
|
|
22
22
|
"stripInternal": true,
|
|
23
|
+
"downlevelIteration": true,
|
|
23
24
|
"skipDefaultLibCheck": true,
|
|
24
25
|
"skipLibCheck": true,
|
|
25
26
|
"moduleResolution": "node",
|
|
26
|
-
"target": "
|
|
27
|
+
"target": "es5",
|
|
27
28
|
"lib": [
|
|
28
29
|
"es2015",
|
|
29
30
|
"es6",
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
],
|
|
36
37
|
"exclude": [
|
|
37
38
|
// "node_modules",
|
|
38
|
-
"test"
|
|
39
|
+
"test",
|
|
40
|
+
"tests"
|
|
39
41
|
]
|
|
40
42
|
}
|