@cloudbase/database 1.5.0 → 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.
- package/dist/commonjs/query.d.ts +1 -1
- package/dist/commonjs/query.js +24 -6
- package/dist/commonjs/utils/utils.d.ts +1 -0
- package/dist/commonjs/utils/utils.js +61 -1
- package/dist/esm/query.d.ts +1 -1
- package/dist/esm/query.js +25 -7
- package/dist/esm/utils/utils.d.ts +1 -0
- package/dist/esm/utils/utils.js +59 -0
- package/package.json +1 -1
- package/src/query.ts +16 -10
- package/src/utils/utils.ts +59 -0
- package/tests/watch-query.test.ts +144 -0
package/dist/commonjs/query.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface QueryOption extends BaseOption {
|
|
|
15
15
|
limit?: number;
|
|
16
16
|
offset?: number;
|
|
17
17
|
projection?: Object;
|
|
18
|
-
order?: Record<string, any
|
|
18
|
+
order?: Record<string, any>;
|
|
19
19
|
}
|
|
20
20
|
export interface UpdateOption extends BaseOption {
|
|
21
21
|
multiple?: boolean;
|
package/dist/commonjs/query.js
CHANGED
|
@@ -46,6 +46,22 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
46
46
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
50
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
51
|
+
if (!m) return o;
|
|
52
|
+
var i = m.call(o), r, ar = [], e;
|
|
53
|
+
try {
|
|
54
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
55
|
+
}
|
|
56
|
+
catch (error) { e = { error: error }; }
|
|
57
|
+
finally {
|
|
58
|
+
try {
|
|
59
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
60
|
+
}
|
|
61
|
+
finally { if (e) throw e.error; }
|
|
62
|
+
}
|
|
63
|
+
return ar;
|
|
64
|
+
};
|
|
49
65
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
66
|
exports.Query = void 0;
|
|
51
67
|
var constant_1 = require("./constant");
|
|
@@ -64,12 +80,14 @@ var Query = (function () {
|
|
|
64
80
|
this.watch = function (options) {
|
|
65
81
|
var ws = (0, utils_1.getWsInstance)(_this._db);
|
|
66
82
|
var _a = _this._apiOptions, limit = _a.limit, order = _a.order;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
var orderBy = order
|
|
84
|
+
? Object.entries(order).reduce(function (acc, _a) {
|
|
85
|
+
var _b = __read(_a, 2), field = _b[0], direction = _b[1];
|
|
86
|
+
acc[field] = direction === -1 ? 'desc' : 'asc';
|
|
87
|
+
return acc;
|
|
88
|
+
}, {})
|
|
89
|
+
: undefined;
|
|
90
|
+
return ws.watch(__assign(__assign({}, options), { envId: _this._db.config.env, collectionName: _this._coll, query: (0, utils_1.toWatchQuery)(_this._fieldFilters), limit: limit, orderBy: orderBy }));
|
|
73
91
|
};
|
|
74
92
|
this._db = db;
|
|
75
93
|
this._coll = coll;
|
|
@@ -8,6 +8,7 @@ export declare const getReqOpts: (apiOptions: QueryOption | UpdateOption) => any
|
|
|
8
8
|
export declare const filterUndefined: (o: any) => any;
|
|
9
9
|
export declare const stringifyByEJSON: (params: any) => string;
|
|
10
10
|
export declare const parseByEJSON: (params: any) => any;
|
|
11
|
+
export declare const toWatchQuery: (ejsonStr: string) => string;
|
|
11
12
|
export declare class TcbError extends Error {
|
|
12
13
|
readonly code: string;
|
|
13
14
|
readonly message: string;
|
|
@@ -25,8 +25,19 @@ var __assign = (this && this.__assign) || function () {
|
|
|
25
25
|
};
|
|
26
26
|
return __assign.apply(this, arguments);
|
|
27
27
|
};
|
|
28
|
+
var __values = (this && this.__values) || function(o) {
|
|
29
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
30
|
+
if (m) return m.call(o);
|
|
31
|
+
if (o && typeof o.length === "number") return {
|
|
32
|
+
next: function () {
|
|
33
|
+
if (o && i >= o.length) o = void 0;
|
|
34
|
+
return { value: o && o[i++], done: !o };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
38
|
+
};
|
|
28
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.processReturn = exports.E = exports.TcbError = exports.parseByEJSON = exports.stringifyByEJSON = exports.filterUndefined = exports.getReqOpts = exports.getWsInstance = exports.autoCount = exports.withCallback = exports.sleep = void 0;
|
|
40
|
+
exports.processReturn = exports.E = exports.TcbError = exports.toWatchQuery = exports.parseByEJSON = exports.stringifyByEJSON = exports.filterUndefined = exports.getReqOpts = exports.getWsInstance = exports.autoCount = exports.withCallback = exports.sleep = void 0;
|
|
30
41
|
var __1 = require("../");
|
|
31
42
|
var bson_1 = require("bson");
|
|
32
43
|
var type_1 = require("./type");
|
|
@@ -106,6 +117,55 @@ var parseByEJSON = function (params) {
|
|
|
106
117
|
return bson_1.EJSON.parse(params);
|
|
107
118
|
};
|
|
108
119
|
exports.parseByEJSON = parseByEJSON;
|
|
120
|
+
var toWatchLegacyValue = function (val) {
|
|
121
|
+
var e_1, _a;
|
|
122
|
+
if (val instanceof Date) {
|
|
123
|
+
return { $date: +val };
|
|
124
|
+
}
|
|
125
|
+
if (val instanceof RegExp) {
|
|
126
|
+
return { $regex: val.source, $options: val.flags };
|
|
127
|
+
}
|
|
128
|
+
if (val && typeof val === 'object') {
|
|
129
|
+
if (val._bsontype === 'ObjectId') {
|
|
130
|
+
return { $oid: val.toHexString() };
|
|
131
|
+
}
|
|
132
|
+
if (val._bsontype === 'BSONRegExp') {
|
|
133
|
+
return { $regex: val.pattern, $options: val.options };
|
|
134
|
+
}
|
|
135
|
+
if (Array.isArray(val)) {
|
|
136
|
+
return val.map(toWatchLegacyValue);
|
|
137
|
+
}
|
|
138
|
+
var ret = {};
|
|
139
|
+
try {
|
|
140
|
+
for (var _b = __values(Object.keys(val)), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
141
|
+
var key = _c.value;
|
|
142
|
+
ret[key] = toWatchLegacyValue(val[key]);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
146
|
+
finally {
|
|
147
|
+
try {
|
|
148
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
149
|
+
}
|
|
150
|
+
finally { if (e_1) throw e_1.error; }
|
|
151
|
+
}
|
|
152
|
+
return ret;
|
|
153
|
+
}
|
|
154
|
+
return val;
|
|
155
|
+
};
|
|
156
|
+
var toWatchQuery = function (ejsonStr) {
|
|
157
|
+
if (!ejsonStr) {
|
|
158
|
+
return '{}';
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
var parsed = bson_1.EJSON.parse(ejsonStr);
|
|
162
|
+
return JSON.stringify(toWatchLegacyValue(parsed));
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
return ejsonStr;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
exports.toWatchQuery = toWatchQuery;
|
|
109
169
|
var TcbError = (function (_super) {
|
|
110
170
|
__extends(TcbError, _super);
|
|
111
171
|
function TcbError(error) {
|
package/dist/esm/query.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface QueryOption extends BaseOption {
|
|
|
15
15
|
limit?: number;
|
|
16
16
|
offset?: number;
|
|
17
17
|
projection?: Object;
|
|
18
|
-
order?: Record<string, any
|
|
18
|
+
order?: Record<string, any>;
|
|
19
19
|
}
|
|
20
20
|
export interface UpdateOption extends BaseOption {
|
|
21
21
|
multiple?: boolean;
|
package/dist/esm/query.js
CHANGED
|
@@ -45,6 +45,22 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
45
45
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
49
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
50
|
+
if (!m) return o;
|
|
51
|
+
var i = m.call(o), r, ar = [], e;
|
|
52
|
+
try {
|
|
53
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
54
|
+
}
|
|
55
|
+
catch (error) { e = { error: error }; }
|
|
56
|
+
finally {
|
|
57
|
+
try {
|
|
58
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
59
|
+
}
|
|
60
|
+
finally { if (e) throw e.error; }
|
|
61
|
+
}
|
|
62
|
+
return ar;
|
|
63
|
+
};
|
|
48
64
|
import { QueryType } from './constant';
|
|
49
65
|
import { Db } from './index';
|
|
50
66
|
import { Validate } from './validate';
|
|
@@ -52,7 +68,7 @@ import { Util } from './util';
|
|
|
52
68
|
import { QuerySerializer } from './serializer/query';
|
|
53
69
|
import { UpdateSerializer } from './serializer/update';
|
|
54
70
|
import { ErrorCode } from './constant';
|
|
55
|
-
import { getWsInstance, getReqOpts, stringifyByEJSON, processReturn, withCallback } from './utils/utils';
|
|
71
|
+
import { getWsInstance, getReqOpts, stringifyByEJSON, toWatchQuery, processReturn, withCallback } from './utils/utils';
|
|
56
72
|
import { ERRORS } from './const/code';
|
|
57
73
|
import { EJSON } from 'bson';
|
|
58
74
|
var Query = (function () {
|
|
@@ -61,12 +77,14 @@ var Query = (function () {
|
|
|
61
77
|
this.watch = function (options) {
|
|
62
78
|
var ws = getWsInstance(_this._db);
|
|
63
79
|
var _a = _this._apiOptions, limit = _a.limit, order = _a.order;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
80
|
+
var orderBy = order
|
|
81
|
+
? Object.entries(order).reduce(function (acc, _a) {
|
|
82
|
+
var _b = __read(_a, 2), field = _b[0], direction = _b[1];
|
|
83
|
+
acc[field] = direction === -1 ? 'desc' : 'asc';
|
|
84
|
+
return acc;
|
|
85
|
+
}, {})
|
|
86
|
+
: undefined;
|
|
87
|
+
return ws.watch(__assign(__assign({}, options), { envId: _this._db.config.env, collectionName: _this._coll, query: toWatchQuery(_this._fieldFilters), limit: limit, orderBy: orderBy }));
|
|
70
88
|
};
|
|
71
89
|
this._db = db;
|
|
72
90
|
this._coll = coll;
|
|
@@ -8,6 +8,7 @@ export declare const getReqOpts: (apiOptions: QueryOption | UpdateOption) => any
|
|
|
8
8
|
export declare const filterUndefined: (o: any) => any;
|
|
9
9
|
export declare const stringifyByEJSON: (params: any) => string;
|
|
10
10
|
export declare const parseByEJSON: (params: any) => any;
|
|
11
|
+
export declare const toWatchQuery: (ejsonStr: string) => string;
|
|
11
12
|
export declare class TcbError extends Error {
|
|
12
13
|
readonly code: string;
|
|
13
14
|
readonly message: string;
|
package/dist/esm/utils/utils.js
CHANGED
|
@@ -24,6 +24,17 @@ var __assign = (this && this.__assign) || function () {
|
|
|
24
24
|
};
|
|
25
25
|
return __assign.apply(this, arguments);
|
|
26
26
|
};
|
|
27
|
+
var __values = (this && this.__values) || function(o) {
|
|
28
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
29
|
+
if (m) return m.call(o);
|
|
30
|
+
if (o && typeof o.length === "number") return {
|
|
31
|
+
next: function () {
|
|
32
|
+
if (o && i >= o.length) o = void 0;
|
|
33
|
+
return { value: o && o[i++], done: !o };
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
37
|
+
};
|
|
27
38
|
import { Db } from '../';
|
|
28
39
|
import { EJSON } from 'bson';
|
|
29
40
|
import { isObject } from './type';
|
|
@@ -95,6 +106,54 @@ export var stringifyByEJSON = function (params) {
|
|
|
95
106
|
export var parseByEJSON = function (params) {
|
|
96
107
|
return EJSON.parse(params);
|
|
97
108
|
};
|
|
109
|
+
var toWatchLegacyValue = function (val) {
|
|
110
|
+
var e_1, _a;
|
|
111
|
+
if (val instanceof Date) {
|
|
112
|
+
return { $date: +val };
|
|
113
|
+
}
|
|
114
|
+
if (val instanceof RegExp) {
|
|
115
|
+
return { $regex: val.source, $options: val.flags };
|
|
116
|
+
}
|
|
117
|
+
if (val && typeof val === 'object') {
|
|
118
|
+
if (val._bsontype === 'ObjectId') {
|
|
119
|
+
return { $oid: val.toHexString() };
|
|
120
|
+
}
|
|
121
|
+
if (val._bsontype === 'BSONRegExp') {
|
|
122
|
+
return { $regex: val.pattern, $options: val.options };
|
|
123
|
+
}
|
|
124
|
+
if (Array.isArray(val)) {
|
|
125
|
+
return val.map(toWatchLegacyValue);
|
|
126
|
+
}
|
|
127
|
+
var ret = {};
|
|
128
|
+
try {
|
|
129
|
+
for (var _b = __values(Object.keys(val)), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
130
|
+
var key = _c.value;
|
|
131
|
+
ret[key] = toWatchLegacyValue(val[key]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
135
|
+
finally {
|
|
136
|
+
try {
|
|
137
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
138
|
+
}
|
|
139
|
+
finally { if (e_1) throw e_1.error; }
|
|
140
|
+
}
|
|
141
|
+
return ret;
|
|
142
|
+
}
|
|
143
|
+
return val;
|
|
144
|
+
};
|
|
145
|
+
export var toWatchQuery = function (ejsonStr) {
|
|
146
|
+
if (!ejsonStr) {
|
|
147
|
+
return '{}';
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
var parsed = EJSON.parse(ejsonStr);
|
|
151
|
+
return JSON.stringify(toWatchLegacyValue(parsed));
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
return ejsonStr;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
98
157
|
var TcbError = (function (_super) {
|
|
99
158
|
__extends(TcbError, _super);
|
|
100
159
|
function TcbError(error) {
|
package/package.json
CHANGED
package/src/query.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { UpdateSerializer } from './serializer/update'
|
|
|
10
10
|
// import { WSClient } from "./websocket/wsclient"
|
|
11
11
|
import { IWatchOptions, DBRealtimeListener } from './typings/index'
|
|
12
12
|
import { ErrorCode } from './constant'
|
|
13
|
-
import { getWsInstance, getReqOpts, stringifyByEJSON, processReturn, withCallback } from './utils/utils'
|
|
13
|
+
import { getWsInstance, getReqOpts, stringifyByEJSON, toWatchQuery, processReturn, withCallback } from './utils/utils'
|
|
14
14
|
import { ERRORS } from './const/code'
|
|
15
15
|
import { EJSON } from 'bson'
|
|
16
16
|
|
|
@@ -34,8 +34,8 @@ export interface QueryOption extends BaseOption {
|
|
|
34
34
|
offset?: number
|
|
35
35
|
// 指定显示或者不显示哪些字段
|
|
36
36
|
projection?: Object
|
|
37
|
-
//
|
|
38
|
-
order?: Record<string, any>
|
|
37
|
+
// 结果排序(orderBy() 存为 { field: 1 | -1 })
|
|
38
|
+
order?: Record<string, any>
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export interface UpdateOption extends BaseOption {
|
|
@@ -598,18 +598,24 @@ export class Query {
|
|
|
598
598
|
|
|
599
599
|
const { limit, order } = this._apiOptions as QueryOption
|
|
600
600
|
|
|
601
|
+
// order 由 orderBy() 存为 { field: 1 | -1 },实时推送协议要求 'asc' | 'desc'
|
|
602
|
+
const orderBy = order
|
|
603
|
+
? Object.entries(order).reduce<Record<string, string>>((acc, [field, direction]) => {
|
|
604
|
+
acc[field] = direction === -1 ? 'desc' : 'asc'
|
|
605
|
+
return acc
|
|
606
|
+
}, {})
|
|
607
|
+
: undefined
|
|
608
|
+
|
|
601
609
|
return ws.watch({
|
|
602
610
|
...options,
|
|
603
611
|
envId: this._db.config.env,
|
|
604
612
|
collectionName: this._coll,
|
|
605
|
-
|
|
613
|
+
// where() 产出 canonical EJSON 字符串,但 watch 协议要求 legacy 内部编码
|
|
614
|
+
// (数字裸值、$date 为 epoch 毫秒数字)——canonical 形态服务端虽接受但会静默匹配不到文档,
|
|
615
|
+
// 且不可再 JSON.stringify(否则双重编码,线上 100003 事故根因)
|
|
616
|
+
query: toWatchQuery(this._fieldFilters),
|
|
606
617
|
limit,
|
|
607
|
-
orderBy
|
|
608
|
-
? order.reduce<Record<string, string>>((acc, cur) => {
|
|
609
|
-
acc[cur.field] = cur.direction
|
|
610
|
-
return acc
|
|
611
|
-
}, {})
|
|
612
|
-
: undefined
|
|
618
|
+
orderBy
|
|
613
619
|
})
|
|
614
620
|
}
|
|
615
621
|
}
|
package/src/utils/utils.ts
CHANGED
|
@@ -99,6 +99,65 @@ export const parseByEJSON = params => {
|
|
|
99
99
|
return EJSON.parse(params)
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* 将 EJSON 值递归转为实时推送 watch 协议要求的 legacy 内部编码(0.12.x 形态)
|
|
104
|
+
* - Date → { $date: <epoch 毫秒数字> }
|
|
105
|
+
* - ObjectId → { $oid }、BSONRegExp/原生 RegExp → { $regex, $options }
|
|
106
|
+
* - 数字/字符串/布尔等基本值保持裸值
|
|
107
|
+
*
|
|
108
|
+
* bson 类型用 _bsontype 字符串判断而非 instanceof:EJSON.parse 产出的实例
|
|
109
|
+
* 来自运行时实际加载的 bson(可能与编译时版本/副本不同),instanceOf 跨副本
|
|
110
|
+
* 会失效,_bsontype 是 bson 各版本稳定的类型标记。
|
|
111
|
+
*/
|
|
112
|
+
const toWatchLegacyValue = (val: any): any => {
|
|
113
|
+
if (val instanceof Date) {
|
|
114
|
+
return { $date: +val }
|
|
115
|
+
}
|
|
116
|
+
if (val instanceof RegExp) {
|
|
117
|
+
// 原生 RegExp 兜底(正常链路编码器已转 $regularExpression,raw 模式等可能漏过)
|
|
118
|
+
return { $regex: val.source, $options: val.flags }
|
|
119
|
+
}
|
|
120
|
+
if (val && typeof val === 'object') {
|
|
121
|
+
if (val._bsontype === 'ObjectId') {
|
|
122
|
+
return { $oid: val.toHexString() }
|
|
123
|
+
}
|
|
124
|
+
if (val._bsontype === 'BSONRegExp') {
|
|
125
|
+
return { $regex: val.pattern, $options: val.options }
|
|
126
|
+
}
|
|
127
|
+
if (Array.isArray(val)) {
|
|
128
|
+
return val.map(toWatchLegacyValue)
|
|
129
|
+
}
|
|
130
|
+
const ret: Record<string, any> = {}
|
|
131
|
+
for (const key of Object.keys(val)) {
|
|
132
|
+
ret[key] = toWatchLegacyValue(val[key])
|
|
133
|
+
}
|
|
134
|
+
return ret
|
|
135
|
+
}
|
|
136
|
+
return val
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 实时推送 watch 协议的 query 编码
|
|
141
|
+
*
|
|
142
|
+
* where() 产出的 _fieldFilters 是 canonical EJSON 字符串(relaxed:false),
|
|
143
|
+
* 但服务端 watch 查询引擎只认 0.12.x 的 legacy 内部编码:
|
|
144
|
+
* - 数字必须裸值({$numberInt}/{$numberLong} 会被静默匹配不到文档,实测 0 命中)
|
|
145
|
+
* - 日期必须是 { $date: <epoch 毫秒数字> }(ISO 串与 {$date:{$numberLong}} 均不命中)
|
|
146
|
+
* 此处 parse 后转成 legacy 形态再序列化。
|
|
147
|
+
*/
|
|
148
|
+
export const toWatchQuery = (ejsonStr: string): string => {
|
|
149
|
+
if (!ejsonStr) {
|
|
150
|
+
return '{}'
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const parsed = EJSON.parse(ejsonStr)
|
|
154
|
+
return JSON.stringify(toWatchLegacyValue(parsed))
|
|
155
|
+
} catch (e) {
|
|
156
|
+
// 非 EJSON 文本(EJSON.parse 兼容普通 JSON,走到这说明确实解析不了),原样透传由调用方保证格式
|
|
157
|
+
return ejsonStr
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
102
161
|
export class TcbError extends Error {
|
|
103
162
|
readonly code: string
|
|
104
163
|
readonly message: string
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query.watch() 回归测试
|
|
3
|
+
*
|
|
4
|
+
* 1.5.0 引入的回归:where() 改用 QuerySerializer.encodeEJSON() 后
|
|
5
|
+
* _fieldFilters 已是 EJSON 字符串,watch() 若再 JSON.stringify 会产生
|
|
6
|
+
* 双重编码(线上 INIT_WATCH 失败、errorCode=100003 的根因)。
|
|
7
|
+
* 伴生问题:orderBy() 改存对象 {field: 1|-1} 后,watch() 仍按 0.12.x
|
|
8
|
+
* 数组形态调用 order.reduce(),带 orderBy 的 watch 会直接抛 TypeError。
|
|
9
|
+
*
|
|
10
|
+
* 第二轮真实环境实测(2026-09-03)追加约束:服务端 watch 查询引擎只认
|
|
11
|
+
* 0.12.x legacy 内部编码——数字裸值、日期 { $date: epoch 毫秒数字 }。
|
|
12
|
+
* canonical EJSON($numberInt/$numberLong/$date ISO)虽不报错但静默匹配
|
|
13
|
+
* 不到文档,因此 watch query 须由 canonical EJSON 转换为 legacy 形态。
|
|
14
|
+
*/
|
|
15
|
+
import { Db } from '../src/index'
|
|
16
|
+
|
|
17
|
+
describe('Query.watch() 参数序列化', () => {
|
|
18
|
+
let watchArgs: any[]
|
|
19
|
+
|
|
20
|
+
class MockRequest {
|
|
21
|
+
constructor(_config: any) {}
|
|
22
|
+
async send(_action: string, _data: any) {
|
|
23
|
+
return { data: { ok: 1 }, requestId: 'mock' }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class MockWsClient {
|
|
28
|
+
constructor(_opts: any) {}
|
|
29
|
+
watch(args: any) {
|
|
30
|
+
watchArgs.push(args)
|
|
31
|
+
return { close: () => {} }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
Db.reqClass = MockRequest as any
|
|
37
|
+
Db.wsClientClass = MockWsClient as any
|
|
38
|
+
watchArgs = []
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
function makeDb() {
|
|
42
|
+
// getWsInstance 按 env 缓存实例,每个用例用独立 env
|
|
43
|
+
return new Db({ env: `env-${Math.random()}` } as any)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
it('数字条件应为裸值(legacy 编码),无 $numberInt/$numberLong 标记', () => {
|
|
47
|
+
const db = makeDb()
|
|
48
|
+
db.collection('test-collection')
|
|
49
|
+
.where({ ts: { $gt: 1788421619261 } } as any)
|
|
50
|
+
.watch({ onChange: () => {}, onError: () => {} })
|
|
51
|
+
|
|
52
|
+
expect(watchArgs).toHaveLength(1)
|
|
53
|
+
const query = watchArgs[0].query
|
|
54
|
+
// legacy 形态:数字裸值
|
|
55
|
+
expect(query).toBe('{"ts":{"$gt":1788421619261}}')
|
|
56
|
+
// 双重编码的标志形态:外层以引号开头 —— 必须不存在
|
|
57
|
+
expect(query.startsWith('"')).toBe(false)
|
|
58
|
+
// canonical 标记 —— 必须不存在
|
|
59
|
+
expect(query).not.toContain('$numberInt')
|
|
60
|
+
expect(query).not.toContain('$numberLong')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('where() + command 查询条件同样转为裸数字', () => {
|
|
64
|
+
const db = makeDb()
|
|
65
|
+
db.collection('test-collection')
|
|
66
|
+
.where({ count: db.command.gt(10) } as any)
|
|
67
|
+
.watch({ onChange: () => {}, onError: () => {} })
|
|
68
|
+
|
|
69
|
+
expect(watchArgs[0].query).toBe('{"count":{"$gt":10}}')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('日期条件应转为 { $date: epoch 毫秒数字 }(0.12.x legacy 形态)', () => {
|
|
73
|
+
const db = makeDb()
|
|
74
|
+
const date = new Date('2020-01-01T00:00:00.000Z')
|
|
75
|
+
db.collection('test-collection')
|
|
76
|
+
.where({ createdAt: db.command.gt(date) } as any)
|
|
77
|
+
.watch({ onChange: () => {}, onError: () => {} })
|
|
78
|
+
|
|
79
|
+
expect(watchArgs[0].query).toBe(`{"createdAt":{"$gt":{"$date":${+date}}}}`)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('db.RegExp / 原生 RegExp 应转为 { $regex, $options }($regularExpression 不被服务端识别)', () => {
|
|
83
|
+
const db = makeDb()
|
|
84
|
+
db.collection('test-collection')
|
|
85
|
+
.where({ text: db.RegExp({ regexp: 'probe-\\d+', options: 'i' }) } as any)
|
|
86
|
+
.watch({ onChange: () => {}, onError: () => {} })
|
|
87
|
+
// canonical 编码为 $regularExpression,watch 须转为 0.12.x 的 $regex/$options
|
|
88
|
+
expect(watchArgs[0].query).toBe('{"text":{"$regex":"probe-\\\\d+","$options":"i"}}')
|
|
89
|
+
expect(watchArgs[0].query).not.toContain('$regularExpression')
|
|
90
|
+
|
|
91
|
+
db.collection('test-collection')
|
|
92
|
+
.where({ text: /probe-\d+/i } as any)
|
|
93
|
+
.watch({ onChange: () => {}, onError: () => {} })
|
|
94
|
+
expect(watchArgs[1].query).toBe('{"text":{"$regex":"probe-\\\\d+","$options":"i"}}')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('ObjectId 条件应保持 { $oid } 形态', () => {
|
|
98
|
+
const db = makeDb()
|
|
99
|
+
// 该仓库 ObjectId 构造签名为 { id } 对象形式(bson 风格位置参数会静默得到空 id)
|
|
100
|
+
db.collection('test-collection')
|
|
101
|
+
.where({ _id: db.ObjectId({ id: '507f1f77bcf86cd799439011' }) } as any)
|
|
102
|
+
.watch({ onChange: () => {}, onError: () => {} })
|
|
103
|
+
|
|
104
|
+
expect(watchArgs[0].query).toBe('{"_id":{"$oid":"507f1f77bcf86cd799439011"}}')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('无 where() 时 query 回退为 "{}"', () => {
|
|
108
|
+
const db = makeDb()
|
|
109
|
+
db.collection('test-collection').watch({ onChange: () => {}, onError: () => {} })
|
|
110
|
+
|
|
111
|
+
expect(watchArgs[0].query).toBe('{}')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('字符串等基本类型条件保持原样', () => {
|
|
115
|
+
const db = makeDb()
|
|
116
|
+
db.collection('test-collection')
|
|
117
|
+
.where({ _id: 'xxx', name: 'foo' } as any)
|
|
118
|
+
.watch({ onChange: () => {}, onError: () => {} })
|
|
119
|
+
|
|
120
|
+
expect(watchArgs[0].query).toBe('{"_id":"xxx","name":"foo"}')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('orderBy() 后 watch() 不抛错且方向映射为 asc/desc', () => {
|
|
124
|
+
const db = makeDb()
|
|
125
|
+
expect(() => {
|
|
126
|
+
db.collection('test-collection')
|
|
127
|
+
.where({ ts: { $gt: 1 } } as any)
|
|
128
|
+
.orderBy('ts', 'desc')
|
|
129
|
+
.watch({ onChange: () => {}, onError: () => {} })
|
|
130
|
+
}).not.toThrow()
|
|
131
|
+
|
|
132
|
+
expect(watchArgs[0].orderBy).toEqual({ ts: 'desc' })
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('limit 应透传到 watch 参数', () => {
|
|
136
|
+
const db = makeDb()
|
|
137
|
+
db.collection('test-collection')
|
|
138
|
+
.where({ a: 1 } as any)
|
|
139
|
+
.limit(10)
|
|
140
|
+
.watch({ onChange: () => {}, onError: () => {} })
|
|
141
|
+
|
|
142
|
+
expect(watchArgs[0].limit).toBe(10)
|
|
143
|
+
})
|
|
144
|
+
})
|