@livequery/mongodb 2.0.145 → 2.0.148
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/README.md +29 -21
- package/build/src/Cursor.d.ts +2 -3
- package/build/src/MongoDatasource.d.ts +2 -22
- package/build/src/MongoDatasource.js +21 -32
- package/build/src/MongoQuery.d.ts +1 -1
- package/build/src/MongoQuery.js +35 -15
- package/build/src/MongodbRealtime.d.ts +5 -11
- package/build/src/MongodbRealtime.js +20 -35
- package/build/src/index.d.ts +0 -1
- package/build/src/index.js +0 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -2
- package/build/src/types.d.ts +0 -46
- package/build/src/types.js +0 -1
package/README.md
CHANGED
|
@@ -314,33 +314,46 @@ MongoDB change stream watcher for realtime Livequery updates. This replaces the
|
|
|
314
314
|
import { WebsocketGateway } from '@livequery/core'
|
|
315
315
|
import { MongoDatasource, MongodbRealtime } from '@livequery/mongodb'
|
|
316
316
|
|
|
317
|
-
const
|
|
317
|
+
const datasource = new MongoDatasource({
|
|
318
|
+
connections: { default: client },
|
|
319
|
+
databases: ['main'],
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
await datasource.init([
|
|
318
323
|
{
|
|
319
324
|
method: 'GET',
|
|
320
325
|
path: '/products',
|
|
321
326
|
collection: 'products',
|
|
322
327
|
realtime: true,
|
|
323
328
|
},
|
|
324
|
-
]
|
|
325
|
-
|
|
326
|
-
const datasource = new MongoDatasource({
|
|
327
|
-
connections: { default: client },
|
|
328
|
-
databases: ['main'],
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
await datasource.init(routes)
|
|
329
|
+
])
|
|
332
330
|
|
|
333
331
|
const websocketGateway = new WebsocketGateway(server)
|
|
334
332
|
|
|
335
333
|
new MongodbRealtime()
|
|
336
|
-
.watch(datasource.config,
|
|
334
|
+
.watch(datasource.config, [
|
|
335
|
+
{
|
|
336
|
+
// LivequeryRequestParser.parse(...).schema — document-id segment already stripped
|
|
337
|
+
schema: 'products',
|
|
338
|
+
options: { collection: 'products', realtime: true },
|
|
339
|
+
},
|
|
340
|
+
])
|
|
337
341
|
.subscribe(websocketGateway)
|
|
338
342
|
```
|
|
339
343
|
|
|
344
|
+
`MongoRealtimeRoute`:
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
type MongoRealtimeRoute = {
|
|
348
|
+
schema: string // parsed route path from @livequery/core, e.g. 'users/:userId/posts'
|
|
349
|
+
options: RouteOptions
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
340
353
|
Realtime route requirements:
|
|
341
354
|
|
|
342
|
-
- `method` must be `GET` or legacy method `0`.
|
|
343
355
|
- `realtime` must be `true`.
|
|
356
|
+
- `schema` is the parsed route path (`LivequeryRequestParser.parse(...).schema`), so the document-id segment is already stripped and each `:param` names the document field holding the parent value.
|
|
344
357
|
- `collection` must be a static string. Dynamic collection, database, or connection resolver functions are skipped because database watchers must be known up front.
|
|
345
358
|
- When watching a `MongoClient`, `db` or `config.databases` decides which database names to watch. When watching a `Db`, that database is used directly.
|
|
346
359
|
|
|
@@ -352,20 +365,17 @@ Disable the `collMod` call when your deployment manages pre/post images separate
|
|
|
352
365
|
new MongodbRealtime({ enablePreAndPostImages: false })
|
|
353
366
|
```
|
|
354
367
|
|
|
355
|
-
For nested collection refs,
|
|
368
|
+
For nested collection refs, name the route param after the document field that holds the parent value:
|
|
356
369
|
|
|
357
370
|
```ts
|
|
358
371
|
{
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
collection: 'posts',
|
|
362
|
-
realtime: true,
|
|
363
|
-
refFields: {
|
|
364
|
-
id: 'userId',
|
|
365
|
-
},
|
|
372
|
+
schema: 'users/:userId/posts',
|
|
373
|
+
options: { collection: 'posts', realtime: true },
|
|
366
374
|
}
|
|
367
375
|
```
|
|
368
376
|
|
|
377
|
+
If the document field is an array (one document belongs to many parents), the change is fanned out to one ref per array element, and array membership changes emit `added`/`removed` per ref.
|
|
378
|
+
|
|
369
379
|
An inserted `{ _id: 'post1', userId: 'user1', title: 'Hello' }` emits:
|
|
370
380
|
|
|
371
381
|
```ts
|
|
@@ -425,7 +435,6 @@ type RouteOptions = {
|
|
|
425
435
|
collection: string | ((req: LivequeryRequest) => Promise<string> | string)
|
|
426
436
|
db?: string | ((req: LivequeryRequest) => Promise<string> | string)
|
|
427
437
|
connection?: string | ((req: LivequeryRequest) => Promise<string> | string)
|
|
428
|
-
refFields?: Record<string, string | { field: string, array?: boolean }>
|
|
429
438
|
objectIdFields?: string[]
|
|
430
439
|
}
|
|
431
440
|
```
|
|
@@ -436,7 +445,6 @@ Fields:
|
|
|
436
445
|
- `collection`: required collection name or resolver function.
|
|
437
446
|
- `db`: optional database name or resolver function.
|
|
438
447
|
- `connection`: optional connection name or resolver function.
|
|
439
|
-
- `refFields`: optional mapping from nested route params to document fields for realtime ref formatting.
|
|
440
448
|
- `objectIdFields`: top-level request fields that should be converted from valid string ids to `ObjectId`.
|
|
441
449
|
|
|
442
450
|
Use function values when tenant, database, or collection depends on request keys.
|
package/build/src/Cursor.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { LivequeryBaseEntity, QueryOption } from "./types.js";
|
|
2
1
|
export declare class Cursor {
|
|
3
|
-
static caculate
|
|
4
|
-
static parse
|
|
2
|
+
static caculate(item: Record<string, any>, options: Record<string, any>): string;
|
|
3
|
+
static parse(cursor: string): any;
|
|
5
4
|
}
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import type { LivequeryContext, LivequeryDatasource as CoreLivequeryDatasource, LivequeryDatasourceInitConfig } from '@livequery/core';
|
|
2
|
-
import type { LivequeryRequest, LivequeryBaseEntity,
|
|
2
|
+
import type { LivequeryRequest, LivequeryBaseEntity, UpdatedData } from '@livequery/core';
|
|
3
3
|
import type { Db, MongoClient } from 'mongodb';
|
|
4
4
|
import { Subject } from 'rxjs';
|
|
5
|
-
export type LivequeryDatasource<Config, RouteOptions> = Subject<WebsocketSyncPayload<LivequeryBaseEntity>> & {
|
|
6
|
-
init(config: Config, routes: Array<{
|
|
7
|
-
path: string;
|
|
8
|
-
method: number;
|
|
9
|
-
options: RouteOptions;
|
|
10
|
-
}>): Promise<void>;
|
|
11
|
-
query: (query: LivequeryRequest, options: RouteOptions) => Promise<any>;
|
|
12
|
-
};
|
|
13
5
|
export type MongoConnection = MongoClient | Db;
|
|
14
6
|
export type MongoDatasourceConfig = {
|
|
15
7
|
connections: {
|
|
@@ -17,31 +9,20 @@ export type MongoDatasourceConfig = {
|
|
|
17
9
|
};
|
|
18
10
|
databases?: string[];
|
|
19
11
|
};
|
|
20
|
-
type LegacyRouteConfig<RouteOptions> = {
|
|
21
|
-
path: string;
|
|
22
|
-
method: number | string;
|
|
23
|
-
options?: RouteOptions;
|
|
24
|
-
config?: RouteOptions;
|
|
25
|
-
};
|
|
26
12
|
export type RouteOptions = {
|
|
27
13
|
realtime?: boolean;
|
|
28
14
|
collection: string | ((req: LivequeryRequest) => Promise<string> | string);
|
|
29
15
|
db?: string | ((req: LivequeryRequest) => Promise<string> | string);
|
|
30
16
|
connection?: string | ((req: LivequeryRequest) => Promise<string> | string);
|
|
31
|
-
refFields?: Record<string, string | {
|
|
32
|
-
field: string;
|
|
33
|
-
array?: boolean;
|
|
34
|
-
}>;
|
|
35
17
|
objectIdFields?: string[];
|
|
36
18
|
};
|
|
37
|
-
export declare class MongoDatasource extends Subject<
|
|
19
|
+
export declare class MongoDatasource extends Subject<UpdatedData<LivequeryBaseEntity>> implements CoreLivequeryDatasource<RouteOptions> {
|
|
38
20
|
#private;
|
|
39
21
|
readonly refs: Map<string, Set<string>>;
|
|
40
22
|
config: MongoDatasourceConfig;
|
|
41
23
|
routes: Map<string, RouteOptions>;
|
|
42
24
|
constructor(config?: MongoDatasourceConfig);
|
|
43
25
|
init(routes: Array<LivequeryDatasourceInitConfig<RouteOptions>>): Promise<void>;
|
|
44
|
-
init(config: MongoDatasourceConfig, routes: Array<LegacyRouteConfig<RouteOptions>>): Promise<void>;
|
|
45
26
|
handle(ctx: LivequeryContext): Promise<{}>;
|
|
46
27
|
query(req: LivequeryRequest, options: RouteOptions): Promise<{
|
|
47
28
|
items: any[];
|
|
@@ -68,4 +49,3 @@ export declare class MongoDatasource extends Subject<WebsocketSyncPayload<Livequ
|
|
|
68
49
|
item: any;
|
|
69
50
|
}>;
|
|
70
51
|
}
|
|
71
|
-
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Cursor } from './Cursor.js';
|
|
2
2
|
import { MongoQuery } from "./MongoQuery.js";
|
|
3
|
-
import { ObjectId } from '
|
|
3
|
+
import { ObjectId } from 'mongodb';
|
|
4
4
|
import { SmartCache } from './SmartCache.js';
|
|
5
5
|
import { Subject } from 'rxjs';
|
|
6
6
|
export class MongoDatasource extends Subject {
|
|
@@ -14,23 +14,18 @@ export class MongoDatasource extends Subject {
|
|
|
14
14
|
this.config = config;
|
|
15
15
|
this.routes = new Map();
|
|
16
16
|
}
|
|
17
|
-
async init(
|
|
18
|
-
const routes = Array.isArray(configOrRoutes)
|
|
19
|
-
? configOrRoutes
|
|
20
|
-
: maybeRoutes || [];
|
|
21
|
-
if (!Array.isArray(configOrRoutes)) {
|
|
22
|
-
this.config = configOrRoutes;
|
|
23
|
-
}
|
|
17
|
+
async init(routes) {
|
|
24
18
|
this.routes = routes.reduce((p, c) => {
|
|
25
|
-
const options =
|
|
26
|
-
|
|
19
|
+
const { method, path, ...options } = c;
|
|
20
|
+
const routeOptions = options;
|
|
21
|
+
if (!routeOptions.collection)
|
|
27
22
|
return p;
|
|
28
23
|
const key = this.#routeKey(c.method, c.path);
|
|
29
24
|
const set = p.get(key) || p.get(c.path);
|
|
30
|
-
if (set && set.collection !=
|
|
25
|
+
if (set && set.collection != routeOptions.collection)
|
|
31
26
|
throw new Error('Collection mismatch for route path "' + c.path + '"');
|
|
32
|
-
p.set(key,
|
|
33
|
-
p.set(c.path,
|
|
27
|
+
p.set(key, routeOptions);
|
|
28
|
+
p.set(c.path, routeOptions);
|
|
34
29
|
return p;
|
|
35
30
|
}, new Map());
|
|
36
31
|
}
|
|
@@ -59,15 +54,6 @@ export class MongoDatasource extends Subject {
|
|
|
59
54
|
return this.#del(query, collection);
|
|
60
55
|
throw { status: 500, code: 'INVAILD_METHOD', message: 'Invaild method' };
|
|
61
56
|
}
|
|
62
|
-
#getRouteOptions(route) {
|
|
63
|
-
const legacy = route;
|
|
64
|
-
if (legacy.options)
|
|
65
|
-
return legacy.options;
|
|
66
|
-
if (legacy.config)
|
|
67
|
-
return legacy.config;
|
|
68
|
-
const { method, path, ...options } = route;
|
|
69
|
-
return options;
|
|
70
|
-
}
|
|
71
57
|
#getOptions(ctx) {
|
|
72
58
|
const routePath = ctx.request.ref || ctx.request.path;
|
|
73
59
|
const options = this.routes.get(this.#routeKey(ctx.request.method, routePath)) || this.routes.get(routePath);
|
|
@@ -87,23 +73,20 @@ export class MongoDatasource extends Subject {
|
|
|
87
73
|
is_collection: !livequery.document_id,
|
|
88
74
|
collection_ref: livequery.collection_ref,
|
|
89
75
|
schema_collection_ref: livequery.schema_collection_ref,
|
|
90
|
-
|
|
76
|
+
document_id: livequery.document_id,
|
|
91
77
|
keys: livequery.keys || {},
|
|
92
78
|
query: livequery.query || {},
|
|
93
|
-
options: livequery.query || {},
|
|
94
79
|
method: livequery.method?.toLowerCase(),
|
|
95
80
|
body: livequery.body,
|
|
96
81
|
};
|
|
97
82
|
}
|
|
98
83
|
#normalizeRequest(req) {
|
|
99
|
-
const
|
|
84
|
+
const query = req.query || {};
|
|
100
85
|
return {
|
|
101
86
|
...req,
|
|
102
87
|
keys: req.keys || {},
|
|
103
|
-
query
|
|
104
|
-
options,
|
|
88
|
+
query,
|
|
105
89
|
is_collection: typeof req.is_collection == 'boolean' ? req.is_collection : !req.document_id,
|
|
106
|
-
doc_id: req.doc_id || req.document_id,
|
|
107
90
|
method: req.method?.toLowerCase(),
|
|
108
91
|
};
|
|
109
92
|
}
|
|
@@ -128,8 +111,8 @@ export class MongoDatasource extends Subject {
|
|
|
128
111
|
const total = current + count.next + count.prev;
|
|
129
112
|
const paging = {
|
|
130
113
|
cursor: {
|
|
131
|
-
last: Cursor.caculate(items[items.length - 1], req.
|
|
132
|
-
first: Cursor.caculate(items[0], req.
|
|
114
|
+
last: Cursor.caculate(items[items.length - 1], req.query),
|
|
115
|
+
first: Cursor.caculate(items[0], req.query)
|
|
133
116
|
},
|
|
134
117
|
has,
|
|
135
118
|
count: {
|
|
@@ -201,7 +184,7 @@ export class MongoDatasource extends Subject {
|
|
|
201
184
|
const keys = req.keys || {};
|
|
202
185
|
const isPlainBody = req.body && typeof req.body === 'object'
|
|
203
186
|
&& !Object.keys(req.body).some(k => k.startsWith('$'));
|
|
204
|
-
const id = keys.id ?? req.
|
|
187
|
+
const id = keys.id ?? req.document_id;
|
|
205
188
|
return {
|
|
206
189
|
...keys,
|
|
207
190
|
...isPlainBody ? req.body : {},
|
|
@@ -213,13 +196,19 @@ export class MongoDatasource extends Subject {
|
|
|
213
196
|
return {
|
|
214
197
|
...p,
|
|
215
198
|
...k == 'id' ? {
|
|
216
|
-
_id:
|
|
199
|
+
_id: this.#objectId('id', req.keys.id)
|
|
217
200
|
} : {
|
|
218
201
|
[k]: c
|
|
219
202
|
}
|
|
220
203
|
};
|
|
221
204
|
}, {});
|
|
222
205
|
}
|
|
206
|
+
#objectId(field, value) {
|
|
207
|
+
if (typeof value != 'string' || !ObjectId.isValid(value)) {
|
|
208
|
+
throw { status: 400, code: 'INVALID_OBJECT_ID', message: `Invalid ObjectId for field "${field}": ${JSON.stringify(value)}` };
|
|
209
|
+
}
|
|
210
|
+
return ObjectId.createFromHexString(value);
|
|
211
|
+
}
|
|
223
212
|
#update(body) {
|
|
224
213
|
if (!body || Object.keys(body).some(key => key.startsWith('$')))
|
|
225
214
|
return body;
|
package/build/src/MongoQuery.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Cursor } from "./Cursor.js";
|
|
2
|
-
import { ObjectId } from "
|
|
2
|
+
import { ObjectId } from "mongodb";
|
|
3
3
|
export class MongoQuery {
|
|
4
4
|
static #is_operator(c) {
|
|
5
5
|
return ['+', '-', '*', '/', '(', ')', '~'].indexOf(c) !== -1;
|
|
@@ -94,6 +94,20 @@ export class MongoQuery {
|
|
|
94
94
|
static #escape_regex(value) {
|
|
95
95
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
96
96
|
}
|
|
97
|
+
static #objectId(field, value) {
|
|
98
|
+
if (typeof value != 'string' || !ObjectId.isValid(value)) {
|
|
99
|
+
throw { status: 400, code: 'INVALID_OBJECT_ID', message: `Invalid ObjectId for field "${field}": ${JSON.stringify(value)}` };
|
|
100
|
+
}
|
|
101
|
+
return ObjectId.createFromHexString(value);
|
|
102
|
+
}
|
|
103
|
+
static #parse_cursor(token) {
|
|
104
|
+
try {
|
|
105
|
+
return Cursor.parse(token);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
throw { status: 400, code: 'INVALID_CURSOR', message: 'Invalid pagination cursor' };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
97
111
|
static #parse_array(value) {
|
|
98
112
|
if (Array.isArray(value))
|
|
99
113
|
return value;
|
|
@@ -109,7 +123,7 @@ export class MongoQuery {
|
|
|
109
123
|
}
|
|
110
124
|
static #parse_summary(req) {
|
|
111
125
|
const parsed = Object
|
|
112
|
-
.entries(req.
|
|
126
|
+
.entries(req.query)
|
|
113
127
|
.map(([key, v], index) => {
|
|
114
128
|
if (!key.startsWith('::'))
|
|
115
129
|
return [];
|
|
@@ -278,11 +292,11 @@ export class MongoQuery {
|
|
|
278
292
|
return { $and: clauses };
|
|
279
293
|
}
|
|
280
294
|
static #build_search_query(req) {
|
|
281
|
-
const search = req.
|
|
295
|
+
const search = req.query[":search"];
|
|
282
296
|
return search ? [{ $match: { $text: { $search: `${search}` } } }] : [];
|
|
283
297
|
}
|
|
284
298
|
static #get_limit(req) {
|
|
285
|
-
const l = Number(req.
|
|
299
|
+
const l = Number(req.query[':limit']);
|
|
286
300
|
if (isNaN(l))
|
|
287
301
|
return 10;
|
|
288
302
|
if (l < 1)
|
|
@@ -300,13 +314,19 @@ export class MongoQuery {
|
|
|
300
314
|
}
|
|
301
315
|
];
|
|
302
316
|
}
|
|
317
|
+
static #topn_sorter($sort) {
|
|
318
|
+
return Object.entries($sort).reduce((p, [key, order]) => ({
|
|
319
|
+
...p,
|
|
320
|
+
[key == '_id' ? 'id' : key]: order
|
|
321
|
+
}), {});
|
|
322
|
+
}
|
|
303
323
|
static #build_cursor_query($sort, req, reverse = false) {
|
|
304
324
|
const limit = this.#get_limit(req);
|
|
305
|
-
const after = req.
|
|
306
|
-
const before = req.
|
|
307
|
-
const around = req.
|
|
325
|
+
const after = req.query[':after'];
|
|
326
|
+
const before = req.query[':before'];
|
|
327
|
+
const around = req.query[':around'];
|
|
308
328
|
const pagination_token = around || before || after;
|
|
309
|
-
const cursor = pagination_token ?
|
|
329
|
+
const cursor = pagination_token ? this.#parse_cursor(pagination_token) : (reverse ? null : {});
|
|
310
330
|
if (!cursor)
|
|
311
331
|
return [{ $limit: 1 }, { $match: { _id: 0 } }];
|
|
312
332
|
const $or = Object.entries({ ...$sort, _id: $sort._id || -1 }).map(([key, order], index, arr) => {
|
|
@@ -341,7 +361,7 @@ export class MongoQuery {
|
|
|
341
361
|
items: {
|
|
342
362
|
[reverse ? '$bottomN' : '$topN']: {
|
|
343
363
|
n: limit,
|
|
344
|
-
sortBy:
|
|
364
|
+
sortBy: this.#topn_sorter($sort),
|
|
345
365
|
output: "$$ROOT"
|
|
346
366
|
}
|
|
347
367
|
}
|
|
@@ -360,7 +380,7 @@ export class MongoQuery {
|
|
|
360
380
|
];
|
|
361
381
|
}
|
|
362
382
|
static #build_cursor_paging($sort, req) {
|
|
363
|
-
if (req.
|
|
383
|
+
if (req.query[':after'] || req.query[':before'] || req.query[':around']) {
|
|
364
384
|
}
|
|
365
385
|
const { pipelines, summary } = this.#parse_summary(req);
|
|
366
386
|
const limit = this.#get_limit(req);
|
|
@@ -409,7 +429,7 @@ export class MongoQuery {
|
|
|
409
429
|
}
|
|
410
430
|
static #build_offset_paging(req) {
|
|
411
431
|
const limit = this.#get_limit(req);
|
|
412
|
-
const page = Math.max(1, Math.floor(Number(req.
|
|
432
|
+
const page = Math.max(1, Math.floor(Number(req.query[':page'])) || 1);
|
|
413
433
|
const skip = (page - 1) * limit;
|
|
414
434
|
const { pipelines, summary } = this.#parse_summary(req);
|
|
415
435
|
return [
|
|
@@ -444,12 +464,12 @@ export class MongoQuery {
|
|
|
444
464
|
];
|
|
445
465
|
}
|
|
446
466
|
static #build_query_filter(req) {
|
|
447
|
-
const { ":after": after, ":before": before, ':around': around, ":limit": _limit, ":page": _page, ":search": search, ...rest } = req.
|
|
467
|
+
const { ":after": after, ":before": before, ':around': around, ":limit": _limit, ":page": _page, ":search": search, ...rest } = req.query;
|
|
448
468
|
return this.#parse_conditions({ ...rest, ...req.keys });
|
|
449
469
|
}
|
|
450
470
|
static #get_sorter(req) {
|
|
451
471
|
let default_sort = -1;
|
|
452
|
-
const $sort = Object.entries(req.
|
|
472
|
+
const $sort = Object.entries(req.query).reduce((p, [k, order]) => {
|
|
453
473
|
if (!k.endsWith(':sort'))
|
|
454
474
|
return p;
|
|
455
475
|
const by = k.split(':sort')[0];
|
|
@@ -474,7 +494,7 @@ export class MongoQuery {
|
|
|
474
494
|
{
|
|
475
495
|
$match: {
|
|
476
496
|
...req.keys,
|
|
477
|
-
...req.keys.id ? { id: undefined, _id:
|
|
497
|
+
...req.keys.id ? { id: undefined, _id: this.#objectId('id', req.keys.id) } : {}
|
|
478
498
|
}
|
|
479
499
|
},
|
|
480
500
|
...this.#rename_id(),
|
|
@@ -493,7 +513,7 @@ export class MongoQuery {
|
|
|
493
513
|
summary: {}
|
|
494
514
|
};
|
|
495
515
|
}
|
|
496
|
-
const is_cursor_paging = req.
|
|
516
|
+
const is_cursor_paging = req.query[':after'] || req.query[':before'] || req.query[':around'] || !req.query[':page'];
|
|
497
517
|
const $sort = this.#get_sorter(req);
|
|
498
518
|
const pipelines = [
|
|
499
519
|
{ $sort },
|
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
import { Observable } from 'rxjs';
|
|
2
|
-
import type {
|
|
2
|
+
import type { UpdatedData } from '@livequery/core';
|
|
3
3
|
import type { MongoDatasourceConfig, RouteOptions } from './MongoDatasource.js';
|
|
4
4
|
export type MongoRealtimeChangeType = 'added' | 'modified' | 'removed';
|
|
5
|
-
export type MongoRealtimeRefField = string | {
|
|
6
|
-
field: string;
|
|
7
|
-
array?: boolean;
|
|
8
|
-
};
|
|
9
5
|
export type MongoRealtimeOptions = {
|
|
10
6
|
enablePreAndPostImages?: boolean;
|
|
11
7
|
};
|
|
12
8
|
export type MongoRealtimeRoute = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
config?: RouteOptions;
|
|
17
|
-
} & Partial<RouteOptions>;
|
|
9
|
+
schema: string;
|
|
10
|
+
options: RouteOptions;
|
|
11
|
+
};
|
|
18
12
|
export declare class MongodbRealtime {
|
|
19
13
|
#private;
|
|
20
14
|
private options;
|
|
21
15
|
constructor(options?: MongoRealtimeOptions);
|
|
22
|
-
watch(config: MongoDatasourceConfig, routes: MongoRealtimeRoute[]): Observable<
|
|
16
|
+
watch(config: MongoDatasourceConfig, routes: MongoRealtimeRoute[]): Observable<UpdatedData<any>>;
|
|
23
17
|
}
|
|
@@ -23,17 +23,6 @@ export class MongodbRealtime {
|
|
|
23
23
|
}, {}),
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
#isGetRoute(method) {
|
|
27
|
-
return method === 0 || String(method).toUpperCase() === 'GET';
|
|
28
|
-
}
|
|
29
|
-
#getRouteOptions(route) {
|
|
30
|
-
if (route.options)
|
|
31
|
-
return route.options;
|
|
32
|
-
if (route.config)
|
|
33
|
-
return route.config;
|
|
34
|
-
const { method: _method, path: _path, options: _options, config: _config, ...options } = route;
|
|
35
|
-
return options.collection ? options : undefined;
|
|
36
|
-
}
|
|
37
26
|
#isDb(connection) {
|
|
38
27
|
return typeof connection.collection == 'function';
|
|
39
28
|
}
|
|
@@ -45,8 +34,8 @@ export class MongodbRealtime {
|
|
|
45
34
|
#watchSources(config, routes) {
|
|
46
35
|
const sources = new Map();
|
|
47
36
|
for (const route of routes) {
|
|
48
|
-
const options =
|
|
49
|
-
if (!options?.realtime
|
|
37
|
+
const options = route.options;
|
|
38
|
+
if (!options?.realtime)
|
|
50
39
|
continue;
|
|
51
40
|
if (typeof options.collection != 'string')
|
|
52
41
|
continue;
|
|
@@ -99,7 +88,7 @@ export class MongodbRealtime {
|
|
|
99
88
|
table: change.ns.coll,
|
|
100
89
|
type,
|
|
101
90
|
new_data: this.#reformatId(change.fullDocument),
|
|
102
|
-
old_data: this.#reformatId(change.fullDocumentBeforeChange),
|
|
91
|
+
old_data: this.#reformatId(change.fullDocumentBeforeChange ?? change.documentKey),
|
|
103
92
|
fields,
|
|
104
93
|
});
|
|
105
94
|
});
|
|
@@ -110,35 +99,31 @@ export class MongodbRealtime {
|
|
|
110
99
|
}), mergeMap(stream => stream), retry());
|
|
111
100
|
}
|
|
112
101
|
#routeRefMetadata(route) {
|
|
113
|
-
const options =
|
|
114
|
-
if (!options?.realtime
|
|
102
|
+
const options = route.options;
|
|
103
|
+
if (!options?.realtime)
|
|
115
104
|
return;
|
|
116
105
|
if (typeof options.collection != 'string')
|
|
117
106
|
return;
|
|
118
|
-
const segments = route.
|
|
119
|
-
const
|
|
120
|
-
const ref = collectionSegments.map(segment => segment.startsWith(':') ? '' : segment).filter(Boolean).join('/');
|
|
107
|
+
const segments = route.schema.split('/').filter(Boolean);
|
|
108
|
+
const ref = segments.filter(segment => !segment.startsWith(':')).join('/');
|
|
121
109
|
if (!ref)
|
|
122
110
|
return;
|
|
123
|
-
const metadata =
|
|
111
|
+
const metadata = segments
|
|
124
112
|
.map((collection, index) => {
|
|
125
113
|
if (index % 2 === 1)
|
|
126
114
|
return [];
|
|
127
|
-
const param =
|
|
115
|
+
const param = segments[index + 1];
|
|
128
116
|
if (!param?.startsWith(':'))
|
|
129
117
|
return [{ collection }];
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
const field = typeof mapped == 'string' ? mapped : mapped.field;
|
|
133
|
-
const array = typeof mapped == 'string' ? undefined : mapped.array;
|
|
134
|
-
return [{ collection, field: field == 'id' ? '_id' : field, array }];
|
|
118
|
+
const field = param.slice(1);
|
|
119
|
+
return [{ collection, field: field == 'id' ? '_id' : field }];
|
|
135
120
|
})
|
|
136
121
|
.flat();
|
|
137
122
|
return [ref, metadata];
|
|
138
123
|
}
|
|
139
124
|
#paths(routes) {
|
|
140
125
|
return routes.reduce((paths, route) => {
|
|
141
|
-
const options =
|
|
126
|
+
const options = route.options;
|
|
142
127
|
const entry = this.#routeRefMetadata(route);
|
|
143
128
|
if (!options || !entry || typeof options.collection != 'string')
|
|
144
129
|
return paths;
|
|
@@ -179,16 +164,16 @@ export class MongodbRealtime {
|
|
|
179
164
|
return 'removed';
|
|
180
165
|
return 'modified';
|
|
181
166
|
};
|
|
182
|
-
const buildRefs = ([{
|
|
167
|
+
const buildRefs = ([{ collection, field }, ...fields]) => {
|
|
183
168
|
if (fields.length === 0 || !field)
|
|
184
169
|
return [{ refs: [collection], type: event.type }];
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
])
|
|
191
|
-
|
|
170
|
+
const oldValues = event.old_data?.[field];
|
|
171
|
+
const newValues = event.new_data?.[field];
|
|
172
|
+
const values = Array.isArray(oldValues) || Array.isArray(newValues)
|
|
173
|
+
? [...new Set([
|
|
174
|
+
...(Array.isArray(oldValues) ? oldValues : []).map((item) => String(item)),
|
|
175
|
+
...(Array.isArray(newValues) ? newValues : []).map((item) => String(item)),
|
|
176
|
+
])]
|
|
192
177
|
: [merged[field] ?? '-'];
|
|
193
178
|
return values.flatMap(value => {
|
|
194
179
|
return buildRefs(fields).map(next => ({
|
package/build/src/index.d.ts
CHANGED
package/build/src/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/cursor.ts","../src/datachangepayload.ts","../src/mongodatasource.ts","../src/mongoquery.ts","../src/mongodbrealtime.ts","../src/smartcache.ts","../src/index.ts"
|
|
1
|
+
{"root":["../src/cursor.ts","../src/datachangepayload.ts","../src/mongodatasource.ts","../src/mongoquery.ts","../src/mongodbrealtime.ts","../src/smartcache.ts","../src/index.ts"],"version":"5.9.3"}
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"repository": {
|
|
7
7
|
"url": "git@github.com:livequery/mongodb.git"
|
|
8
8
|
},
|
|
9
|
-
"version": "2.0.
|
|
9
|
+
"version": "2.0.148",
|
|
10
10
|
"description": "MongoDB datasource mapping for @livequery ecosystem",
|
|
11
11
|
"main": "./build/src/index.js",
|
|
12
12
|
"types": "./build/src/index.d.ts",
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
"deploy": "rm -rf build && yarn build; git add .; git commit -m \"Update\"; git push origin master; npm publish --access public"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"bson": "*",
|
|
38
37
|
"mongodb": "^6.20.0"
|
|
39
38
|
}
|
|
40
39
|
}
|
package/build/src/types.d.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { LivequeryRequest as CoreLivequeryRequest } from '@livequery/core';
|
|
2
|
-
export type LivequeryBaseEntity = {
|
|
3
|
-
id?: string;
|
|
4
|
-
[key: string]: any;
|
|
5
|
-
};
|
|
6
|
-
export type QueryOption<T extends LivequeryBaseEntity = LivequeryBaseEntity> = Record<string, any>;
|
|
7
|
-
export type FilterConditions<T extends LivequeryBaseEntity = LivequeryBaseEntity> = Record<string, any>;
|
|
8
|
-
export type Paging = {
|
|
9
|
-
cursor: {
|
|
10
|
-
last: string | null;
|
|
11
|
-
first: string | null;
|
|
12
|
-
};
|
|
13
|
-
has: {
|
|
14
|
-
prev: boolean;
|
|
15
|
-
next: boolean;
|
|
16
|
-
};
|
|
17
|
-
count: {
|
|
18
|
-
prev: number;
|
|
19
|
-
next: number;
|
|
20
|
-
current: number;
|
|
21
|
-
total: number;
|
|
22
|
-
};
|
|
23
|
-
page: {
|
|
24
|
-
current: number;
|
|
25
|
-
total: number;
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
export type WebsocketSyncPayload<T extends LivequeryBaseEntity = LivequeryBaseEntity> = {
|
|
29
|
-
ref?: string;
|
|
30
|
-
type?: string;
|
|
31
|
-
data?: T;
|
|
32
|
-
[key: string]: any;
|
|
33
|
-
};
|
|
34
|
-
export type LivequeryRequest<T = any> = Partial<CoreLivequeryRequest<T>> & {
|
|
35
|
-
keys: Record<string, any>;
|
|
36
|
-
ref: string;
|
|
37
|
-
collection_ref?: string;
|
|
38
|
-
schema_collection_ref?: string;
|
|
39
|
-
method: string;
|
|
40
|
-
body?: T;
|
|
41
|
-
query?: Record<string, any>;
|
|
42
|
-
options: Record<string, any>;
|
|
43
|
-
is_collection: boolean;
|
|
44
|
-
doc_id?: string;
|
|
45
|
-
document_id?: string;
|
|
46
|
-
};
|
package/build/src/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|