@fintekkers/ledger-models 0.1.131 → 0.1.132
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/portfolio/portfolio_pb.js +19 -13
- package/node/fintekkers/models/position/field_pb.js +7 -1
- package/node/fintekkers/models/position/measure_pb.js +7 -1
- package/node/fintekkers/models/position/position_filter_pb.js +13 -7
- package/node/fintekkers/models/position/position_pb.js +17 -11
- package/node/fintekkers/models/position/position_status_pb.js +7 -1
- package/node/fintekkers/models/position/position_util_pb.js +17 -11
- package/node/fintekkers/models/price/price_pb.js +20 -14
- package/node/fintekkers/models/price/price_type_pb.js +7 -1
- package/node/fintekkers/models/security/bond/auction_type_pb.js +7 -1
- package/node/fintekkers/models/security/bond/issuance_pb.js +23 -17
- package/node/fintekkers/models/security/coupon_frequency_pb.js +7 -1
- package/node/fintekkers/models/security/coupon_type_pb.js +7 -1
- package/node/fintekkers/models/security/identifier/identifier_pb.js +15 -9
- package/node/fintekkers/models/security/identifier/identifier_type_pb.js +7 -1
- package/node/fintekkers/models/security/index/index_type_pb.js +7 -1
- package/node/fintekkers/models/security/index_composition_grpc_pb.js +1 -0
- package/node/fintekkers/models/security/index_composition_pb.js +29 -23
- package/node/fintekkers/models/security/security_pb.js +96 -90
- package/node/fintekkers/models/security/security_quantity_type_pb.js +7 -1
- package/node/fintekkers/models/security/security_type_pb.js +7 -1
- package/node/fintekkers/models/security/tenor_pb.js +15 -9
- package/node/fintekkers/models/security/tenor_type_pb.js +7 -1
- package/node/fintekkers/models/strategy/strategy_allocation_pb.js +19 -13
- package/node/fintekkers/models/strategy/strategy_pb.js +20 -14
- package/node/fintekkers/models/transaction/transaction_pb.js +30 -24
- package/node/fintekkers/models/transaction/transaction_type_pb.js +7 -1
- package/node/fintekkers/models/util/api/api_key_pb.js +16 -10
- package/node/fintekkers/models/util/currency_grpc_pb.js +1 -0
- package/node/fintekkers/models/util/currency_pb.js +10 -4
- package/node/fintekkers/models/util/date_range_pb.js +14 -8
- package/node/fintekkers/models/util/decimal_value_pb.js +10 -4
- package/node/fintekkers/models/util/endpoint_pb.js +13 -7
- package/node/fintekkers/models/util/local_date_pb.js +11 -5
- package/node/fintekkers/models/util/local_timestamp_pb.js +11 -5
- package/node/fintekkers/models/util/lock/node_partition_pb.js +15 -9
- package/node/fintekkers/models/util/lock/node_state_pb.js +16 -10
- package/node/fintekkers/models/util/uuid_pb.js +9 -3
- package/node/fintekkers/models/valuation/cashflow_grpc_pb.js +1 -0
- package/node/fintekkers/models/valuation/cashflow_pb.js +13 -7
- package/node/fintekkers/requests/index_composition/create_index_composition_request_grpc_pb.js +1 -0
- package/node/fintekkers/requests/index_composition/create_index_composition_request_pb.js +22 -16
- package/node/fintekkers/requests/index_composition/get_index_composition_request_grpc_pb.js +1 -0
- package/node/fintekkers/requests/index_composition/get_index_composition_request_pb.js +22 -16
- package/node/fintekkers/requests/portfolio/create_portfolio_request_pb.js +13 -7
- package/node/fintekkers/requests/portfolio/create_portfolio_response_pb.js +14 -8
- package/node/fintekkers/requests/portfolio/query_portfolio_request_pb.js +17 -11
- package/node/fintekkers/requests/portfolio/query_portfolio_response_pb.js +14 -8
- package/node/fintekkers/requests/position/query_position_request_pb.js +27 -15
- package/node/fintekkers/requests/position/query_position_response_pb.js +16 -10
- package/node/fintekkers/requests/price/create_price_request_pb.js +13 -7
- package/node/fintekkers/requests/price/create_price_response_pb.js +14 -8
- package/node/fintekkers/requests/price/query_price_request_pb.js +18 -12
- package/node/fintekkers/requests/price/query_price_response_pb.js +14 -8
- package/node/fintekkers/requests/security/create_security_request_pb.js +13 -7
- package/node/fintekkers/requests/security/create_security_response_pb.js +15 -9
- package/node/fintekkers/requests/security/get_field_values_request_pb.js +13 -7
- package/node/fintekkers/requests/security/get_field_values_response_pb.js +13 -7
- package/node/fintekkers/requests/security/get_fields_response_pb.js +17 -8
- package/node/fintekkers/requests/security/query_security_request_pb.js +17 -11
- package/node/fintekkers/requests/security/query_security_response_pb.js +15 -9
- package/node/fintekkers/requests/transaction/create_transaction_request_pb.js +13 -7
- package/node/fintekkers/requests/transaction/create_transaction_response_pb.js +14 -8
- package/node/fintekkers/requests/transaction/query_transaction_request_pb.js +16 -10
- package/node/fintekkers/requests/transaction/query_transaction_response_pb.js +15 -9
- package/node/fintekkers/requests/util/delete_request_grpc_pb.js +1 -0
- package/node/fintekkers/requests/util/delete_request_pb.js +34 -28
- package/node/fintekkers/requests/util/errors/error_pb.js +13 -7
- package/node/fintekkers/requests/util/errors/message_pb.js +12 -6
- package/node/fintekkers/requests/util/errors/summary_pb.js +10 -4
- package/node/fintekkers/requests/util/lock/lock_request_pb.js +14 -8
- package/node/fintekkers/requests/util/lock/lock_response_pb.js +15 -9
- package/node/fintekkers/requests/util/operation_pb.js +7 -1
- package/node/fintekkers/requests/valuation/curve_request_grpc_pb.js +1 -0
- package/node/fintekkers/requests/valuation/curve_request_pb.d.ts +12 -0
- package/node/fintekkers/requests/valuation/curve_request_pb.js +125 -14
- package/node/fintekkers/requests/valuation/curve_response_grpc_pb.js +1 -0
- package/node/fintekkers/requests/valuation/curve_response_pb.js +21 -15
- package/node/fintekkers/requests/valuation/product_inputs.test.d.ts +6 -0
- package/node/fintekkers/requests/valuation/product_inputs.test.js +146 -0
- package/node/fintekkers/requests/valuation/product_inputs.test.js.map +1 -0
- package/node/fintekkers/requests/valuation/product_inputs_grpc_pb.js +1 -0
- package/node/fintekkers/requests/valuation/product_inputs_pb.d.ts +42 -0
- package/node/fintekkers/requests/valuation/product_inputs_pb.js +360 -27
- package/node/fintekkers/requests/valuation/valuation_request_pb.js +25 -16
- package/node/fintekkers/requests/valuation/valuation_response_pb.js +16 -10
- package/node/fintekkers/services/index-composition-service/index_composition_service_grpc_pb.js +14 -14
- package/node/fintekkers/services/index-composition-service/index_composition_service_pb.js +7 -1
- package/node/fintekkers/services/lock-service/lock_service_grpc_pb.js +23 -23
- package/node/fintekkers/services/lock-service/lock_service_pb.js +21 -15
- package/node/fintekkers/services/portfolio-service/portfolio_service_grpc_pb.js +8 -8
- package/node/fintekkers/services/portfolio-service/portfolio_service_pb.js +7 -1
- package/node/fintekkers/services/position-service/position_service_grpc_pb.js +10 -10
- package/node/fintekkers/services/position-service/position_service_pb.js +7 -1
- package/node/fintekkers/services/price-service/price_service_grpc_pb.js +6 -6
- package/node/fintekkers/services/price-service/price_service_pb.js +7 -1
- package/node/fintekkers/services/security-service/security_service_grpc_pb.js +12 -12
- package/node/fintekkers/services/security-service/security_service_pb.js +7 -1
- package/node/fintekkers/services/transaction-service/transaction_service_grpc_pb.js +11 -11
- package/node/fintekkers/services/transaction-service/transaction_service_pb.js +7 -1
- package/node/fintekkers/services/valuation-service/valuation_service_grpc_pb.js +5 -5
- package/node/fintekkers/services/valuation-service/valuation_service_pb.js +7 -1
- package/node/wrappers/models/price/Price.d.ts +5 -0
- package/node/wrappers/models/price/Price.js +7 -0
- package/node/wrappers/models/price/Price.js.map +1 -1
- package/node/wrappers/models/price/Price.ts +8 -0
- package/node/wrappers/models/security/security.d.ts +6 -0
- package/node/wrappers/models/security/security.js +8 -0
- package/node/wrappers/models/security/security.js.map +1 -1
- package/node/wrappers/models/security/security.ts +9 -0
- package/node/wrappers/services/price-service/PriceService.d.ts +19 -0
- package/node/wrappers/services/price-service/PriceService.js +26 -0
- package/node/wrappers/services/price-service/PriceService.js.map +1 -1
- package/node/wrappers/services/price-service/PriceService.ts +29 -0
- package/node/wrappers/services/searchWithSecurities.test.js +125 -0
- package/node/wrappers/services/searchWithSecurities.test.js.map +1 -0
- package/node/wrappers/services/searchWithSecurities.test.ts +103 -0
- package/node/wrappers/services/transaction-service/TransactionService.d.ts +14 -0
- package/node/wrappers/services/transaction-service/TransactionService.js +25 -0
- package/node/wrappers/services/transaction-service/TransactionService.js.map +1 -1
- package/node/wrappers/services/transaction-service/TransactionService.ts +29 -0
- package/node/wrappers/util/link-resolver.d.ts +127 -0
- package/node/wrappers/util/link-resolver.js +378 -0
- package/node/wrappers/util/link-resolver.js.map +1 -0
- package/node/wrappers/util/link-resolver.test.d.ts +1 -0
- package/node/wrappers/util/link-resolver.test.js +349 -0
- package/node/wrappers/util/link-resolver.test.js.map +1 -0
- package/node/wrappers/util/link-resolver.test.ts +402 -0
- package/node/wrappers/util/link-resolver.ts +448 -0
- package/package.json +1 -1
- package/node/wrappers/services/security-service/SecurityService.searchByUuid.test.js +0 -38
- package/node/wrappers/services/security-service/SecurityService.searchByUuid.test.js.map +0 -1
- package/node/wrappers/services/security-service/SecurityService.searchByUuid.test.ts +0 -32
- /package/node/wrappers/services/{security-service/SecurityService.searchByUuid.test.d.ts → searchWithSecurities.test.d.ts} +0 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import LinkResolver from './link-resolver';
|
|
2
|
+
import Price from '../models/price/Price';
|
|
3
|
+
import { UUID } from '../models/utils/uuid';
|
|
4
|
+
|
|
5
|
+
import { SecurityProto } from '../../fintekkers/models/security/security_pb';
|
|
6
|
+
import { PortfolioProto } from '../../fintekkers/models/portfolio/portfolio_pb';
|
|
7
|
+
import { PriceProto } from '../../fintekkers/models/price/price_pb';
|
|
8
|
+
import { DecimalValueProto } from '../../fintekkers/models/util/decimal_value_pb';
|
|
9
|
+
import { IdentifierProto } from '../../fintekkers/models/security/identifier/identifier_pb';
|
|
10
|
+
import { LocalTimestampProto } from '../../fintekkers/models/util/local_timestamp_pb';
|
|
11
|
+
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
|
12
|
+
import { QuerySecurityResponseProto } from '../../fintekkers/requests/security/query_security_response_pb';
|
|
13
|
+
import { QuerySecurityRequestProto } from '../../fintekkers/requests/security/query_security_request_pb';
|
|
14
|
+
import { QueryPortfolioResponseProto } from '../../fintekkers/requests/portfolio/query_portfolio_response_pb';
|
|
15
|
+
import { QueryPortfolioRequestProto } from '../../fintekkers/requests/portfolio/query_portfolio_request_pb';
|
|
16
|
+
|
|
17
|
+
interface CallLog {
|
|
18
|
+
count: number;
|
|
19
|
+
uuids: string[][];
|
|
20
|
+
asOfSeconds: (number | null)[]; // null = unset (latest)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function newCallLog(): CallLog {
|
|
24
|
+
return { count: 0, uuids: [], asOfSeconds: [] };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Minimal mock for the security gRPC client. The LinkResolver only calls
|
|
28
|
+
// `getByIds`; we node-style invoke the callback with a fake response built
|
|
29
|
+
// from a pre-canned UUID → SecurityProto map. The mock also records each
|
|
30
|
+
// call's as_of (in seconds since epoch, or null if unset) so tests can
|
|
31
|
+
// assert per-bucket RPC behavior.
|
|
32
|
+
function mockSecurityClient(store: Map<string, SecurityProto>, callLog: CallLog) {
|
|
33
|
+
return {
|
|
34
|
+
getByIds: (
|
|
35
|
+
request: QuerySecurityRequestProto,
|
|
36
|
+
callback: (err: Error | null, response: QuerySecurityResponseProto) => void,
|
|
37
|
+
) => {
|
|
38
|
+
const uuidProtos = request.getUuidsList();
|
|
39
|
+
const requestedUuids = uuidProtos.map((u) => UUID.fromU8Array(u.getRawUuid_asU8()).toString());
|
|
40
|
+
callLog.count += 1;
|
|
41
|
+
callLog.uuids.push(requestedUuids);
|
|
42
|
+
const asOf = request.getAsOf();
|
|
43
|
+
callLog.asOfSeconds.push(asOf?.getTimestamp()?.getSeconds() ?? null);
|
|
44
|
+
|
|
45
|
+
const response = new QuerySecurityResponseProto();
|
|
46
|
+
const found: SecurityProto[] = [];
|
|
47
|
+
for (const u of requestedUuids) {
|
|
48
|
+
const proto = store.get(u);
|
|
49
|
+
if (proto) found.push(proto);
|
|
50
|
+
}
|
|
51
|
+
response.setSecurityResponseList(found);
|
|
52
|
+
// Schedule async like a real gRPC client would.
|
|
53
|
+
setImmediate(() => callback(null, response));
|
|
54
|
+
},
|
|
55
|
+
} as any;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function mockPortfolioClient(store: Map<string, PortfolioProto>, callLog: CallLog) {
|
|
59
|
+
return {
|
|
60
|
+
getByIds: (
|
|
61
|
+
request: QueryPortfolioRequestProto,
|
|
62
|
+
callback: (err: Error | null, response: QueryPortfolioResponseProto) => void,
|
|
63
|
+
) => {
|
|
64
|
+
const uuidProtos = request.getUuidsList();
|
|
65
|
+
const requestedUuids = uuidProtos.map((u) => UUID.fromU8Array(u.getRawUuid_asU8()).toString());
|
|
66
|
+
callLog.count += 1;
|
|
67
|
+
callLog.uuids.push(requestedUuids);
|
|
68
|
+
const asOf = request.getAsOf();
|
|
69
|
+
callLog.asOfSeconds.push(asOf?.getTimestamp()?.getSeconds() ?? null);
|
|
70
|
+
|
|
71
|
+
const response = new QueryPortfolioResponseProto();
|
|
72
|
+
const found: PortfolioProto[] = [];
|
|
73
|
+
for (const u of requestedUuids) {
|
|
74
|
+
const proto = store.get(u);
|
|
75
|
+
if (proto) found.push(proto);
|
|
76
|
+
}
|
|
77
|
+
response.setPortfolioResponseList(found);
|
|
78
|
+
setImmediate(() => callback(null, response));
|
|
79
|
+
},
|
|
80
|
+
} as any;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function makeAsOf(epochSeconds: number): LocalTimestampProto {
|
|
84
|
+
const ts = new Timestamp();
|
|
85
|
+
ts.setSeconds(epochSeconds);
|
|
86
|
+
ts.setNanos(0);
|
|
87
|
+
const lt = new LocalTimestampProto();
|
|
88
|
+
lt.setTimestamp(ts);
|
|
89
|
+
lt.setTimeZone('UTC');
|
|
90
|
+
return lt;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function fullSecurity(uuid: UUID, issuerName: string): SecurityProto {
|
|
94
|
+
const proto = new SecurityProto();
|
|
95
|
+
proto.setObjectClass('Security');
|
|
96
|
+
proto.setVersion('0.0.1');
|
|
97
|
+
proto.setUuid(uuid.toUUIDProto());
|
|
98
|
+
proto.setIsLink(false);
|
|
99
|
+
proto.setIssuerName(issuerName);
|
|
100
|
+
const ident = new IdentifierProto();
|
|
101
|
+
ident.setIdentifierValue(`TICKER-${issuerName}`);
|
|
102
|
+
proto.setIdentifier(ident);
|
|
103
|
+
return proto;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function linkPrice(securityUuid: UUID, priceValue: string, asOf?: LocalTimestampProto): Price {
|
|
107
|
+
const linkSec = new SecurityProto();
|
|
108
|
+
linkSec.setUuid(securityUuid.toUUIDProto());
|
|
109
|
+
linkSec.setIsLink(true);
|
|
110
|
+
if (asOf) linkSec.setAsOf(asOf);
|
|
111
|
+
|
|
112
|
+
const priceProto = new PriceProto();
|
|
113
|
+
priceProto.setObjectClass('Price');
|
|
114
|
+
priceProto.setVersion('0.0.1');
|
|
115
|
+
priceProto.setUuid(UUID.random().toUUIDProto());
|
|
116
|
+
priceProto.setSecurity(linkSec);
|
|
117
|
+
const dv = new DecimalValueProto();
|
|
118
|
+
dv.setArbitraryPrecisionValue(priceValue);
|
|
119
|
+
priceProto.setPrice(dv);
|
|
120
|
+
return new Price(priceProto);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
describe('LinkResolver', () => {
|
|
124
|
+
test('bulk resolveSecurities dedupes UUIDs (5 prices, 3 unique → 1 RPC, 3 UUIDs)', async () => {
|
|
125
|
+
const uuidA = UUID.random();
|
|
126
|
+
const uuidB = UUID.random();
|
|
127
|
+
const uuidC = UUID.random();
|
|
128
|
+
|
|
129
|
+
const store = new Map<string, SecurityProto>([
|
|
130
|
+
[uuidA.toString(), fullSecurity(uuidA, 'AAPL')],
|
|
131
|
+
[uuidB.toString(), fullSecurity(uuidB, 'MSFT')],
|
|
132
|
+
[uuidC.toString(), fullSecurity(uuidC, 'GOOG')],
|
|
133
|
+
]);
|
|
134
|
+
const callLog = newCallLog();
|
|
135
|
+
|
|
136
|
+
const resolver = new LinkResolver({
|
|
137
|
+
securityClient: mockSecurityClient(store, callLog),
|
|
138
|
+
portfolioClient: mockPortfolioClient(new Map(), newCallLog()),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const prices = [
|
|
142
|
+
linkPrice(uuidA, '100'),
|
|
143
|
+
linkPrice(uuidA, '101'),
|
|
144
|
+
linkPrice(uuidB, '200'),
|
|
145
|
+
linkPrice(uuidA, '102'),
|
|
146
|
+
linkPrice(uuidC, '300'),
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
await resolver.resolveSecurities(prices);
|
|
150
|
+
|
|
151
|
+
expect(callLog.count).toBe(1);
|
|
152
|
+
expect(callLog.uuids[0].length).toBe(3);
|
|
153
|
+
expect(new Set(callLog.uuids[0])).toEqual(new Set([uuidA.toString(), uuidB.toString(), uuidC.toString()]));
|
|
154
|
+
|
|
155
|
+
// Each price's embedded security is now hydrated.
|
|
156
|
+
for (const p of prices) {
|
|
157
|
+
expect(p.proto.getSecurity()!.getIsLink()).toBe(false);
|
|
158
|
+
expect(p.proto.getSecurity()!.getIssuerName().length).toBeGreaterThan(0);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('cache hit: second getSecurity call for the same UUID does not re-RPC', async () => {
|
|
163
|
+
const uuid = UUID.random();
|
|
164
|
+
const store = new Map<string, SecurityProto>([
|
|
165
|
+
[uuid.toString(), fullSecurity(uuid, 'AAPL')],
|
|
166
|
+
]);
|
|
167
|
+
const callLog = newCallLog();
|
|
168
|
+
|
|
169
|
+
const resolver = new LinkResolver({
|
|
170
|
+
securityClient: mockSecurityClient(store, callLog),
|
|
171
|
+
portfolioClient: mockPortfolioClient(new Map(), newCallLog()),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const sec1 = await resolver.getSecurity(uuid);
|
|
175
|
+
const sec2 = await resolver.getSecurity(uuid);
|
|
176
|
+
|
|
177
|
+
expect(callLog.count).toBe(1);
|
|
178
|
+
expect(sec1.getIssuerName()).toBe('AAPL');
|
|
179
|
+
expect(sec2.getIssuerName()).toBe('AAPL');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('concurrent same-UUID requests collapse to one RPC', async () => {
|
|
183
|
+
const uuid = UUID.random();
|
|
184
|
+
const store = new Map<string, SecurityProto>([
|
|
185
|
+
[uuid.toString(), fullSecurity(uuid, 'AAPL')],
|
|
186
|
+
]);
|
|
187
|
+
const callLog = newCallLog();
|
|
188
|
+
|
|
189
|
+
const resolver = new LinkResolver({
|
|
190
|
+
securityClient: mockSecurityClient(store, callLog),
|
|
191
|
+
portfolioClient: mockPortfolioClient(new Map(), newCallLog()),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Fire N parallel calls for the same UUID before any resolves.
|
|
195
|
+
const results = await Promise.all([
|
|
196
|
+
resolver.getSecurity(uuid),
|
|
197
|
+
resolver.getSecurity(uuid),
|
|
198
|
+
resolver.getSecurity(uuid),
|
|
199
|
+
resolver.getSecurity(uuid),
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
expect(callLog.count).toBe(1);
|
|
203
|
+
for (const r of results) expect(r.getIssuerName()).toBe('AAPL');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('caching disabled (cacheSize=0) re-RPCs every call', async () => {
|
|
207
|
+
const uuid = UUID.random();
|
|
208
|
+
const store = new Map<string, SecurityProto>([
|
|
209
|
+
[uuid.toString(), fullSecurity(uuid, 'AAPL')],
|
|
210
|
+
]);
|
|
211
|
+
const callLog = newCallLog();
|
|
212
|
+
|
|
213
|
+
const resolver = new LinkResolver({
|
|
214
|
+
cacheSize: 0,
|
|
215
|
+
securityClient: mockSecurityClient(store, callLog),
|
|
216
|
+
portfolioClient: mockPortfolioClient(new Map(), newCallLog()),
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
await resolver.getSecurity(uuid);
|
|
220
|
+
await resolver.getSecurity(uuid);
|
|
221
|
+
|
|
222
|
+
expect(callLog.count).toBe(2);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('non-link items pass through unchanged (resolveSecurities is a no-op for them)', async () => {
|
|
226
|
+
const callLog = newCallLog();
|
|
227
|
+
const resolver = new LinkResolver({
|
|
228
|
+
securityClient: mockSecurityClient(new Map(), callLog),
|
|
229
|
+
portfolioClient: mockPortfolioClient(new Map(), newCallLog()),
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Build a Price whose embedded Security is NOT a link (full entity).
|
|
233
|
+
const fullSec = fullSecurity(UUID.random(), 'AAPL');
|
|
234
|
+
const priceProto = new PriceProto();
|
|
235
|
+
priceProto.setObjectClass('Price');
|
|
236
|
+
priceProto.setUuid(UUID.random().toUUIDProto());
|
|
237
|
+
priceProto.setSecurity(fullSec);
|
|
238
|
+
const dv = new DecimalValueProto();
|
|
239
|
+
dv.setArbitraryPrecisionValue('100');
|
|
240
|
+
priceProto.setPrice(dv);
|
|
241
|
+
const price = new Price(priceProto);
|
|
242
|
+
|
|
243
|
+
await resolver.resolveSecurities([price]);
|
|
244
|
+
|
|
245
|
+
expect(callLog.count).toBe(0);
|
|
246
|
+
expect(price.proto.getSecurity()!.getIssuerName()).toBe('AAPL');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('resolveSecurities skips items missing security', async () => {
|
|
250
|
+
const callLog = newCallLog();
|
|
251
|
+
const resolver = new LinkResolver({
|
|
252
|
+
securityClient: mockSecurityClient(new Map(), callLog),
|
|
253
|
+
portfolioClient: mockPortfolioClient(new Map(), newCallLog()),
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const priceProto = new PriceProto();
|
|
257
|
+
priceProto.setObjectClass('Price');
|
|
258
|
+
priceProto.setUuid(UUID.random().toUUIDProto());
|
|
259
|
+
// No security set on this price.
|
|
260
|
+
const dv = new DecimalValueProto();
|
|
261
|
+
dv.setArbitraryPrecisionValue('100');
|
|
262
|
+
priceProto.setPrice(dv);
|
|
263
|
+
const price = new Price(priceProto);
|
|
264
|
+
|
|
265
|
+
await resolver.resolveSecurities([price]);
|
|
266
|
+
|
|
267
|
+
expect(callLog.count).toBe(0);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test('cache cross-call: a second resolveSecurities call reuses the cache from the first', async () => {
|
|
271
|
+
const uuidA = UUID.random();
|
|
272
|
+
const uuidB = UUID.random();
|
|
273
|
+
const store = new Map<string, SecurityProto>([
|
|
274
|
+
[uuidA.toString(), fullSecurity(uuidA, 'AAPL')],
|
|
275
|
+
[uuidB.toString(), fullSecurity(uuidB, 'MSFT')],
|
|
276
|
+
]);
|
|
277
|
+
const callLog = newCallLog();
|
|
278
|
+
|
|
279
|
+
const resolver = new LinkResolver({
|
|
280
|
+
securityClient: mockSecurityClient(store, callLog),
|
|
281
|
+
portfolioClient: mockPortfolioClient(new Map(), newCallLog()),
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// First call: 2 unique UUIDs → 1 RPC fetching both.
|
|
285
|
+
await resolver.resolveSecurities([linkPrice(uuidA, '1'), linkPrice(uuidB, '2')]);
|
|
286
|
+
expect(callLog.count).toBe(1);
|
|
287
|
+
expect(callLog.uuids[0].length).toBe(2);
|
|
288
|
+
|
|
289
|
+
// Second call: both UUIDs already cached → 0 additional RPCs.
|
|
290
|
+
await resolver.resolveSecurities([linkPrice(uuidA, '3'), linkPrice(uuidB, '4')]);
|
|
291
|
+
expect(callLog.count).toBe(1);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// ---------- as_of-aware behavior (per is_link_pattern.md addendum) ----------
|
|
295
|
+
|
|
296
|
+
test('link without as_of → request omits as_of (server returns latest)', async () => {
|
|
297
|
+
const uuid = UUID.random();
|
|
298
|
+
const store = new Map<string, SecurityProto>([
|
|
299
|
+
[uuid.toString(), fullSecurity(uuid, 'AAPL')],
|
|
300
|
+
]);
|
|
301
|
+
const callLog = newCallLog();
|
|
302
|
+
const resolver = new LinkResolver({
|
|
303
|
+
securityClient: mockSecurityClient(store, callLog),
|
|
304
|
+
portfolioClient: mockPortfolioClient(new Map(), newCallLog()),
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
await resolver.resolveSecurities([linkPrice(uuid, '1')]);
|
|
308
|
+
expect(callLog.count).toBe(1);
|
|
309
|
+
expect(callLog.asOfSeconds[0]).toBeNull();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('link with as_of → request carries that as_of', async () => {
|
|
313
|
+
const uuid = UUID.random();
|
|
314
|
+
const store = new Map<string, SecurityProto>([
|
|
315
|
+
[uuid.toString(), fullSecurity(uuid, 'AAPL')],
|
|
316
|
+
]);
|
|
317
|
+
const callLog = newCallLog();
|
|
318
|
+
const resolver = new LinkResolver({
|
|
319
|
+
securityClient: mockSecurityClient(store, callLog),
|
|
320
|
+
portfolioClient: mockPortfolioClient(new Map(), newCallLog()),
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const t1 = makeAsOf(1_700_000_000);
|
|
324
|
+
await resolver.resolveSecurities([linkPrice(uuid, '1', t1)]);
|
|
325
|
+
expect(callLog.count).toBe(1);
|
|
326
|
+
expect(callLog.asOfSeconds[0]).toBe(1_700_000_000);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test('two as_of buckets for the same UUID → 2 separate RPCs (one per bucket)', async () => {
|
|
330
|
+
const uuid = UUID.random();
|
|
331
|
+
const store = new Map<string, SecurityProto>([
|
|
332
|
+
[uuid.toString(), fullSecurity(uuid, 'AAPL')],
|
|
333
|
+
]);
|
|
334
|
+
const callLog = newCallLog();
|
|
335
|
+
const resolver = new LinkResolver({
|
|
336
|
+
securityClient: mockSecurityClient(store, callLog),
|
|
337
|
+
portfolioClient: mockPortfolioClient(new Map(), newCallLog()),
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const t1 = makeAsOf(1_700_000_000);
|
|
341
|
+
const t2 = makeAsOf(1_800_000_000);
|
|
342
|
+
|
|
343
|
+
await resolver.resolveSecurities([
|
|
344
|
+
linkPrice(uuid, '1', t1),
|
|
345
|
+
linkPrice(uuid, '2', t2),
|
|
346
|
+
]);
|
|
347
|
+
|
|
348
|
+
expect(callLog.count).toBe(2);
|
|
349
|
+
expect(new Set(callLog.asOfSeconds)).toEqual(new Set([1_700_000_000, 1_800_000_000]));
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('same as_of for the same UUID → still 1 RPC (proper bucket dedupe)', async () => {
|
|
353
|
+
const uuid = UUID.random();
|
|
354
|
+
const store = new Map<string, SecurityProto>([
|
|
355
|
+
[uuid.toString(), fullSecurity(uuid, 'AAPL')],
|
|
356
|
+
]);
|
|
357
|
+
const callLog = newCallLog();
|
|
358
|
+
const resolver = new LinkResolver({
|
|
359
|
+
securityClient: mockSecurityClient(store, callLog),
|
|
360
|
+
portfolioClient: mockPortfolioClient(new Map(), newCallLog()),
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const t1a = makeAsOf(1_700_000_000);
|
|
364
|
+
const t1b = makeAsOf(1_700_000_000); // same moment, different proto instance
|
|
365
|
+
|
|
366
|
+
await resolver.resolveSecurities([
|
|
367
|
+
linkPrice(uuid, '1', t1a),
|
|
368
|
+
linkPrice(uuid, '2', t1b),
|
|
369
|
+
]);
|
|
370
|
+
|
|
371
|
+
expect(callLog.count).toBe(1);
|
|
372
|
+
expect(callLog.uuids[0]).toEqual([uuid.toString()]);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test('cache key includes as_of: latest cached does NOT serve a t1 lookup, and vice versa', async () => {
|
|
376
|
+
const uuid = UUID.random();
|
|
377
|
+
const store = new Map<string, SecurityProto>([
|
|
378
|
+
[uuid.toString(), fullSecurity(uuid, 'AAPL')],
|
|
379
|
+
]);
|
|
380
|
+
const callLog = newCallLog();
|
|
381
|
+
const resolver = new LinkResolver({
|
|
382
|
+
securityClient: mockSecurityClient(store, callLog),
|
|
383
|
+
portfolioClient: mockPortfolioClient(new Map(), newCallLog()),
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
const t1 = makeAsOf(1_700_000_000);
|
|
387
|
+
|
|
388
|
+
// First: latest. RPC fired.
|
|
389
|
+
await resolver.getSecurity(uuid);
|
|
390
|
+
expect(callLog.count).toBe(1);
|
|
391
|
+
expect(callLog.asOfSeconds[0]).toBeNull();
|
|
392
|
+
|
|
393
|
+
// Second: as_of=t1. Should NOT be served by the "latest" cache → another RPC.
|
|
394
|
+
await resolver.getSecurity(uuid, t1);
|
|
395
|
+
expect(callLog.count).toBe(2);
|
|
396
|
+
expect(callLog.asOfSeconds[1]).toBe(1_700_000_000);
|
|
397
|
+
|
|
398
|
+
// Third: same (uuid, t1) → cache hit, no new RPC.
|
|
399
|
+
await resolver.getSecurity(uuid, makeAsOf(1_700_000_000));
|
|
400
|
+
expect(callLog.count).toBe(2);
|
|
401
|
+
});
|
|
402
|
+
});
|