liwi-mongo 13.3.3 → 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.
Files changed (40) hide show
  1. package/README.md +27 -2
  2. package/dist/definitions/MongoConnection.d.ts +7 -1
  3. package/dist/definitions/MongoConnection.d.ts.map +1 -1
  4. package/dist/definitions/MongoConnection.test.d.ts +2 -0
  5. package/dist/definitions/MongoConnection.test.d.ts.map +1 -0
  6. package/dist/definitions/MongoCursor.d.ts.map +1 -1
  7. package/dist/definitions/MongoQuerySingleItem.d.ts +1 -0
  8. package/dist/definitions/MongoQuerySingleItem.d.ts.map +1 -1
  9. package/dist/definitions/MongoQuerySingleItem.test.d.ts +2 -0
  10. package/dist/definitions/MongoQuerySingleItem.test.d.ts.map +1 -0
  11. package/dist/definitions/MongoStore.d.ts +18 -4
  12. package/dist/definitions/MongoStore.d.ts.map +1 -1
  13. package/dist/definitions/SubscribeStore.integration.test.d.ts +2 -0
  14. package/dist/definitions/SubscribeStore.integration.test.d.ts.map +1 -0
  15. package/dist/definitions/writes/createReplacePipeline.d.ts +10 -0
  16. package/dist/definitions/writes/createReplacePipeline.d.ts.map +1 -0
  17. package/dist/definitions/writes/stampPartialUpdate.d.ts +9 -0
  18. package/dist/definitions/writes/stampPartialUpdate.d.ts.map +1 -0
  19. package/dist/definitions/writes/stampPartialUpdate.test.d.ts +2 -0
  20. package/dist/definitions/writes/stampPartialUpdate.test.d.ts.map +1 -0
  21. package/dist/index-node.mjs +225 -93
  22. package/dist/index-node.mjs.map +1 -1
  23. package/package.json +9 -8
  24. package/src/MongoConnection.test.ts +75 -0
  25. package/src/MongoConnection.ts +38 -25
  26. package/src/MongoCursor.ts +2 -4
  27. package/src/MongoQueryCollection.test.ts +9 -23
  28. package/src/MongoQueryCollection.ts +2 -2
  29. package/src/MongoQuerySingleItem.test.ts +162 -0
  30. package/src/MongoQuerySingleItem.ts +58 -52
  31. package/src/MongoStore.test.ts +259 -18
  32. package/src/MongoStore.ts +122 -26
  33. package/src/MongoStoreIndexes.test.ts +10 -24
  34. package/src/SubscribeStore.integration.test.ts +414 -0
  35. package/src/indexes/diffIndexes.test.ts +2 -2
  36. package/src/indexes/normalizeIndex.test.ts +2 -2
  37. package/src/indexes/normalizeIndex.ts +2 -2
  38. package/src/writes/createReplacePipeline.ts +23 -0
  39. package/src/writes/stampPartialUpdate.test.ts +93 -0
  40. 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; the store fills `created` / `updated` on insert and `updated` on every update. `insertOne` mutates and returns the object you passed.
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
 
@@ -6,14 +6,20 @@ export interface MongoConfig {
6
6
  database: string;
7
7
  user?: string;
8
8
  password?: string;
9
+ authSource?: string;
9
10
  }
11
+ interface BuildMongoConnectionStringParams extends MongoConfig {
12
+ redactCredentials: boolean;
13
+ }
14
+ export declare const buildMongoConnectionString: ({ host, port, database, user, password, authSource, redactCredentials, }: BuildMongoConnectionStringParams) => string;
10
15
  export default class MongoConnection extends AbstractConnection {
11
16
  _connection?: mongodb.MongoClient;
12
17
  _connecting?: Promise<mongodb.MongoClient>;
13
18
  connectionFailed?: boolean;
14
- constructor({ host, port, database, user, password, }: MongoConfig);
19
+ constructor(config: MongoConfig);
15
20
  connect(connectionString: string, connectionStringRedacted: string): void;
16
21
  getConnection(): Promise<mongodb.MongoClient>;
17
22
  close(): Promise<void>;
18
23
  }
24
+ export {};
19
25
  //# sourceMappingURL=MongoConnection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MongoConnection.d.ts","sourceRoot":"","sources":["../../src/MongoConnection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,OAAO,MAAM,SAAS,CAAC;AAK9B,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,kBAAkB;IAC7D,WAAW,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC;IAElC,WAAW,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAE3C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAG3B,YAAY,EACV,IAAkB,EAClB,IAAc,EACd,QAAQ,EACR,IAAI,EACJ,QAAQ,GACT,EAAE,WAAW,EA0Bb;IAED,OAAO,CAAC,gBAAgB,EAAE,MAAM,EAAE,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAgDxE;IAED,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAE5C;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAS3B;CACF"}
1
+ {"version":3,"file":"MongoConnection.d.ts","sourceRoot":"","sources":["../../src/MongoConnection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,OAAO,MAAM,SAAS,CAAC;AAK9B,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,gCAAiC,SAAQ,WAAW;IAC5D,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,eAAO,MAAM,0BAA0B,6EAQpC,gCAAgC,KAAG,MAalC,CAAC;AAEL,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,kBAAkB;IAC7D,WAAW,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC;IAElC,WAAW,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAE3C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAG3B,YAAY,MAAM,EAAE,WAAW,EAiB9B;IAED,OAAO,CAAC,gBAAgB,EAAE,MAAM,EAAE,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAgDxE;IAED,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAE5C;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAS3B;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=MongoConnection.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MongoConnection.test.d.ts","sourceRoot":"","sources":["../../src/MongoConnection.test.ts"],"names":[],"mappings":""}
@@ -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;IAED,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAMrB;IAED,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAE3B;CACF"}
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;IAED,UAAU,CACR,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,EAC7C,eAAe,EAAE,OAAO,GACvB,iBAAiB,CAmFnB;YAEa,iBAAiB;CAehC"}
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=MongoQuerySingleItem.test.d.ts.map
@@ -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>): Promise<Model>;
31
- partialUpdateOne(object: Model, partialUpdate: Update<Model>): Promise<Model>;
32
- partialUpdateMany(criteria: Criteria<Model>, partialUpdate: Update<Model>): Promise<void>;
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":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,kBAAkB,EAClB,QAAQ,EACR,MAAM,EACN,8BAA8B,EAC9B,WAAW,EACX,IAAI,EACJ,iBAAiB,EACjB,MAAM,EACN,mBAAmB,EACnB,YAAY,EACb,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,UAAU,EAKX,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,YAAY,EACb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,eAAe,MAAM,sBAAsB,CAAC;AACxD,OAAO,WAAW,MAAM,kBAAkB,CAAC;AAC3C,OAAO,oBAAoB,MAAM,2BAA2B,CAAC;AAC7D,OAAO,oBAAoB,MAAM,2BAA2B,CAAC;AAK7D,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,iBAAiB,CAC3B,QAAQ,SAAS,eAAe,EAChC,KAAK,SAAS,cAAc,CAAC,QAAQ,CAAC,IACpC,YAAY,CAAC,KAAK,CAAC,CAAC;AAExB,MAAM,WAAW,iBAAiB,CAAC,KAAK,SAAS,cAAc,CAAC,GAAG,CAAC;IAClE,OAAO,CAAC,EAAE,SAAS,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;CACxC;AAED,MAAM,CAAC,OAAO,OAAO,UAAU,CAC7B,KAAK,SAAS,cAAc,CAAC,QAAQ,CAAC,EACtC,QAAQ,SAAS,eAAe,GAAG,KAAK,CAAC,YAAY,CAAC,CACtD,YAAW,iBAAiB,CAC5B,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,eAAe,CAAC,KAAK,CAAC,EACtB,eAAe,CAChB;IACC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAS;IAEvC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;IAErC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAEhC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA+B;IAE/D,OAAO,CAAC,WAAW,CAAiD;IAEpE,YACE,UAAU,EAAE,eAAe,EAC3B,cAAc,EAAE,MAAM,EACtB,EAAE,OAAY,EAAE,GAAE,iBAAiB,CAAC,KAAK,CAAM,EAsBhD;IAED,IAAI,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAM3C;IAEK,WAAW,CAAC,EAChB,qBAA4B,GAC7B,GAAE,kBAAuB,GAAG,OAAO,CAAC,cAAc,CAAC,CAiBnD;IAEK,WAAW,CACf,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAW/B;IAED,qBAAqB,CACnB,MAAM,SAAS,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,GAAG,KAAK,EACrD,MAAM,SAAS,WAAW,CAAC,MAAM,CAAC,GAAG,KAAK,EAC1C,EACA,WAAW,EACX,GAAG,OAAO,EACX,EAAE,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,oBAAoB,CACzD,KAAK,EACL,MAAM,EACN,MAAM,EACN,QAAQ,CACT,CAMA;IAED,qBAAqB,CACnB,IAAI,SAAS,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,GAAG,KAAK,EACnD,MAAM,SAAS,WAAW,CAAC,MAAM,CAAC,GAAG,KAAK,EAC1C,EACA,WAAW,EACX,GAAG,OAAO,EACX,EAAE,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,oBAAoB,CACvD,KAAK,EACL,MAAM,EACN,KAAK,CAAC,KAAK,CAAC,EACZ,IAAI,CACL,CAMA;IAEK,SAAS,CAAC,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAkB9D;IAEK,UAAU,CAAC,MAAM,EAAE,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAM9C;IAEK,SAAS,CACb,CAAC,SAAS,OAAO,CACf,MAAM,KAAK,EACX,YAAY,GAAG,8BAA8B,CAC9C,EAED,MAAM,EAAE,mBAAmB,CAAC,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,EAC7D,wBAAwB,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,GACvD,OAAO,CAAC,KAAK,CAAC,CAMhB;IAEK,iBAAiB,CACrB,CAAC,SAAS,OAAO,CACf,MAAM,KAAK,EACX,YAAY,GAAG,8BAA8B,CAC9C,EAED,MAAM,EAAE,mBAAmB,CAAC,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,EAC7D,wBAAwB,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,GACvD,OAAO,CAAC,iBAAiB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAuC7C;IAED,cAAc,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAEjD;IAEK,kBAAkB,CACtB,GAAG,EAAE,QAAQ,EACb,aAAa,EAAE,MAAM,CAAC,KAAK,CAAC,EAC5B,QAAQ,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,GACzB,OAAO,CAAC,KAAK,CAAC,CAYhB;IAED,gBAAgB,CACd,MAAM,EAAE,KAAK,EACb,aAAa,EAAE,MAAM,CAAC,KAAK,CAAC,GAC3B,OAAO,CAAC,KAAK,CAAC,CAEhB;IAED,iBAAiB,CACf,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,EACzB,aAAa,EAAE,MAAM,CAAC,KAAK,CAAC,GAC3B,OAAO,CAAC,IAAI,CAAC,CAOf;IAED,WAAW,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAMpE;IAED,SAAS,CAAC,MAAM,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtC;IAED,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAInD;IAEK,KAAK,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAKrD;IAEK,MAAM,CAAC,MAAM,SAAS,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,EAChD,MAAM,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,EACxB,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAClB,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,GACrB,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAQ/C;IAEK,SAAS,CACb,GAAG,EAAE,QAAQ,EACb,QAAQ,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,GACzB,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAO5B;IAED,OAAO,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAIxE;IAEK,OAAO,CACX,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,EACvB,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GACjB,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAM5B;CACF"}
1
+ {"version":3,"file":"MongoStore.d.ts","sourceRoot":"","sources":["../../src/MongoStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=SubscribeStore.integration.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stampPartialUpdate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stampPartialUpdate.test.d.ts","sourceRoot":"","sources":["../../../src/writes/stampPartialUpdate.test.ts"],"names":[],"mappings":""}
@@ -1,7 +1,7 @@
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
- import mingo from 'mingo';
4
+ import { Query } from 'mingo';
5
5
  import { Logger } from 'nightingale-logger';
6
6
 
7
7
  class MongoCursor extends AbstractStoreCursor {
@@ -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();
@@ -66,7 +65,7 @@ class MongoQueryCollection extends AbstractSubscribableStoreQuery {
66
65
  if ("$text" in this.options.criteria) {
67
66
  return () => false;
68
67
  }
69
- const mingoQuery = new mingo.Query(this.options.criteria);
68
+ const mingoQuery = new Query(this.options.criteria);
70
69
  this.testCriteria = mingoQuery.test.bind(mingoQuery);
71
70
  }
72
71
  return this.testCriteria;
@@ -195,7 +194,7 @@ class MongoQuerySingleItem extends AbstractSubscribableStoreQuery {
195
194
  if (!this.options.criteria) {
196
195
  return () => true;
197
196
  }
198
- const mingoQuery = new mingo.Query(this.options.criteria);
197
+ const mingoQuery = new Query(this.options.criteria);
199
198
  this.testCriteria = mingoQuery.test.bind(mingoQuery);
200
199
  }
201
200
  return this.testCriteria;
@@ -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
- const changes = [];
233
- switch (action.type) {
234
- case "inserted": {
235
- const filtered = action.next.filter(testCriteria);
236
- if (filtered.length > 0) {
237
- changes.push({
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.replaceOne({ _id: object._id }, object);
809
- return object;
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, ...$set };
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 commandResult = await collection.updateOne(
953
+ const next = await collection.findOneAndUpdate(
857
954
  { _id: key, ...criteria },
858
- partialUpdate
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
859
983
  );
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
984
  }
867
- partialUpdateOne(object, partialUpdate) {
868
- return this.partialUpdateByKey(object._id, partialUpdate);
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
992
+ );
869
993
  }
870
- partialUpdateMany(criteria, partialUpdate) {
871
- return this.collection.then(
872
- (collection) => (
873
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
874
- collection.updateMany(criteria, partialUpdate)
875
- )
876
- ).then((res) => void 0);
994
+ async deleteByKey(key, criteria) {
995
+ const collection = await this.collection;
996
+ await collection.deleteOne({ _id: key, ...criteria });
877
997
  }
878
- deleteByKey(key, criteria) {
879
- return this.collection.then(
880
- (collection) => collection.deleteOne({ _id: key, ...criteria })
881
- ).then(() => void 0);
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);
@@ -920,25 +1044,33 @@ class MongoStore {
920
1044
  }
921
1045
 
922
1046
  const logger$1 = new Logger("liwi:mongo:MongoConnection");
1047
+ const buildMongoConnectionString = ({
1048
+ host = "localhost",
1049
+ port = "27017",
1050
+ database,
1051
+ user,
1052
+ password,
1053
+ authSource,
1054
+ redactCredentials
1055
+ }) => `mongodb://${user ? `${redactCredentials ? `${user.slice(0, 2)}[redacted]` : encodeURIComponent(user)}:${redactCredentials ? "[redacted]" : encodeURIComponent(password ?? "")}@` : ""}${host}:${port}/${encodeURIComponent(database)}${authSource ? `?authSource=${encodeURIComponent(authSource)}` : ""}`;
923
1056
  class MongoConnection extends AbstractConnection {
924
1057
  _connection;
925
1058
  _connecting;
926
1059
  connectionFailed;
927
1060
  // TODO interface
928
- constructor({
929
- host = "localhost",
930
- port = "27017",
931
- database,
932
- user,
933
- password
934
- }) {
1061
+ constructor(config) {
935
1062
  super();
936
- if (!database) {
1063
+ if (!config.database) {
937
1064
  throw new Error("Missing config database");
938
1065
  }
939
- const buildConnectionString = (redactCredentials) => `mongodb://${user ? `${redactCredentials ? `${user.slice(0, 2)}[redacted]` : encodeURIComponent(user)}:${redactCredentials ? "[redacted]" : encodeURIComponent(password ?? "")}@` : ""}${host}:${port}/${encodeURIComponent(database)}`;
940
- const connectionString = buildConnectionString(false);
941
- const connectionStringRedacted = buildConnectionString(true);
1066
+ const connectionString = buildMongoConnectionString({
1067
+ ...config,
1068
+ redactCredentials: false
1069
+ });
1070
+ const connectionStringRedacted = buildMongoConnectionString({
1071
+ ...config,
1072
+ redactCredentials: true
1073
+ });
942
1074
  this.connect(connectionString, connectionStringRedacted);
943
1075
  }
944
1076
  connect(connectionString, connectionStringRedacted) {