@onlineapps/service-wrapper 3.0.6 → 3.0.9
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/package.json +1 -1
- package/src/OperationContext.js +108 -2
package/package.json
CHANGED
package/src/OperationContext.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { ValidationError } = require('./ErrorMapper');
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* OperationContext — immutable per-invocation context passed to every
|
|
5
7
|
* business handler by ServiceWrapper.invokeOperation().
|
|
@@ -29,12 +31,19 @@
|
|
|
29
31
|
* abortSignal — AbortSignal
|
|
30
32
|
* config — read-only service config snapshot
|
|
31
33
|
*
|
|
34
|
+
* Prototype-level helpers (not fields, not frozen on the instance):
|
|
35
|
+
* - normalizeDateRange(dateFrom, dateTo) → { from: Date, to: Date }
|
|
36
|
+
* Promoted from property/v3 statements handler (W2-R-D3) so every biz
|
|
37
|
+
* service can normalise `{dateFrom, dateTo}` query parameters the same
|
|
38
|
+
* way. Fail-fast: throws ValidationError on null/undefined, non-string,
|
|
39
|
+
* unparseable, or inverted range.
|
|
40
|
+
*
|
|
32
41
|
* Design notes:
|
|
33
|
-
* - No getter helpers, no `.scoped()`, no methods. The handler destructures
|
|
34
|
-
* what it needs: `const { tenant_id, db, logger } = ctx;`.
|
|
35
42
|
* - `Object.freeze` prevents reassignment of top-level fields
|
|
36
43
|
* (`ctx.tenant_id = 99` throws in strict mode) but is shallow by design —
|
|
37
44
|
* `ctx.db.query(...)` and `ctx.logger.info(...)` must remain callable.
|
|
45
|
+
* Prototype methods like `normalizeDateRange` live on the prototype and
|
|
46
|
+
* are therefore unaffected by the instance freeze.
|
|
38
47
|
* - Lifecycle (release of db client, logger flush) is owned by ContextBuilder;
|
|
39
48
|
* see its `build()` return contract.
|
|
40
49
|
*/
|
|
@@ -54,6 +63,103 @@ class OperationContext {
|
|
|
54
63
|
Object.assign(this, fields);
|
|
55
64
|
Object.freeze(this);
|
|
56
65
|
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Normalise a `{dateFrom, dateTo}` pair into two JavaScript `Date` objects
|
|
69
|
+
* suitable for SQL range predicates (e.g. `BETWEEN :from AND :to`).
|
|
70
|
+
*
|
|
71
|
+
* Input rules:
|
|
72
|
+
* - Bare ISO date `YYYY-MM-DD`:
|
|
73
|
+
* from → 00:00:00.000 UTC of that day
|
|
74
|
+
* to → 23:59:59.999 UTC of that day
|
|
75
|
+
* - ISO datetime (`YYYY-MM-DDTHH:MM[:SS[.sss]][Z|±HH:MM]`):
|
|
76
|
+
* time is preserved as given (UTC-anchored by JS `Date` semantics).
|
|
77
|
+
*
|
|
78
|
+
* Fail-fast (throws {@link ValidationError}) if either argument is:
|
|
79
|
+
* - null / undefined
|
|
80
|
+
* - not a string
|
|
81
|
+
* - unparseable as ISO date or datetime
|
|
82
|
+
* and if the resulting `to` is earlier than `from`.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} dateFrom - ISO date or datetime string.
|
|
85
|
+
* @param {string} dateTo - ISO date or datetime string.
|
|
86
|
+
* @returns {{ from: Date, to: Date }}
|
|
87
|
+
* @throws {ValidationError}
|
|
88
|
+
*/
|
|
89
|
+
normalizeDateRange(dateFrom, dateTo) {
|
|
90
|
+
const from = OperationContext._parseBoundary(dateFrom, 'dateFrom', 'start');
|
|
91
|
+
const to = OperationContext._parseBoundary(dateTo, 'dateTo', 'end');
|
|
92
|
+
|
|
93
|
+
if (to.getTime() < from.getTime()) {
|
|
94
|
+
throw new ValidationError({
|
|
95
|
+
message:
|
|
96
|
+
'[OperationContext.normalizeDateRange] dateTo is earlier than dateFrom - ' +
|
|
97
|
+
'Expected ISO date string (YYYY-MM-DD) or ISO datetime'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { from, to };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @private
|
|
106
|
+
* @param {*} value - candidate boundary
|
|
107
|
+
* @param {string} field - 'dateFrom' | 'dateTo' (used in error messages)
|
|
108
|
+
* @param {'start'|'end'} mode - defaults applied to bare-date inputs
|
|
109
|
+
* @returns {Date}
|
|
110
|
+
*/
|
|
111
|
+
static _parseBoundary(value, field, mode) {
|
|
112
|
+
if (value === null || value === undefined) {
|
|
113
|
+
throw new ValidationError({
|
|
114
|
+
message:
|
|
115
|
+
`[OperationContext.normalizeDateRange] ${field} is null or undefined - ` +
|
|
116
|
+
'Expected ISO date string (YYYY-MM-DD) or ISO datetime'
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (typeof value !== 'string') {
|
|
120
|
+
throw new ValidationError({
|
|
121
|
+
message:
|
|
122
|
+
`[OperationContext.normalizeDateRange] ${field} is not a string - ` +
|
|
123
|
+
'Expected ISO date string (YYYY-MM-DD) or ISO datetime'
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const trimmed = value.trim();
|
|
128
|
+
if (trimmed.length === 0) {
|
|
129
|
+
throw new ValidationError({
|
|
130
|
+
message:
|
|
131
|
+
`[OperationContext.normalizeDateRange] ${field} is empty - ` +
|
|
132
|
+
'Expected ISO date string (YYYY-MM-DD) or ISO datetime'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const isoDate = /^\d{4}-\d{2}-\d{2}$/;
|
|
137
|
+
const isoDateTime = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:?\d{2})?$/;
|
|
138
|
+
|
|
139
|
+
let d;
|
|
140
|
+
if (isoDate.test(trimmed)) {
|
|
141
|
+
const suffix = mode === 'end' ? 'T23:59:59.999Z' : 'T00:00:00.000Z';
|
|
142
|
+
d = new Date(`${trimmed}${suffix}`);
|
|
143
|
+
} else if (isoDateTime.test(trimmed)) {
|
|
144
|
+
d = new Date(trimmed);
|
|
145
|
+
} else {
|
|
146
|
+
throw new ValidationError({
|
|
147
|
+
message:
|
|
148
|
+
`[OperationContext.normalizeDateRange] ${field} is unparseable - ` +
|
|
149
|
+
'Expected ISO date string (YYYY-MM-DD) or ISO datetime'
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (isNaN(d.getTime())) {
|
|
154
|
+
throw new ValidationError({
|
|
155
|
+
message:
|
|
156
|
+
`[OperationContext.normalizeDateRange] ${field} is unparseable - ` +
|
|
157
|
+
'Expected ISO date string (YYYY-MM-DD) or ISO datetime'
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return d;
|
|
162
|
+
}
|
|
57
163
|
}
|
|
58
164
|
|
|
59
165
|
module.exports = { OperationContext };
|