@fintekkers/ledger-models 0.3.0 → 0.3.1
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/node/wrappers/models/utils/datetime.d.ts +14 -0
- package/node/wrappers/models/utils/datetime.js +20 -0
- package/node/wrappers/models/utils/datetime.js.map +1 -1
- package/node/wrappers/models/utils/datetime.test.js +38 -0
- package/node/wrappers/models/utils/datetime.test.js.map +1 -1
- package/node/wrappers/models/utils/datetime.test.ts +46 -0
- package/node/wrappers/models/utils/datetime.ts +22 -0
- package/package.json +1 -1
|
@@ -2,6 +2,20 @@ import { LocalTimestampProto } from '../../../fintekkers/models/util/local_times
|
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
3
|
declare class ZonedDateTime {
|
|
4
4
|
private proto;
|
|
5
|
+
/**
|
|
6
|
+
* Wraps a LocalTimestampProto.
|
|
7
|
+
*
|
|
8
|
+
* Throws if `time_zone` is empty/whitespace — luxon's DateTime would
|
|
9
|
+
* otherwise silently produce an invalid DateTime (isValid=false,
|
|
10
|
+
* year=NaN), which propagates as silent corruption rather than a clear
|
|
11
|
+
* failure. See second-brain#276 for the original report from
|
|
12
|
+
* backend-dev-ledger during #268 verification.
|
|
13
|
+
*
|
|
14
|
+
* Callers with optional/unset timestamps should gate
|
|
15
|
+
* `new ZonedDateTime(parent.getAsOf())` with a `parent.hasAsOf()`
|
|
16
|
+
* check at the call site rather than relying on the constructor to
|
|
17
|
+
* substitute a default.
|
|
18
|
+
*/
|
|
5
19
|
constructor(proto: LocalTimestampProto);
|
|
6
20
|
getTimezone(): string;
|
|
7
21
|
getSeconds(): number;
|
|
@@ -5,7 +5,27 @@ const local_timestamp_pb_1 = require("../../../fintekkers/models/util/local_time
|
|
|
5
5
|
const timestamp_pb_1 = require("google-protobuf/google/protobuf/timestamp_pb");
|
|
6
6
|
const luxon_1 = require("luxon");
|
|
7
7
|
class ZonedDateTime {
|
|
8
|
+
/**
|
|
9
|
+
* Wraps a LocalTimestampProto.
|
|
10
|
+
*
|
|
11
|
+
* Throws if `time_zone` is empty/whitespace — luxon's DateTime would
|
|
12
|
+
* otherwise silently produce an invalid DateTime (isValid=false,
|
|
13
|
+
* year=NaN), which propagates as silent corruption rather than a clear
|
|
14
|
+
* failure. See second-brain#276 for the original report from
|
|
15
|
+
* backend-dev-ledger during #268 verification.
|
|
16
|
+
*
|
|
17
|
+
* Callers with optional/unset timestamps should gate
|
|
18
|
+
* `new ZonedDateTime(parent.getAsOf())` with a `parent.hasAsOf()`
|
|
19
|
+
* check at the call site rather than relying on the constructor to
|
|
20
|
+
* substitute a default.
|
|
21
|
+
*/
|
|
8
22
|
constructor(proto) {
|
|
23
|
+
const tz = proto.getTimeZone();
|
|
24
|
+
if (!tz || tz.trim().length === 0) {
|
|
25
|
+
throw new Error("LocalTimestampProto.time_zone is required but was empty. "
|
|
26
|
+
+ "Producers must set time_zone (e.g. \"UTC\" or \"America/New_York\") "
|
|
27
|
+
+ "when populating LocalTimestampProto. See second-brain#276.");
|
|
28
|
+
}
|
|
9
29
|
this.proto = proto;
|
|
10
30
|
}
|
|
11
31
|
getTimezone() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"datetime.js","sourceRoot":"","sources":["datetime.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"datetime.js","sourceRoot":"","sources":["datetime.ts"],"names":[],"mappings":"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"}
|
|
@@ -50,4 +50,42 @@ test('test the date time', () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
50
50
|
//Expect timestamp match
|
|
51
51
|
expect(timestampStr).toMatch(/^[0-9]{4}\/[0-9]{2}\/[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/);
|
|
52
52
|
}));
|
|
53
|
+
// second-brain#276 — lock in that the ZonedDateTime constructor throws on
|
|
54
|
+
// empty time_zone. Pre-fix: luxon's DateTime silently produced
|
|
55
|
+
// isValid=false / year=NaN, indistinguishable from a real value at most
|
|
56
|
+
// call sites until much later. Now: loud failure at construction.
|
|
57
|
+
describe('ZonedDateTime constructor — second-brain#276', () => {
|
|
58
|
+
test('throws when time_zone is empty (proto3 default)', () => {
|
|
59
|
+
const proto = new local_timestamp_pb_1.LocalTimestampProto();
|
|
60
|
+
const ts = new timestamp_pb_js_1.Timestamp();
|
|
61
|
+
ts.setSeconds(1700000000);
|
|
62
|
+
proto.setTimestamp(ts);
|
|
63
|
+
// time_zone left at the proto3 default ""
|
|
64
|
+
expect(() => new datetime_1.ZonedDateTime(proto)).toThrow(/time_zone is required/);
|
|
65
|
+
});
|
|
66
|
+
test('throws when time_zone is whitespace-only', () => {
|
|
67
|
+
const proto = new local_timestamp_pb_1.LocalTimestampProto();
|
|
68
|
+
const ts = new timestamp_pb_js_1.Timestamp();
|
|
69
|
+
ts.setSeconds(1700000000);
|
|
70
|
+
proto.setTimestamp(ts);
|
|
71
|
+
proto.setTimeZone(' ');
|
|
72
|
+
expect(() => new datetime_1.ZonedDateTime(proto)).toThrow(/time_zone is required/);
|
|
73
|
+
});
|
|
74
|
+
test('throws on fully-default LocalTimestampProto', () => {
|
|
75
|
+
// No timestamp, no time_zone — wholly default instance.
|
|
76
|
+
expect(() => new datetime_1.ZonedDateTime(new local_timestamp_pb_1.LocalTimestampProto()))
|
|
77
|
+
.toThrow(/time_zone is required/);
|
|
78
|
+
});
|
|
79
|
+
test('happy path with UTC still constructs successfully', () => {
|
|
80
|
+
const proto = new local_timestamp_pb_1.LocalTimestampProto();
|
|
81
|
+
const ts = new timestamp_pb_js_1.Timestamp();
|
|
82
|
+
ts.setSeconds(1700000000);
|
|
83
|
+
proto.setTimestamp(ts);
|
|
84
|
+
proto.setTimeZone('UTC');
|
|
85
|
+
const zdt = new datetime_1.ZonedDateTime(proto);
|
|
86
|
+
const dt = zdt.toDateTime();
|
|
87
|
+
expect(dt.isValid).toBe(true);
|
|
88
|
+
expect(dt.year).toBe(2023);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
53
91
|
//# sourceMappingURL=datetime.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"datetime.test.js","sourceRoot":"","sources":["datetime.test.ts"],"names":[],"mappings":";;;;;;;;;;;AACA,2EAA0E;AAC1E,iFAAsH;AACtH,2FAAqF;AACrF,mDAAgD;AAChD,yCAA2C;AAC3C,mEAA6D;AAC7D,2FAAuF;AACvF,qFAA4E;AAC5E,wEAAwE;AAExE,IAAI,CAAC,oBAAoB,EAAE,GAAS,EAAE;IAClC,MAAM,mBAAmB,GAAG,IAAI,wCAAmB,EAAE,CAAC;IACtD,MAAM,SAAS,GAAG,IAAI,2BAAS,EAAE,CAAC;IAClC,SAAS,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,kBAAkB;IACpD,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB;IAC7C,mBAAmB,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IAEhD,oBAAoB;IAChB,mBAAmB,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,IAAI,wBAAa,CAAC,mBAAmB,CAAC,CAAC;IAEnD,IAAI,kBAAkB,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,CAAC;IACrD,MAAM,CAAC,kBAAkB,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IACnD,MAAM,CAAC,kBAAkB,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAEjD,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,IAAI,YAAG,EAAE,CAAC;IAC5B,SAAS,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;IACvC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,eAAe,EAAE,CAAC,CAAC;IAE/C,MAAM,QAAQ,GAAG,IAAI,2BAAa,EAAE,CAAC;IAErC,iBAAiB;IACjB,QAAQ,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;IACpC,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IAC3B,QAAQ,CAAC,eAAe,CAAC,+BAAiB,CAAC,YAAY,CAAC,CAAC;IACzD,QAAQ,CAAC,eAAe,CAAC,+BAAiB,CAAC,WAAW,CAAC,CAAC;IAExD,aAAa;IACb,MAAM,MAAM,GAAG,IAAI,gCAAa,EAAE,CAAC;IACnC,MAAM,CAAC,QAAQ,CAAC,qBAAU,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACtC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAE3B,MAAM,GAAG,GAAG,IAAI,mBAAQ,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,YAAY,GAAG,GAAG,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAEjD,wBAAwB;IACxB,MAAM,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,2DAA2D,CAAC,CAAC;AAC9F,CAAC,CAAA,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"datetime.test.js","sourceRoot":"","sources":["datetime.test.ts"],"names":[],"mappings":";;;;;;;;;;;AACA,2EAA0E;AAC1E,iFAAsH;AACtH,2FAAqF;AACrF,mDAAgD;AAChD,yCAA2C;AAC3C,mEAA6D;AAC7D,2FAAuF;AACvF,qFAA4E;AAC5E,wEAAwE;AAExE,IAAI,CAAC,oBAAoB,EAAE,GAAS,EAAE;IAClC,MAAM,mBAAmB,GAAG,IAAI,wCAAmB,EAAE,CAAC;IACtD,MAAM,SAAS,GAAG,IAAI,2BAAS,EAAE,CAAC;IAClC,SAAS,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,kBAAkB;IACpD,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB;IAC7C,mBAAmB,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IAEhD,oBAAoB;IAChB,mBAAmB,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,IAAI,wBAAa,CAAC,mBAAmB,CAAC,CAAC;IAEnD,IAAI,kBAAkB,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,CAAC;IACrD,MAAM,CAAC,kBAAkB,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IACnD,MAAM,CAAC,kBAAkB,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAEjD,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,IAAI,YAAG,EAAE,CAAC;IAC5B,SAAS,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;IACvC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,eAAe,EAAE,CAAC,CAAC;IAE/C,MAAM,QAAQ,GAAG,IAAI,2BAAa,EAAE,CAAC;IAErC,iBAAiB;IACjB,QAAQ,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;IACpC,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IAC3B,QAAQ,CAAC,eAAe,CAAC,+BAAiB,CAAC,YAAY,CAAC,CAAC;IACzD,QAAQ,CAAC,eAAe,CAAC,+BAAiB,CAAC,WAAW,CAAC,CAAC;IAExD,aAAa;IACb,MAAM,MAAM,GAAG,IAAI,gCAAa,EAAE,CAAC;IACnC,MAAM,CAAC,QAAQ,CAAC,qBAAU,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACtC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAE3B,MAAM,GAAG,GAAG,IAAI,mBAAQ,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,YAAY,GAAG,GAAG,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAEjD,wBAAwB;IACxB,MAAM,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,2DAA2D,CAAC,CAAC;AAC9F,CAAC,CAAA,CAAC,CAAC;AAEH,0EAA0E;AAC1E,+DAA+D;AAC/D,wEAAwE;AACxE,kEAAkE;AAElE,QAAQ,CAAC,8CAA8C,EAAE,GAAG,EAAE;IAC1D,IAAI,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,KAAK,GAAG,IAAI,wCAAmB,EAAE,CAAC;QACxC,MAAM,EAAE,GAAG,IAAI,2BAAS,EAAE,CAAC;QAC3B,EAAE,CAAC,UAAU,CAAC,UAAa,CAAC,CAAC;QAC7B,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QACvB,0CAA0C;QAE1C,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,wBAAa,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,KAAK,GAAG,IAAI,wCAAmB,EAAE,CAAC;QACxC,MAAM,EAAE,GAAG,IAAI,2BAAS,EAAE,CAAC;QAC3B,EAAE,CAAC,UAAU,CAAC,UAAa,CAAC,CAAC;QAC7B,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QACvB,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAEzB,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,wBAAa,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,wDAAwD;QACxD,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,wBAAa,CAAC,IAAI,wCAAmB,EAAE,CAAC,CAAC;aACrD,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,KAAK,GAAG,IAAI,wCAAmB,EAAE,CAAC;QACxC,MAAM,EAAE,GAAG,IAAI,2BAAS,EAAE,CAAC;QAC3B,EAAE,CAAC,UAAU,CAAC,UAAa,CAAC,CAAC;QAC7B,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QACvB,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAEzB,MAAM,GAAG,GAAG,IAAI,wBAAa,CAAC,KAAK,CAAC,CAAC;QACrC,MAAM,EAAE,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;QAC5B,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -48,4 +48,50 @@ test('test the date time', async () => {
|
|
|
48
48
|
|
|
49
49
|
//Expect timestamp match
|
|
50
50
|
expect(timestampStr).toMatch(/^[0-9]{4}\/[0-9]{2}\/[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// second-brain#276 — lock in that the ZonedDateTime constructor throws on
|
|
54
|
+
// empty time_zone. Pre-fix: luxon's DateTime silently produced
|
|
55
|
+
// isValid=false / year=NaN, indistinguishable from a real value at most
|
|
56
|
+
// call sites until much later. Now: loud failure at construction.
|
|
57
|
+
|
|
58
|
+
describe('ZonedDateTime constructor — second-brain#276', () => {
|
|
59
|
+
test('throws when time_zone is empty (proto3 default)', () => {
|
|
60
|
+
const proto = new LocalTimestampProto();
|
|
61
|
+
const ts = new Timestamp();
|
|
62
|
+
ts.setSeconds(1_700_000_000);
|
|
63
|
+
proto.setTimestamp(ts);
|
|
64
|
+
// time_zone left at the proto3 default ""
|
|
65
|
+
|
|
66
|
+
expect(() => new ZonedDateTime(proto)).toThrow(/time_zone is required/);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('throws when time_zone is whitespace-only', () => {
|
|
70
|
+
const proto = new LocalTimestampProto();
|
|
71
|
+
const ts = new Timestamp();
|
|
72
|
+
ts.setSeconds(1_700_000_000);
|
|
73
|
+
proto.setTimestamp(ts);
|
|
74
|
+
proto.setTimeZone(' ');
|
|
75
|
+
|
|
76
|
+
expect(() => new ZonedDateTime(proto)).toThrow(/time_zone is required/);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('throws on fully-default LocalTimestampProto', () => {
|
|
80
|
+
// No timestamp, no time_zone — wholly default instance.
|
|
81
|
+
expect(() => new ZonedDateTime(new LocalTimestampProto()))
|
|
82
|
+
.toThrow(/time_zone is required/);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('happy path with UTC still constructs successfully', () => {
|
|
86
|
+
const proto = new LocalTimestampProto();
|
|
87
|
+
const ts = new Timestamp();
|
|
88
|
+
ts.setSeconds(1_700_000_000);
|
|
89
|
+
proto.setTimestamp(ts);
|
|
90
|
+
proto.setTimeZone('UTC');
|
|
91
|
+
|
|
92
|
+
const zdt = new ZonedDateTime(proto);
|
|
93
|
+
const dt = zdt.toDateTime();
|
|
94
|
+
expect(dt.isValid).toBe(true);
|
|
95
|
+
expect(dt.year).toBe(2023);
|
|
96
|
+
});
|
|
51
97
|
});
|
|
@@ -5,7 +5,29 @@ import { DateTime } from 'luxon';
|
|
|
5
5
|
class ZonedDateTime {
|
|
6
6
|
private proto: LocalTimestampProto;
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Wraps a LocalTimestampProto.
|
|
10
|
+
*
|
|
11
|
+
* Throws if `time_zone` is empty/whitespace — luxon's DateTime would
|
|
12
|
+
* otherwise silently produce an invalid DateTime (isValid=false,
|
|
13
|
+
* year=NaN), which propagates as silent corruption rather than a clear
|
|
14
|
+
* failure. See second-brain#276 for the original report from
|
|
15
|
+
* backend-dev-ledger during #268 verification.
|
|
16
|
+
*
|
|
17
|
+
* Callers with optional/unset timestamps should gate
|
|
18
|
+
* `new ZonedDateTime(parent.getAsOf())` with a `parent.hasAsOf()`
|
|
19
|
+
* check at the call site rather than relying on the constructor to
|
|
20
|
+
* substitute a default.
|
|
21
|
+
*/
|
|
8
22
|
constructor(proto: LocalTimestampProto) {
|
|
23
|
+
const tz = proto.getTimeZone();
|
|
24
|
+
if (!tz || tz.trim().length === 0) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
"LocalTimestampProto.time_zone is required but was empty. "
|
|
27
|
+
+ "Producers must set time_zone (e.g. \"UTC\" or \"America/New_York\") "
|
|
28
|
+
+ "when populating LocalTimestampProto. See second-brain#276."
|
|
29
|
+
);
|
|
30
|
+
}
|
|
9
31
|
this.proto = proto;
|
|
10
32
|
}
|
|
11
33
|
|