liwi-mongo 13.4.0 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -2
- package/dist/definitions/MongoCursor.d.ts.map +1 -1
- package/dist/definitions/MongoQuerySingleItem.d.ts +1 -0
- package/dist/definitions/MongoQuerySingleItem.d.ts.map +1 -1
- package/dist/definitions/MongoQuerySingleItem.test.d.ts +2 -0
- package/dist/definitions/MongoQuerySingleItem.test.d.ts.map +1 -0
- package/dist/definitions/MongoStore.d.ts +18 -4
- package/dist/definitions/MongoStore.d.ts.map +1 -1
- package/dist/definitions/SubscribeStore.integration.test.d.ts +2 -0
- package/dist/definitions/SubscribeStore.integration.test.d.ts.map +1 -0
- package/dist/definitions/writes/createReplacePipeline.d.ts +10 -0
- package/dist/definitions/writes/createReplacePipeline.d.ts.map +1 -0
- package/dist/definitions/writes/stampPartialUpdate.d.ts +9 -0
- package/dist/definitions/writes/stampPartialUpdate.d.ts.map +1 -0
- package/dist/definitions/writes/stampPartialUpdate.test.d.ts +2 -0
- package/dist/definitions/writes/stampPartialUpdate.test.d.ts.map +1 -0
- package/dist/index-node.mjs +205 -81
- package/dist/index-node.mjs.map +1 -1
- package/package.json +5 -5
- package/src/MongoCursor.ts +2 -4
- package/src/MongoQuerySingleItem.test.ts +162 -0
- package/src/MongoQuerySingleItem.ts +56 -50
- package/src/MongoStore.test.ts +250 -0
- package/src/MongoStore.ts +122 -26
- package/src/SubscribeStore.integration.test.ts +414 -0
- package/src/indexes/diffIndexes.test.ts +2 -2
- package/src/indexes/normalizeIndex.test.ts +2 -2
- package/src/indexes/normalizeIndex.ts +2 -2
- package/src/writes/createReplacePipeline.ts +23 -0
- package/src/writes/stampPartialUpdate.test.ts +93 -0
- package/src/writes/stampPartialUpdate.ts +84 -0
package/README.md
CHANGED
|
@@ -81,7 +81,9 @@ interface MongoBaseModel<KeyValue extends AllowedKeyValue = string> {
|
|
|
81
81
|
}
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
The key path is always `_id`. `MongoInsertType<Model>` makes `_id`, `created` and `updated` optional
|
|
84
|
+
The key path is always `_id`. `MongoInsertType<Model>` makes `_id`, `created` and `updated` optional. Every write sets `updated` to the write time from the app clock, and an update or a replace never changes `created`: see [`created` and `updated`](../liwi-store#created-and-updated) for the rule of each method. `insertOne` and `upsertOne` mutate and return the object you passed; `replaceOne` leaves it untouched and returns the stored document.
|
|
85
|
+
|
|
86
|
+
Requires MongoDB ≥ 4.2: replaces are update pipelines.
|
|
85
87
|
|
|
86
88
|
## `MongoConnection`
|
|
87
89
|
|
|
@@ -111,6 +113,29 @@ const set: Update<Task>["$set"] = { completed: true };
|
|
|
111
113
|
|
|
112
114
|
The store also exposes the underlying driver handles when you need them: `store.collection` resolves the `mongodb` collection.
|
|
113
115
|
|
|
116
|
+
### Writes and their cost
|
|
117
|
+
|
|
118
|
+
The plain methods do one round trip and do not read the previous document:
|
|
119
|
+
|
|
120
|
+
| Method | Driver call | Missing key |
|
|
121
|
+
| -------------------- | --------------------------------------------------------------------- | ---------------------- |
|
|
122
|
+
| `partialUpdateByKey` | `findOneAndUpdate` with `returnDocument: "after"` | throws `NotFoundError` |
|
|
123
|
+
| `partialUpdateMany` | one `updateMany`; every document gets the same `updated` | — |
|
|
124
|
+
| `replaceOne` | `findOneAndUpdate` with a replace pipeline, `returnDocument: "after"` | throws `NotFoundError` |
|
|
125
|
+
| `deleteByKey` | `deleteOne` | no-op |
|
|
126
|
+
|
|
127
|
+
The replace pipeline is `$replaceWith` of the object (wrapped in `$literal`, so string values starting with `$` stay data) merged with the stored `created`. A plain `replaceOne` command could not keep `created`.
|
|
128
|
+
|
|
129
|
+
The [`…WithInfo` variants](../liwi-store#writes-with-info) also return the previous document, read atomically with the write. The subscribe store uses them:
|
|
130
|
+
|
|
131
|
+
| Method | Driver calls |
|
|
132
|
+
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
133
|
+
| `partialUpdateByKeyWithInfo` | `findOneAndUpdate` with `returnDocument: "before"`, then `findOne` for `next` |
|
|
134
|
+
| `replaceOneWithInfo` | the replace pipeline with `returnDocument: "before"`; `next` is the replacement with the stored `created`, no read back |
|
|
135
|
+
| `deleteByKeyWithInfo` | `findOneAndDelete` |
|
|
136
|
+
|
|
137
|
+
Mongo cannot return both the before and after documents of one update, so `partialUpdateByKeyWithInfo` reads `next` back. That read may already include a later concurrent write (which emits its own change), and it finds nothing if a concurrent delete landed first: `next` is then `undefined` although the update was applied.
|
|
138
|
+
|
|
114
139
|
## Indexes
|
|
115
140
|
|
|
116
141
|
Indexes are declared on the store and applied by an explicit sync step. The declaration is validated eagerly in the constructor (empty key, a name resolving to `_id_`, duplicate names, more than one text index).
|
|
@@ -259,7 +284,7 @@ const subscription = query.fetchAndSubscribe((error, changes) => {
|
|
|
259
284
|
subscription.stop();
|
|
260
285
|
```
|
|
261
286
|
|
|
262
|
-
`createQuerySingleItem` behaves the same but resolves a single item.
|
|
287
|
+
`createQuerySingleItem` behaves the same but resolves a single item. Without `sort`, its criteria must match at most one document: when one update moves several matching documents, the subscription reports the error `should not match more than 1, use sort if you can have multiple match` through its callback. With `sort`, it refetches on every relevant update.
|
|
263
288
|
|
|
264
289
|
Subscriptions are driven in-process: writes going through the subscribe store are matched against the query criteria with [mingo](https://github.com/kofrasa/mingo).
|
|
265
290
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MongoCursor.d.ts","sourceRoot":"","sources":["../../src/MongoCursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC;AAE3C,MAAM,CAAC,OAAO,OAAO,WAAW,CAC9B,KAAK,SAAS,cAAc,CAAC,QAAQ,CAAC,EACtC,MAAM,SAAS,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,EACrC,QAAQ,SAAS,eAAe,GAAG,KAAK,CAAC,KAAK,CAAC,CAC/C,SAAQ,mBAAmB,CAC3B,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,EAC3B,QAAQ,EACR,KAAK,EACL,MAAM,CACP;IAGC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAE5C,OAAO,CAAC,OAAO,CAAC,CAAgB;IAEhC,YAAY,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,EAGzE;IAED,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE3B;IAED,IAAI,IAAI,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAMpC;IAEc,OAAO,CACpB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GACjD,OAAO,CAAC,IAAI,CAAC,CAIf;IAED,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGrC;IAEQ,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAGjC;
|
|
1
|
+
{"version":3,"file":"MongoCursor.d.ts","sourceRoot":"","sources":["../../src/MongoCursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC;AAE3C,MAAM,CAAC,OAAO,OAAO,WAAW,CAC9B,KAAK,SAAS,cAAc,CAAC,QAAQ,CAAC,EACtC,MAAM,SAAS,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,EACrC,QAAQ,SAAS,eAAe,GAAG,KAAK,CAAC,KAAK,CAAC,CAC/C,SAAQ,mBAAmB,CAC3B,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,EAC3B,QAAQ,EACR,KAAK,EACL,MAAM,CACP;IAGC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAE5C,OAAO,CAAC,OAAO,CAAC,CAAgB;IAEhC,YAAY,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,EAGzE;IAED,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE3B;IAED,IAAI,IAAI,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAMpC;IAEc,OAAO,CACpB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GACjD,OAAO,CAAC,IAAI,CAAC,CAIf;IAED,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGrC;IAEQ,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAGjC;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAI3B;IAED,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAE3B;CACF"}
|
|
@@ -11,6 +11,7 @@ export default class MongoQuerySingleItem<Model extends MongoBaseModel<KeyValue>
|
|
|
11
11
|
constructor(store: MongoStore<Model, KeyValue>, options: QueryOptions<Model>, transformer?: Transformer<Model, Result>);
|
|
12
12
|
createMingoTestCriteria(): TestCriteria;
|
|
13
13
|
fetch<T>(onFulfilled: (result: QueryResult<Result>) => T): Promise<T>;
|
|
14
|
+
private computeChanges;
|
|
14
15
|
_subscribe(callback: SubscribeCallback<KeyValue, Result>, _includeInitial: boolean): QuerySubscription;
|
|
15
16
|
private createMongoCursor;
|
|
16
17
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MongoQuerySingleItem.d.ts","sourceRoot":"","sources":["../../src/MongoQuerySingleItem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAEf,YAAY,EACZ,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,8BAA8B,EAAE,MAAM,sBAAsB,CAAC;AAEtE,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,YAAY,EACb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC;AAS3C,KAAK,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;AAE1C,MAAM,CAAC,OAAO,OAAO,oBAAoB,CACvC,KAAK,SAAS,cAAc,CAAC,QAAQ,CAAC,EACtC,MAAM,SAAS,WAAW,CAAC,MAAM,CAAC,GAAG,KAAK,EAC1C,MAAM,SAAS,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,EACnE,QAAQ,SAAS,eAAe,GAAG,KAAK,CAAC,KAAK,CAAC,CAC/C,SAAQ,8BAA8B,CACtC,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,EAChC,MAAM,EACN,MAAM,CACP;IACC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA8B;IAEpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAE9C,OAAO,CAAC,YAAY,CAAC,CAAe;IAEpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IAEzD,YACE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,EAClC,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,EAC5B,WAAW,GAAE,WAAW,CAAC,KAAK,EAAE,MAAM,CAAuB,EAM9D;IAED,uBAAuB,IAAI,YAAY,CAWtC;IAEK,KAAK,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAe1E;
|
|
1
|
+
{"version":3,"file":"MongoQuerySingleItem.d.ts","sourceRoot":"","sources":["../../src/MongoQuerySingleItem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAEf,YAAY,EACZ,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,8BAA8B,EAAE,MAAM,sBAAsB,CAAC;AAEtE,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,YAAY,EACb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC;AAS3C,KAAK,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;AAE1C,MAAM,CAAC,OAAO,OAAO,oBAAoB,CACvC,KAAK,SAAS,cAAc,CAAC,QAAQ,CAAC,EACtC,MAAM,SAAS,WAAW,CAAC,MAAM,CAAC,GAAG,KAAK,EAC1C,MAAM,SAAS,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,EACnE,QAAQ,SAAS,eAAe,GAAG,KAAK,CAAC,KAAK,CAAC,CAC/C,SAAQ,8BAA8B,CACtC,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,EAChC,MAAM,EACN,MAAM,CACP;IACC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA8B;IAEpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAE9C,OAAO,CAAC,YAAY,CAAC,CAAe;IAEpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IAEzD,YACE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,EAClC,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,EAC5B,WAAW,GAAE,WAAW,CAAC,KAAK,EAAE,MAAM,CAAuB,EAM9D;IAED,uBAAuB,IAAI,YAAY,CAWtC;IAEK,KAAK,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAe1E;YAEa,cAAc;IAkD5B,UAAU,CACR,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,EAC7C,eAAe,EAAE,OAAO,GACvB,iBAAiB,CAuCnB;YAEa,iBAAiB;CAehC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MongoQuerySingleItem.test.d.ts","sourceRoot":"","sources":["../../src/MongoQuerySingleItem.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AllowedKeyValue, CreateQueryOptions, Criteria, Fields, OptionalBaseModelKeysForInsert, QueryParams, Sort, SubscribableStore, Update, UpsertPartialObject, UpsertResult } from "liwi-store";
|
|
1
|
+
import type { AllowedKeyValue, CreateQueryOptions, Criteria, DeleteResult, Fields, OptionalBaseModelKeysForInsert, PartialUpdateOptions, PartialUpdateResult, QueryParams, Sort, SubscribableStore, Update, UpsertPartialObject, UpsertResult, WriteResult } from "liwi-store";
|
|
2
2
|
import type { Collection } from "mongodb";
|
|
3
3
|
import type { MongoBaseModel, MongoInsertType, MongoKeyPath } from "./MongoBaseModel.ts";
|
|
4
4
|
import type MongoConnection from "./MongoConnection.ts";
|
|
@@ -23,14 +23,28 @@ export default class MongoStore<Model extends MongoBaseModel<KeyValue>, KeyValue
|
|
|
23
23
|
createQuerySingleItem<Result extends Record<MongoKeyPath, KeyValue> = Model, Params extends QueryParams<Params> = never>({ transformer, ...options }: CreateQueryOptions<Model, Result>): MongoQuerySingleItem<Model, Params, Result, KeyValue>;
|
|
24
24
|
createQueryCollection<Item extends Record<MongoKeyPath, KeyValue> = Model, Params extends QueryParams<Params> = never>({ transformer, ...options }: CreateQueryOptions<Model, Item>): MongoQueryCollection<Model, Params, Model["_id"], Item>;
|
|
25
25
|
insertOne(object: MongoInsertType<Model>): Promise<Model>;
|
|
26
|
+
/**
|
|
27
|
+
* Always sets `updated` to the write time and keeps the stored `created`: the
|
|
28
|
+
* object's own `created` and `updated` are ignored. The object is not mutated.
|
|
29
|
+
*/
|
|
26
30
|
replaceOne(object: Model): Promise<Model>;
|
|
31
|
+
/** Same rules as `replaceOne`; `next` is derived from the atomic `prev`, no read back. */
|
|
32
|
+
replaceOneWithInfo(object: Model): Promise<WriteResult<Model> | undefined>;
|
|
27
33
|
upsertOne<K extends Exclude<keyof Model, MongoKeyPath | OptionalBaseModelKeysForInsert>>(object: UpsertPartialObject<MongoKeyPath, KeyValue, Model, K>, setOnInsertPartialObject?: Update<Model>["$setOnInsert"]): Promise<Model>;
|
|
28
34
|
upsertOneWithInfo<K extends Exclude<keyof Model, MongoKeyPath | OptionalBaseModelKeysForInsert>>(object: UpsertPartialObject<MongoKeyPath, KeyValue, Model, K>, setOnInsertPartialObject?: Update<Model>["$setOnInsert"]): Promise<MongoUpsertResult<KeyValue, Model>>;
|
|
29
35
|
replaceSeveral(objects: Model[]): Promise<Model[]>;
|
|
30
|
-
partialUpdateByKey(key: KeyValue, partialUpdate: Update<Model>, criteria?: Criteria<Model
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
partialUpdateByKey(key: KeyValue, partialUpdate: Update<Model>, criteria?: Criteria<Model>, options?: PartialUpdateOptions): Promise<Model>;
|
|
37
|
+
/**
|
|
38
|
+
* `prev` is read atomically with the write. `next` is read right after it,
|
|
39
|
+
* so it may already include a later concurrent write, which emits its own change.
|
|
40
|
+
* Resolves `undefined` when no document matches.
|
|
41
|
+
*/
|
|
42
|
+
partialUpdateByKeyWithInfo(key: KeyValue, partialUpdate: Update<Model>, criteria?: Criteria<Model>, options?: PartialUpdateOptions): Promise<PartialUpdateResult<Model> | undefined>;
|
|
43
|
+
partialUpdateOne(object: Model, partialUpdate: Update<Model>, options?: PartialUpdateOptions): Promise<Model>;
|
|
44
|
+
/** One `updateMany` command; every document gets the same `updated`. */
|
|
45
|
+
partialUpdateMany(criteria: Criteria<Model>, partialUpdate: Update<Model>, options?: PartialUpdateOptions): Promise<void>;
|
|
33
46
|
deleteByKey(key: KeyValue, criteria?: Criteria<Model>): Promise<void>;
|
|
47
|
+
deleteByKeyWithInfo(key: KeyValue, criteria?: Criteria<Model>): Promise<DeleteResult<Model> | undefined>;
|
|
34
48
|
deleteOne(object: Model): Promise<void>;
|
|
35
49
|
deleteMany(selector: Criteria<Model>): Promise<void>;
|
|
36
50
|
count(filter?: Criteria<Model>): Promise<number>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MongoStore.d.ts","sourceRoot":"","sources":["../../src/MongoStore.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MongoStore.d.ts","sourceRoot":"","sources":["../../src/MongoStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubscribeStore.integration.test.d.ts","sourceRoot":"","sources":["../../src/SubscribeStore.integration.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Document } from "mongodb";
|
|
2
|
+
import type { MongoBaseModel } from "../MongoBaseModel.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Replaces the stored document with `replacement` while keeping the stored `created`
|
|
5
|
+
* (falling back to the replacement's when the stored document has none).
|
|
6
|
+
* `$literal` keeps string values starting with `$` from being read as field paths.
|
|
7
|
+
* Update pipelines require MongoDB >= 4.2.
|
|
8
|
+
*/
|
|
9
|
+
export declare const createReplacePipeline: (replacement: MongoBaseModel<any>) => Document[];
|
|
10
|
+
//# sourceMappingURL=createReplacePipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createReplacePipeline.d.ts","sourceRoot":"","sources":["../../../src/writes/createReplacePipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,gBACnB,cAAc,CAAC,GAAG,CAAC,KAC/B,QAAQ,EAWV,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BaseModel, Update } from "liwi-store";
|
|
2
|
+
/**
|
|
3
|
+
* Throws `ProtectedFieldUpdateError` when the update touches `created`, or touches
|
|
4
|
+
* `updated` with an operator other than `$set`, `$currentDate` or `$max`.
|
|
5
|
+
* Otherwise returns the update with `updated` set to `now`, unless the caller sets
|
|
6
|
+
* `updated` itself or `now` is `undefined`. The caller's update is never mutated.
|
|
7
|
+
*/
|
|
8
|
+
export declare const stampPartialUpdate: <Model extends BaseModel>(update: Update<Model>, now: Date | undefined) => Update<Model>;
|
|
9
|
+
//# sourceMappingURL=stampPartialUpdate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stampPartialUpdate.d.ts","sourceRoot":"","sources":["../../../src/writes/stampPartialUpdate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAoEpD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,KAAK,SAAS,SAAS,UAChD,MAAM,CAAC,KAAK,CAAC,OAChB,IAAI,GAAG,SAAS,KACpB,MAAM,CAAC,KAAK,CAKd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stampPartialUpdate.test.d.ts","sourceRoot":"","sources":["../../../src/writes/stampPartialUpdate.test.ts"],"names":[],"mappings":""}
|
package/dist/index-node.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { AbstractStoreCursor, ProtectedFieldUpdateError, NotFoundError, AbstractConnection } from 'liwi-store';
|
|
1
2
|
import mongodb from 'mongodb';
|
|
2
|
-
import { AbstractStoreCursor, AbstractConnection } from 'liwi-store';
|
|
3
3
|
import { AbstractSubscribableStoreQuery, SubscribeStore } from 'liwi-subscribe-store';
|
|
4
4
|
import { Query } from 'mingo';
|
|
5
5
|
import { Logger } from 'nightingale-logger';
|
|
@@ -35,11 +35,10 @@ class MongoCursor extends AbstractStoreCursor {
|
|
|
35
35
|
if (!this._result) throw new Error("Cannot call result() before next()");
|
|
36
36
|
return Promise.resolve(this._result);
|
|
37
37
|
}
|
|
38
|
-
close() {
|
|
38
|
+
async close() {
|
|
39
39
|
if (this.cursor) {
|
|
40
|
-
this.cursor.close();
|
|
40
|
+
await this.cursor.close();
|
|
41
41
|
}
|
|
42
|
-
return Promise.resolve();
|
|
43
42
|
}
|
|
44
43
|
toArray() {
|
|
45
44
|
return this.cursor.toArray();
|
|
@@ -215,6 +214,49 @@ class MongoQuerySingleItem extends AbstractSubscribableStoreQuery {
|
|
|
215
214
|
});
|
|
216
215
|
});
|
|
217
216
|
}
|
|
217
|
+
async computeChanges(action, testCriteria) {
|
|
218
|
+
switch (action.type) {
|
|
219
|
+
case "inserted": {
|
|
220
|
+
const filtered = action.next.filter(testCriteria);
|
|
221
|
+
if (filtered.length === 0) return [];
|
|
222
|
+
return [{ type: "updated", result: this.transformer(filtered[0]) }];
|
|
223
|
+
}
|
|
224
|
+
case "deleted": {
|
|
225
|
+
const filtered = action.prev.filter(testCriteria);
|
|
226
|
+
if (filtered.length === 0) return [];
|
|
227
|
+
return [
|
|
228
|
+
{
|
|
229
|
+
type: "deleted",
|
|
230
|
+
keys: filtered.map((object) => object[this.store.keyPath])
|
|
231
|
+
}
|
|
232
|
+
];
|
|
233
|
+
}
|
|
234
|
+
case "updated": {
|
|
235
|
+
const filtered = action.changes.filter(
|
|
236
|
+
([prev, next2]) => testCriteria(prev) || testCriteria(next2)
|
|
237
|
+
);
|
|
238
|
+
if (filtered.length === 0) return [];
|
|
239
|
+
if (this.options.sort) {
|
|
240
|
+
const { result } = await this.fetch((res) => res);
|
|
241
|
+
return [{ type: "updated", result }];
|
|
242
|
+
}
|
|
243
|
+
if (filtered.length > 1) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
"should not match more than 1, use sort if you can have multiple match"
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
const [, next] = filtered[0];
|
|
249
|
+
return [
|
|
250
|
+
{
|
|
251
|
+
type: "updated",
|
|
252
|
+
result: testCriteria(next) ? this.transformer(next) : null
|
|
253
|
+
}
|
|
254
|
+
];
|
|
255
|
+
}
|
|
256
|
+
default:
|
|
257
|
+
throw new Error("Unsupported type");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
218
260
|
_subscribe(callback, _includeInitial) {
|
|
219
261
|
const store = super.getSubscribeStore();
|
|
220
262
|
const testCriteria = this.createMingoTestCriteria();
|
|
@@ -229,55 +271,12 @@ class MongoQuerySingleItem extends AbstractSubscribableStoreQuery {
|
|
|
229
271
|
]);
|
|
230
272
|
}) : Promise.resolve();
|
|
231
273
|
const unsubscribe = store.subscribe(async (action) => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
type: "updated",
|
|
239
|
-
result: this.transformer(filtered[0])
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
break;
|
|
243
|
-
}
|
|
244
|
-
case "deleted": {
|
|
245
|
-
const filtered = action.prev.filter(testCriteria);
|
|
246
|
-
if (filtered.length > 0) {
|
|
247
|
-
changes.push({
|
|
248
|
-
type: "deleted",
|
|
249
|
-
keys: filtered.map((object) => object[this.store.keyPath])
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
case "updated": {
|
|
255
|
-
const filtered = action.changes.filter(
|
|
256
|
-
([prev, next]) => testCriteria(prev)
|
|
257
|
-
);
|
|
258
|
-
if (filtered.length > 0) {
|
|
259
|
-
if (this.options.sort) {
|
|
260
|
-
const { result } = await this.fetch((res) => res);
|
|
261
|
-
changes.push({
|
|
262
|
-
type: "updated",
|
|
263
|
-
result
|
|
264
|
-
});
|
|
265
|
-
} else if (filtered.length !== 1) {
|
|
266
|
-
throw new Error(
|
|
267
|
-
"should not match more than 1, use sort if you can have multiple match"
|
|
268
|
-
);
|
|
269
|
-
} else {
|
|
270
|
-
const [, next] = filtered[0];
|
|
271
|
-
changes.push({
|
|
272
|
-
type: "updated",
|
|
273
|
-
result: testCriteria(next) ? this.transformer(next) : null
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
} else if (filtered.length === 0) ;
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
default:
|
|
280
|
-
throw new Error("Unsupported type");
|
|
274
|
+
let changes;
|
|
275
|
+
try {
|
|
276
|
+
changes = await this.computeChanges(action, testCriteria);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
callback(error, []);
|
|
279
|
+
return;
|
|
281
280
|
}
|
|
282
281
|
if (changes.length === 0) return;
|
|
283
282
|
callback(null, changes);
|
|
@@ -709,6 +708,72 @@ const diffIndexes = ({
|
|
|
709
708
|
};
|
|
710
709
|
const isMongoIndexPlanEmpty = (plan) => plan.toCreate.length === 0 && plan.toRecreate.length === 0 && plan.toCollMod.length === 0 && plan.toDrop.length === 0;
|
|
711
710
|
|
|
711
|
+
const createReplacePipeline = (replacement) => [
|
|
712
|
+
{
|
|
713
|
+
$replaceWith: {
|
|
714
|
+
$mergeObjects: [
|
|
715
|
+
{ $literal: replacement },
|
|
716
|
+
{
|
|
717
|
+
created: { $ifNull: ["$created", { $literal: replacement.created }] }
|
|
718
|
+
}
|
|
719
|
+
]
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
];
|
|
723
|
+
|
|
724
|
+
const protectedFields = ["created", "updated"];
|
|
725
|
+
const updatedOverrideOperators = /* @__PURE__ */ new Set([
|
|
726
|
+
"$set",
|
|
727
|
+
"$currentDate",
|
|
728
|
+
"$max"
|
|
729
|
+
]);
|
|
730
|
+
const findProtectedField = (path) => protectedFields.find(
|
|
731
|
+
(field) => path === field || path.startsWith(`${field}.`)
|
|
732
|
+
);
|
|
733
|
+
const listTouchedPaths = (operator, fields) => {
|
|
734
|
+
const keys = Object.keys(fields);
|
|
735
|
+
if (operator !== "$rename") return keys;
|
|
736
|
+
const renameTargets = Object.values(fields).filter(
|
|
737
|
+
(target) => typeof target === "string"
|
|
738
|
+
);
|
|
739
|
+
return [...keys, ...renameTargets];
|
|
740
|
+
};
|
|
741
|
+
const describeRejection = (field, operator, path) => field === "created" ? `Cannot use ${operator} on "${path}": created is set on insert and never updated` : `Cannot use ${operator} on "${path}": updated can only be set with $set, $currentDate or $max`;
|
|
742
|
+
const validateProtectedFields = (update) => {
|
|
743
|
+
let isUpdatedSetByCaller = false;
|
|
744
|
+
const operations = Object.entries(
|
|
745
|
+
update
|
|
746
|
+
);
|
|
747
|
+
for (const [operator, fields] of operations) {
|
|
748
|
+
if (!fields) continue;
|
|
749
|
+
for (const path of listTouchedPaths(operator, fields)) {
|
|
750
|
+
const field = findProtectedField(path);
|
|
751
|
+
if (!field) continue;
|
|
752
|
+
if (field === "updated" && path === "updated" && updatedOverrideOperators.has(operator)) {
|
|
753
|
+
isUpdatedSetByCaller = true;
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
throw new ProtectedFieldUpdateError(
|
|
757
|
+
describeRejection(field, operator, path)
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return { isUpdatedSetByCaller };
|
|
762
|
+
};
|
|
763
|
+
const stampPartialUpdate = (update, now) => {
|
|
764
|
+
const { isUpdatedSetByCaller } = validateProtectedFields(update);
|
|
765
|
+
if (isUpdatedSetByCaller || !now) return update;
|
|
766
|
+
const $set = { ...update.$set, updated: now };
|
|
767
|
+
return { ...update, $set };
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
const resolveWriteTime = (options) => options?.touchUpdated === false ? void 0 : /* @__PURE__ */ new Date();
|
|
771
|
+
const toStoredValues = (values) => Object.fromEntries(
|
|
772
|
+
Object.entries(values).map(([field, value]) => [
|
|
773
|
+
field,
|
|
774
|
+
value === void 0 ? null : value
|
|
775
|
+
])
|
|
776
|
+
);
|
|
712
777
|
class MongoStore {
|
|
713
778
|
keyPath = "_id";
|
|
714
779
|
connection;
|
|
@@ -802,11 +867,37 @@ class MongoStore {
|
|
|
802
867
|
}
|
|
803
868
|
return object;
|
|
804
869
|
}
|
|
870
|
+
/**
|
|
871
|
+
* Always sets `updated` to the write time and keeps the stored `created`: the
|
|
872
|
+
* object's own `created` and `updated` are ignored. The object is not mutated.
|
|
873
|
+
*/
|
|
805
874
|
async replaceOne(object) {
|
|
806
|
-
if (!object.updated) object.updated = /* @__PURE__ */ new Date();
|
|
807
875
|
const collection = await this.collection;
|
|
808
|
-
await collection.
|
|
809
|
-
|
|
876
|
+
const next = await collection.findOneAndUpdate(
|
|
877
|
+
{ _id: object._id },
|
|
878
|
+
createReplacePipeline({ ...object, updated: /* @__PURE__ */ new Date() }),
|
|
879
|
+
{ returnDocument: "after" }
|
|
880
|
+
);
|
|
881
|
+
if (!next) {
|
|
882
|
+
throw new NotFoundError(`Document not found: ${String(object._id)}`);
|
|
883
|
+
}
|
|
884
|
+
return next;
|
|
885
|
+
}
|
|
886
|
+
/** Same rules as `replaceOne`; `next` is derived from the atomic `prev`, no read back. */
|
|
887
|
+
async replaceOneWithInfo(object) {
|
|
888
|
+
const replacement = { ...object, updated: /* @__PURE__ */ new Date() };
|
|
889
|
+
const collection = await this.collection;
|
|
890
|
+
const prev = await collection.findOneAndUpdate(
|
|
891
|
+
{ _id: object._id },
|
|
892
|
+
createReplacePipeline(replacement),
|
|
893
|
+
{ returnDocument: "before" }
|
|
894
|
+
);
|
|
895
|
+
if (!prev) return void 0;
|
|
896
|
+
const storedCreated = prev.created;
|
|
897
|
+
return {
|
|
898
|
+
prev,
|
|
899
|
+
next: { ...replacement, created: storedCreated ?? replacement.created }
|
|
900
|
+
};
|
|
810
901
|
}
|
|
811
902
|
async upsertOne(object, setOnInsertPartialObject) {
|
|
812
903
|
const result = await this.upsertOneWithInfo(
|
|
@@ -816,6 +907,11 @@ class MongoStore {
|
|
|
816
907
|
return result.object;
|
|
817
908
|
}
|
|
818
909
|
async upsertOneWithInfo(object, setOnInsertPartialObject) {
|
|
910
|
+
if (setOnInsertPartialObject && "updated" in setOnInsertPartialObject) {
|
|
911
|
+
throw new ProtectedFieldUpdateError(
|
|
912
|
+
'Cannot use $setOnInsert on "updated": upsert always sets it with $set, pass it in the object instead'
|
|
913
|
+
);
|
|
914
|
+
}
|
|
819
915
|
const $setOnInsert = {
|
|
820
916
|
// @ts-expect-error -- created is Date as set in BaseModel
|
|
821
917
|
created: object.created || /* @__PURE__ */ new Date(),
|
|
@@ -840,7 +936,7 @@ class MongoStore {
|
|
|
840
936
|
object
|
|
841
937
|
};
|
|
842
938
|
}
|
|
843
|
-
const next = { ...prev,
|
|
939
|
+
const next = { ...prev, ...toStoredValues($set) };
|
|
844
940
|
return {
|
|
845
941
|
resolvedAs: "updated",
|
|
846
942
|
inserted: false,
|
|
@@ -851,34 +947,62 @@ class MongoStore {
|
|
|
851
947
|
replaceSeveral(objects) {
|
|
852
948
|
return Promise.all(objects.map((object) => this.replaceOne(object)));
|
|
853
949
|
}
|
|
854
|
-
async partialUpdateByKey(key, partialUpdate, criteria) {
|
|
950
|
+
async partialUpdateByKey(key, partialUpdate, criteria, options) {
|
|
951
|
+
const update = stampPartialUpdate(partialUpdate, resolveWriteTime(options));
|
|
855
952
|
const collection = await this.collection;
|
|
856
|
-
const
|
|
953
|
+
const next = await collection.findOneAndUpdate(
|
|
857
954
|
{ _id: key, ...criteria },
|
|
858
|
-
|
|
955
|
+
update,
|
|
956
|
+
{ returnDocument: "after" }
|
|
957
|
+
);
|
|
958
|
+
if (!next) throw new NotFoundError(`Document not found: ${String(key)}`);
|
|
959
|
+
return next;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* `prev` is read atomically with the write. `next` is read right after it,
|
|
963
|
+
* so it may already include a later concurrent write, which emits its own change.
|
|
964
|
+
* Resolves `undefined` when no document matches.
|
|
965
|
+
*/
|
|
966
|
+
async partialUpdateByKeyWithInfo(key, partialUpdate, criteria, options) {
|
|
967
|
+
const update = stampPartialUpdate(partialUpdate, resolveWriteTime(options));
|
|
968
|
+
const collection = await this.collection;
|
|
969
|
+
const prev = await collection.findOneAndUpdate(
|
|
970
|
+
{ _id: key, ...criteria },
|
|
971
|
+
update,
|
|
972
|
+
{ returnDocument: "before" }
|
|
973
|
+
);
|
|
974
|
+
if (!prev) return void 0;
|
|
975
|
+
return { prev, next: await this.findByKey(key) };
|
|
976
|
+
}
|
|
977
|
+
partialUpdateOne(object, partialUpdate, options) {
|
|
978
|
+
return this.partialUpdateByKey(
|
|
979
|
+
object._id,
|
|
980
|
+
partialUpdate,
|
|
981
|
+
void 0,
|
|
982
|
+
options
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
/** One `updateMany` command; every document gets the same `updated`. */
|
|
986
|
+
async partialUpdateMany(criteria, partialUpdate, options) {
|
|
987
|
+
const update = stampPartialUpdate(partialUpdate, resolveWriteTime(options));
|
|
988
|
+
const collection = await this.collection;
|
|
989
|
+
await collection.updateMany(
|
|
990
|
+
criteria,
|
|
991
|
+
update
|
|
859
992
|
);
|
|
860
|
-
if (!commandResult.acknowledged) {
|
|
861
|
-
console.error(commandResult);
|
|
862
|
-
throw new Error("Update failed");
|
|
863
|
-
}
|
|
864
|
-
const object = await this.findByKey(key);
|
|
865
|
-
return object;
|
|
866
993
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
)
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
return this.collection.then(
|
|
880
|
-
(collection) => collection.deleteOne({ _id: key, ...criteria })
|
|
881
|
-
).then(() => void 0);
|
|
994
|
+
async deleteByKey(key, criteria) {
|
|
995
|
+
const collection = await this.collection;
|
|
996
|
+
await collection.deleteOne({ _id: key, ...criteria });
|
|
997
|
+
}
|
|
998
|
+
async deleteByKeyWithInfo(key, criteria) {
|
|
999
|
+
const collection = await this.collection;
|
|
1000
|
+
const prev = await collection.findOneAndDelete({
|
|
1001
|
+
_id: key,
|
|
1002
|
+
...criteria
|
|
1003
|
+
});
|
|
1004
|
+
if (!prev) return void 0;
|
|
1005
|
+
return { prev };
|
|
882
1006
|
}
|
|
883
1007
|
deleteOne(object) {
|
|
884
1008
|
return this.deleteByKey(object._id);
|