@toa.io/storages.mongodb 1.0.0-alpha.31 → 1.0.0-alpha.310

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/src/record.js CHANGED
@@ -1,32 +1,100 @@
1
- 'use strict'
1
+ export function to(entity) {
2
+ const { id, ...rest } = entity
3
+
4
+ return /** @type {toa.mongodb.Record} */ { _id: id, ...rest }
5
+ }
2
6
 
3
7
  /**
4
- * @param {toa.core.storages.Record} entity
5
- * @returns {toa.mongodb.Record}
8
+ * Renamed in the object the driver decoded, which is a new one for every document; an entity
9
+ * copies a state before an operation that writes changes it. A copy costs ten times the rename,
10
+ * and what it would keep is only `id` in front, which a reply does not promise.
6
11
  */
7
- const to = (entity) => {
8
- const {
9
- id,
10
- ...rest
11
- } = entity
12
+ export function from(record) {
13
+ if (record === undefined || record === null) return null
12
14
 
13
- return /** @type {toa.mongodb.Record} */ { _id: id, ...rest }
15
+ record.id = record._id
16
+ delete record._id
17
+
18
+ return record
14
19
  }
15
20
 
16
21
  /**
17
- * @param {toa.mongodb.Record} record
18
- * @returns {toa.core.storages.Record}
22
+ * How this collection's records are written and read back.
23
+ *
24
+ * A property the entity declares as a moment is held as a BSON date rather than as what the
25
+ * entity carries. It is what the TTL monitor reads — it skips a field that is not a date,
26
+ * silently — and what sorts and compares as a moment rather than as text or as a number that
27
+ * happens to be one. The entity keeps what it declared, so nothing outside this connector
28
+ * learns a type only one storage has.
29
+ *
30
+ * Two ways to say it, because they differ in what userland holds rather than in what is
31
+ * stored: `{ string, date-time }` for an application that carries ISO strings, and
32
+ * `{ integer, epoch-millis }` for one that carries what `Date.now()` answers. The system
33
+ * timestamps of every record are the second.
34
+ *
35
+ * Top-level properties only. One nested inside an object or an array is left as it is, and
36
+ * `norm` refuses it, so that the declaration does not mean two things.
37
+ *
38
+ * A component that declares no moment holds the plain pair, and pays nothing for any of this.
19
39
  */
20
- const from = (record) => {
21
- if (record === undefined || record === null) return null
40
+ export function codec(properties) {
41
+ /** @type {Array<[string, (value: Date) => unknown]>} */
42
+ const read = []
43
+
44
+ for (const [name, schema] of Object.entries(properties ?? {})) {
45
+ const cast = READ[schema?.format]
46
+
47
+ if (cast !== undefined && schema.type === TYPES[schema.format]) read.push([name, cast])
48
+ }
49
+
50
+ const dates = read.map(([name]) => name)
51
+
52
+ if (dates.length === 0) return { to, from, dates }
53
+
54
+ return {
55
+ dates,
22
56
 
23
- const {
24
- _id,
25
- ...rest
26
- } = record
57
+ // one way in for both: `Date` takes the milliseconds and the ISO string alike
58
+ to: (entity) => convert(to(entity), dates.map((name) => [name, date])),
27
59
 
28
- return { id: _id, ...rest }
60
+ from: (record) => {
61
+ const state = from(record)
62
+
63
+ return state === null ? null : convert(state, read)
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Writes into an object either `to` or `from` has just made, so nothing the caller holds is
70
+ * touched.
71
+ *
72
+ * @private
73
+ */
74
+ function convert(record, casts) {
75
+ for (const [name, cast] of casts) {
76
+ const value = record[name]
77
+
78
+ // absent, or the null a property that has not been written to holds
79
+ if (value === undefined || value === null) continue
80
+
81
+ record[name] = cast(value)
82
+ }
83
+
84
+ return record
85
+ }
86
+
87
+ const date = (value) => new Date(value)
88
+
89
+ /*
90
+ * What a record written before the property was declared a moment holds is what it was given,
91
+ * and it is answered as it is: a collection is converted by a migration rather than by every
92
+ * read that finds one.
93
+ */
94
+ const READ = {
95
+ 'date-time': (value) => (value instanceof Date ? value.toISOString() : value),
96
+ 'epoch-millis': (value) => (value instanceof Date ? value.getTime() : value)
29
97
  }
30
98
 
31
- exports.to = to
32
- exports.from = from
99
+ /** What each format is written on, so that one said of the wrong type is not acted on. */
100
+ const TYPES = { 'date-time': 'string', 'epoch-millis': 'integer' }