@olane/o-node 0.7.12-alpha.9 → 0.7.12
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/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts +1 -0
- package/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts.map +1 -1
- package/dist/src/connection/interfaces/o-node-connection.config.d.ts +1 -0
- package/dist/src/connection/interfaces/o-node-connection.config.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.d.ts +7 -2
- package/dist/src/connection/o-node-connection.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.js +56 -32
- package/dist/src/connection/o-node-connection.manager.d.ts +20 -5
- package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.manager.js +95 -65
- package/dist/src/connection/o-stream.request.d.ts +11 -0
- package/dist/src/connection/o-stream.request.d.ts.map +1 -0
- package/dist/src/connection/o-stream.request.js +7 -0
- package/dist/src/connection/stream-handler.config.d.ts +46 -0
- package/dist/src/connection/stream-handler.config.d.ts.map +1 -0
- package/dist/src/connection/stream-handler.config.js +1 -0
- package/dist/src/connection/stream-handler.d.ts +98 -0
- package/dist/src/connection/stream-handler.d.ts.map +1 -0
- package/dist/src/connection/stream-handler.js +310 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/interfaces/i-heartbeatable-node.d.ts +49 -0
- package/dist/src/interfaces/i-heartbeatable-node.d.ts.map +1 -0
- package/dist/src/interfaces/i-heartbeatable-node.js +1 -0
- package/dist/src/interfaces/i-reconnectable-node.d.ts +5 -0
- package/dist/src/interfaces/i-reconnectable-node.d.ts.map +1 -1
- package/dist/src/interfaces/o-node.config.d.ts +16 -22
- package/dist/src/interfaces/o-node.config.d.ts.map +1 -1
- package/dist/src/managers/o-connection-heartbeat.manager.d.ts +17 -17
- package/dist/src/managers/o-connection-heartbeat.manager.d.ts.map +1 -1
- package/dist/src/managers/o-connection-heartbeat.manager.js +83 -78
- package/dist/src/managers/o-reconnection.manager.d.ts +12 -0
- package/dist/src/managers/o-reconnection.manager.d.ts.map +1 -1
- package/dist/src/managers/o-reconnection.manager.js +138 -22
- package/dist/src/o-node.d.ts +23 -12
- package/dist/src/o-node.d.ts.map +1 -1
- package/dist/src/o-node.js +227 -72
- package/dist/src/o-node.notification-manager.d.ts.map +1 -1
- package/dist/src/o-node.notification-manager.js +17 -12
- package/dist/src/o-node.tool.d.ts +1 -0
- package/dist/src/o-node.tool.d.ts.map +1 -1
- package/dist/src/o-node.tool.js +18 -34
- package/dist/src/router/o-node.router.d.ts +1 -0
- package/dist/src/router/o-node.router.d.ts.map +1 -1
- package/dist/src/router/o-node.router.js +62 -9
- package/dist/src/router/o-node.routing-policy.d.ts.map +1 -1
- package/dist/src/router/o-node.routing-policy.js +7 -2
- package/dist/src/router/resolvers/o-node.resolver.d.ts.map +1 -1
- package/dist/src/router/resolvers/o-node.resolver.js +5 -1
- package/dist/src/router/resolvers/o-node.search-resolver.d.ts.map +1 -1
- package/dist/src/router/resolvers/o-node.search-resolver.js +34 -9
- package/dist/src/utils/index.d.ts +3 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +2 -0
- package/dist/src/utils/stream.utils.d.ts +6 -0
- package/dist/src/utils/stream.utils.d.ts.map +1 -0
- package/dist/src/utils/stream.utils.js +31 -0
- package/dist/test/helpers/test-node.tool.d.ts +15 -0
- package/dist/test/helpers/test-node.tool.d.ts.map +1 -0
- package/dist/test/helpers/test-node.tool.js +27 -0
- package/package.json +6 -6
- package/dist/src/router/resolvers/o-node.child-resolver.d.ts +0 -11
- package/dist/src/router/resolvers/o-node.child-resolver.d.ts.map +0 -1
- package/dist/src/router/resolvers/o-node.child-resolver.js +0 -58
- package/dist/src/utils/leader-request-wrapper.d.ts +0 -45
- package/dist/src/utils/leader-request-wrapper.d.ts.map +0 -1
- package/dist/src/utils/leader-request-wrapper.js +0 -89
- package/dist/test/o-node.spec.d.ts +0 -2
- package/dist/test/o-node.spec.d.ts.map +0 -1
- package/dist/test/o-node.spec.js +0 -20
- package/dist/test/search-resolver.spec.d.ts +0 -2
- package/dist/test/search-resolver.spec.d.ts.map +0 -1
- package/dist/test/search-resolver.spec.js +0 -693
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { oAddress, oError, oErrorCodes, oRequest, } from '@olane/o-core';
|
|
1
|
+
import { CoreUtils, oAddress, oError, oErrorCodes, oRequest, ResponseBuilder, } from '@olane/o-core';
|
|
2
2
|
import { oToolRouter } from '@olane/o-tool';
|
|
3
|
-
import { oNodeConnection } from '../connection/o-node-connection.js';
|
|
4
3
|
import { oNodeRoutingPolicy } from './o-node.routing-policy.js';
|
|
4
|
+
import { oStreamRequest } from '../connection/o-stream.request.js';
|
|
5
5
|
export class oNodeRouter extends oToolRouter {
|
|
6
6
|
constructor() {
|
|
7
7
|
super();
|
|
@@ -25,6 +25,9 @@ export class oNodeRouter extends oToolRouter {
|
|
|
25
25
|
params: request.params,
|
|
26
26
|
id: request.id,
|
|
27
27
|
});
|
|
28
|
+
if (request.stream) {
|
|
29
|
+
nextHopRequest.stream = request.stream;
|
|
30
|
+
}
|
|
28
31
|
// Handle self-routing: execute locally instead of dialing
|
|
29
32
|
if (this.routingPolicy.isSelfAddress(address, node)) {
|
|
30
33
|
return this.executeSelfRouting(request, node);
|
|
@@ -39,17 +42,55 @@ export class oNodeRouter extends oToolRouter {
|
|
|
39
42
|
}
|
|
40
43
|
/**
|
|
41
44
|
* Executes a request locally when routing to self.
|
|
45
|
+
* Now uses ResponseBuilder for consistency with useSelf() behavior.
|
|
42
46
|
*/
|
|
43
47
|
async executeSelfRouting(request, node) {
|
|
44
48
|
const { payload } = request.params;
|
|
45
49
|
const params = payload.params;
|
|
46
50
|
const localRequest = new oRequest({
|
|
47
51
|
method: payload.method,
|
|
48
|
-
params: {
|
|
52
|
+
params: {
|
|
53
|
+
...params,
|
|
54
|
+
_connectionId: request.params._connectionId,
|
|
55
|
+
_requestMethod: payload.method,
|
|
56
|
+
},
|
|
49
57
|
id: request.id,
|
|
50
58
|
});
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
// Create ResponseBuilder with metrics tracking
|
|
60
|
+
const responseBuilder = ResponseBuilder.create().withMetrics(node.metrics);
|
|
61
|
+
// Handle streaming requests
|
|
62
|
+
const isStream = request.params._isStreaming;
|
|
63
|
+
if (isStream && request.stream) {
|
|
64
|
+
// For streaming, we need to handle the stream chunks
|
|
65
|
+
try {
|
|
66
|
+
const result = await node.execute(localRequest, request.stream);
|
|
67
|
+
const response = await responseBuilder.build(localRequest, result, null, {
|
|
68
|
+
isStream: true,
|
|
69
|
+
});
|
|
70
|
+
// Return unwrapped data for consistency with dialAndTransmit
|
|
71
|
+
return response.result.data;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const errorResponse = await responseBuilder.buildError(localRequest, error, {
|
|
75
|
+
isStream: true,
|
|
76
|
+
});
|
|
77
|
+
// For errors, throw to match remote behavior
|
|
78
|
+
throw responseBuilder.normalizeError(error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Handle non-streaming requests with error handling
|
|
82
|
+
try {
|
|
83
|
+
const result = await node.execute(localRequest);
|
|
84
|
+
const response = await responseBuilder.build(localRequest, result, null);
|
|
85
|
+
// Return unwrapped data to match dialAndTransmit behavior
|
|
86
|
+
return response.result.data;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
// Build error response for metrics tracking
|
|
90
|
+
await responseBuilder.buildError(localRequest, error);
|
|
91
|
+
// Then throw the normalized error
|
|
92
|
+
throw responseBuilder.normalizeError(error);
|
|
93
|
+
}
|
|
53
94
|
}
|
|
54
95
|
/**
|
|
55
96
|
* Checks if the next hop is the final destination address.
|
|
@@ -65,10 +106,11 @@ export class oNodeRouter extends oToolRouter {
|
|
|
65
106
|
unwrapDestinationRequest(request) {
|
|
66
107
|
const { payload } = request.params;
|
|
67
108
|
const params = payload.params;
|
|
68
|
-
return new
|
|
109
|
+
return new oStreamRequest({
|
|
69
110
|
method: payload.method,
|
|
70
111
|
params: { ...params },
|
|
71
112
|
id: request.id,
|
|
113
|
+
stream: request.stream,
|
|
72
114
|
});
|
|
73
115
|
}
|
|
74
116
|
/**
|
|
@@ -76,13 +118,24 @@ export class oNodeRouter extends oToolRouter {
|
|
|
76
118
|
*/
|
|
77
119
|
async dialAndTransmit(address, request, node) {
|
|
78
120
|
try {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
121
|
+
const isStream = request.params._isStreaming ||
|
|
122
|
+
request.params.payload?.params?._isStreaming;
|
|
123
|
+
const nodeConnection = await node.connectionManager.connect({
|
|
82
124
|
nextHopAddress: address,
|
|
83
125
|
address: node.address,
|
|
84
126
|
callerAddress: node.address,
|
|
127
|
+
isStream: isStream,
|
|
85
128
|
});
|
|
129
|
+
if (isStream) {
|
|
130
|
+
const routeRequest = request;
|
|
131
|
+
if (!routeRequest.stream) {
|
|
132
|
+
throw new oError(oErrorCodes.INVALID_REQUEST, 'Stream is required');
|
|
133
|
+
}
|
|
134
|
+
nodeConnection.onChunk(async (response) => {
|
|
135
|
+
CoreUtils.sendStreamResponse(response, routeRequest.stream);
|
|
136
|
+
});
|
|
137
|
+
// allow this to continue as we will tell the transmitter to stream the response and we will intercept via the above listener
|
|
138
|
+
}
|
|
86
139
|
const response = await nodeConnection.transmit(request);
|
|
87
140
|
return response.result.data;
|
|
88
141
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node.routing-policy.d.ts","sourceRoot":"","sources":["../../../src/router/o-node.routing-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAG1C;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,cAAc;IACpD;;;;;;;;;OASG;IACH,iBAAiB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO;
|
|
1
|
+
{"version":3,"file":"o-node.routing-policy.d.ts","sourceRoot":"","sources":["../../../src/router/o-node.routing-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAG1C;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,cAAc;IACpD;;;;;;;;;OASG;IACH,iBAAiB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO;IAuB1D;;;;;;;;OAQG;IACH,0BAA0B,CACxB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,KAAK,GACV,aAAa,GAAG,IAAI;CAmBxB"}
|
|
@@ -17,11 +17,16 @@ export class oNodeRoutingPolicy extends oRoutingPolicy {
|
|
|
17
17
|
*/
|
|
18
18
|
isInternalAddress(address, node) {
|
|
19
19
|
const nodeAddress = address;
|
|
20
|
+
// if we are trying to connect to a parent, it's internal
|
|
21
|
+
if (node.hierarchyManager.parents.some((p) => p.equals(address))) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
20
24
|
if (nodeAddress.paths.indexOf(oAddress.leader().paths) !== -1 && // if the address has a leader
|
|
21
25
|
nodeAddress.libp2pTransports?.length > 0) {
|
|
22
26
|
// transports are provided, let's see if they match our known leaders
|
|
23
27
|
const isLeaderRef = nodeAddress.toString() === oAddress.leader().toString();
|
|
24
|
-
const isOurLeaderRef = node.
|
|
28
|
+
const isOurLeaderRef = node.address.equals(nodeAddress) ||
|
|
29
|
+
node.hierarchyManager.leaders.some((l) => l.equals(nodeAddress));
|
|
25
30
|
return isLeaderRef || isOurLeaderRef;
|
|
26
31
|
}
|
|
27
32
|
return true;
|
|
@@ -40,7 +45,7 @@ export class oNodeRoutingPolicy extends oRoutingPolicy {
|
|
|
40
45
|
const isInternal = this.isInternalAddress(address, node);
|
|
41
46
|
if (!isInternal) {
|
|
42
47
|
// external address, so we need to route
|
|
43
|
-
this.logger.debug('Address is external, routing...', nodeAddress
|
|
48
|
+
this.logger.debug('Address is external, routing...', nodeAddress);
|
|
44
49
|
// route to leader of external OS
|
|
45
50
|
return {
|
|
46
51
|
nextHopAddress: new oNodeAddress(oAddress.leader().toString(), nodeAddress.libp2pTransports),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node.resolver.d.ts","sourceRoot":"","sources":["../../../../src/router/resolvers/o-node.resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAEhB,aAAa,
|
|
1
|
+
{"version":3,"file":"o-node.resolver.d.ts","sourceRoot":"","sources":["../../../../src/router/resolvers/o-node.resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAEhB,aAAa,EAId,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,qBAAa,aAAc,SAAQ,gBAAgB;IACrC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY;gBAArB,OAAO,EAAE,YAAY;IAIpD,IAAI,mBAAmB,IAAI,aAAa,EAAE,CAEzC;IAEK,OAAO,CAAC,YAAY,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC;CA8C1E"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { oAddressResolver, TransportType, } from '@olane/o-core';
|
|
1
|
+
import { oAddressResolver, oAddress, TransportType, oError, oErrorCodes, } from '@olane/o-core';
|
|
2
2
|
import { oNodeAddress } from '../o-node.address.js';
|
|
3
3
|
export class oNodeResolver extends oAddressResolver {
|
|
4
4
|
constructor(address) {
|
|
@@ -32,6 +32,10 @@ export class oNodeResolver extends oAddressResolver {
|
|
|
32
32
|
requestOverride: request,
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
+
// no child address, and we have already been to the leader, fail
|
|
36
|
+
if (address.toString().indexOf(oAddress.leader().toString()) > -1) {
|
|
37
|
+
throw new oError(oErrorCodes.NOT_FOUND, targetAddress.toString() + ' node not found.');
|
|
38
|
+
}
|
|
35
39
|
return {
|
|
36
40
|
nextHopAddress: address,
|
|
37
41
|
targetAddress: targetAddress,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node.search-resolver.d.ts","sourceRoot":"","sources":["../../../../src/router/resolvers/o-node.search-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"o-node.search-resolver.d.ts","sourceRoot":"","sources":["../../../../src/router/resolvers/o-node.search-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EACR,gBAAgB,EAChB,KAAK,EAEL,UAAU,EACV,cAAc,EAEd,aAAa,EAEd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,qBAAa,eAAgB,SAAQ,gBAAgB;IACvC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ;gBAAjB,OAAO,EAAE,QAAQ;IAIhD,IAAI,gBAAgB,IAAI,UAAU,EAAE,CAEnC;IAED;;;;OAIG;IACH,SAAS,CAAC,kBAAkB,IAAI,QAAQ;IAIxC;;;;OAIG;IACH,SAAS,CAAC,eAAe,IAAI,MAAM;IAInC;;;;;OAKG;IACH,SAAS,CAAC,iBAAiB,CAAC,OAAO,EAAE,QAAQ,GAAG,GAAG;IAOnD;;;;;;OAMG;IACH,SAAS,CAAC,mBAAmB,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,GAAG,GAAG,EAAE;IASjE;;;;;OAKG;IACH,SAAS,CAAC,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI;IAIlD;;;;;OAKG;IACH,SAAS,CAAC,aAAa,CAAC,MAAM,EAAE,GAAG,GAAG,cAAc,EAAE;IAOtD;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,wBAAwB,CAChC,OAAO,EAAE,QAAQ,EACjB,gBAAgB,EAAE,cAAc,EAAE,EAClC,IAAI,EAAE,KAAK,GACV,cAAc,EAAE;IAgBnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAyCG;IACH,SAAS,CAAC,gBAAgB,CACxB,IAAI,EAAE,KAAK,EACX,qBAAqB,EAAE,QAAQ,EAC/B,YAAY,EAAE,GAAG,GAChB,QAAQ;IAeL,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAsG/D"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { oAddress, oAddressResolver, oCustomTransport, RestrictedAddresses, } from '@olane/o-core';
|
|
1
|
+
import { NodeState, oAddress, oAddressResolver, oCustomTransport, RestrictedAddresses, } from '@olane/o-core';
|
|
2
2
|
import { oNodeTransport } from '../o-node.transport.js';
|
|
3
3
|
/**
|
|
4
4
|
* Address resolver that searches a registry to find transports for addresses.
|
|
@@ -78,7 +78,7 @@ export class oSearchResolver extends oAddressResolver {
|
|
|
78
78
|
* @returns The registry address to query
|
|
79
79
|
*/
|
|
80
80
|
getRegistryAddress() {
|
|
81
|
-
return new oAddress(
|
|
81
|
+
return new oAddress('o://leader/registry');
|
|
82
82
|
}
|
|
83
83
|
/**
|
|
84
84
|
* Returns the method name to call on the registry.
|
|
@@ -201,7 +201,6 @@ export class oSearchResolver extends oAddressResolver {
|
|
|
201
201
|
determineNextHop(node, resolvedTargetAddress, searchResult) {
|
|
202
202
|
// Determine next hop using standard hierarchy logic
|
|
203
203
|
const nextHopAddress = oAddress.next(node.address, resolvedTargetAddress);
|
|
204
|
-
this.logger.debug('determineNextHop with params', 'node.address: ' + node.address.toString(), 'resolvedTargetAddress: ' + resolvedTargetAddress.toString(), 'searchResult.address: ' + searchResult.address, 'next hop: ' + nextHopAddress.toString());
|
|
205
204
|
// Map transports from search result
|
|
206
205
|
const targetTransports = this.mapTransports(searchResult);
|
|
207
206
|
// Set transports on the next hop based on routing logic
|
|
@@ -218,13 +217,40 @@ export class oSearchResolver extends oAddressResolver {
|
|
|
218
217
|
requestOverride: resolveRequest,
|
|
219
218
|
};
|
|
220
219
|
}
|
|
221
|
-
|
|
220
|
+
if (node.state !== NodeState.RUNNING) {
|
|
221
|
+
return {
|
|
222
|
+
nextHopAddress: address,
|
|
223
|
+
targetAddress: targetAddress,
|
|
224
|
+
requestOverride: resolveRequest,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
// Perform registry search with error handling
|
|
222
228
|
const searchParams = this.buildSearchParams(address);
|
|
223
229
|
const registryAddress = this.getRegistryAddress();
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
230
|
+
let searchResponse;
|
|
231
|
+
try {
|
|
232
|
+
searchResponse = await node.use(registryAddress, {
|
|
233
|
+
method: this.getSearchMethod(),
|
|
234
|
+
params: searchParams,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
// Log the error but don't throw - allow fallback resolvers to handle it
|
|
239
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
240
|
+
// Check if this is a circuit breaker error (fast-fail scenario)
|
|
241
|
+
if (errorMessage.includes('Circuit breaker is OPEN')) {
|
|
242
|
+
this.logger.warn(`Registry search blocked by circuit breaker for ${address.toString()}: ${errorMessage}`);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
this.logger.error(`Registry search failed for ${address.toString()}: ${errorMessage}`);
|
|
246
|
+
}
|
|
247
|
+
// Return original address without transports, letting next resolver in chain handle it
|
|
248
|
+
return {
|
|
249
|
+
nextHopAddress: address,
|
|
250
|
+
targetAddress: targetAddress,
|
|
251
|
+
requestOverride: resolveRequest,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
228
254
|
// Filter and select result
|
|
229
255
|
const filteredResults = this.filterSearchResults(searchResponse.result.data, node);
|
|
230
256
|
const selectedResult = this.selectResult(filteredResults);
|
|
@@ -240,7 +266,6 @@ export class oSearchResolver extends oAddressResolver {
|
|
|
240
266
|
const extraParams = address
|
|
241
267
|
.toString() // o://embeddings-text replace o://embeddings-text = ''
|
|
242
268
|
.replace(address.toRootAddress().toString(), '');
|
|
243
|
-
this.logger.debug('Extra params:', extraParams);
|
|
244
269
|
// Check if selectedResult.address already contains the complete path
|
|
245
270
|
// This happens when registry finds via staticAddress - the returned address
|
|
246
271
|
// is the canonical hierarchical location, so we shouldn't append extraParams
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Stream } from '@olane/o-config';
|
|
2
|
+
import { oObject, oRequest } from '@olane/o-core';
|
|
3
|
+
export declare class StreamUtils extends oObject {
|
|
4
|
+
static processGenerator(request: oRequest, generator: AsyncGenerator<any>, stream: Stream): Promise<any>;
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=stream.utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/stream.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAEL,OAAO,EACP,QAAQ,EAGT,MAAM,eAAe,CAAC;AAEvB,qBAAa,WAAY,SAAQ,OAAO;WAClB,gBAAgB,CAClC,OAAO,EAAE,QAAQ,EACjB,SAAS,EAAE,cAAc,CAAC,GAAG,CAAC,EAC9B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,GAAG,CAAC;CA6BhB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CoreUtils, oObject, ResponseBuilder, } from '@olane/o-core';
|
|
2
|
+
export class StreamUtils extends oObject {
|
|
3
|
+
static async processGenerator(request, generator, stream) {
|
|
4
|
+
const utils = new StreamUtils();
|
|
5
|
+
const responseBuilder = ResponseBuilder.create();
|
|
6
|
+
let aggregatedResult = '';
|
|
7
|
+
try {
|
|
8
|
+
// Send each chunk from the generator
|
|
9
|
+
// result should not be an oResponse, but rather a key value pair dict
|
|
10
|
+
for await (const result of generator) {
|
|
11
|
+
if (result.delta) {
|
|
12
|
+
aggregatedResult += result.delta;
|
|
13
|
+
}
|
|
14
|
+
const chunkResponse = await responseBuilder.buildChunk(request, result);
|
|
15
|
+
await CoreUtils.sendStreamResponse(chunkResponse, stream);
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
message: aggregatedResult,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
// If error occurs during streaming, send error response
|
|
23
|
+
const errorResponse = await responseBuilder.buildError(request, error, {
|
|
24
|
+
isStream: true,
|
|
25
|
+
isLast: true,
|
|
26
|
+
});
|
|
27
|
+
await CoreUtils.sendStreamResponse(errorResponse, stream);
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { oNodeTool } from '../../src/o-node.tool.js';
|
|
2
|
+
/**
|
|
3
|
+
* Test-only extension of oNodeTool that adds streaming test methods.
|
|
4
|
+
* This class should only be used in test files and is not part of the production code.
|
|
5
|
+
*/
|
|
6
|
+
export declare class TestNodeTool extends oNodeTool {
|
|
7
|
+
/**
|
|
8
|
+
* Test method that emits chunks for 10 seconds at 100ms intervals.
|
|
9
|
+
* Used for testing streaming functionality across hierarchical networks.
|
|
10
|
+
*
|
|
11
|
+
* @returns AsyncGenerator that yields 100 chunks over 10 seconds
|
|
12
|
+
*/
|
|
13
|
+
_tool_test_stream(): AsyncGenerator<any>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=test-node.tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-node.tool.d.ts","sourceRoot":"","sources":["../../../test/helpers/test-node.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD;;;GAGG;AACH,qBAAa,YAAa,SAAQ,SAAS;IACzC;;;;;OAKG;IACI,iBAAiB,IAAI,cAAc,CAAC,GAAG,CAAC;CAehD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { oNodeTool } from '../../src/o-node.tool.js';
|
|
2
|
+
/**
|
|
3
|
+
* Test-only extension of oNodeTool that adds streaming test methods.
|
|
4
|
+
* This class should only be used in test files and is not part of the production code.
|
|
5
|
+
*/
|
|
6
|
+
export class TestNodeTool extends oNodeTool {
|
|
7
|
+
/**
|
|
8
|
+
* Test method that emits chunks for 10 seconds at 100ms intervals.
|
|
9
|
+
* Used for testing streaming functionality across hierarchical networks.
|
|
10
|
+
*
|
|
11
|
+
* @returns AsyncGenerator that yields 100 chunks over 10 seconds
|
|
12
|
+
*/
|
|
13
|
+
async *_tool_test_stream() {
|
|
14
|
+
const totalDuration = 10000; // 10 seconds
|
|
15
|
+
const intervalMs = 100; // 100ms between chunks
|
|
16
|
+
const totalChunks = totalDuration / intervalMs; // 100 chunks
|
|
17
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
18
|
+
yield {
|
|
19
|
+
chunk: i + 1,
|
|
20
|
+
timestamp: new Date().toISOString(),
|
|
21
|
+
nodeAddress: this.address.toString(),
|
|
22
|
+
message: `Chunk ${i + 1} of ${totalChunks}`,
|
|
23
|
+
};
|
|
24
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olane/o-node",
|
|
3
|
-
"version": "0.7.12
|
|
3
|
+
"version": "0.7.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -54,12 +54,12 @@
|
|
|
54
54
|
"typescript": "5.4.5"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@olane/o-config": "0.7.12
|
|
58
|
-
"@olane/o-core": "0.7.12
|
|
59
|
-
"@olane/o-protocol": "0.7.12
|
|
60
|
-
"@olane/o-tool": "0.7.12
|
|
57
|
+
"@olane/o-config": "0.7.12",
|
|
58
|
+
"@olane/o-core": "0.7.12",
|
|
59
|
+
"@olane/o-protocol": "0.7.12",
|
|
60
|
+
"@olane/o-tool": "0.7.12",
|
|
61
61
|
"debug": "^4.4.1",
|
|
62
62
|
"dotenv": "^16.5.0"
|
|
63
63
|
},
|
|
64
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "580d8c51fe0a12695ba1d77f077029200f0c8af4"
|
|
65
65
|
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { oAddressResolver, TransportType } from '@olane/o-core';
|
|
2
|
-
import { oNodeAddress } from '../o-node.address.js';
|
|
3
|
-
import { oNodeRouterResponse } from '../interfaces/o-node-router.response.js';
|
|
4
|
-
import { ResolveRequest } from '@olane/o-core';
|
|
5
|
-
export declare class oNodeChildResolver extends oAddressResolver {
|
|
6
|
-
protected readonly address: oNodeAddress;
|
|
7
|
-
constructor(address: oNodeAddress);
|
|
8
|
-
get supportedTransports(): TransportType[];
|
|
9
|
-
resolve(routeRequest: ResolveRequest): Promise<oNodeRouterResponse>;
|
|
10
|
-
}
|
|
11
|
-
//# sourceMappingURL=o-node.child-resolver.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"o-node.child-resolver.d.ts","sourceRoot":"","sources":["../../../../src/router/resolvers/o-node.child-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAEhB,aAAa,EAEd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,qBAAa,kBAAmB,SAAQ,gBAAgB;IAC1C,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY;gBAArB,OAAO,EAAE,YAAY;IAIpD,IAAI,mBAAmB,IAAI,aAAa,EAAE,CAEzC;IAEK,OAAO,CAAC,YAAY,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC;CA4D1E"}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { oAddressResolver, TransportType, } from '@olane/o-core';
|
|
2
|
-
export class oNodeChildResolver extends oAddressResolver {
|
|
3
|
-
constructor(address) {
|
|
4
|
-
super(address);
|
|
5
|
-
this.address = address;
|
|
6
|
-
}
|
|
7
|
-
get supportedTransports() {
|
|
8
|
-
return [TransportType.LIBP2P];
|
|
9
|
-
}
|
|
10
|
-
async resolve(routeRequest) {
|
|
11
|
-
const { address, node, request } = routeRequest;
|
|
12
|
-
if (!node) {
|
|
13
|
-
return {
|
|
14
|
-
nextHopAddress: address,
|
|
15
|
-
targetAddress: address,
|
|
16
|
-
requestOverride: request,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
// if we are the same address, return the address
|
|
20
|
-
if (address.toStaticAddress().equals(node.address.toStaticAddress())) {
|
|
21
|
-
return {
|
|
22
|
-
nextHopAddress: node.address,
|
|
23
|
-
targetAddress: node.address,
|
|
24
|
-
requestOverride: request,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
// get the next node & check for child address existence
|
|
28
|
-
const remainingPath = address.protocol.replace(node.address.protocol + '/', '');
|
|
29
|
-
// ensure this is going down in the hierarchy
|
|
30
|
-
if (remainingPath === address.protocol && node.isLeader === false) {
|
|
31
|
-
return {
|
|
32
|
-
nextHopAddress: node.address,
|
|
33
|
-
targetAddress: address,
|
|
34
|
-
requestOverride: request,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
// static resolver
|
|
38
|
-
this.logger.debug(`[${node?.address}]: Next node: ${address.toString()}`);
|
|
39
|
-
const childAddress = node?.hierarchyManager.getChild(address);
|
|
40
|
-
this.logger.debug(`[${node?.address}]: Children: ${node?.hierarchyManager.children.map((c) => c.toString()).join(', ')}`);
|
|
41
|
-
this.logger.debug(`[${node?.address}]: Resolving address: ${address.toString()} and child address: ${childAddress?.toString()}`);
|
|
42
|
-
this.logger.debug('Child transports: ' +
|
|
43
|
-
childAddress?.transports.map((t) => t.toString()).join(', '));
|
|
44
|
-
// get the child address from hierarchy (which includes transports)
|
|
45
|
-
if (childAddress) {
|
|
46
|
-
return {
|
|
47
|
-
nextHopAddress: childAddress,
|
|
48
|
-
targetAddress: address,
|
|
49
|
-
requestOverride: request,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
nextHopAddress: address,
|
|
54
|
-
targetAddress: address,
|
|
55
|
-
requestOverride: request,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { oObject, oAddress } from '@olane/o-core';
|
|
2
|
-
export interface LeaderRetryConfig {
|
|
3
|
-
enabled: boolean;
|
|
4
|
-
maxAttempts: number;
|
|
5
|
-
baseDelayMs: number;
|
|
6
|
-
maxDelayMs: number;
|
|
7
|
-
timeoutMs: number;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Leader Request Wrapper
|
|
11
|
-
*
|
|
12
|
-
* Wraps requests to leader/registry with aggressive retry logic.
|
|
13
|
-
* Used when leader may be temporarily unavailable (healing, maintenance).
|
|
14
|
-
*
|
|
15
|
-
* Strategy:
|
|
16
|
-
* 1. Detect if request is to leader or registry
|
|
17
|
-
* 2. Apply retry logic with exponential backoff
|
|
18
|
-
* 3. Timeout individual attempts
|
|
19
|
-
* 4. Log retries for observability
|
|
20
|
-
*/
|
|
21
|
-
export declare class LeaderRequestWrapper extends oObject {
|
|
22
|
-
private config;
|
|
23
|
-
constructor(config: LeaderRetryConfig);
|
|
24
|
-
/**
|
|
25
|
-
* Check if address is a leader-related address that needs retry
|
|
26
|
-
*/
|
|
27
|
-
private isLeaderAddress;
|
|
28
|
-
/**
|
|
29
|
-
* Execute request with retry logic
|
|
30
|
-
*/
|
|
31
|
-
execute<T>(requestFn: () => Promise<T>, address: oAddress, context?: string): Promise<T>;
|
|
32
|
-
/**
|
|
33
|
-
* Calculate exponential backoff delay
|
|
34
|
-
*/
|
|
35
|
-
private calculateBackoffDelay;
|
|
36
|
-
/**
|
|
37
|
-
* Sleep utility
|
|
38
|
-
*/
|
|
39
|
-
private sleep;
|
|
40
|
-
/**
|
|
41
|
-
* Get current configuration
|
|
42
|
-
*/
|
|
43
|
-
getConfig(): LeaderRetryConfig;
|
|
44
|
-
}
|
|
45
|
-
//# sourceMappingURL=leader-request-wrapper.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"leader-request-wrapper.d.ts","sourceRoot":"","sources":["../../../src/utils/leader-request-wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAuB,MAAM,eAAe,CAAC;AAEvE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,oBAAqB,SAAQ,OAAO;IACnC,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,iBAAiB;IAI7C;;OAEG;IACH,OAAO,CAAC,eAAe;IAKvB;;OAEG;IACG,OAAO,CAAC,CAAC,EACb,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,OAAO,EAAE,QAAQ,EACjB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,CAAC,CAAC;IAoEb;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAK7B;;OAEG;IACH,OAAO,CAAC,KAAK;IAIb;;OAEG;IACH,SAAS,IAAI,iBAAiB;CAG/B"}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { oObject } from '@olane/o-core';
|
|
2
|
-
/**
|
|
3
|
-
* Leader Request Wrapper
|
|
4
|
-
*
|
|
5
|
-
* Wraps requests to leader/registry with aggressive retry logic.
|
|
6
|
-
* Used when leader may be temporarily unavailable (healing, maintenance).
|
|
7
|
-
*
|
|
8
|
-
* Strategy:
|
|
9
|
-
* 1. Detect if request is to leader or registry
|
|
10
|
-
* 2. Apply retry logic with exponential backoff
|
|
11
|
-
* 3. Timeout individual attempts
|
|
12
|
-
* 4. Log retries for observability
|
|
13
|
-
*/
|
|
14
|
-
export class LeaderRequestWrapper extends oObject {
|
|
15
|
-
constructor(config) {
|
|
16
|
-
super();
|
|
17
|
-
this.config = config;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Check if address is a leader-related address that needs retry
|
|
21
|
-
*/
|
|
22
|
-
isLeaderAddress(address) {
|
|
23
|
-
const addressStr = address.toString();
|
|
24
|
-
return addressStr === 'o://leader' || addressStr === 'o://registry';
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Execute request with retry logic
|
|
28
|
-
*/
|
|
29
|
-
async execute(requestFn, address, context) {
|
|
30
|
-
// If retry disabled or not a leader address, execute directly
|
|
31
|
-
if (!this.config.enabled || !this.isLeaderAddress(address)) {
|
|
32
|
-
return await requestFn();
|
|
33
|
-
}
|
|
34
|
-
let attempt = 0;
|
|
35
|
-
let lastError;
|
|
36
|
-
while (attempt < this.config.maxAttempts) {
|
|
37
|
-
attempt++;
|
|
38
|
-
try {
|
|
39
|
-
this.logger.debug(`Leader request attempt ${attempt}/${this.config.maxAttempts}` +
|
|
40
|
-
(context ? ` (${context})` : ''));
|
|
41
|
-
// Create timeout promise
|
|
42
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
43
|
-
setTimeout(() => reject(new Error(`Leader request timeout after ${this.config.timeoutMs}ms`)), this.config.timeoutMs);
|
|
44
|
-
});
|
|
45
|
-
// Race between request and timeout
|
|
46
|
-
const result = await Promise.race([requestFn(), timeoutPromise]);
|
|
47
|
-
// Success!
|
|
48
|
-
if (attempt > 1) {
|
|
49
|
-
this.logger.info(`Leader request succeeded after ${attempt} attempts` +
|
|
50
|
-
(context ? ` (${context})` : ''));
|
|
51
|
-
}
|
|
52
|
-
return result;
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
56
|
-
this.logger.warn(`Leader request attempt ${attempt} failed: ${lastError.message}` +
|
|
57
|
-
(context ? ` (${context})` : ''));
|
|
58
|
-
if (attempt < this.config.maxAttempts) {
|
|
59
|
-
const delay = this.calculateBackoffDelay(attempt);
|
|
60
|
-
this.logger.debug(`Waiting ${delay}ms before next attempt...`);
|
|
61
|
-
await this.sleep(delay);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
// All attempts failed
|
|
66
|
-
this.logger.error(`Leader request failed after ${this.config.maxAttempts} attempts` +
|
|
67
|
-
(context ? ` (${context})` : ''));
|
|
68
|
-
throw lastError || new Error('Leader request failed');
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Calculate exponential backoff delay
|
|
72
|
-
*/
|
|
73
|
-
calculateBackoffDelay(attempt) {
|
|
74
|
-
const delay = this.config.baseDelayMs * Math.pow(2, attempt - 1);
|
|
75
|
-
return Math.min(delay, this.config.maxDelayMs);
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Sleep utility
|
|
79
|
-
*/
|
|
80
|
-
sleep(ms) {
|
|
81
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Get current configuration
|
|
85
|
-
*/
|
|
86
|
-
getConfig() {
|
|
87
|
-
return { ...this.config };
|
|
88
|
-
}
|
|
89
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"o-node.spec.d.ts","sourceRoot":"","sources":["../../test/o-node.spec.ts"],"names":[],"mappings":""}
|
package/dist/test/o-node.spec.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { NodeState } from '@olane/o-core';
|
|
2
|
-
import { expect } from 'chai';
|
|
3
|
-
import { oNode } from '../src/index.js';
|
|
4
|
-
import { oNodeAddress } from '../src/router/o-node.address.js';
|
|
5
|
-
describe('in-process @memory', () => {
|
|
6
|
-
it('should be able to start a single node with no leader', async () => {
|
|
7
|
-
const node = new oNode({
|
|
8
|
-
address: new oNodeAddress('o://test'),
|
|
9
|
-
leader: null,
|
|
10
|
-
parent: null,
|
|
11
|
-
});
|
|
12
|
-
await node.start();
|
|
13
|
-
expect(node.state).to.equal(NodeState.RUNNING);
|
|
14
|
-
const transports = node.transports;
|
|
15
|
-
// expect(transports.length).to.equal(1);
|
|
16
|
-
// expect(transports[0].toString()).to.contain('/memory');
|
|
17
|
-
await node.stop();
|
|
18
|
-
expect(node.state).to.equal(NodeState.STOPPED);
|
|
19
|
-
});
|
|
20
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"search-resolver.spec.d.ts","sourceRoot":"","sources":["../../test/search-resolver.spec.ts"],"names":[],"mappings":""}
|