@cloudbase/database 1.4.3 → 1.5.1

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 (197) hide show
  1. package/dist/commonjs/ObjectId/index.d.ts +1 -1
  2. package/dist/commonjs/ObjectId/index.js +16 -9
  3. package/dist/commonjs/aggregate.js +126 -75
  4. package/dist/commonjs/collection.d.ts +3 -2
  5. package/dist/commonjs/collection.js +131 -58
  6. package/dist/commonjs/command.d.ts +5 -5
  7. package/dist/commonjs/command.js +181 -154
  8. package/dist/commonjs/commands/logic.js +30 -20
  9. package/dist/commonjs/commands/query.d.ts +1 -1
  10. package/dist/commonjs/commands/query.js +85 -65
  11. package/dist/commonjs/commands/update.js +9 -7
  12. package/dist/commonjs/config/error.config.js +1 -0
  13. package/dist/commonjs/const/code.js +1 -0
  14. package/dist/commonjs/constant.d.ts +7 -7
  15. package/dist/commonjs/constant.js +13 -11
  16. package/dist/commonjs/document.d.ts +10 -6
  17. package/dist/commonjs/document.js +305 -193
  18. package/dist/commonjs/geo/index.js +20 -9
  19. package/dist/commonjs/geo/lineString.d.ts +1 -1
  20. package/dist/commonjs/geo/lineString.js +62 -33
  21. package/dist/commonjs/geo/multiLineString.d.ts +1 -1
  22. package/dist/commonjs/geo/multiLineString.js +71 -32
  23. package/dist/commonjs/geo/multiPoint.d.ts +1 -1
  24. package/dist/commonjs/geo/multiPoint.js +58 -29
  25. package/dist/commonjs/geo/multiPolygon.d.ts +1 -1
  26. package/dist/commonjs/geo/multiPolygon.js +96 -36
  27. package/dist/commonjs/geo/point.d.ts +1 -1
  28. package/dist/commonjs/geo/point.js +30 -23
  29. package/dist/commonjs/geo/polygon.d.ts +1 -1
  30. package/dist/commonjs/geo/polygon.js +78 -39
  31. package/dist/commonjs/helper/symbol.js +17 -5
  32. package/dist/commonjs/index.d.ts +8 -2
  33. package/dist/commonjs/index.js +73 -20
  34. package/dist/commonjs/lib/util.d.ts +1 -0
  35. package/dist/commonjs/lib/util.js +26 -0
  36. package/dist/commonjs/operator-map.js +7 -6
  37. package/dist/commonjs/query.d.ts +9 -5
  38. package/dist/commonjs/query.js +330 -205
  39. package/dist/commonjs/regexp/index.d.ts +1 -1
  40. package/dist/commonjs/regexp/index.js +16 -9
  41. package/dist/commonjs/serializer/common.d.ts +2 -2
  42. package/dist/commonjs/serializer/common.js +59 -22
  43. package/dist/commonjs/serializer/datatype.d.ts +2 -2
  44. package/dist/commonjs/serializer/datatype.js +58 -21
  45. package/dist/commonjs/serializer/query.d.ts +1 -1
  46. package/dist/commonjs/serializer/query.js +137 -127
  47. package/dist/commonjs/serializer/update.d.ts +1 -1
  48. package/dist/commonjs/serializer/update.js +83 -78
  49. package/dist/commonjs/serverDate/index.d.ts +1 -1
  50. package/dist/commonjs/serverDate/index.js +16 -9
  51. package/dist/commonjs/transaction/collection.d.ts +7 -0
  52. package/dist/commonjs/transaction/collection.js +49 -0
  53. package/dist/commonjs/transaction/document.d.ts +27 -0
  54. package/dist/commonjs/transaction/document.js +205 -0
  55. package/dist/commonjs/transaction/index.js +196 -86
  56. package/dist/commonjs/transaction/query.d.ts +43 -0
  57. package/dist/commonjs/transaction/query.js +261 -0
  58. package/dist/commonjs/typings/index.d.ts +4 -4
  59. package/dist/commonjs/typings/realtime.d.ts +16 -16
  60. package/dist/commonjs/util.js +123 -92
  61. package/dist/commonjs/utils/error.d.ts +2 -1
  62. package/dist/commonjs/utils/error.js +74 -38
  63. package/dist/commonjs/utils/symbol.js +33 -14
  64. package/dist/commonjs/utils/type.d.ts +1 -1
  65. package/dist/commonjs/utils/type.js +25 -13
  66. package/dist/commonjs/utils/utils.d.ts +5 -1
  67. package/dist/commonjs/utils/utils.js +153 -22
  68. package/dist/commonjs/validate.js +79 -63
  69. package/dist/esm/ObjectId/index.d.ts +1 -1
  70. package/dist/esm/ObjectId/index.js +15 -8
  71. package/dist/esm/aggregate.js +118 -66
  72. package/dist/esm/collection.d.ts +3 -2
  73. package/dist/esm/collection.js +125 -52
  74. package/dist/esm/command.d.ts +5 -5
  75. package/dist/esm/command.js +175 -147
  76. package/dist/esm/commands/logic.js +30 -20
  77. package/dist/esm/commands/query.d.ts +1 -1
  78. package/dist/esm/commands/query.js +91 -71
  79. package/dist/esm/commands/update.js +8 -6
  80. package/dist/esm/config/error.config.js +1 -1
  81. package/dist/esm/const/code.js +1 -1
  82. package/dist/esm/constant.d.ts +7 -7
  83. package/dist/esm/constant.js +12 -11
  84. package/dist/esm/document.d.ts +10 -6
  85. package/dist/esm/document.js +297 -185
  86. package/dist/esm/geo/interface.js +1 -0
  87. package/dist/esm/geo/lineString.d.ts +1 -1
  88. package/dist/esm/geo/lineString.js +57 -28
  89. package/dist/esm/geo/multiLineString.d.ts +1 -1
  90. package/dist/esm/geo/multiLineString.js +66 -27
  91. package/dist/esm/geo/multiPoint.d.ts +1 -1
  92. package/dist/esm/geo/multiPoint.js +53 -24
  93. package/dist/esm/geo/multiPolygon.d.ts +1 -1
  94. package/dist/esm/geo/multiPolygon.js +91 -31
  95. package/dist/esm/geo/point.d.ts +1 -1
  96. package/dist/esm/geo/point.js +26 -19
  97. package/dist/esm/geo/polygon.d.ts +1 -1
  98. package/dist/esm/geo/polygon.js +73 -34
  99. package/dist/esm/helper/symbol.js +13 -13
  100. package/dist/esm/index.d.ts +8 -2
  101. package/dist/esm/index.js +62 -9
  102. package/dist/esm/lib/util.d.ts +1 -0
  103. package/dist/esm/lib/util.js +22 -0
  104. package/dist/esm/operator-map.js +4 -4
  105. package/dist/esm/query.d.ts +9 -5
  106. package/dist/esm/query.js +321 -196
  107. package/dist/esm/regexp/index.d.ts +1 -1
  108. package/dist/esm/regexp/index.js +15 -8
  109. package/dist/esm/serializer/common.d.ts +2 -2
  110. package/dist/esm/serializer/common.js +49 -13
  111. package/dist/esm/serializer/datatype.d.ts +2 -2
  112. package/dist/esm/serializer/datatype.js +44 -8
  113. package/dist/esm/serializer/query.d.ts +1 -1
  114. package/dist/esm/serializer/query.js +112 -102
  115. package/dist/esm/serializer/update.d.ts +1 -1
  116. package/dist/esm/serializer/update.js +69 -64
  117. package/dist/esm/serverDate/index.d.ts +1 -1
  118. package/dist/esm/serverDate/index.js +15 -8
  119. package/dist/esm/transaction/collection.d.ts +7 -0
  120. package/dist/esm/transaction/collection.js +46 -0
  121. package/dist/esm/transaction/document.d.ts +27 -0
  122. package/dist/esm/transaction/document.js +202 -0
  123. package/dist/esm/transaction/index.js +191 -81
  124. package/dist/esm/transaction/query.d.ts +43 -0
  125. package/dist/esm/transaction/query.js +258 -0
  126. package/dist/esm/typings/index.d.ts +4 -4
  127. package/dist/esm/typings/index.js +1 -0
  128. package/dist/esm/typings/realtime.d.ts +16 -16
  129. package/dist/esm/typings/realtime.js +1 -0
  130. package/dist/esm/util.js +121 -90
  131. package/dist/esm/utils/error.d.ts +2 -1
  132. package/dist/esm/utils/error.js +70 -35
  133. package/dist/esm/utils/symbol.js +33 -14
  134. package/dist/esm/utils/type.d.ts +1 -1
  135. package/dist/esm/utils/type.js +12 -12
  136. package/dist/esm/utils/utils.d.ts +5 -1
  137. package/dist/esm/utils/utils.js +137 -16
  138. package/dist/esm/validate.js +65 -49
  139. package/jest.config.js +14 -0
  140. package/package.json +14 -10
  141. package/src/aggregate.ts +1 -5
  142. package/src/collection.ts +15 -2
  143. package/src/document.ts +45 -34
  144. package/src/index.ts +51 -4
  145. package/src/query.ts +48 -38
  146. package/src/serializer/query.ts +18 -6
  147. package/src/transaction/index.ts +1 -1
  148. package/src/typings/index.ts +0 -4
  149. package/src/utils/utils.ts +105 -0
  150. package/src/validate.ts +3 -6
  151. package/tests/gateway-contract.test.ts +180 -0
  152. package/tests/index.test.ts +283 -0
  153. package/tests/transaction-query.test.ts +207 -0
  154. package/tests/watch-query.test.ts +144 -0
  155. package/tsconfig.esm.json +4 -2
  156. package/tsconfig.json +16 -6
  157. package/tsconfig.test.json +15 -0
  158. package/dist/commonjs/realtime/error.d.ts +0 -7
  159. package/dist/commonjs/realtime/error.js +0 -11
  160. package/dist/commonjs/realtime/listener.d.ts +0 -14
  161. package/dist/commonjs/realtime/listener.js +0 -17
  162. package/dist/commonjs/realtime/message.d.ts +0 -3
  163. package/dist/commonjs/realtime/message.js +0 -10
  164. package/dist/commonjs/realtime/snapshot.d.ts +0 -16
  165. package/dist/commonjs/realtime/snapshot.js +0 -42
  166. package/dist/commonjs/realtime/virtual-websocket-client.d.ts +0 -63
  167. package/dist/commonjs/realtime/virtual-websocket-client.js +0 -736
  168. package/dist/commonjs/realtime/websocket-client.d.ts +0 -78
  169. package/dist/commonjs/realtime/websocket-client.js +0 -567
  170. package/dist/commonjs/realtime/ws-event.d.ts +0 -126
  171. package/dist/commonjs/realtime/ws-event.js +0 -139
  172. package/dist/commonjs/transaction.d.ts +0 -42
  173. package/dist/commonjs/transaction.js +0 -126
  174. package/dist/esm/realtime/error.d.ts +0 -7
  175. package/dist/esm/realtime/error.js +0 -8
  176. package/dist/esm/realtime/listener.d.ts +0 -14
  177. package/dist/esm/realtime/listener.js +0 -14
  178. package/dist/esm/realtime/message.d.ts +0 -3
  179. package/dist/esm/realtime/message.js +0 -6
  180. package/dist/esm/realtime/snapshot.d.ts +0 -16
  181. package/dist/esm/realtime/snapshot.js +0 -39
  182. package/dist/esm/realtime/virtual-websocket-client.d.ts +0 -63
  183. package/dist/esm/realtime/virtual-websocket-client.js +0 -733
  184. package/dist/esm/realtime/websocket-client.d.ts +0 -78
  185. package/dist/esm/realtime/websocket-client.js +0 -564
  186. package/dist/esm/realtime/ws-event.d.ts +0 -126
  187. package/dist/esm/realtime/ws-event.js +0 -137
  188. package/dist/esm/transaction.d.ts +0 -42
  189. package/dist/esm/transaction.js +0 -121
  190. package/src/realtime/error.ts +0 -19
  191. package/src/realtime/listener.ts +0 -35
  192. package/src/realtime/message.ts +0 -14
  193. package/src/realtime/snapshot.ts +0 -60
  194. package/src/realtime/virtual-websocket-client.ts +0 -1195
  195. package/src/realtime/websocket-client.ts +0 -995
  196. package/src/realtime/ws-event.ts +0 -163
  197. 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
+ })