@fintekkers/ledger-models 0.4.5 → 0.4.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/node/fintekkers/models/transaction/transaction_type_pb.d.ts +2 -0
- package/node/fintekkers/models/transaction/transaction_type_pb.js +3 -1
- package/node/wrappers/models/security/security.d.ts +9 -3
- package/node/wrappers/models/security/security.js +57 -15
- package/node/wrappers/models/security/security.js.map +1 -1
- package/node/wrappers/models/security/security.lazy-hydrate.test.d.ts +1 -0
- package/node/wrappers/models/security/security.lazy-hydrate.test.js +127 -0
- package/node/wrappers/models/security/security.lazy-hydrate.test.js.map +1 -0
- package/node/wrappers/models/security/security.lazy-hydrate.test.ts +123 -0
- package/node/wrappers/models/security/security.ts +35 -17
- package/node/wrappers/models/transaction/transaction_type.js +2 -0
- package/node/wrappers/models/transaction/transaction_type.js.map +1 -1
- package/node/wrappers/models/transaction/transaction_type.ts +2 -0
- package/node/wrappers/util/link-cache.d.ts +56 -0
- package/node/wrappers/util/link-cache.js +92 -0
- package/node/wrappers/util/link-cache.js.map +1 -0
- package/node/wrappers/util/link-cache.test.d.ts +1 -0
- package/node/wrappers/util/link-cache.test.js +107 -0
- package/node/wrappers/util/link-cache.test.js.map +1 -0
- package/node/wrappers/util/link-cache.test.ts +112 -0
- package/node/wrappers/util/link-cache.ts +110 -0
- package/package.json +1 -1
|
@@ -32,7 +32,9 @@ proto.fintekkers.models.transaction.TransactionTypeProto = {
|
|
|
32
32
|
DEPOSIT: 3,
|
|
33
33
|
WITHDRAWAL: 4,
|
|
34
34
|
MATURATION: 5,
|
|
35
|
-
MATURATION_OFFSET: 6
|
|
35
|
+
MATURATION_OFFSET: 6,
|
|
36
|
+
PRINCIPAL_PAYDOWN: 7,
|
|
37
|
+
PRINCIPAL_PAYDOWN_OFFSET: 8
|
|
36
38
|
};
|
|
37
39
|
|
|
38
40
|
goog.object.extend(exports, proto.fintekkers.models.transaction);
|
|
@@ -28,10 +28,16 @@ declare class Security {
|
|
|
28
28
|
private static _uuidToProto;
|
|
29
29
|
private static _zonedDateTimeToProto;
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
31
|
+
* Lazy hydration. If this Security is in link mode, swap in the resolved
|
|
32
|
+
* proto from LinkCache. On cache miss, throws — caller must pre-warm via
|
|
33
|
+
* LinkResolver. See docs/adr/lazy-link-hydration.md.
|
|
34
|
+
*
|
|
35
|
+
* TS variant is cache-only (no fetcher hook) because the gRPC stubs are
|
|
36
|
+
* async and chaining the resolver into every getter would force every
|
|
37
|
+
* accessor to become async. Pre-warming through LinkResolver keeps the
|
|
38
|
+
* sync getter API.
|
|
33
39
|
*/
|
|
34
|
-
private
|
|
40
|
+
private ensureHydrated;
|
|
35
41
|
/**
|
|
36
42
|
* Factory method to create the appropriate Security subclass based on
|
|
37
43
|
* the proto's product_type. Dispatch rules:
|
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
26
|
const field_pb_1 = require("../../../fintekkers/models/position/field_pb");
|
|
4
27
|
const security_pb_1 = require("../../../fintekkers/models/security/security_pb");
|
|
@@ -8,6 +31,7 @@ const date_1 = require("../utils/date");
|
|
|
8
31
|
const product_type_pb_1 = require("../../../fintekkers/models/security/product_type_pb");
|
|
9
32
|
const identifier_1 = require("./identifier");
|
|
10
33
|
const product_hierarchy_1 = require("./product_hierarchy");
|
|
34
|
+
const LinkCacheModule = __importStar(require("../../util/link-cache"));
|
|
11
35
|
class Security {
|
|
12
36
|
constructor(proto) {
|
|
13
37
|
this.proto = proto;
|
|
@@ -54,15 +78,33 @@ class Security {
|
|
|
54
78
|
return asOf.toProto();
|
|
55
79
|
}
|
|
56
80
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
81
|
+
* Lazy hydration. If this Security is in link mode, swap in the resolved
|
|
82
|
+
* proto from LinkCache. On cache miss, throws — caller must pre-warm via
|
|
83
|
+
* LinkResolver. See docs/adr/lazy-link-hydration.md.
|
|
84
|
+
*
|
|
85
|
+
* TS variant is cache-only (no fetcher hook) because the gRPC stubs are
|
|
86
|
+
* async and chaining the resolver into every getter would force every
|
|
87
|
+
* accessor to become async. Pre-warming through LinkResolver keeps the
|
|
88
|
+
* sync getter API.
|
|
59
89
|
*/
|
|
60
|
-
|
|
61
|
-
if (this.proto.getIsLink())
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
90
|
+
ensureHydrated() {
|
|
91
|
+
if (!this.proto.getIsLink())
|
|
92
|
+
return;
|
|
93
|
+
const uuidProto = this.proto.getUuid();
|
|
94
|
+
if (!uuidProto) {
|
|
95
|
+
throw new Error("Cannot read fields on link-mode Security with no UUID set.");
|
|
96
|
+
}
|
|
97
|
+
const uuidKey = uuid_1.UUID.fromU8Array(uuidProto.getRawUuid_asU8()).toString();
|
|
98
|
+
const asOfProto = this.proto.getAsOf();
|
|
99
|
+
const asOf = asOfProto ? new datetime_1.ZonedDateTime(asOfProto) : null;
|
|
100
|
+
const cached = LinkCacheModule.SECURITY.get(uuidKey, asOf);
|
|
101
|
+
if (cached) {
|
|
102
|
+
this.proto = cached;
|
|
103
|
+
return;
|
|
65
104
|
}
|
|
105
|
+
throw new Error(`Cannot read fields on link-mode Security uuid=${uuidKey} `
|
|
106
|
+
+ `— LinkCache miss. Pre-warm via LinkResolver. `
|
|
107
|
+
+ `See docs/adr/lazy-link-hydration.md.`);
|
|
66
108
|
}
|
|
67
109
|
/**
|
|
68
110
|
* Factory method to create the appropriate Security subclass based on
|
|
@@ -236,15 +278,15 @@ class Security {
|
|
|
236
278
|
return new datetime_1.ZonedDateTime(asOf);
|
|
237
279
|
}
|
|
238
280
|
getAssetClass() {
|
|
239
|
-
this.
|
|
281
|
+
this.ensureHydrated();
|
|
240
282
|
return this.proto.getAssetClass();
|
|
241
283
|
}
|
|
242
284
|
getProductClass() {
|
|
243
|
-
this.
|
|
285
|
+
this.ensureHydrated();
|
|
244
286
|
throw new Error('Not implemented yet. See Java implementation for reference');
|
|
245
287
|
}
|
|
246
288
|
getProductType() {
|
|
247
|
-
this.
|
|
289
|
+
this.ensureHydrated();
|
|
248
290
|
const securityType = this.proto.getProductType();
|
|
249
291
|
const securityTypeString = Object.keys(product_type_pb_1.ProductTypeProto).find(key => product_type_pb_1.ProductTypeProto[key] === securityType);
|
|
250
292
|
return securityTypeString || 'UNKNOWN_SECURITY_TYPE';
|
|
@@ -254,7 +296,7 @@ class Security {
|
|
|
254
296
|
* Empty list if none are set. Throws on a link-mode Security.
|
|
255
297
|
*/
|
|
256
298
|
getIdentifiers() {
|
|
257
|
-
this.
|
|
299
|
+
this.ensureHydrated();
|
|
258
300
|
const list = this.proto.getIdentifiersList();
|
|
259
301
|
if (!list)
|
|
260
302
|
return [];
|
|
@@ -265,7 +307,7 @@ class Security {
|
|
|
265
307
|
* or undefined if none is present. Throws on a link-mode Security.
|
|
266
308
|
*/
|
|
267
309
|
getIdentifierByType(type) {
|
|
268
|
-
this.
|
|
310
|
+
this.ensureHydrated();
|
|
269
311
|
const list = this.proto.getIdentifiersList();
|
|
270
312
|
if (!list)
|
|
271
313
|
return undefined;
|
|
@@ -286,7 +328,7 @@ class Security {
|
|
|
286
328
|
* properly-formed bond).
|
|
287
329
|
*/
|
|
288
330
|
getIssueDate() {
|
|
289
|
-
this.
|
|
331
|
+
this.ensureHydrated();
|
|
290
332
|
const bond = this.getBondLikeDetails();
|
|
291
333
|
const date = bond ? bond.getIssueDate() : undefined;
|
|
292
334
|
return (0, date_1.localDateProtoToDate)(date);
|
|
@@ -298,7 +340,7 @@ class Security {
|
|
|
298
340
|
* code paths.
|
|
299
341
|
*/
|
|
300
342
|
getMaturityDate() {
|
|
301
|
-
this.
|
|
343
|
+
this.ensureHydrated();
|
|
302
344
|
const bond = this.getBondLikeDetails();
|
|
303
345
|
const date = bond ? bond.getMaturityDate() : undefined;
|
|
304
346
|
return (0, date_1.localDateProtoToDate)(date);
|
|
@@ -315,7 +357,7 @@ class Security {
|
|
|
315
357
|
return (_a = this.proto.getBondDetails()) !== null && _a !== void 0 ? _a : undefined;
|
|
316
358
|
}
|
|
317
359
|
getIssuerName() {
|
|
318
|
-
this.
|
|
360
|
+
this.ensureHydrated();
|
|
319
361
|
return this.proto.getIssuerName();
|
|
320
362
|
}
|
|
321
363
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security.js","sourceRoot":"","sources":["security.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"security.js","sourceRoot":"","sources":["security.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
const security_1 = __importDefault(require("./security"));
|
|
30
|
+
const uuid_1 = require("../utils/uuid");
|
|
31
|
+
const datetime_1 = require("../utils/datetime");
|
|
32
|
+
const LinkCache = __importStar(require("../../util/link-cache"));
|
|
33
|
+
const security_pb_1 = require("../../../fintekkers/models/security/security_pb");
|
|
34
|
+
const local_timestamp_pb_1 = require("../../../fintekkers/models/util/local_timestamp_pb");
|
|
35
|
+
const timestamp_pb_1 = require("google-protobuf/google/protobuf/timestamp_pb");
|
|
36
|
+
function makeAsOf(epochSecondsOffset = 0) {
|
|
37
|
+
const ts = new timestamp_pb_1.Timestamp();
|
|
38
|
+
ts.setSeconds(1700000000 + epochSecondsOffset);
|
|
39
|
+
ts.setNanos(0);
|
|
40
|
+
const proto = new local_timestamp_pb_1.LocalTimestampProto();
|
|
41
|
+
proto.setTimestamp(ts);
|
|
42
|
+
proto.setTimeZone('UTC');
|
|
43
|
+
return new datetime_1.ZonedDateTime(proto);
|
|
44
|
+
}
|
|
45
|
+
function makeFullProto(uuid, asOf, issuer = 'ACME') {
|
|
46
|
+
const p = new security_pb_1.SecurityProto();
|
|
47
|
+
p.setUuid(uuid.toUUIDProto());
|
|
48
|
+
p.setAsOf(asOf.toProto());
|
|
49
|
+
p.setIssuerName(issuer);
|
|
50
|
+
p.setAssetClass('Equity');
|
|
51
|
+
return p;
|
|
52
|
+
}
|
|
53
|
+
describe('Security lazy hydrate', () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
LinkCache.SECURITY.clear();
|
|
56
|
+
});
|
|
57
|
+
// ---- A. Hydration on accessors ----
|
|
58
|
+
test('A — getAssetClass on link-mode wrapper hydrates from cache', () => {
|
|
59
|
+
const uuid = uuid_1.UUID.fromU8Array(new Uint8Array(16).fill(0x42));
|
|
60
|
+
const asOf = makeAsOf();
|
|
61
|
+
const resolved = makeFullProto(uuid, asOf, 'ACME-resolved');
|
|
62
|
+
LinkCache.SECURITY.put(uuid.toString(), resolved, asOf);
|
|
63
|
+
const wrapper = new security_1.default(security_1.default.linkOf(uuid, asOf));
|
|
64
|
+
expect(wrapper.isLink()).toBe(true);
|
|
65
|
+
expect(wrapper.getAssetClass()).toBe('Equity');
|
|
66
|
+
// After first accessor, wrapper has swapped in the resolved proto.
|
|
67
|
+
expect(wrapper.proto.getIsLink()).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
test('A — link-safe accessors do not require hydration', () => {
|
|
70
|
+
const uuid = uuid_1.UUID.fromU8Array(new Uint8Array(16).fill(0x07));
|
|
71
|
+
const asOf = makeAsOf();
|
|
72
|
+
// Cache is empty — link-safe accessors must still work.
|
|
73
|
+
const wrapper = new security_1.default(security_1.default.linkOf(uuid, asOf));
|
|
74
|
+
expect(wrapper.isLink()).toBe(true);
|
|
75
|
+
expect(wrapper.getID().toString()).toBe(uuid.toString());
|
|
76
|
+
expect(wrapper.getAsOf().getSeconds()).toBe(asOf.getSeconds());
|
|
77
|
+
});
|
|
78
|
+
// ---- B. Cache behavior ----
|
|
79
|
+
test('B.i — first accessor call hydrates from a pre-populated cache', () => {
|
|
80
|
+
const uuid = uuid_1.UUID.fromU8Array(new Uint8Array(16).fill(0x01));
|
|
81
|
+
const asOf = makeAsOf();
|
|
82
|
+
const resolved = makeFullProto(uuid, asOf, 'hydrated');
|
|
83
|
+
LinkCache.SECURITY.put(uuid.toString(), resolved, asOf);
|
|
84
|
+
const wrapper = new security_1.default(security_1.default.linkOf(uuid, asOf));
|
|
85
|
+
expect(wrapper.getIssuerName()).toBe('hydrated');
|
|
86
|
+
});
|
|
87
|
+
test('B.ii — second accessor call hits the swapped proto, not the cache', () => {
|
|
88
|
+
const uuid = uuid_1.UUID.fromU8Array(new Uint8Array(16).fill(0x02));
|
|
89
|
+
const asOf = makeAsOf();
|
|
90
|
+
const resolved = makeFullProto(uuid, asOf, 'firstRead');
|
|
91
|
+
LinkCache.SECURITY.put(uuid.toString(), resolved, asOf);
|
|
92
|
+
const wrapper = new security_1.default(security_1.default.linkOf(uuid, asOf));
|
|
93
|
+
expect(wrapper.getAssetClass()).toBe('Equity');
|
|
94
|
+
// Evict the cache; wrapper has already swapped in the proto so the
|
|
95
|
+
// second read must succeed without touching the cache.
|
|
96
|
+
LinkCache.SECURITY.evict(uuid.toString());
|
|
97
|
+
expect(wrapper.getIssuerName()).toBe('firstRead');
|
|
98
|
+
});
|
|
99
|
+
test('B.iii — fresh wrapper for same (uuid, asOf) reads cache populated by prior wrapper', () => {
|
|
100
|
+
const uuid = uuid_1.UUID.fromU8Array(new Uint8Array(16).fill(0x03));
|
|
101
|
+
const asOf = makeAsOf();
|
|
102
|
+
const resolved = makeFullProto(uuid, asOf, 'shared');
|
|
103
|
+
LinkCache.SECURITY.put(uuid.toString(), resolved, asOf);
|
|
104
|
+
// Two independent wrappers share the cache entry.
|
|
105
|
+
const wrapperA = new security_1.default(security_1.default.linkOf(uuid, asOf));
|
|
106
|
+
const wrapperB = new security_1.default(security_1.default.linkOf(uuid, asOf));
|
|
107
|
+
expect(wrapperA.getIssuerName()).toBe('shared');
|
|
108
|
+
expect(wrapperB.getIssuerName()).toBe('shared');
|
|
109
|
+
});
|
|
110
|
+
// ---- C. asOf semantics ----
|
|
111
|
+
test('C — link asOf differing from cached asOf is a miss', () => {
|
|
112
|
+
const uuid = uuid_1.UUID.fromU8Array(new Uint8Array(16).fill(0x04));
|
|
113
|
+
const asOfT1 = makeAsOf(0);
|
|
114
|
+
const asOfT2 = makeAsOf(86400);
|
|
115
|
+
LinkCache.SECURITY.put(uuid.toString(), makeFullProto(uuid, asOfT2, 'T2'), asOfT2);
|
|
116
|
+
const wrapper = new security_1.default(security_1.default.linkOf(uuid, asOfT1));
|
|
117
|
+
expect(() => wrapper.getIssuerName()).toThrow(/LinkCache miss/);
|
|
118
|
+
});
|
|
119
|
+
// ---- D. Resolve failure ----
|
|
120
|
+
test('D — cache miss throws with uuid in message', () => {
|
|
121
|
+
const uuid = uuid_1.UUID.fromU8Array(new Uint8Array(16).fill(0x05));
|
|
122
|
+
const asOf = makeAsOf();
|
|
123
|
+
const wrapper = new security_1.default(security_1.default.linkOf(uuid, asOf));
|
|
124
|
+
expect(() => wrapper.getAssetClass()).toThrow(new RegExp(uuid.toString()));
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
//# sourceMappingURL=security.lazy-hydrate.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.lazy-hydrate.test.js","sourceRoot":"","sources":["security.lazy-hydrate.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import Security from './security';
|
|
2
|
+
import { UUID } from '../utils/uuid';
|
|
3
|
+
import { ZonedDateTime } from '../utils/datetime';
|
|
4
|
+
|
|
5
|
+
import * as LinkCache from '../../util/link-cache';
|
|
6
|
+
|
|
7
|
+
import { SecurityProto } from '../../../fintekkers/models/security/security_pb';
|
|
8
|
+
import { LocalTimestampProto } from '../../../fintekkers/models/util/local_timestamp_pb';
|
|
9
|
+
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
|
10
|
+
|
|
11
|
+
function makeAsOf(epochSecondsOffset: number = 0): ZonedDateTime {
|
|
12
|
+
const ts = new Timestamp();
|
|
13
|
+
ts.setSeconds(1700000000 + epochSecondsOffset);
|
|
14
|
+
ts.setNanos(0);
|
|
15
|
+
const proto = new LocalTimestampProto();
|
|
16
|
+
proto.setTimestamp(ts);
|
|
17
|
+
proto.setTimeZone('UTC');
|
|
18
|
+
return new ZonedDateTime(proto);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function makeFullProto(uuid: UUID, asOf: ZonedDateTime, issuer = 'ACME'): SecurityProto {
|
|
22
|
+
const p = new SecurityProto();
|
|
23
|
+
p.setUuid(uuid.toUUIDProto());
|
|
24
|
+
p.setAsOf(asOf.toProto());
|
|
25
|
+
p.setIssuerName(issuer);
|
|
26
|
+
p.setAssetClass('Equity');
|
|
27
|
+
return p;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('Security lazy hydrate', () => {
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
LinkCache.SECURITY.clear();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// ---- A. Hydration on accessors ----
|
|
36
|
+
|
|
37
|
+
test('A — getAssetClass on link-mode wrapper hydrates from cache', () => {
|
|
38
|
+
const uuid = UUID.fromU8Array(new Uint8Array(16).fill(0x42));
|
|
39
|
+
const asOf = makeAsOf();
|
|
40
|
+
const resolved = makeFullProto(uuid, asOf, 'ACME-resolved');
|
|
41
|
+
LinkCache.SECURITY.put(uuid.toString(), resolved, asOf);
|
|
42
|
+
|
|
43
|
+
const wrapper = new Security(Security.linkOf(uuid, asOf));
|
|
44
|
+
expect(wrapper.isLink()).toBe(true);
|
|
45
|
+
|
|
46
|
+
expect(wrapper.getAssetClass()).toBe('Equity');
|
|
47
|
+
// After first accessor, wrapper has swapped in the resolved proto.
|
|
48
|
+
expect(wrapper.proto.getIsLink()).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('A — link-safe accessors do not require hydration', () => {
|
|
52
|
+
const uuid = UUID.fromU8Array(new Uint8Array(16).fill(0x07));
|
|
53
|
+
const asOf = makeAsOf();
|
|
54
|
+
// Cache is empty — link-safe accessors must still work.
|
|
55
|
+
const wrapper = new Security(Security.linkOf(uuid, asOf));
|
|
56
|
+
|
|
57
|
+
expect(wrapper.isLink()).toBe(true);
|
|
58
|
+
expect(wrapper.getID().toString()).toBe(uuid.toString());
|
|
59
|
+
expect(wrapper.getAsOf().getSeconds()).toBe(asOf.getSeconds());
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// ---- B. Cache behavior ----
|
|
63
|
+
|
|
64
|
+
test('B.i — first accessor call hydrates from a pre-populated cache', () => {
|
|
65
|
+
const uuid = UUID.fromU8Array(new Uint8Array(16).fill(0x01));
|
|
66
|
+
const asOf = makeAsOf();
|
|
67
|
+
const resolved = makeFullProto(uuid, asOf, 'hydrated');
|
|
68
|
+
LinkCache.SECURITY.put(uuid.toString(), resolved, asOf);
|
|
69
|
+
|
|
70
|
+
const wrapper = new Security(Security.linkOf(uuid, asOf));
|
|
71
|
+
expect(wrapper.getIssuerName()).toBe('hydrated');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('B.ii — second accessor call hits the swapped proto, not the cache', () => {
|
|
75
|
+
const uuid = UUID.fromU8Array(new Uint8Array(16).fill(0x02));
|
|
76
|
+
const asOf = makeAsOf();
|
|
77
|
+
const resolved = makeFullProto(uuid, asOf, 'firstRead');
|
|
78
|
+
LinkCache.SECURITY.put(uuid.toString(), resolved, asOf);
|
|
79
|
+
|
|
80
|
+
const wrapper = new Security(Security.linkOf(uuid, asOf));
|
|
81
|
+
expect(wrapper.getAssetClass()).toBe('Equity');
|
|
82
|
+
|
|
83
|
+
// Evict the cache; wrapper has already swapped in the proto so the
|
|
84
|
+
// second read must succeed without touching the cache.
|
|
85
|
+
LinkCache.SECURITY.evict(uuid.toString());
|
|
86
|
+
expect(wrapper.getIssuerName()).toBe('firstRead');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('B.iii — fresh wrapper for same (uuid, asOf) reads cache populated by prior wrapper', () => {
|
|
90
|
+
const uuid = UUID.fromU8Array(new Uint8Array(16).fill(0x03));
|
|
91
|
+
const asOf = makeAsOf();
|
|
92
|
+
const resolved = makeFullProto(uuid, asOf, 'shared');
|
|
93
|
+
LinkCache.SECURITY.put(uuid.toString(), resolved, asOf);
|
|
94
|
+
|
|
95
|
+
// Two independent wrappers share the cache entry.
|
|
96
|
+
const wrapperA = new Security(Security.linkOf(uuid, asOf));
|
|
97
|
+
const wrapperB = new Security(Security.linkOf(uuid, asOf));
|
|
98
|
+
expect(wrapperA.getIssuerName()).toBe('shared');
|
|
99
|
+
expect(wrapperB.getIssuerName()).toBe('shared');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ---- C. asOf semantics ----
|
|
103
|
+
|
|
104
|
+
test('C — link asOf differing from cached asOf is a miss', () => {
|
|
105
|
+
const uuid = UUID.fromU8Array(new Uint8Array(16).fill(0x04));
|
|
106
|
+
const asOfT1 = makeAsOf(0);
|
|
107
|
+
const asOfT2 = makeAsOf(86400);
|
|
108
|
+
LinkCache.SECURITY.put(uuid.toString(), makeFullProto(uuid, asOfT2, 'T2'), asOfT2);
|
|
109
|
+
|
|
110
|
+
const wrapper = new Security(Security.linkOf(uuid, asOfT1));
|
|
111
|
+
expect(() => wrapper.getIssuerName()).toThrow(/LinkCache miss/);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// ---- D. Resolve failure ----
|
|
115
|
+
|
|
116
|
+
test('D — cache miss throws with uuid in message', () => {
|
|
117
|
+
const uuid = UUID.fromU8Array(new Uint8Array(16).fill(0x05));
|
|
118
|
+
const asOf = makeAsOf();
|
|
119
|
+
const wrapper = new Security(Security.linkOf(uuid, asOf));
|
|
120
|
+
|
|
121
|
+
expect(() => wrapper.getAssetClass()).toThrow(new RegExp(uuid.toString()));
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -9,6 +9,7 @@ import { ProductTypeProto } from "../../../fintekkers/models/security/product_ty
|
|
|
9
9
|
import { IdentifierTypeProto } from "../../../fintekkers/models/security/identifier/identifier_type_pb";
|
|
10
10
|
import { Identifier } from "./identifier";
|
|
11
11
|
import { isDescendantOf } from "./product_hierarchy";
|
|
12
|
+
import * as LinkCacheModule from "../../util/link-cache";
|
|
12
13
|
|
|
13
14
|
class Security {
|
|
14
15
|
proto: SecurityProto;
|
|
@@ -60,17 +61,34 @@ class Security {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
64
|
+
* Lazy hydration. If this Security is in link mode, swap in the resolved
|
|
65
|
+
* proto from LinkCache. On cache miss, throws — caller must pre-warm via
|
|
66
|
+
* LinkResolver. See docs/adr/lazy-link-hydration.md.
|
|
67
|
+
*
|
|
68
|
+
* TS variant is cache-only (no fetcher hook) because the gRPC stubs are
|
|
69
|
+
* async and chaining the resolver into every getter would force every
|
|
70
|
+
* accessor to become async. Pre-warming through LinkResolver keeps the
|
|
71
|
+
* sync getter API.
|
|
65
72
|
*/
|
|
66
|
-
private
|
|
67
|
-
if (this.proto.getIsLink())
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
private ensureHydrated(): void {
|
|
74
|
+
if (!this.proto.getIsLink()) return;
|
|
75
|
+
const uuidProto = this.proto.getUuid();
|
|
76
|
+
if (!uuidProto) {
|
|
77
|
+
throw new Error("Cannot read fields on link-mode Security with no UUID set.");
|
|
78
|
+
}
|
|
79
|
+
const uuidKey = UUID.fromU8Array(uuidProto.getRawUuid_asU8()).toString();
|
|
80
|
+
const asOfProto = this.proto.getAsOf();
|
|
81
|
+
const asOf = asOfProto ? new ZonedDateTime(asOfProto) : null;
|
|
82
|
+
const cached = LinkCacheModule.SECURITY.get(uuidKey, asOf);
|
|
83
|
+
if (cached) {
|
|
84
|
+
this.proto = cached;
|
|
85
|
+
return;
|
|
73
86
|
}
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Cannot read fields on link-mode Security uuid=${uuidKey} `
|
|
89
|
+
+ `— LinkCache miss. Pre-warm via LinkResolver. `
|
|
90
|
+
+ `See docs/adr/lazy-link-hydration.md.`
|
|
91
|
+
);
|
|
74
92
|
}
|
|
75
93
|
|
|
76
94
|
/**
|
|
@@ -258,17 +276,17 @@ class Security {
|
|
|
258
276
|
}
|
|
259
277
|
|
|
260
278
|
getAssetClass(): string {
|
|
261
|
-
this.
|
|
279
|
+
this.ensureHydrated();
|
|
262
280
|
return this.proto.getAssetClass();
|
|
263
281
|
}
|
|
264
282
|
|
|
265
283
|
getProductClass(): string {
|
|
266
|
-
this.
|
|
284
|
+
this.ensureHydrated();
|
|
267
285
|
throw new Error('Not implemented yet. See Java implementation for reference');
|
|
268
286
|
}
|
|
269
287
|
|
|
270
288
|
getProductType(): string {
|
|
271
|
-
this.
|
|
289
|
+
this.ensureHydrated();
|
|
272
290
|
const securityType = this.proto.getProductType();
|
|
273
291
|
const securityTypeString = (Object.keys(ProductTypeProto) as Array<keyof typeof ProductTypeProto>).find(
|
|
274
292
|
key => ProductTypeProto[key] === securityType
|
|
@@ -282,7 +300,7 @@ class Security {
|
|
|
282
300
|
* Empty list if none are set. Throws on a link-mode Security.
|
|
283
301
|
*/
|
|
284
302
|
getIdentifiers(): Identifier[] {
|
|
285
|
-
this.
|
|
303
|
+
this.ensureHydrated();
|
|
286
304
|
const list = this.proto.getIdentifiersList();
|
|
287
305
|
if (!list) return [];
|
|
288
306
|
return list.map(p => new Identifier(p));
|
|
@@ -293,7 +311,7 @@ class Security {
|
|
|
293
311
|
* or undefined if none is present. Throws on a link-mode Security.
|
|
294
312
|
*/
|
|
295
313
|
getIdentifierByType(type: IdentifierTypeProto): Identifier | undefined {
|
|
296
|
-
this.
|
|
314
|
+
this.ensureHydrated();
|
|
297
315
|
const list = this.proto.getIdentifiersList();
|
|
298
316
|
if (!list) return undefined;
|
|
299
317
|
const found = list.find(p => p.getIdentifierType() === type);
|
|
@@ -314,7 +332,7 @@ class Security {
|
|
|
314
332
|
* properly-formed bond).
|
|
315
333
|
*/
|
|
316
334
|
getIssueDate(): Date | null {
|
|
317
|
-
this.
|
|
335
|
+
this.ensureHydrated();
|
|
318
336
|
const bond = this.getBondLikeDetails();
|
|
319
337
|
const date = bond ? bond.getIssueDate() : undefined;
|
|
320
338
|
return localDateProtoToDate(date);
|
|
@@ -327,7 +345,7 @@ class Security {
|
|
|
327
345
|
* code paths.
|
|
328
346
|
*/
|
|
329
347
|
getMaturityDate(): Date | null {
|
|
330
|
-
this.
|
|
348
|
+
this.ensureHydrated();
|
|
331
349
|
const bond = this.getBondLikeDetails();
|
|
332
350
|
const date = bond ? bond.getMaturityDate() : undefined;
|
|
333
351
|
return localDateProtoToDate(date);
|
|
@@ -344,7 +362,7 @@ class Security {
|
|
|
344
362
|
}
|
|
345
363
|
|
|
346
364
|
getIssuerName(): string {
|
|
347
|
-
this.
|
|
365
|
+
this.ensureHydrated();
|
|
348
366
|
return this.proto.getIssuerName();
|
|
349
367
|
}
|
|
350
368
|
|
|
@@ -11,10 +11,12 @@ class TransactionType {
|
|
|
11
11
|
case transaction_type_pb_1.TransactionTypeProto.BUY:
|
|
12
12
|
case transaction_type_pb_1.TransactionTypeProto.DEPOSIT:
|
|
13
13
|
case transaction_type_pb_1.TransactionTypeProto.MATURATION_OFFSET:
|
|
14
|
+
case transaction_type_pb_1.TransactionTypeProto.PRINCIPAL_PAYDOWN_OFFSET:
|
|
14
15
|
return 1;
|
|
15
16
|
case transaction_type_pb_1.TransactionTypeProto.SELL:
|
|
16
17
|
case transaction_type_pb_1.TransactionTypeProto.WITHDRAWAL:
|
|
17
18
|
case transaction_type_pb_1.TransactionTypeProto.MATURATION:
|
|
19
|
+
case transaction_type_pb_1.TransactionTypeProto.PRINCIPAL_PAYDOWN:
|
|
18
20
|
return -1;
|
|
19
21
|
case transaction_type_pb_1.TransactionTypeProto.UNKNOWN:
|
|
20
22
|
throw new Error('Unknown transaction type: ' + this.toString());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transaction_type.js","sourceRoot":"","sources":["transaction_type.ts"],"names":[],"mappings":";;;AAAA,oGAAkG;AAElG,MAAa,eAAe;IAGxB,YAAY,KAA2B;QACnC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;IAYD,sBAAsB;QAClB,QAAQ,IAAI,CAAC,KAAK,EAAE;YAChB,KAAK,0CAAoB,CAAC,GAAG,CAAC;YAC9B,KAAK,0CAAoB,CAAC,OAAO,CAAC;YAClC,KAAK,0CAAoB,CAAC,iBAAiB;
|
|
1
|
+
{"version":3,"file":"transaction_type.js","sourceRoot":"","sources":["transaction_type.ts"],"names":[],"mappings":";;;AAAA,oGAAkG;AAElG,MAAa,eAAe;IAGxB,YAAY,KAA2B;QACnC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;IAYD,sBAAsB;QAClB,QAAQ,IAAI,CAAC,KAAK,EAAE;YAChB,KAAK,0CAAoB,CAAC,GAAG,CAAC;YAC9B,KAAK,0CAAoB,CAAC,OAAO,CAAC;YAClC,KAAK,0CAAoB,CAAC,iBAAiB,CAAC;YAC5C,KAAK,0CAAoB,CAAC,wBAAwB;gBAC9C,OAAO,CAAC,CAAC;YACb,KAAK,0CAAoB,CAAC,IAAI,CAAC;YAC/B,KAAK,0CAAoB,CAAC,UAAU,CAAC;YACrC,KAAK,0CAAoB,CAAC,UAAU,CAAC;YACrC,KAAK,0CAAoB,CAAC,iBAAiB;gBACvC,OAAO,CAAC,CAAC,CAAC;YACd,KAAK,0CAAoB,CAAC,OAAO;gBAC7B,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;SACvE;IACL,CAAC;IAED;;;;;;;;OAQG;IACH,QAAQ;;QACJ,OAAO,MAAA,eAAe,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,mCAAI,SAAS,CAAC;IAClE,CAAC;CACJ;AA9CD,0CA8CC;AArCG;IACI,eAAe,CAAC,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEtD,MAAM,CAAC,IAAI,CAAC,0CAAoB,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;QAC5C,eAAe,CAAC,SAAS,CAAC,GAAG,CAAC,0CAAoB,CAAC,GAAwC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvG,CAAC,CAAC,CAAC;AACP,CAAC,GAAA,CAAA"}
|
|
@@ -22,10 +22,12 @@ export class TransactionType {
|
|
|
22
22
|
case TransactionTypeProto.BUY:
|
|
23
23
|
case TransactionTypeProto.DEPOSIT:
|
|
24
24
|
case TransactionTypeProto.MATURATION_OFFSET:
|
|
25
|
+
case TransactionTypeProto.PRINCIPAL_PAYDOWN_OFFSET:
|
|
25
26
|
return 1;
|
|
26
27
|
case TransactionTypeProto.SELL:
|
|
27
28
|
case TransactionTypeProto.WITHDRAWAL:
|
|
28
29
|
case TransactionTypeProto.MATURATION:
|
|
30
|
+
case TransactionTypeProto.PRINCIPAL_PAYDOWN:
|
|
29
31
|
return -1;
|
|
30
32
|
case TransactionTypeProto.UNKNOWN:
|
|
31
33
|
throw new Error('Unknown transaction type: ' + this.toString());
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { SecurityProto } from '../../fintekkers/models/security/security_pb';
|
|
2
|
+
import { PortfolioProto } from '../../fintekkers/models/portfolio/portfolio_pb';
|
|
3
|
+
import { PriceProto } from '../../fintekkers/models/price/price_pb';
|
|
4
|
+
import { TransactionProto } from '../../fintekkers/models/transaction/transaction_pb';
|
|
5
|
+
import { ZonedDateTime } from '../models/utils/datetime';
|
|
6
|
+
/**
|
|
7
|
+
* LinkCache — process-wide cache of resolved proto bodies backing link-mode
|
|
8
|
+
* wrappers. TypeScript mirror of `common.util.LinkCache` (Java) and
|
|
9
|
+
* `fintekkers.wrappers.util.link_cache` (Python). See
|
|
10
|
+
* `docs/adr/lazy-link-hydration.md`.
|
|
11
|
+
*
|
|
12
|
+
* Read semantics (`get`):
|
|
13
|
+
* - `requestedAsOf == null` ("latest acceptable") — cache hit allowed;
|
|
14
|
+
* subject to `ttlForLatestMs` to bound cross-process staleness this
|
|
15
|
+
* process can't observe. Past TTL ⇒ miss.
|
|
16
|
+
* - `requestedAsOf != null` (bitemporal-precise) — cache hit only when the
|
|
17
|
+
* cached entry's asOf equals the requested. No TTL — history doesn't
|
|
18
|
+
* change, so a past vintage cached arbitrarily long is fine.
|
|
19
|
+
*
|
|
20
|
+
* Write semantics (`put`): newest-vintage wins. An older-vintage put does
|
|
21
|
+
* not evict a newer cached entry.
|
|
22
|
+
*
|
|
23
|
+
* Later Portfolio can likely be 1 day, security 1 day, transaction 1 minute,
|
|
24
|
+
* price 30 seconds — once per-entity TTLs are wired up, the shared singletons
|
|
25
|
+
* below should be constructed with those values.
|
|
26
|
+
*/
|
|
27
|
+
export declare const DEFAULT_TTL_FOR_LATEST_MS = 600000;
|
|
28
|
+
export declare class LinkCache<V> {
|
|
29
|
+
private ttlForLatestMs;
|
|
30
|
+
private map;
|
|
31
|
+
constructor(ttlForLatestMs?: number);
|
|
32
|
+
/**
|
|
33
|
+
* @param uuidKey the entity uuid rendered as a stable string key
|
|
34
|
+
* (use uuid.toString())
|
|
35
|
+
* @param requestedAsOf null = "latest acceptable" (TTL-bounded);
|
|
36
|
+
* non-null = exact-vintage match required (no TTL)
|
|
37
|
+
* @returns the cached value if the lookup is a hit; otherwise undefined
|
|
38
|
+
* (caller must refetch)
|
|
39
|
+
*/
|
|
40
|
+
get(uuidKey: string, requestedAsOf: ZonedDateTime | null): V | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Newest-wins write: if a cached entry for `uuidKey` already exists with
|
|
43
|
+
* an asOf strictly after the incoming asOf, the write is ignored.
|
|
44
|
+
*/
|
|
45
|
+
put(uuidKey: string, value: V, asOf: ZonedDateTime): void;
|
|
46
|
+
evict(uuidKey: string): void;
|
|
47
|
+
clear(): void;
|
|
48
|
+
/** Test helper. */
|
|
49
|
+
size(): number;
|
|
50
|
+
private _sameAsOf;
|
|
51
|
+
private _isStrictlyAfter;
|
|
52
|
+
}
|
|
53
|
+
export declare const SECURITY: LinkCache<SecurityProto>;
|
|
54
|
+
export declare const PORTFOLIO: LinkCache<PortfolioProto>;
|
|
55
|
+
export declare const PRICE: LinkCache<PriceProto>;
|
|
56
|
+
export declare const TRANSACTION: LinkCache<TransactionProto>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TRANSACTION = exports.PRICE = exports.PORTFOLIO = exports.SECURITY = exports.LinkCache = exports.DEFAULT_TTL_FOR_LATEST_MS = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* LinkCache — process-wide cache of resolved proto bodies backing link-mode
|
|
6
|
+
* wrappers. TypeScript mirror of `common.util.LinkCache` (Java) and
|
|
7
|
+
* `fintekkers.wrappers.util.link_cache` (Python). See
|
|
8
|
+
* `docs/adr/lazy-link-hydration.md`.
|
|
9
|
+
*
|
|
10
|
+
* Read semantics (`get`):
|
|
11
|
+
* - `requestedAsOf == null` ("latest acceptable") — cache hit allowed;
|
|
12
|
+
* subject to `ttlForLatestMs` to bound cross-process staleness this
|
|
13
|
+
* process can't observe. Past TTL ⇒ miss.
|
|
14
|
+
* - `requestedAsOf != null` (bitemporal-precise) — cache hit only when the
|
|
15
|
+
* cached entry's asOf equals the requested. No TTL — history doesn't
|
|
16
|
+
* change, so a past vintage cached arbitrarily long is fine.
|
|
17
|
+
*
|
|
18
|
+
* Write semantics (`put`): newest-vintage wins. An older-vintage put does
|
|
19
|
+
* not evict a newer cached entry.
|
|
20
|
+
*
|
|
21
|
+
* Later Portfolio can likely be 1 day, security 1 day, transaction 1 minute,
|
|
22
|
+
* price 30 seconds — once per-entity TTLs are wired up, the shared singletons
|
|
23
|
+
* below should be constructed with those values.
|
|
24
|
+
*/
|
|
25
|
+
exports.DEFAULT_TTL_FOR_LATEST_MS = 600000;
|
|
26
|
+
class LinkCache {
|
|
27
|
+
constructor(ttlForLatestMs = exports.DEFAULT_TTL_FOR_LATEST_MS) {
|
|
28
|
+
this.ttlForLatestMs = ttlForLatestMs;
|
|
29
|
+
this.map = new Map();
|
|
30
|
+
if (ttlForLatestMs < 0) {
|
|
31
|
+
throw new Error(`ttlForLatestMs must be non-negative; got ${ttlForLatestMs}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* @param uuidKey the entity uuid rendered as a stable string key
|
|
36
|
+
* (use uuid.toString())
|
|
37
|
+
* @param requestedAsOf null = "latest acceptable" (TTL-bounded);
|
|
38
|
+
* non-null = exact-vintage match required (no TTL)
|
|
39
|
+
* @returns the cached value if the lookup is a hit; otherwise undefined
|
|
40
|
+
* (caller must refetch)
|
|
41
|
+
*/
|
|
42
|
+
get(uuidKey, requestedAsOf) {
|
|
43
|
+
const entry = this.map.get(uuidKey);
|
|
44
|
+
if (!entry)
|
|
45
|
+
return undefined;
|
|
46
|
+
if (requestedAsOf == null) {
|
|
47
|
+
if (Date.now() - entry.cachedAtMs > this.ttlForLatestMs) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
return entry.value;
|
|
51
|
+
}
|
|
52
|
+
return this._sameAsOf(entry.asOf, requestedAsOf) ? entry.value : undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Newest-wins write: if a cached entry for `uuidKey` already exists with
|
|
56
|
+
* an asOf strictly after the incoming asOf, the write is ignored.
|
|
57
|
+
*/
|
|
58
|
+
put(uuidKey, value, asOf) {
|
|
59
|
+
if (!uuidKey || !value || !asOf) {
|
|
60
|
+
throw new Error(`uuidKey / value / asOf must all be set; got uuidKey=${uuidKey} value=${value ? '<non-null>' : 'null'} asOf=${asOf}`);
|
|
61
|
+
}
|
|
62
|
+
const existing = this.map.get(uuidKey);
|
|
63
|
+
if (existing && this._isStrictlyAfter(existing.asOf, asOf)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.map.set(uuidKey, { value, asOf, cachedAtMs: Date.now() });
|
|
67
|
+
}
|
|
68
|
+
evict(uuidKey) {
|
|
69
|
+
this.map.delete(uuidKey);
|
|
70
|
+
}
|
|
71
|
+
clear() {
|
|
72
|
+
this.map.clear();
|
|
73
|
+
}
|
|
74
|
+
/** Test helper. */
|
|
75
|
+
size() {
|
|
76
|
+
return this.map.size;
|
|
77
|
+
}
|
|
78
|
+
_sameAsOf(a, b) {
|
|
79
|
+
return a.getSeconds() === b.getSeconds() && a.getNanoSeconds() === b.getNanoSeconds();
|
|
80
|
+
}
|
|
81
|
+
_isStrictlyAfter(a, b) {
|
|
82
|
+
if (a.getSeconds() !== b.getSeconds())
|
|
83
|
+
return a.getSeconds() > b.getSeconds();
|
|
84
|
+
return a.getNanoSeconds() > b.getNanoSeconds();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.LinkCache = LinkCache;
|
|
88
|
+
exports.SECURITY = new LinkCache();
|
|
89
|
+
exports.PORTFOLIO = new LinkCache();
|
|
90
|
+
exports.PRICE = new LinkCache();
|
|
91
|
+
exports.TRANSACTION = new LinkCache();
|
|
92
|
+
//# sourceMappingURL=link-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-cache.js","sourceRoot":"","sources":["link-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const link_cache_1 = require("./link-cache");
|
|
13
|
+
const datetime_1 = require("../models/utils/datetime");
|
|
14
|
+
const local_timestamp_pb_1 = require("../../fintekkers/models/util/local_timestamp_pb");
|
|
15
|
+
const timestamp_pb_1 = require("google-protobuf/google/protobuf/timestamp_pb");
|
|
16
|
+
const security_pb_1 = require("../../fintekkers/models/security/security_pb");
|
|
17
|
+
function makeAsOf(epochSecondsOffset = 0) {
|
|
18
|
+
const ts = new timestamp_pb_1.Timestamp();
|
|
19
|
+
ts.setSeconds(1700000000 + epochSecondsOffset);
|
|
20
|
+
ts.setNanos(0);
|
|
21
|
+
const proto = new local_timestamp_pb_1.LocalTimestampProto();
|
|
22
|
+
proto.setTimestamp(ts);
|
|
23
|
+
proto.setTimeZone('UTC');
|
|
24
|
+
return new datetime_1.ZonedDateTime(proto);
|
|
25
|
+
}
|
|
26
|
+
function makeSecurityProto(name) {
|
|
27
|
+
const p = new security_pb_1.SecurityProto();
|
|
28
|
+
p.setIssuerName(name);
|
|
29
|
+
return p;
|
|
30
|
+
}
|
|
31
|
+
describe('LinkCache', () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
link_cache_1.SECURITY.clear();
|
|
34
|
+
link_cache_1.PORTFOLIO.clear();
|
|
35
|
+
link_cache_1.PRICE.clear();
|
|
36
|
+
link_cache_1.TRANSACTION.clear();
|
|
37
|
+
});
|
|
38
|
+
// ---- A. Basic get/put ----
|
|
39
|
+
test('get on empty cache returns undefined', () => {
|
|
40
|
+
expect(link_cache_1.SECURITY.get('uuid-1', makeAsOf())).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
test('put then get with matching asOf returns value', () => {
|
|
43
|
+
const asOf = makeAsOf();
|
|
44
|
+
const val = makeSecurityProto('ACME');
|
|
45
|
+
link_cache_1.SECURITY.put('uuid-1', val, asOf);
|
|
46
|
+
expect(link_cache_1.SECURITY.get('uuid-1', asOf)).toBe(val);
|
|
47
|
+
});
|
|
48
|
+
test('get with different asOf returns undefined', () => {
|
|
49
|
+
link_cache_1.SECURITY.put('uuid-1', makeSecurityProto('v1'), makeAsOf(0));
|
|
50
|
+
expect(link_cache_1.SECURITY.get('uuid-1', makeAsOf(10))).toBeUndefined();
|
|
51
|
+
});
|
|
52
|
+
// ---- B. Null asOf semantics ----
|
|
53
|
+
test('null asOf within TTL returns value', () => {
|
|
54
|
+
const cache = new link_cache_1.LinkCache(60000);
|
|
55
|
+
cache.put('uuid-1', makeSecurityProto('v1'), makeAsOf());
|
|
56
|
+
expect(cache.get('uuid-1', null)).toBeDefined();
|
|
57
|
+
});
|
|
58
|
+
test('null asOf past TTL returns undefined', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
59
|
+
const cache = new link_cache_1.LinkCache(50);
|
|
60
|
+
cache.put('uuid-1', makeSecurityProto('v1'), makeAsOf());
|
|
61
|
+
yield new Promise((resolve) => setTimeout(resolve, 100));
|
|
62
|
+
expect(cache.get('uuid-1', null)).toBeUndefined();
|
|
63
|
+
}));
|
|
64
|
+
test('non-null asOf is not subject to TTL', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
|
+
const cache = new link_cache_1.LinkCache(50);
|
|
66
|
+
const asOf = makeAsOf();
|
|
67
|
+
const val = makeSecurityProto('v1');
|
|
68
|
+
cache.put('uuid-1', val, asOf);
|
|
69
|
+
yield new Promise((resolve) => setTimeout(resolve, 100));
|
|
70
|
+
// Bitemporal: history doesn't change, exact-asOf reads never expire.
|
|
71
|
+
expect(cache.get('uuid-1', asOf)).toBe(val);
|
|
72
|
+
}));
|
|
73
|
+
// ---- C. Newest-wins merge ----
|
|
74
|
+
test('put with older asOf does not overwrite newer', () => {
|
|
75
|
+
var _a;
|
|
76
|
+
link_cache_1.SECURITY.put('uuid-1', makeSecurityProto('newer'), makeAsOf(100));
|
|
77
|
+
link_cache_1.SECURITY.put('uuid-1', makeSecurityProto('older'), makeAsOf(50));
|
|
78
|
+
expect((_a = link_cache_1.SECURITY.get('uuid-1', makeAsOf(100))) === null || _a === void 0 ? void 0 : _a.getIssuerName()).toBe('newer');
|
|
79
|
+
expect(link_cache_1.SECURITY.get('uuid-1', makeAsOf(50))).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
test('put with newer asOf replaces older', () => {
|
|
82
|
+
var _a;
|
|
83
|
+
link_cache_1.SECURITY.put('uuid-1', makeSecurityProto('older'), makeAsOf(50));
|
|
84
|
+
link_cache_1.SECURITY.put('uuid-1', makeSecurityProto('newer'), makeAsOf(100));
|
|
85
|
+
expect((_a = link_cache_1.SECURITY.get('uuid-1', makeAsOf(100))) === null || _a === void 0 ? void 0 : _a.getIssuerName()).toBe('newer');
|
|
86
|
+
});
|
|
87
|
+
// ---- D. Evict & clear ----
|
|
88
|
+
test('evict removes entry', () => {
|
|
89
|
+
link_cache_1.SECURITY.put('uuid-1', makeSecurityProto('v1'), makeAsOf());
|
|
90
|
+
link_cache_1.SECURITY.evict('uuid-1');
|
|
91
|
+
expect(link_cache_1.SECURITY.get('uuid-1', makeAsOf())).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
test('clear empties cache', () => {
|
|
94
|
+
link_cache_1.SECURITY.put('a', makeSecurityProto('a'), makeAsOf());
|
|
95
|
+
link_cache_1.SECURITY.put('b', makeSecurityProto('b'), makeAsOf());
|
|
96
|
+
link_cache_1.SECURITY.clear();
|
|
97
|
+
expect(link_cache_1.SECURITY.size()).toBe(0);
|
|
98
|
+
});
|
|
99
|
+
// ---- E. Singleton isolation ----
|
|
100
|
+
test('singletons have independent state', () => {
|
|
101
|
+
link_cache_1.SECURITY.put('uuid-1', makeSecurityProto('sec'), makeAsOf());
|
|
102
|
+
expect(link_cache_1.PORTFOLIO.get('uuid-1', makeAsOf())).toBeUndefined();
|
|
103
|
+
expect(link_cache_1.PRICE.get('uuid-1', makeAsOf())).toBeUndefined();
|
|
104
|
+
expect(link_cache_1.TRANSACTION.get('uuid-1', makeAsOf())).toBeUndefined();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
//# sourceMappingURL=link-cache.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-cache.test.js","sourceRoot":"","sources":["link-cache.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { LinkCache, SECURITY, PORTFOLIO, PRICE, TRANSACTION } from './link-cache';
|
|
2
|
+
import { ZonedDateTime } from '../models/utils/datetime';
|
|
3
|
+
import { LocalTimestampProto } from '../../fintekkers/models/util/local_timestamp_pb';
|
|
4
|
+
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
|
5
|
+
import { SecurityProto } from '../../fintekkers/models/security/security_pb';
|
|
6
|
+
|
|
7
|
+
function makeAsOf(epochSecondsOffset: number = 0): ZonedDateTime {
|
|
8
|
+
const ts = new Timestamp();
|
|
9
|
+
ts.setSeconds(1700000000 + epochSecondsOffset);
|
|
10
|
+
ts.setNanos(0);
|
|
11
|
+
const proto = new LocalTimestampProto();
|
|
12
|
+
proto.setTimestamp(ts);
|
|
13
|
+
proto.setTimeZone('UTC');
|
|
14
|
+
return new ZonedDateTime(proto);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function makeSecurityProto(name: string): SecurityProto {
|
|
18
|
+
const p = new SecurityProto();
|
|
19
|
+
p.setIssuerName(name);
|
|
20
|
+
return p;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('LinkCache', () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
SECURITY.clear();
|
|
26
|
+
PORTFOLIO.clear();
|
|
27
|
+
PRICE.clear();
|
|
28
|
+
TRANSACTION.clear();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// ---- A. Basic get/put ----
|
|
32
|
+
|
|
33
|
+
test('get on empty cache returns undefined', () => {
|
|
34
|
+
expect(SECURITY.get('uuid-1', makeAsOf())).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('put then get with matching asOf returns value', () => {
|
|
38
|
+
const asOf = makeAsOf();
|
|
39
|
+
const val = makeSecurityProto('ACME');
|
|
40
|
+
SECURITY.put('uuid-1', val, asOf);
|
|
41
|
+
expect(SECURITY.get('uuid-1', asOf)).toBe(val);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('get with different asOf returns undefined', () => {
|
|
45
|
+
SECURITY.put('uuid-1', makeSecurityProto('v1'), makeAsOf(0));
|
|
46
|
+
expect(SECURITY.get('uuid-1', makeAsOf(10))).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// ---- B. Null asOf semantics ----
|
|
50
|
+
|
|
51
|
+
test('null asOf within TTL returns value', () => {
|
|
52
|
+
const cache = new LinkCache<SecurityProto>(60_000);
|
|
53
|
+
cache.put('uuid-1', makeSecurityProto('v1'), makeAsOf());
|
|
54
|
+
expect(cache.get('uuid-1', null)).toBeDefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('null asOf past TTL returns undefined', async () => {
|
|
58
|
+
const cache = new LinkCache<SecurityProto>(50);
|
|
59
|
+
cache.put('uuid-1', makeSecurityProto('v1'), makeAsOf());
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
61
|
+
expect(cache.get('uuid-1', null)).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('non-null asOf is not subject to TTL', async () => {
|
|
65
|
+
const cache = new LinkCache<SecurityProto>(50);
|
|
66
|
+
const asOf = makeAsOf();
|
|
67
|
+
const val = makeSecurityProto('v1');
|
|
68
|
+
cache.put('uuid-1', val, asOf);
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
70
|
+
// Bitemporal: history doesn't change, exact-asOf reads never expire.
|
|
71
|
+
expect(cache.get('uuid-1', asOf)).toBe(val);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// ---- C. Newest-wins merge ----
|
|
75
|
+
|
|
76
|
+
test('put with older asOf does not overwrite newer', () => {
|
|
77
|
+
SECURITY.put('uuid-1', makeSecurityProto('newer'), makeAsOf(100));
|
|
78
|
+
SECURITY.put('uuid-1', makeSecurityProto('older'), makeAsOf(50));
|
|
79
|
+
expect(SECURITY.get('uuid-1', makeAsOf(100))?.getIssuerName()).toBe('newer');
|
|
80
|
+
expect(SECURITY.get('uuid-1', makeAsOf(50))).toBeUndefined();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('put with newer asOf replaces older', () => {
|
|
84
|
+
SECURITY.put('uuid-1', makeSecurityProto('older'), makeAsOf(50));
|
|
85
|
+
SECURITY.put('uuid-1', makeSecurityProto('newer'), makeAsOf(100));
|
|
86
|
+
expect(SECURITY.get('uuid-1', makeAsOf(100))?.getIssuerName()).toBe('newer');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// ---- D. Evict & clear ----
|
|
90
|
+
|
|
91
|
+
test('evict removes entry', () => {
|
|
92
|
+
SECURITY.put('uuid-1', makeSecurityProto('v1'), makeAsOf());
|
|
93
|
+
SECURITY.evict('uuid-1');
|
|
94
|
+
expect(SECURITY.get('uuid-1', makeAsOf())).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('clear empties cache', () => {
|
|
98
|
+
SECURITY.put('a', makeSecurityProto('a'), makeAsOf());
|
|
99
|
+
SECURITY.put('b', makeSecurityProto('b'), makeAsOf());
|
|
100
|
+
SECURITY.clear();
|
|
101
|
+
expect(SECURITY.size()).toBe(0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ---- E. Singleton isolation ----
|
|
105
|
+
|
|
106
|
+
test('singletons have independent state', () => {
|
|
107
|
+
SECURITY.put('uuid-1', makeSecurityProto('sec'), makeAsOf());
|
|
108
|
+
expect(PORTFOLIO.get('uuid-1', makeAsOf())).toBeUndefined();
|
|
109
|
+
expect(PRICE.get('uuid-1', makeAsOf())).toBeUndefined();
|
|
110
|
+
expect(TRANSACTION.get('uuid-1', makeAsOf())).toBeUndefined();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { SecurityProto } from '../../fintekkers/models/security/security_pb';
|
|
2
|
+
import { PortfolioProto } from '../../fintekkers/models/portfolio/portfolio_pb';
|
|
3
|
+
import { PriceProto } from '../../fintekkers/models/price/price_pb';
|
|
4
|
+
import { TransactionProto } from '../../fintekkers/models/transaction/transaction_pb';
|
|
5
|
+
import { ZonedDateTime } from '../models/utils/datetime';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* LinkCache — process-wide cache of resolved proto bodies backing link-mode
|
|
9
|
+
* wrappers. TypeScript mirror of `common.util.LinkCache` (Java) and
|
|
10
|
+
* `fintekkers.wrappers.util.link_cache` (Python). See
|
|
11
|
+
* `docs/adr/lazy-link-hydration.md`.
|
|
12
|
+
*
|
|
13
|
+
* Read semantics (`get`):
|
|
14
|
+
* - `requestedAsOf == null` ("latest acceptable") — cache hit allowed;
|
|
15
|
+
* subject to `ttlForLatestMs` to bound cross-process staleness this
|
|
16
|
+
* process can't observe. Past TTL ⇒ miss.
|
|
17
|
+
* - `requestedAsOf != null` (bitemporal-precise) — cache hit only when the
|
|
18
|
+
* cached entry's asOf equals the requested. No TTL — history doesn't
|
|
19
|
+
* change, so a past vintage cached arbitrarily long is fine.
|
|
20
|
+
*
|
|
21
|
+
* Write semantics (`put`): newest-vintage wins. An older-vintage put does
|
|
22
|
+
* not evict a newer cached entry.
|
|
23
|
+
*
|
|
24
|
+
* Later Portfolio can likely be 1 day, security 1 day, transaction 1 minute,
|
|
25
|
+
* price 30 seconds — once per-entity TTLs are wired up, the shared singletons
|
|
26
|
+
* below should be constructed with those values.
|
|
27
|
+
*/
|
|
28
|
+
export const DEFAULT_TTL_FOR_LATEST_MS = 600_000;
|
|
29
|
+
|
|
30
|
+
interface CacheEntry<V> {
|
|
31
|
+
value: V;
|
|
32
|
+
/** Bitemporal asOf carried by the cached value. */
|
|
33
|
+
asOf: ZonedDateTime;
|
|
34
|
+
/** Wall-clock ms at the moment this entry was cached. */
|
|
35
|
+
cachedAtMs: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class LinkCache<V> {
|
|
39
|
+
private map = new Map<string, CacheEntry<V>>();
|
|
40
|
+
|
|
41
|
+
constructor(private ttlForLatestMs: number = DEFAULT_TTL_FOR_LATEST_MS) {
|
|
42
|
+
if (ttlForLatestMs < 0) {
|
|
43
|
+
throw new Error(`ttlForLatestMs must be non-negative; got ${ttlForLatestMs}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param uuidKey the entity uuid rendered as a stable string key
|
|
49
|
+
* (use uuid.toString())
|
|
50
|
+
* @param requestedAsOf null = "latest acceptable" (TTL-bounded);
|
|
51
|
+
* non-null = exact-vintage match required (no TTL)
|
|
52
|
+
* @returns the cached value if the lookup is a hit; otherwise undefined
|
|
53
|
+
* (caller must refetch)
|
|
54
|
+
*/
|
|
55
|
+
get(uuidKey: string, requestedAsOf: ZonedDateTime | null): V | undefined {
|
|
56
|
+
const entry = this.map.get(uuidKey);
|
|
57
|
+
if (!entry) return undefined;
|
|
58
|
+
if (requestedAsOf == null) {
|
|
59
|
+
if (Date.now() - entry.cachedAtMs > this.ttlForLatestMs) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return entry.value;
|
|
63
|
+
}
|
|
64
|
+
return this._sameAsOf(entry.asOf, requestedAsOf) ? entry.value : undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Newest-wins write: if a cached entry for `uuidKey` already exists with
|
|
69
|
+
* an asOf strictly after the incoming asOf, the write is ignored.
|
|
70
|
+
*/
|
|
71
|
+
put(uuidKey: string, value: V, asOf: ZonedDateTime): void {
|
|
72
|
+
if (!uuidKey || !value || !asOf) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`uuidKey / value / asOf must all be set; got uuidKey=${uuidKey} value=${value ? '<non-null>' : 'null'} asOf=${asOf}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const existing = this.map.get(uuidKey);
|
|
78
|
+
if (existing && this._isStrictlyAfter(existing.asOf, asOf)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
this.map.set(uuidKey, { value, asOf, cachedAtMs: Date.now() });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
evict(uuidKey: string): void {
|
|
85
|
+
this.map.delete(uuidKey);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
clear(): void {
|
|
89
|
+
this.map.clear();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Test helper. */
|
|
93
|
+
size(): number {
|
|
94
|
+
return this.map.size;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private _sameAsOf(a: ZonedDateTime, b: ZonedDateTime): boolean {
|
|
98
|
+
return a.getSeconds() === b.getSeconds() && a.getNanoSeconds() === b.getNanoSeconds();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private _isStrictlyAfter(a: ZonedDateTime, b: ZonedDateTime): boolean {
|
|
102
|
+
if (a.getSeconds() !== b.getSeconds()) return a.getSeconds() > b.getSeconds();
|
|
103
|
+
return a.getNanoSeconds() > b.getNanoSeconds();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const SECURITY = new LinkCache<SecurityProto>();
|
|
108
|
+
export const PORTFOLIO = new LinkCache<PortfolioProto>();
|
|
109
|
+
export const PRICE = new LinkCache<PriceProto>();
|
|
110
|
+
export const TRANSACTION = new LinkCache<TransactionProto>();
|