@toa.io/storages.mongodb 1.0.0-alpha.32 → 1.0.0-alpha.320
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/CHANGELOG.md +282 -0
- package/package.json +10 -9
- package/readme.md +38 -0
- package/src/client.js +181 -40
- package/src/factory.js +6 -12
- package/src/inbox.js +117 -0
- package/src/index.js +1 -7
- package/src/indexes.js +84 -0
- package/src/match.js +103 -0
- package/src/measurements.js +39 -0
- package/src/migrations.js +357 -0
- package/src/outbox.js +131 -0
- package/src/record.js +88 -20
- package/src/storage.js +517 -130
- package/src/streams.js +363 -0
- package/src/translate/criteria.js +25 -16
- package/src/translate/options.js +20 -11
- package/src/translate/rename.js +1 -5
- package/src/translate.js +14 -15
- package/test/match.test.js +67 -0
- package/test/migrations.test.js +388 -0
- package/test/query/criteria.fixtures.js +2 -7
- package/test/query/criteria.test.js +5 -4
- package/test/record.test.js +98 -25
- package/test/storage.test.js +258 -0
- package/test/translate.test.js +59 -7
- package/types/connection.d.ts +18 -12
- package/types/pointer.d.ts +4 -6
- package/types/record.d.ts +5 -7
- package/src/collection.js +0 -69
- package/src/deployment.js +0 -22
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
# [1.0.0-alpha.320](https://github.com/toa-io/toa/compare/v1.0.0-alpha.319...v1.0.0-alpha.320) (2026-09-28)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* page a stream by CREATED, and stop it after its first page ([038dc52](https://github.com/toa-io/toa/commit/038dc52f2db1a4dd58c6ec58a00de8914b3d38a3))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# [1.0.0-alpha.319](https://github.com/toa-io/toa/compare/v1.0.0-alpha.318...v1.0.0-alpha.319) (2026-09-27)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **storages.mongodb:** read a set and its changes from a token ([d0a8955](https://github.com/toa-io/toa/commit/d0a8955683b7566e704f7e9cde69f67272c7be1b))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# [1.0.0-alpha.318](https://github.com/toa-io/toa/compare/v1.0.0-alpha.317...v1.0.0-alpha.318) (2026-09-23)
|
|
21
|
+
|
|
22
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# [1.0.0-alpha.317](https://github.com/toa-io/toa/compare/v1.0.0-alpha.316...v1.0.0-alpha.317) (2026-09-23)
|
|
29
|
+
|
|
30
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# [1.0.0-alpha.316](https://github.com/toa-io/toa/compare/v1.0.0-alpha.315...v1.0.0-alpha.316) (2026-09-23)
|
|
37
|
+
|
|
38
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# [1.0.0-alpha.315](https://github.com/toa-io/toa/compare/v1.0.0-alpha.314...v1.0.0-alpha.315) (2026-09-23)
|
|
45
|
+
|
|
46
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# [1.0.0-alpha.314](https://github.com/toa-io/toa/compare/v1.0.0-alpha.313...v1.0.0-alpha.314) (2026-09-19)
|
|
53
|
+
|
|
54
|
+
* feat(core)!: let an operation state how long its calls are remembered ([af177c2](https://github.com/toa-io/toa/commit/af177c2ab3d00d475840b5ef9e612f04e82b72bd))
|
|
55
|
+
|
|
56
|
+
### BREAKING CHANGES
|
|
57
|
+
|
|
58
|
+
* `inbox.retention` under 600 is refused. See
|
|
59
|
+
migrations/313.md.
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# [1.0.0-alpha.313](https://github.com/toa-io/toa/compare/v1.0.0-alpha.312...v1.0.0-alpha.313) (2026-09-18)
|
|
63
|
+
|
|
64
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# [1.0.0-alpha.312](https://github.com/toa-io/toa/compare/v1.0.0-alpha.311...v1.0.0-alpha.312) (2026-09-18)
|
|
71
|
+
|
|
72
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# [1.0.0-alpha.311](https://github.com/toa-io/toa/compare/v1.0.0-alpha.310...v1.0.0-alpha.311) (2026-09-17)
|
|
79
|
+
|
|
80
|
+
### Features
|
|
81
|
+
|
|
82
|
+
* name the database after the scope ([4d7203f](https://github.com/toa-io/toa/commit/4d7203fd18828895272a9dae76e351d92b49277f))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# [1.0.0-alpha.310](https://github.com/toa-io/toa/compare/v1.0.0-alpha.309...v1.0.0-alpha.310) (2026-09-16)
|
|
86
|
+
|
|
87
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# [1.0.0-alpha.309](https://github.com/toa-io/toa/compare/v1.0.0-alpha.308...v1.0.0-alpha.309) (2026-09-16)
|
|
94
|
+
|
|
95
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# [1.0.0-alpha.308](https://github.com/toa-io/toa/compare/v1.0.0-alpha.307...v1.0.0-alpha.308) (2026-09-15)
|
|
102
|
+
|
|
103
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# [1.0.0-alpha.307](https://github.com/toa-io/toa/compare/v1.0.0-alpha.306...v1.0.0-alpha.307) (2026-09-15)
|
|
110
|
+
|
|
111
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# [1.0.0-alpha.306](https://github.com/toa-io/toa/compare/v1.0.0-alpha.305...v1.0.0-alpha.306) (2026-09-14)
|
|
118
|
+
|
|
119
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# [1.0.0-alpha.305](https://github.com/toa-io/toa/compare/v1.0.0-alpha.304...v1.0.0-alpha.305) (2026-09-13)
|
|
126
|
+
|
|
127
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# [1.0.0-alpha.304](https://github.com/toa-io/toa/compare/v1.0.0-alpha.303...v1.0.0-alpha.304) (2026-09-13)
|
|
134
|
+
|
|
135
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# [1.0.0-alpha.303](https://github.com/toa-io/toa/compare/v1.0.0-alpha.302...v1.0.0-alpha.303) (2026-09-12)
|
|
142
|
+
|
|
143
|
+
### Features
|
|
144
|
+
|
|
145
|
+
* **metrics:** measure storage, stash, blob storages and fetch ([252298b](https://github.com/toa-io/toa/commit/252298bb9f34421694da4ee89742d62b47c39dfb))
|
|
146
|
+
|
|
147
|
+
### Performance Improvements
|
|
148
|
+
|
|
149
|
+
* **storages.mongodb:** rename a record's _id to id in place ([4aeb2a1](https://github.com/toa-io/toa/commit/4aeb2a1d61f7f2b0ddab203b5887152e8917ef7f))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# [1.0.0-alpha.302](https://github.com/toa-io/toa/compare/v1.0.0-alpha.301...v1.0.0-alpha.302) (2026-09-11)
|
|
153
|
+
|
|
154
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# [1.0.0-alpha.301](https://github.com/toa-io/toa/compare/v1.0.0-alpha.300...v1.0.0-alpha.301) (2026-09-11)
|
|
161
|
+
|
|
162
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# [1.0.0-alpha.300](https://github.com/toa-io/toa/compare/v1.0.0-alpha.299...v1.0.0-alpha.300) (2026-09-11)
|
|
169
|
+
|
|
170
|
+
* feat(core)!: an operation may ask to run once ([dca616c](https://github.com/toa-io/toa/commit/dca616cb380a7a389e277875d3bd5ca55a6e4d45))
|
|
171
|
+
|
|
172
|
+
### Bug Fixes
|
|
173
|
+
|
|
174
|
+
* **extensions:** what a factory remembers does not outlive the tree it was made for ([dd36631](https://github.com/toa-io/toa/commit/dd3663160f500eadfb701bf840ef3741a2d075f0))
|
|
175
|
+
|
|
176
|
+
### Features
|
|
177
|
+
|
|
178
|
+
* **core:** an assignment may ask to run once as well ([10fbcbd](https://github.com/toa-io/toa/commit/10fbcbd86a5e82960e65f62c09d0a465ae8d0353))
|
|
179
|
+
|
|
180
|
+
### BREAKING CHANGES
|
|
181
|
+
|
|
182
|
+
* `Storage.store`, `.upsert` and `.ensure` take one more
|
|
183
|
+
argument, and `Storage` has two more members. A connector that ignores them
|
|
184
|
+
works exactly as it did; a component that asks it for `once` is refused at
|
|
185
|
+
boot. See migrations/299.md.
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# [1.0.0-alpha.299](https://github.com/toa-io/toa/compare/v1.0.0-alpha.298...v1.0.0-alpha.299) (2026-09-10)
|
|
189
|
+
|
|
190
|
+
### Features
|
|
191
|
+
|
|
192
|
+
* **core:** a record carries the region that wrote it ([13ed187](https://github.com/toa-io/toa/commit/13ed187604f06dec66feec2e26ccf9dae30bb048))
|
|
193
|
+
* **core:** an outbox row is published to destinations ([cfb77b4](https://github.com/toa-io/toa/commit/cfb77b4826ff0f9560769a0996981e143d6525af))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# [1.0.0-alpha.298](https://github.com/toa-io/toa/compare/v1.0.0-alpha.297...v1.0.0-alpha.298) (2026-09-08)
|
|
197
|
+
|
|
198
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# [1.0.0-alpha.297](https://github.com/toa-io/toa/compare/v1.0.0-alpha.296...v1.0.0-alpha.297) (2026-09-08)
|
|
205
|
+
|
|
206
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# [1.0.0-alpha.296](https://github.com/toa-io/toa/compare/v1.0.0-alpha.295...v1.0.0-alpha.296) (2026-09-08)
|
|
213
|
+
|
|
214
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# [1.0.0-alpha.295](https://github.com/toa-io/toa/compare/v1.0.0-alpha.294...v1.0.0-alpha.295) (2026-09-07)
|
|
221
|
+
|
|
222
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# [1.0.0-alpha.294](https://github.com/toa-io/toa/compare/v1.0.0-alpha.293...v1.0.0-alpha.294) (2026-09-07)
|
|
229
|
+
|
|
230
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# [1.0.0-alpha.293](https://github.com/toa-io/toa/compare/v1.0.0-alpha.292...v1.0.0-alpha.293) (2026-09-07)
|
|
237
|
+
|
|
238
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# [1.0.0-alpha.292](https://github.com/toa-io/toa/compare/v1.0.0-alpha.291...v1.0.0-alpha.292) (2026-09-07)
|
|
245
|
+
|
|
246
|
+
### Features
|
|
247
|
+
|
|
248
|
+
* **definitions:** what a package declares is read from one package ([7052341](https://github.com/toa-io/toa/commit/70523411b5d9e2b204c999aa02365173f3bb518d))
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# [1.0.0-alpha.291](https://github.com/toa-io/toa/compare/v1.0.0-alpha.290...v1.0.0-alpha.291) (2026-09-07)
|
|
252
|
+
|
|
253
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# [1.0.0-alpha.289](https://github.com/toa-io/toa/compare/v1.0.0-alpha.288...v1.0.0-alpha.289) (2026-09-07)
|
|
260
|
+
|
|
261
|
+
* A deploy moves only what changed, and a component sees none of the runtime's environment (#1073) ([f38e3db](https://github.com/toa-io/toa/commit/f38e3db533f24db866c57bfa1ef294eff1eaf499)), closes [#1073](https://github.com/toa-io/toa/issues/1073) [#1064](https://github.com/toa-io/toa/issues/1064) [#1066](https://github.com/toa-io/toa/issues/1066) [#1067](https://github.com/toa-io/toa/issues/1067) [#1068](https://github.com/toa-io/toa/issues/1068) [#1069](https://github.com/toa-io/toa/issues/1069) [#1071](https://github.com/toa-io/toa/issues/1071) [#1070](https://github.com/toa-io/toa/issues/1070) [#1072](https://github.com/toa-io/toa/issues/1072)
|
|
262
|
+
|
|
263
|
+
### BREAKING CHANGES
|
|
264
|
+
|
|
265
|
+
* a component that read `process.env.TOA_*` reads `context` instead;
|
|
266
|
+
`echo(input)` no longer substitutes from the environment; a bash operation sees no
|
|
267
|
+
`TOA_*`; images no longer set `USER node` — see migrations/289.md.
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# [1.0.0-alpha.288](https://github.com/toa-io/toa/compare/v1.0.0-alpha.287...v1.0.0-alpha.288) (2026-09-06)
|
|
271
|
+
|
|
272
|
+
**Note:** Version bump only for package @toa.io/storages.mongodb
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# [1.0.0-alpha.287](https://github.com/toa-io/toa/compare/v1.0.0-alpha.286...v1.0.0-alpha.287) (2026-09-06)
|
|
279
|
+
|
|
280
|
+
### Features
|
|
281
|
+
|
|
282
|
+
* **mongodb:** a migration says what it is doing ([a917a81](https://github.com/toa-io/toa/commit/a917a81fdc94eb23fd73182c0edf891e6b2df843))
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/storages.mongodb",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.320",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"description": "Toa MongoDB Storage Connector",
|
|
5
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
7
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -19,13 +20,13 @@
|
|
|
19
20
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
|
-
"@toa.io/
|
|
23
|
-
"@toa.io/
|
|
24
|
-
"@toa.io/
|
|
25
|
-
"@toa.io/generic": "1.0.0-alpha.
|
|
26
|
-
"@toa.io/pointer": "1.0.0-alpha.
|
|
27
|
-
"mongodb": "6.
|
|
28
|
-
"
|
|
23
|
+
"@toa.io/conveyor": "1.0.0-alpha.311",
|
|
24
|
+
"@toa.io/core": "1.0.0-alpha.320",
|
|
25
|
+
"@toa.io/definitions": "1.0.0-alpha.320",
|
|
26
|
+
"@toa.io/generic": "1.0.0-alpha.311",
|
|
27
|
+
"@toa.io/pointer": "1.0.0-alpha.311",
|
|
28
|
+
"mongodb": "7.6.0",
|
|
29
|
+
"openspan": "1.0.0-alpha.305"
|
|
29
30
|
},
|
|
30
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "5ef92ab939a596e4ac820965c59c015820eba7b2"
|
|
31
32
|
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# MongoDB Storage
|
|
2
|
+
|
|
3
|
+
## Tracing
|
|
4
|
+
|
|
5
|
+
Commands are recorded as `client` spans within the trace of the current invocation.
|
|
6
|
+
|
|
7
|
+
Spans are named `{command} {collection}` and carry `db.*` attributes following the
|
|
8
|
+
[OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/database/mongodb/):
|
|
9
|
+
`db.system`, `db.namespace`, `db.collection.name`, `db.operation.name`.
|
|
10
|
+
|
|
11
|
+
Commands executed outside of a sampled trace context (e.g. index management on startup)
|
|
12
|
+
and internal driver commands (`hello`, `ping`, authentication) are not recorded.
|
|
13
|
+
|
|
14
|
+
Monitoring is client-side only and does not affect the MongoDB server. Span recording
|
|
15
|
+
adds no waiting to the query path: exporting is buffered and happens in the background.
|
|
16
|
+
|
|
17
|
+
## Stream tokens
|
|
18
|
+
|
|
19
|
+
A [stream](/documentation/collections.md) ends with a token of changes where two things hold:
|
|
20
|
+
|
|
21
|
+
- **MongoDB runs as a replica set**, which it does wherever there is an outbox.
|
|
22
|
+
- **The collection keeps images** of what a record was before each change, which is how a change
|
|
23
|
+
that takes an entry out of the collection is told from a change outside it. They cost a copy of every
|
|
24
|
+
changed record for as long as the oplog holds it, so a collection keeps them only by a
|
|
25
|
+
[migration](/documentation/component/declaration.md#migrations):
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
# migrations/0003-images.yaml
|
|
29
|
+
- images: true
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`- images: false` stops keeping them, and a token issued before is then refused with `410`.
|
|
33
|
+
|
|
34
|
+
A token lasts as long as the oplog of the replica set holds the point it names — its window, which
|
|
35
|
+
the size of the oplog and the rate of writes decide.
|
|
36
|
+
|
|
37
|
+
A read from a token scans the oplog from the point it names: it costs what the whole replica set
|
|
38
|
+
wrote since, whatever of it concerns the collection.
|
package/src/client.js
CHANGED
|
@@ -1,28 +1,67 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* @typedef {import('mongodb').MongoClient} MongoClient
|
|
5
3
|
* @typedef {{ count: number, client: MongoClient }} Instance
|
|
6
4
|
* @typedef {import('@toa.io/core').Locator} Locator
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
import { console } from 'openspan'
|
|
8
|
+
import { environment } from '@toa.io/generic'
|
|
9
|
+
import { Connector } from '@toa.io/core'
|
|
10
|
+
import { resolve } from '@toa.io/pointer'
|
|
11
|
+
import { ID } from '@toa.io/definitions/storages.mongodb'
|
|
12
|
+
import { MongoClient } from 'mongodb'
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @type {Record<string, Promise<Instance>>}
|
|
16
16
|
*/
|
|
17
17
|
const INSTANCES = {}
|
|
18
18
|
|
|
19
|
-
class Client extends Connector {
|
|
19
|
+
export class Client extends Connector {
|
|
20
|
+
name
|
|
21
|
+
|
|
20
22
|
/**
|
|
21
23
|
* @public
|
|
22
24
|
* @type {import('mongodb').Collection}
|
|
23
25
|
*/
|
|
24
26
|
collection
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* The outbox rows of this component, absent unless something consumes its events. Created
|
|
30
|
+
* eagerly beside the entity collection, because a transaction cannot create a collection and
|
|
31
|
+
* an index build cannot run inside one.
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
* @type {import('mongodb').Collection | undefined}
|
|
35
|
+
*/
|
|
36
|
+
outbox
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The calls this component has made good on, absent unless it declares `once` anywhere.
|
|
40
|
+
* Created eagerly beside the entity collection, for the reason the outbox's is.
|
|
41
|
+
*
|
|
42
|
+
* @public
|
|
43
|
+
* @type {import('mongodb').Collection | undefined}
|
|
44
|
+
*/
|
|
45
|
+
inbox
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether this deployment can run transactions at all. A standalone mongod cannot, and an
|
|
49
|
+
* outbox without atomicity is worse than none, so the storage falls back to inline emission.
|
|
50
|
+
*
|
|
51
|
+
* @public
|
|
52
|
+
* @type {boolean}
|
|
53
|
+
*/
|
|
54
|
+
transactional = false
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The database this component's collections live in, which is where the migration state
|
|
58
|
+
* is kept as well.
|
|
59
|
+
*
|
|
60
|
+
* @public
|
|
61
|
+
* @type {import('mongodb').Db}
|
|
62
|
+
*/
|
|
63
|
+
db
|
|
64
|
+
|
|
26
65
|
/**
|
|
27
66
|
* @private
|
|
28
67
|
* @type {Locator}
|
|
@@ -41,13 +80,30 @@ class Client extends Connector {
|
|
|
41
80
|
*/
|
|
42
81
|
key
|
|
43
82
|
|
|
83
|
+
/**
|
|
84
|
+
* @private
|
|
85
|
+
* @type {boolean}
|
|
86
|
+
*/
|
|
87
|
+
publishes
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @private
|
|
91
|
+
* @type {boolean}
|
|
92
|
+
*/
|
|
93
|
+
claims
|
|
94
|
+
|
|
44
95
|
/**
|
|
45
96
|
* @param {Locator} locator
|
|
97
|
+
* @param {boolean} [publishes] whether this component publishes anything
|
|
98
|
+
* @param {boolean} [claims] whether any of its operations declares `once`
|
|
46
99
|
*/
|
|
47
|
-
constructor
|
|
100
|
+
constructor(locator, publishes = false, claims = false) {
|
|
48
101
|
super()
|
|
49
102
|
|
|
50
103
|
this.locator = locator
|
|
104
|
+
this.name = locator.lowercase
|
|
105
|
+
this.publishes = publishes
|
|
106
|
+
this.claims = claims
|
|
51
107
|
}
|
|
52
108
|
|
|
53
109
|
/**
|
|
@@ -55,29 +111,66 @@ class Client extends Connector {
|
|
|
55
111
|
* @override
|
|
56
112
|
* @return {Promise<void>}
|
|
57
113
|
*/
|
|
58
|
-
async open
|
|
114
|
+
async open() {
|
|
59
115
|
const urls = await this.resolveURLs()
|
|
60
116
|
const dbname = this.resolveDB()
|
|
61
|
-
const collname = this.locator.lowercase
|
|
62
117
|
|
|
63
118
|
this.key = getKey(dbname, urls)
|
|
64
119
|
|
|
65
|
-
|
|
120
|
+
try {
|
|
121
|
+
INSTANCES[this.key] ??= this.createInstance(urls)
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('Failed to connect to MongoDB', { urls, error })
|
|
124
|
+
}
|
|
66
125
|
|
|
67
126
|
this.instance = await INSTANCES[this.key]
|
|
68
127
|
this.instance.count++
|
|
69
128
|
|
|
70
129
|
const db = this.instance.client.db(dbname)
|
|
71
130
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (e.code !== ALREADY_EXISTS) {
|
|
76
|
-
throw e
|
|
77
|
-
}
|
|
131
|
+
this.db = db
|
|
132
|
+
this.collection = await collection(db, this.name)
|
|
133
|
+
this.transactional = await transactional(db)
|
|
78
134
|
|
|
79
|
-
|
|
135
|
+
/*
|
|
136
|
+
* The outbox may fall back and this may not: inline emission still delivers, where a call
|
|
137
|
+
* that is not recorded is a call that will be made twice, which is the opposite of what was
|
|
138
|
+
* asked for. So this refuses rather than warns.
|
|
139
|
+
*/
|
|
140
|
+
if (this.claims) {
|
|
141
|
+
if (!this.transactional)
|
|
142
|
+
throw new Error(
|
|
143
|
+
`Component '${this.name}' declares 'once', which needs a MongoDB replica set ` +
|
|
144
|
+
'or a sharded cluster to commit a call with the entity it changed'
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
this.inbox = await collection(db, this.name + INBOX)
|
|
80
148
|
}
|
|
149
|
+
|
|
150
|
+
if (!this.publishes) return
|
|
151
|
+
|
|
152
|
+
if (this.transactional) this.outbox = await collection(db, this.name + OUTBOX)
|
|
153
|
+
else
|
|
154
|
+
console.warn(
|
|
155
|
+
'MongoDB is not a replica set; events are emitted inline, without an outbox',
|
|
156
|
+
{ collection: this.name }
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Runs `fn` in a transaction and answers what it returned. The driver may call `fn` more
|
|
162
|
+
* than once, so it must not hold state of its own — an outbox row is built by the caller
|
|
163
|
+
* and reused, and a rolled back attempt leaves nothing behind.
|
|
164
|
+
*
|
|
165
|
+
* @public
|
|
166
|
+
* @template T
|
|
167
|
+
* @param {(session: import('mongodb').ClientSession) => Promise<T>} fn
|
|
168
|
+
* @return {Promise<T>}
|
|
169
|
+
*/
|
|
170
|
+
async transaction(fn) {
|
|
171
|
+
return this.instance.client.withSession(async (session) =>
|
|
172
|
+
session.withTransaction(async () => fn(session))
|
|
173
|
+
)
|
|
81
174
|
}
|
|
82
175
|
|
|
83
176
|
/**
|
|
@@ -85,14 +178,26 @@ class Client extends Connector {
|
|
|
85
178
|
* @override
|
|
86
179
|
* @return {Promise<void>}
|
|
87
180
|
*/
|
|
88
|
-
async close
|
|
89
|
-
|
|
181
|
+
async close() {
|
|
182
|
+
/*
|
|
183
|
+
* What was never counted is not discounted. An `open` that threw between taking the
|
|
184
|
+
* instance and incrementing it leaves the count one high, and a client nothing ever
|
|
185
|
+
* closes — which a process that is taken down and built again, as a halt does, would
|
|
186
|
+
* otherwise leak once per cycle.
|
|
187
|
+
*/
|
|
188
|
+
if (this.instance === undefined) return
|
|
189
|
+
|
|
190
|
+
const instance = this.instance
|
|
191
|
+
|
|
192
|
+
this.instance = undefined
|
|
90
193
|
|
|
91
194
|
instance.count--
|
|
92
195
|
|
|
93
196
|
if (instance.count === 0) {
|
|
94
197
|
await instance.client.close()
|
|
95
|
-
|
|
198
|
+
|
|
199
|
+
// another `open` may have taken it in the meantime, and that one is not this one
|
|
200
|
+
if ((await INSTANCES[this.key]) === instance) delete INSTANCES[this.key]
|
|
96
201
|
}
|
|
97
202
|
}
|
|
98
203
|
|
|
@@ -101,11 +206,11 @@ class Client extends Connector {
|
|
|
101
206
|
* @param {string[]} urls
|
|
102
207
|
* @return {Promise<Instance>}
|
|
103
208
|
*/
|
|
104
|
-
async createInstance
|
|
209
|
+
async createInstance(urls) {
|
|
105
210
|
const client = new MongoClient(urls.join(','), OPTIONS)
|
|
106
211
|
const hosts = urls.map((str) => new URL(str).host)
|
|
107
212
|
|
|
108
|
-
console.info('Connecting to MongoDB
|
|
213
|
+
console.info('Connecting to MongoDB', { address: hosts.join(', ') })
|
|
109
214
|
|
|
110
215
|
await client.connect()
|
|
111
216
|
|
|
@@ -119,9 +224,11 @@ class Client extends Connector {
|
|
|
119
224
|
* @private
|
|
120
225
|
* @return {Promise<string[]>}
|
|
121
226
|
*/
|
|
122
|
-
async resolveURLs
|
|
123
|
-
|
|
124
|
-
|
|
227
|
+
async resolveURLs() {
|
|
228
|
+
// Toa's own development stack is not on the conventional ports: the applications built on
|
|
229
|
+
// Toa are, and they share the machine. See CONTRIBUTING.md.
|
|
230
|
+
if (environment.get('TOA_DEV') === '1') {
|
|
231
|
+
return ['mongodb://developer:secret@localhost:31020']
|
|
125
232
|
} else {
|
|
126
233
|
return await resolve(ID, this.locator.id)
|
|
127
234
|
}
|
|
@@ -131,29 +238,63 @@ class Client extends Connector {
|
|
|
131
238
|
* @private
|
|
132
239
|
* @return {string}
|
|
133
240
|
*/
|
|
134
|
-
resolveDB
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
241
|
+
resolveDB() {
|
|
242
|
+
const scope = environment.scope()
|
|
243
|
+
const length = Buffer.byteLength(scope)
|
|
138
244
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
245
|
+
// MongoDB refuses the name only when something is first written, far from what caused it
|
|
246
|
+
if (length > MAX_DB_LENGTH)
|
|
247
|
+
throw new Error(
|
|
248
|
+
`Database name '${scope}' is ${length} bytes, and MongoDB takes no more than ` +
|
|
249
|
+
`${MAX_DB_LENGTH}: shorten TOA_CONTEXT or TOA_SUFFIX`
|
|
250
|
+
)
|
|
142
251
|
|
|
143
|
-
|
|
252
|
+
return scope
|
|
144
253
|
}
|
|
145
254
|
}
|
|
146
255
|
|
|
147
|
-
|
|
256
|
+
/** what MongoDB takes for a database name, in bytes */
|
|
257
|
+
const MAX_DB_LENGTH = 63
|
|
258
|
+
|
|
259
|
+
function getKey(db, urls) {
|
|
148
260
|
return db + ':' + urls.sort().join(' ')
|
|
149
261
|
}
|
|
150
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Concurrent pods race to create the same collection, and losing that race is not an error.
|
|
265
|
+
*/
|
|
266
|
+
async function collection(db, name) {
|
|
267
|
+
try {
|
|
268
|
+
return await db.createCollection(name)
|
|
269
|
+
} catch (e) {
|
|
270
|
+
if (e.code !== ALREADY_EXISTS) throw e
|
|
271
|
+
|
|
272
|
+
return db.collection(name)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function transactional(db) {
|
|
277
|
+
try {
|
|
278
|
+
const hello = await db.admin().command({ hello: 1 })
|
|
279
|
+
|
|
280
|
+
return hello.setName !== undefined || hello.msg === 'isdbgrid'
|
|
281
|
+
} catch (e) {
|
|
282
|
+
console.warn('MongoDB transaction support could not be determined', { error: e })
|
|
283
|
+
|
|
284
|
+
return false
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* `monitorCommands` is deliberately absent. It makes the driver materialize every reply
|
|
290
|
+
* eagerly to populate the monitoring event (`CommandSucceededEvent`), which defeats the
|
|
291
|
+
* lazy per-document deserialization a cursor exists for — a 100-document batch is then
|
|
292
|
+
* deserialized twice. `Storage` times its own calls instead.
|
|
293
|
+
*/
|
|
151
294
|
const OPTIONS = {
|
|
152
|
-
ignoreUndefined: true
|
|
153
|
-
connectTimeoutMS: 0,
|
|
154
|
-
serverSelectionTimeoutMS: 0
|
|
295
|
+
ignoreUndefined: true
|
|
155
296
|
}
|
|
156
297
|
|
|
157
298
|
const ALREADY_EXISTS = 48
|
|
158
|
-
|
|
159
|
-
|
|
299
|
+
const OUTBOX = '_outbox'
|
|
300
|
+
const INBOX = '_inbox'
|
package/src/factory.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { Client } from './client.js'
|
|
2
|
+
import { Storage } from './storage.js'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
4
|
+
export class Factory {
|
|
5
|
+
storage(locator, entity, options = {}) {
|
|
6
|
+
const client = new Client(locator, options.outbox === true, options.inbox === true)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
storage (locator, entity) {
|
|
9
|
-
const client = new Client(locator)
|
|
10
|
-
const connection = new Collection(client)
|
|
11
|
-
|
|
12
|
-
return new Storage(connection, entity)
|
|
8
|
+
return new Storage(client, entity)
|
|
13
9
|
}
|
|
14
10
|
}
|
|
15
|
-
|
|
16
|
-
exports.Factory = Factory
|