@livequery/mongodb 2.0.153 → 2.0.154
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 +66 -76
- package/build/src/Cursor.d.ts +1 -1
- package/build/src/MongoDatasource.d.ts +21 -0
- package/build/src/MongoDatasource.js +11 -6
- package/build/src/MongoQuery.js +76 -40
- package/build/src/MongodbCollection.d.ts +2 -2
- package/build/src/MongodbCollection.js +3 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -4,17 +4,14 @@ Native MongoDB datasource adapter for the `@livequery` ecosystem.
|
|
|
4
4
|
|
|
5
5
|
This package translates Livequery request shapes into MongoDB native driver operations. It is intended for projects that want to use `@livequery/core` with plain `mongodb` collections, without Mongoose models or schema introspection.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- Core style: `new MongoDatasource(config)`, `init(routes)`, then `handle(ctx)`.
|
|
10
|
-
- Legacy style: `new MongoDatasource()`, `init(config, routes)`, then direct `query(req, options)`.
|
|
7
|
+
Integration with `@livequery/core`: `new MongoDatasource(config)`, `init(routes)`, then `handle(ctx)`. For imperative use you can also call `query(req, options)` directly.
|
|
11
8
|
|
|
12
9
|
Reads are executed with MongoDB aggregation pipelines through `Collection.aggregate(...).toArray()`. Writes use native collection methods such as `insertOne`, `updateOne`, and `deleteOne`.
|
|
13
10
|
|
|
14
11
|
## Installation
|
|
15
12
|
|
|
16
13
|
```sh
|
|
17
|
-
bun add @livequery/mongodb mongodb
|
|
14
|
+
bun add @livequery/mongodb mongodb rxjs
|
|
18
15
|
```
|
|
19
16
|
|
|
20
17
|
For local development in this workspace, `@livequery/core` is installed as a dev dependency from `file:../core`. Runtime JavaScript does not import `@livequery/core`; the generated declaration files use core types.
|
|
@@ -26,7 +23,6 @@ export * from './MongoDatasource.js'
|
|
|
26
23
|
export * from './DataChangePayload.js'
|
|
27
24
|
export * from './MongodbRealtime.js'
|
|
28
25
|
export * from './MongodbCollection.js'
|
|
29
|
-
export * from './types.js'
|
|
30
26
|
```
|
|
31
27
|
|
|
32
28
|
## Project Meaning
|
|
@@ -40,7 +36,7 @@ Typical request flow with `@livequery/core`:
|
|
|
40
36
|
1. A framework adapter creates a `LivequeryContext`.
|
|
41
37
|
2. `LivequeryRequestParser` reads `ctx.request` and writes `ctx.livequery`.
|
|
42
38
|
3. `MongoDatasource.handle(ctx)` resolves route options from `ctx.request.method` and `ctx.request.ref`.
|
|
43
|
-
4. `MongoDatasource`
|
|
39
|
+
4. `MongoDatasource` reads `ctx.livequery` (keys, query, body, method) as the adapter request.
|
|
44
40
|
5. Reads are delegated to `MongoQuery`; writes go directly to the native collection.
|
|
45
41
|
6. The result is assigned to `ctx.response`.
|
|
46
42
|
|
|
@@ -51,13 +47,13 @@ Typical request flow with `@livequery/core`:
|
|
|
51
47
|
Main adapter class.
|
|
52
48
|
|
|
53
49
|
```ts
|
|
54
|
-
class MongoDatasource extends Subject<
|
|
50
|
+
class MongoDatasource extends Subject<UpdatedData<LivequeryBaseEntity>>
|
|
55
51
|
```
|
|
56
52
|
|
|
57
53
|
Responsibilities:
|
|
58
54
|
|
|
59
55
|
- Store datasource config and route options.
|
|
60
|
-
-
|
|
56
|
+
- Initialize from a list of routes.
|
|
61
57
|
- Resolve connection, database, and collection for each request.
|
|
62
58
|
- Normalize configured ObjectId fields.
|
|
63
59
|
- Execute reads, inserts, updates, and deletes.
|
|
@@ -80,7 +76,7 @@ const datasource = new MongoDatasource({
|
|
|
80
76
|
})
|
|
81
77
|
```
|
|
82
78
|
|
|
83
|
-
|
|
79
|
+
`config` is required before any request runs. Omit it here only if you assign `datasource.config` before the first `handle` / `query` call.
|
|
84
80
|
|
|
85
81
|
#### `init(routes)`
|
|
86
82
|
|
|
@@ -104,35 +100,6 @@ await datasource.init([
|
|
|
104
100
|
|
|
105
101
|
Route lookup uses `METHOD path`, for example `GET /products`. A path-only fallback is also stored for compatibility.
|
|
106
102
|
|
|
107
|
-
#### `init(config, routes)`
|
|
108
|
-
|
|
109
|
-
Legacy-style initialization.
|
|
110
|
-
|
|
111
|
-
Parameters:
|
|
112
|
-
|
|
113
|
-
- `config: MongoDatasourceConfig`: MongoDB connection configuration.
|
|
114
|
-
- `routes: Array<{ method; path; options }>`: legacy route entries. The datasource options are nested under `options`.
|
|
115
|
-
|
|
116
|
-
Example:
|
|
117
|
-
|
|
118
|
-
```ts
|
|
119
|
-
await datasource.init(
|
|
120
|
-
{
|
|
121
|
-
connections: { default: client },
|
|
122
|
-
databases: ['main'],
|
|
123
|
-
},
|
|
124
|
-
[
|
|
125
|
-
{
|
|
126
|
-
method: 'GET',
|
|
127
|
-
path: '/products',
|
|
128
|
-
options: {
|
|
129
|
-
collection: 'products',
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
]
|
|
133
|
-
)
|
|
134
|
-
```
|
|
135
|
-
|
|
136
103
|
#### `handle(ctx)`
|
|
137
104
|
|
|
138
105
|
Core handler entry point.
|
|
@@ -165,7 +132,7 @@ Executes a parsed Livequery request against one MongoDB collection.
|
|
|
165
132
|
|
|
166
133
|
Parameters:
|
|
167
134
|
|
|
168
|
-
- `req: LivequeryRequest`:
|
|
135
|
+
- `req: LivequeryRequest`: parsed Livequery request. Reads `req.keys`, `req.query`, `req.method`, and `req.body`.
|
|
169
136
|
- `options: RouteOptions`: route configuration that tells the adapter which collection, database, connection, and ObjectId fields to use.
|
|
170
137
|
|
|
171
138
|
Supported `req.method` values:
|
|
@@ -193,7 +160,7 @@ const response = await datasource.query(
|
|
|
193
160
|
ref: 'products',
|
|
194
161
|
is_collection: true,
|
|
195
162
|
keys: {},
|
|
196
|
-
|
|
163
|
+
query: { ':limit': 10 },
|
|
197
164
|
},
|
|
198
165
|
{
|
|
199
166
|
collection: 'products',
|
|
@@ -218,7 +185,7 @@ Responsibilities:
|
|
|
218
185
|
|
|
219
186
|
Parameters:
|
|
220
187
|
|
|
221
|
-
- `req: LivequeryRequest`:
|
|
188
|
+
- `req: LivequeryRequest`: parsed adapter request. Reads `req.keys`, `req.query`, and `req.is_collection` (`req.query` is normalized to `{}` when absent).
|
|
222
189
|
- `collection: Collection<T>`: native MongoDB collection.
|
|
223
190
|
|
|
224
191
|
Behavior:
|
|
@@ -231,8 +198,8 @@ Known behavior:
|
|
|
231
198
|
- `:limit` defaults to `10`.
|
|
232
199
|
- Minimum `:limit` is `1`.
|
|
233
200
|
- Maximum `:limit` is `100`.
|
|
234
|
-
- Cursor paging is
|
|
235
|
-
- Offset paging
|
|
201
|
+
- Cursor paging (`:after` / `:before` / `:around`) is the default.
|
|
202
|
+
- Offset paging is used when `:page` is provided.
|
|
236
203
|
|
|
237
204
|
Filter examples:
|
|
238
205
|
|
|
@@ -268,7 +235,7 @@ Builds a cursor from a response item and active sort options.
|
|
|
268
235
|
Parameters:
|
|
269
236
|
|
|
270
237
|
- `item: LivequeryBaseEntity`: response item. Must contain `id`.
|
|
271
|
-
- `options:
|
|
238
|
+
- `options: Record<string, any>`: the request query (`req.query`). Sort options ending with `:sort` are included in the cursor.
|
|
272
239
|
|
|
273
240
|
Returns:
|
|
274
241
|
|
|
@@ -405,30 +372,45 @@ Responsibilities:
|
|
|
405
372
|
- Bump `updated_at` on every update.
|
|
406
373
|
- Accept a `string` id, an `ObjectId`, or a filter object for single-document operations.
|
|
407
374
|
|
|
408
|
-
#### `constructor(db,
|
|
375
|
+
#### `defineCollection<T>(config)` and `constructor(db, config)`
|
|
409
376
|
|
|
410
|
-
|
|
377
|
+
A collection is described once with `defineCollection`, then bound to a `Db` instance.
|
|
378
|
+
|
|
379
|
+
`defineCollection<T>({ collection, defaults? })` returns a typed `CollectionDef<T>`:
|
|
380
|
+
|
|
381
|
+
- `collection: string`: collection name. The handle is resolved lazily via `db.collection(name)`.
|
|
382
|
+
- `defaults?: (input: Partial<T>) => Partial<T>`: optional default-field resolver, a replacement for Mongoose `@Prop({ default })`. It runs on every `create` / `insertMany` with the input document; the input always overrides the returned defaults.
|
|
383
|
+
|
|
384
|
+
`new MongodbCollection<T>(db, config)`:
|
|
411
385
|
|
|
412
386
|
- `db: Db`: a connected `mongodb` `Db`. It is passed in explicitly (no hidden module singleton), so one class works across databases and connections.
|
|
413
|
-
- `
|
|
414
|
-
- `resolveDefaults?: (input: Partial<T>) => Partial<T>`: optional default-field resolver, a replacement for Mongoose `@Prop({ default })`. It runs on every `create` / `insertMany` with the input document; the input always overrides the returned defaults.
|
|
387
|
+
- `config: CollectionDef<T>`: the definition returned by `defineCollection`. The generic `T` is inferred from it, so the instance is fully typed.
|
|
415
388
|
|
|
416
389
|
Example:
|
|
417
390
|
|
|
418
391
|
```ts
|
|
419
392
|
import { MongoClient } from 'mongodb'
|
|
420
|
-
import { MongodbCollection } from '@livequery/mongodb'
|
|
393
|
+
import { MongodbCollection, defineCollection } from '@livequery/mongodb'
|
|
421
394
|
|
|
422
395
|
const client = new MongoClient(process.env.MONGO_URL!)
|
|
423
396
|
await client.connect()
|
|
424
397
|
const db = client.db('main')
|
|
425
398
|
|
|
426
399
|
type Order = { video_id: string; amount: number; started: boolean; running: boolean }
|
|
400
|
+
type Video = { title: string }
|
|
401
|
+
|
|
402
|
+
// With a defaults resolver (replaces Mongoose @Prop({ default })):
|
|
403
|
+
const Orders = new MongodbCollection(db, defineCollection<Order>({
|
|
404
|
+
collection: 'orders',
|
|
405
|
+
defaults: () => ({ started: false, running: true }),
|
|
406
|
+
}))
|
|
427
407
|
|
|
428
|
-
|
|
429
|
-
const Videos = new MongodbCollection<Video>(
|
|
408
|
+
// Without defaults:
|
|
409
|
+
const Videos = new MongodbCollection(db, defineCollection<Video>({ collection: 'videos' }))
|
|
430
410
|
```
|
|
431
411
|
|
|
412
|
+
Define each collection once at composition time and reuse the instance across the app.
|
|
413
|
+
|
|
432
414
|
#### Document shape: `MongoDoc<T>`
|
|
433
415
|
|
|
434
416
|
```ts
|
|
@@ -468,17 +450,38 @@ Behavior:
|
|
|
468
450
|
Example:
|
|
469
451
|
|
|
470
452
|
```ts
|
|
453
|
+
// create — applies defaults (started/running) + created_at/updated_at; strips any client id/_id
|
|
471
454
|
const order = await Orders.create({ video_id: 'v1', amount: 50 })
|
|
472
455
|
order.id // '507f1f77bcf86cd799439011'
|
|
456
|
+
order.started // false — from the defaults resolver
|
|
473
457
|
order._id // ObjectId — still readable internally
|
|
474
458
|
JSON.stringify(order) // contains "id", not "_id"
|
|
475
459
|
|
|
476
|
-
|
|
460
|
+
// insertMany — same preparation as create, returns hydrated docs
|
|
461
|
+
const [a, b] = await Orders.insertMany([{ video_id: 'v2', amount: 10 }, { video_id: 'v3', amount: 20 }])
|
|
462
|
+
|
|
463
|
+
// reads — single-doc helpers accept a string id, an ObjectId, or a filter object
|
|
464
|
+
await Orders.findOne('507f1f77bcf86cd799439011') // by string id (24-hex → _id)
|
|
477
465
|
await Orders.findById(order._id) // by ObjectId
|
|
478
466
|
await Orders.findOne({ video_id: 'v1' }) // by filter
|
|
467
|
+
await Orders.find({ started: false }) // many
|
|
468
|
+
|
|
469
|
+
// updates — plain bodies are wrapped in $set; operator bodies pass through; updated_at always bumped
|
|
470
|
+
await Orders.updateOne(order.id, { amount: 80 }) // → { $set: { amount: 80, updated_at } }
|
|
471
|
+
await Orders.updateOne(order.id, { $inc: { amount: 5 } }) // → { $inc, $set: { updated_at } }
|
|
472
|
+
await Orders.updateMany({ started: false }, { running: true })
|
|
479
473
|
|
|
480
|
-
|
|
481
|
-
await Orders.
|
|
474
|
+
// existence / counting
|
|
475
|
+
await Orders.exists(order.id) // boolean
|
|
476
|
+
await Orders.countDocuments({ video_id: 'v1' }) // number
|
|
477
|
+
|
|
478
|
+
// delete
|
|
479
|
+
await Orders.deleteOne(order.id)
|
|
480
|
+
await Orders.deleteMany({ video_id: 'v3' })
|
|
481
|
+
|
|
482
|
+
// escape hatches
|
|
483
|
+
await Orders.aggregate([{ $group: { _id: '$video_id', total: { $sum: '$amount' } } }])
|
|
484
|
+
Orders.collection // raw native Collection
|
|
482
485
|
```
|
|
483
486
|
|
|
484
487
|
### `DataChangePayload<T>`
|
|
@@ -609,7 +612,9 @@ await datasource.handle(ctx)
|
|
|
609
612
|
|
|
610
613
|
`LivequeryRequestParser` will set `ctx.livequery.document_id` and `ctx.livequery.keys.id`. The datasource converts `id` to Mongo `_id` for document reads and writes.
|
|
611
614
|
|
|
612
|
-
##
|
|
615
|
+
## Direct Query Example
|
|
616
|
+
|
|
617
|
+
For imperative use, call `query(req, options)` directly without going through `handle(ctx)`. Pass the config to the constructor; `init(routes)` is not required for this path since the collection is given in `options`.
|
|
613
618
|
|
|
614
619
|
```ts
|
|
615
620
|
import { MongoClient } from 'mongodb'
|
|
@@ -618,33 +623,18 @@ import { MongoDatasource } from '@livequery/mongodb'
|
|
|
618
623
|
const client = new MongoClient(process.env.MONGO_URL!)
|
|
619
624
|
await client.connect()
|
|
620
625
|
|
|
621
|
-
const datasource = new MongoDatasource(
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
connections: { default: client },
|
|
626
|
-
databases: ['main'],
|
|
627
|
-
},
|
|
628
|
-
[
|
|
629
|
-
{
|
|
630
|
-
method: 'GET',
|
|
631
|
-
path: '/products',
|
|
632
|
-
options: {
|
|
633
|
-
collection: 'products',
|
|
634
|
-
},
|
|
635
|
-
},
|
|
636
|
-
]
|
|
637
|
-
)
|
|
626
|
+
const datasource = new MongoDatasource({
|
|
627
|
+
connections: { default: client },
|
|
628
|
+
databases: ['main'],
|
|
629
|
+
})
|
|
638
630
|
|
|
639
631
|
const response = await datasource.query(
|
|
640
632
|
{
|
|
641
633
|
method: 'get',
|
|
642
634
|
ref: 'products',
|
|
643
635
|
is_collection: true,
|
|
644
|
-
collection_ref: 'products',
|
|
645
|
-
schema_collection_ref: 'products',
|
|
646
636
|
keys: {},
|
|
647
|
-
|
|
637
|
+
query: { ':limit': 10 },
|
|
648
638
|
},
|
|
649
639
|
{
|
|
650
640
|
collection: 'products',
|
package/build/src/Cursor.d.ts
CHANGED
|
@@ -46,6 +46,27 @@ export declare class MongoDatasource extends Subject<UpdatedData<LivequeryBaseEn
|
|
|
46
46
|
total: number;
|
|
47
47
|
};
|
|
48
48
|
} | {
|
|
49
|
+
has: {
|
|
50
|
+
prev: boolean;
|
|
51
|
+
next: boolean;
|
|
52
|
+
};
|
|
53
|
+
count: {
|
|
54
|
+
prev: number;
|
|
55
|
+
next: number;
|
|
56
|
+
current: number;
|
|
57
|
+
total: number;
|
|
58
|
+
};
|
|
59
|
+
page: {
|
|
60
|
+
current: number;
|
|
61
|
+
total: number;
|
|
62
|
+
};
|
|
49
63
|
item: any;
|
|
64
|
+
cursor: {
|
|
65
|
+
last: string | null;
|
|
66
|
+
first: string | null;
|
|
67
|
+
};
|
|
68
|
+
summary: any;
|
|
69
|
+
} | {
|
|
70
|
+
item: Record<string, any>;
|
|
50
71
|
}>;
|
|
51
72
|
}
|
|
@@ -111,8 +111,8 @@ export class MongoDatasource extends Subject {
|
|
|
111
111
|
const total = current + count.next + count.prev;
|
|
112
112
|
const paging = {
|
|
113
113
|
cursor: {
|
|
114
|
-
last: Cursor.caculate(items[items.length - 1], req.query),
|
|
115
|
-
first: Cursor.caculate(items[0], req.query)
|
|
114
|
+
last: Cursor.caculate(items[items.length - 1], req.query || {}),
|
|
115
|
+
first: Cursor.caculate(items[0], req.query || {})
|
|
116
116
|
},
|
|
117
117
|
has,
|
|
118
118
|
count: {
|
|
@@ -162,11 +162,11 @@ export class MongoDatasource extends Subject {
|
|
|
162
162
|
};
|
|
163
163
|
const result = await collection.insertOne(merged);
|
|
164
164
|
return {
|
|
165
|
-
item: {
|
|
165
|
+
item: this.#stringifyOids({
|
|
166
166
|
...merged,
|
|
167
167
|
_id: undefined,
|
|
168
168
|
id: result.insertedId.toString()
|
|
169
|
-
}
|
|
169
|
+
})
|
|
170
170
|
};
|
|
171
171
|
}
|
|
172
172
|
async #put(req, collection) {
|
|
@@ -186,11 +186,16 @@ export class MongoDatasource extends Subject {
|
|
|
186
186
|
const isPlainBody = req.body && typeof req.body === 'object'
|
|
187
187
|
&& !Object.keys(req.body).some(k => k.startsWith('$'));
|
|
188
188
|
const id = keys.id ?? req.document_id;
|
|
189
|
-
return {
|
|
189
|
+
return this.#stringifyOids({
|
|
190
190
|
...keys,
|
|
191
191
|
...isPlainBody ? req.body : {},
|
|
192
192
|
...id ? { id } : {}
|
|
193
|
-
};
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
#stringifyOids(item) {
|
|
196
|
+
return Object.entries(item).reduce((p, [k, v]) => {
|
|
197
|
+
return { ...p, [k]: v instanceof ObjectId ? v.toString() : v };
|
|
198
|
+
}, {});
|
|
194
199
|
}
|
|
195
200
|
#keys(req) {
|
|
196
201
|
return Object.entries(req.keys).reduce((p, [k, c]) => {
|
package/build/src/MongoQuery.js
CHANGED
|
@@ -159,7 +159,7 @@ export class MongoQuery {
|
|
|
159
159
|
}
|
|
160
160
|
}).filter(Boolean);
|
|
161
161
|
const is_distinc_count = `${v}`.includes('distinc');
|
|
162
|
-
const simple = is_distinc_count ? key : (exprs.length == 1 ? fns[0].key : false);
|
|
162
|
+
const simple = is_distinc_count ? key : (exprs.length == 1 && fns[0] ? fns[0].key : false);
|
|
163
163
|
const pipelines = [
|
|
164
164
|
...$match.length > 0 ? [{ $match }] : [],
|
|
165
165
|
{
|
|
@@ -238,34 +238,14 @@ export class MongoQuery {
|
|
|
238
238
|
if (k.endsWith(':like'))
|
|
239
239
|
return p;
|
|
240
240
|
const [key, expression] = k.split(':');
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
lt: () => ({ $lt: !isNaN(Number(value)) ? Number(value) : 0 }),
|
|
244
|
-
lte: () => ({ $lte: !isNaN(Number(value)) ? Number(value) : 0 }),
|
|
245
|
-
gt: () => ({ $gt: !isNaN(Number(value)) ? Number(value) : 0 }),
|
|
246
|
-
gte: () => ({ $gte: !isNaN(Number(value)) ? Number(value) : 0 }),
|
|
247
|
-
ne: () => {
|
|
248
|
-
return { $ne: value };
|
|
249
|
-
},
|
|
250
|
-
in: () => ({ $in: this.#parse_array(value) }),
|
|
251
|
-
nin: () => ({ $nin: this.#parse_array(value) }),
|
|
252
|
-
'eq-number': () => ({ $eq: !isNaN(Number(value)) ? Number(value) : 0 }),
|
|
253
|
-
'neq-number': () => ({ $ne: !isNaN(Number(value)) ? Number(value) : 0 }),
|
|
254
|
-
'eq-boolean': () => ({ $eq: `${value}`.toLowerCase() == 'true' ? true : false }),
|
|
255
|
-
'neq-boolean': () => ({ $ne: `${value}`.toLowerCase() == 'false' ? false : true }),
|
|
256
|
-
'eq-null': () => ({ $eq: null }),
|
|
257
|
-
'neq-null': () => ({ $ne: null }),
|
|
258
|
-
'eq-oid': () => ({ $eq: ObjectId.isValid(value) ? new ObjectId(value) : value }),
|
|
259
|
-
'neq-oid': () => ({ $ne: ObjectId.isValid(value) ? new ObjectId(value) : value }),
|
|
260
|
-
};
|
|
261
|
-
const fn = map[expression || 'eq'];
|
|
262
|
-
if (!fn)
|
|
241
|
+
const clause = this.#operator_clause(expression, value);
|
|
242
|
+
if (!clause)
|
|
263
243
|
return p;
|
|
264
244
|
return {
|
|
265
245
|
...p,
|
|
266
246
|
[key]: {
|
|
267
247
|
...p[key] || {},
|
|
268
|
-
...
|
|
248
|
+
...clause
|
|
269
249
|
}
|
|
270
250
|
};
|
|
271
251
|
}, {});
|
|
@@ -277,20 +257,75 @@ export class MongoQuery {
|
|
|
277
257
|
const andMatch = and ? this.#build_match(and) : {};
|
|
278
258
|
if (Object.keys(andMatch).length > 0)
|
|
279
259
|
clauses.push(andMatch);
|
|
280
|
-
const
|
|
281
|
-
if (
|
|
282
|
-
clauses.push({ $or:
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
clauses.push({ $nor: Object.entries(notMatch).map(([k, v]) => ({ [k]: v })) });
|
|
287
|
-
}
|
|
260
|
+
const orBranches = or ? this.#branches(or) : [];
|
|
261
|
+
if (orBranches.length > 0)
|
|
262
|
+
clauses.push({ $or: orBranches });
|
|
263
|
+
const notBranches = not ? this.#branches(not) : [];
|
|
264
|
+
if (notBranches.length > 0)
|
|
265
|
+
clauses.push({ $nor: notBranches });
|
|
288
266
|
if (clauses.length === 0)
|
|
289
267
|
return {};
|
|
290
268
|
if (clauses.length === 1)
|
|
291
269
|
return clauses[0];
|
|
292
270
|
return { $and: clauses };
|
|
293
271
|
}
|
|
272
|
+
static #operator_clause(expression, value) {
|
|
273
|
+
const map = {
|
|
274
|
+
eq: () => ({ $eq: value }),
|
|
275
|
+
lt: () => ({ $lt: !isNaN(Number(value)) ? Number(value) : 0 }),
|
|
276
|
+
lte: () => ({ $lte: !isNaN(Number(value)) ? Number(value) : 0 }),
|
|
277
|
+
gt: () => ({ $gt: !isNaN(Number(value)) ? Number(value) : 0 }),
|
|
278
|
+
gte: () => ({ $gte: !isNaN(Number(value)) ? Number(value) : 0 }),
|
|
279
|
+
ne: () => ({ $ne: value }),
|
|
280
|
+
in: () => ({ $in: this.#parse_array(value) }),
|
|
281
|
+
nin: () => ({ $nin: this.#parse_array(value) }),
|
|
282
|
+
'eq-number': () => ({ $eq: !isNaN(Number(value)) ? Number(value) : 0 }),
|
|
283
|
+
'neq-number': () => ({ $ne: !isNaN(Number(value)) ? Number(value) : 0 }),
|
|
284
|
+
'eq-boolean': () => ({ $eq: `${value}`.toLowerCase() == 'true' ? true : false }),
|
|
285
|
+
'neq-boolean': () => ({ $ne: `${value}`.toLowerCase() == 'false' ? false : true }),
|
|
286
|
+
'eq-null': () => ({ $eq: null }),
|
|
287
|
+
'neq-null': () => ({ $ne: null }),
|
|
288
|
+
'eq-oid': () => ({ $eq: ObjectId.isValid(value) ? new ObjectId(value) : value }),
|
|
289
|
+
'neq-oid': () => ({ $ne: ObjectId.isValid(value) ? new ObjectId(value) : value }),
|
|
290
|
+
};
|
|
291
|
+
const fn = map[expression || 'eq'];
|
|
292
|
+
return fn ? fn() : null;
|
|
293
|
+
}
|
|
294
|
+
static #branches(filters) {
|
|
295
|
+
if (!filters)
|
|
296
|
+
return [];
|
|
297
|
+
const { ':and': and, ':or': or, ':not': not, ...rest } = filters;
|
|
298
|
+
const branches = [];
|
|
299
|
+
for (const [k, value] of Object.entries(rest)) {
|
|
300
|
+
if (k.startsWith('::'))
|
|
301
|
+
continue;
|
|
302
|
+
if (k.endsWith(':like')) {
|
|
303
|
+
const key = k.split(':like')[0];
|
|
304
|
+
branches.push({ [key]: { $regex: this.#escape_regex(`${value}`), $options: 'i' } });
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const [key, expression] = k.split(':');
|
|
308
|
+
const clause = this.#operator_clause(expression, value);
|
|
309
|
+
if (clause)
|
|
310
|
+
branches.push({ [key]: clause });
|
|
311
|
+
}
|
|
312
|
+
if (and) {
|
|
313
|
+
const m = this.#build_match(and);
|
|
314
|
+
if (Object.keys(m).length > 0)
|
|
315
|
+
branches.push(m);
|
|
316
|
+
}
|
|
317
|
+
if (or) {
|
|
318
|
+
const m = this.#build_match({ ':or': or });
|
|
319
|
+
if (Object.keys(m).length > 0)
|
|
320
|
+
branches.push(m);
|
|
321
|
+
}
|
|
322
|
+
if (not) {
|
|
323
|
+
const m = this.#build_match({ ':not': not });
|
|
324
|
+
if (Object.keys(m).length > 0)
|
|
325
|
+
branches.push(m);
|
|
326
|
+
}
|
|
327
|
+
return branches;
|
|
328
|
+
}
|
|
294
329
|
static #build_search_query(req) {
|
|
295
330
|
const search = req.query[":search"];
|
|
296
331
|
return search ? [{ $match: { $text: { $search: `${search}` } } }] : [];
|
|
@@ -489,8 +524,9 @@ export class MongoQuery {
|
|
|
489
524
|
return $sort;
|
|
490
525
|
}
|
|
491
526
|
static async query(req, collection) {
|
|
492
|
-
|
|
493
|
-
|
|
527
|
+
const request = { ...req, keys: req.keys ?? {}, query: req.query ?? {} };
|
|
528
|
+
if (!request.is_collection) {
|
|
529
|
+
const { id, _id: _rawId, ...keysWithoutId } = request.keys;
|
|
494
530
|
const aggregates = [
|
|
495
531
|
{
|
|
496
532
|
$match: {
|
|
@@ -514,19 +550,19 @@ export class MongoQuery {
|
|
|
514
550
|
summary: {}
|
|
515
551
|
};
|
|
516
552
|
}
|
|
517
|
-
const is_cursor_paging =
|
|
518
|
-
const $sort = this.#get_sorter(
|
|
553
|
+
const is_cursor_paging = request.query[':after'] || request.query[':before'] || request.query[':around'] || !request.query[':page'];
|
|
554
|
+
const $sort = this.#get_sorter(request);
|
|
519
555
|
const pipelines = [
|
|
520
556
|
{ $sort },
|
|
521
|
-
...this.#build_query_filter(
|
|
522
|
-
...this.#build_search_query(
|
|
557
|
+
...this.#build_query_filter(request),
|
|
558
|
+
...this.#build_search_query(request),
|
|
523
559
|
...this.#rename_id(),
|
|
524
|
-
...is_cursor_paging ? this.#build_cursor_paging($sort,
|
|
560
|
+
...is_cursor_paging ? this.#build_cursor_paging($sort, request) : this.#build_offset_paging(request)
|
|
525
561
|
];
|
|
526
562
|
const response = await collection.aggregate(pipelines).toArray();
|
|
527
563
|
return {
|
|
528
564
|
...response[0],
|
|
529
|
-
limit: this.#get_limit(
|
|
565
|
+
limit: this.#get_limit(request)
|
|
530
566
|
};
|
|
531
567
|
}
|
|
532
568
|
}
|
|
@@ -7,9 +7,9 @@ export type MongoDoc<T> = T & {
|
|
|
7
7
|
export type DefaultsResolver<T> = (input: Partial<T>) => Partial<T>;
|
|
8
8
|
export declare class CollectionDef<T> {
|
|
9
9
|
readonly collection: string;
|
|
10
|
-
readonly defaults?: DefaultsResolver<T
|
|
10
|
+
readonly defaults?: DefaultsResolver<T> | undefined;
|
|
11
11
|
readonly _type: T;
|
|
12
|
-
constructor(collection: string, defaults?: DefaultsResolver<T>);
|
|
12
|
+
constructor(collection: string, defaults?: DefaultsResolver<T> | undefined);
|
|
13
13
|
}
|
|
14
14
|
export declare function defineCollection<T>(config: {
|
|
15
15
|
collection: string;
|
|
@@ -4,6 +4,9 @@ function hydrate(doc) {
|
|
|
4
4
|
return null;
|
|
5
5
|
const oid = doc._id;
|
|
6
6
|
Object.defineProperty(doc, '_id', { value: oid, enumerable: false, configurable: true, writable: true });
|
|
7
|
+
if (Object.prototype.hasOwnProperty.call(doc, '__v')) {
|
|
8
|
+
Object.defineProperty(doc, '__v', { value: doc.__v, enumerable: false, configurable: true, writable: true });
|
|
9
|
+
}
|
|
7
10
|
Object.defineProperty(doc, 'id', {
|
|
8
11
|
value: oid ? oid.toString() : undefined,
|
|
9
12
|
enumerable: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/cursor.ts","../src/datachangepayload.ts","../src/mongodatasource.ts","../src/mongoquery.ts","../src/mongodbcollection.ts","../src/mongodbrealtime.ts","../src/smartcache.ts","../src/index.ts"],"version":"
|
|
1
|
+
{"root":["../src/cursor.ts","../src/datachangepayload.ts","../src/mongodatasource.ts","../src/mongoquery.ts","../src/mongodbcollection.ts","../src/mongodbrealtime.ts","../src/smartcache.ts","../src/index.ts"],"version":"6.0.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.154",
|
|
10
10
|
"description": "MongoDB datasource mapping for @livequery ecosystem",
|
|
11
11
|
"main": "./build/src/index.js",
|
|
12
12
|
"types": "./build/src/index.d.ts",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"test": "bun test",
|
|
33
|
-
"build": "rm -rf build
|
|
34
|
-
"deploy": "rm -rf build && yarn build
|
|
33
|
+
"build": "rm -rf build && tsc -b .",
|
|
34
|
+
"deploy": "rm -rf build && yarn build && git add . && git commit -m \"Update\" && git push origin main && npm publish --access public"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"mongodb": "^6.20.0"
|