@topgunbuild/server 0.10.1 → 0.11.0
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/{chunk-IQNKZPW3.mjs → chunk-Z3TONATT.mjs} +346 -10
- package/dist/chunk-Z3TONATT.mjs.map +1 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +379 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/start-server.js +409 -73
- package/dist/start-server.js.map +1 -1
- package/dist/start-server.mjs +1 -1
- package/package.json +3 -3
- package/dist/chunk-IQNKZPW3.mjs.map +0 -1
package/dist/start-server.js
CHANGED
|
@@ -462,7 +462,7 @@ var require_serialize = __commonJS({
|
|
|
462
462
|
"../../node_modules/.pnpm/better-sqlite3@11.10.0/node_modules/better-sqlite3/lib/methods/serialize.js"(exports2, module2) {
|
|
463
463
|
"use strict";
|
|
464
464
|
var { cppdb } = require_util();
|
|
465
|
-
module2.exports = function
|
|
465
|
+
module2.exports = function serialize8(options) {
|
|
466
466
|
if (options == null) options = {};
|
|
467
467
|
if (typeof options !== "object") throw new TypeError("Expected first argument to be an options object");
|
|
468
468
|
const attachedName = "attached" in options ? options.attached : "main";
|
|
@@ -805,7 +805,7 @@ var require_lib = __commonJS({
|
|
|
805
805
|
|
|
806
806
|
// src/ServerFactory.ts
|
|
807
807
|
var import_http = require("http");
|
|
808
|
-
var
|
|
808
|
+
var import_core42 = require("@topgunbuild/core");
|
|
809
809
|
|
|
810
810
|
// src/ServerCoordinator.ts
|
|
811
811
|
var import_core = require("@topgunbuild/core");
|
|
@@ -8938,17 +8938,18 @@ function buildTLSOptions(config2) {
|
|
|
8938
8938
|
return options;
|
|
8939
8939
|
}
|
|
8940
8940
|
function createNetworkModule(config2, _deps) {
|
|
8941
|
+
let currentRequestHandler = (_req, res) => {
|
|
8942
|
+
res.writeHead(200);
|
|
8943
|
+
res.end(config2.tls?.enabled ? "TopGun Server Running (Secure)" : "TopGun Server Running");
|
|
8944
|
+
};
|
|
8945
|
+
const requestDispatcher = (req, res) => {
|
|
8946
|
+
currentRequestHandler(req, res);
|
|
8947
|
+
};
|
|
8941
8948
|
let httpServer;
|
|
8942
8949
|
if (config2.tls?.enabled) {
|
|
8943
|
-
httpServer = (0, import_node_https.createServer)(buildTLSOptions(config2.tls),
|
|
8944
|
-
res.writeHead(200);
|
|
8945
|
-
res.end("TopGun Server Running (Secure)");
|
|
8946
|
-
});
|
|
8950
|
+
httpServer = (0, import_node_https.createServer)(buildTLSOptions(config2.tls), requestDispatcher);
|
|
8947
8951
|
} else {
|
|
8948
|
-
httpServer = (0, import_node_http.createServer)(
|
|
8949
|
-
res.writeHead(200);
|
|
8950
|
-
res.end("TopGun Server Running");
|
|
8951
|
-
});
|
|
8952
|
+
httpServer = (0, import_node_http.createServer)(requestDispatcher);
|
|
8952
8953
|
}
|
|
8953
8954
|
httpServer.maxConnections = config2.maxConnections ?? 1e4;
|
|
8954
8955
|
httpServer.timeout = config2.serverTimeout ?? 12e4;
|
|
@@ -8986,13 +8987,18 @@ function createNetworkModule(config2, _deps) {
|
|
|
8986
8987
|
httpServer.listen(config2.port, () => {
|
|
8987
8988
|
logger.info({ port: config2.port }, "Server Coordinator listening");
|
|
8988
8989
|
});
|
|
8990
|
+
},
|
|
8991
|
+
// Deferred wiring: allows ServerFactory to inject the /sync handler
|
|
8992
|
+
// after HttpSyncHandler is assembled
|
|
8993
|
+
setHttpRequestHandler: (handler) => {
|
|
8994
|
+
currentRequestHandler = handler;
|
|
8989
8995
|
}
|
|
8990
8996
|
};
|
|
8991
8997
|
}
|
|
8992
8998
|
|
|
8993
8999
|
// src/modules/handlers-module.ts
|
|
8994
9000
|
var import_ws9 = require("ws");
|
|
8995
|
-
var
|
|
9001
|
+
var import_core38 = require("@topgunbuild/core");
|
|
8996
9002
|
|
|
8997
9003
|
// src/coordinator/auth-handler.ts
|
|
8998
9004
|
var jwt = __toESM(require("jsonwebtoken"));
|
|
@@ -12048,13 +12054,260 @@ var QueryHandler = class {
|
|
|
12048
12054
|
}
|
|
12049
12055
|
};
|
|
12050
12056
|
|
|
12057
|
+
// src/coordinator/http-sync-handler.ts
|
|
12058
|
+
var import_core25 = require("@topgunbuild/core");
|
|
12059
|
+
var HttpSyncHandler = class {
|
|
12060
|
+
constructor(config2) {
|
|
12061
|
+
this.config = config2;
|
|
12062
|
+
}
|
|
12063
|
+
/**
|
|
12064
|
+
* Process a complete HTTP sync request and return a response.
|
|
12065
|
+
*
|
|
12066
|
+
* @param request - Parsed and validated HttpSyncRequest body
|
|
12067
|
+
* @param authToken - JWT token from Authorization header
|
|
12068
|
+
* @returns HttpSyncResponse with acks, deltas, query/search results
|
|
12069
|
+
* @throws Error with message starting with '401:' for auth failures
|
|
12070
|
+
* @throws Error with message starting with '403:' for permission failures
|
|
12071
|
+
*/
|
|
12072
|
+
async handleSyncRequest(request, authToken) {
|
|
12073
|
+
let principal;
|
|
12074
|
+
try {
|
|
12075
|
+
principal = this.config.authHandler.verifyToken(authToken);
|
|
12076
|
+
} catch (err) {
|
|
12077
|
+
throw new Error(`401: Authentication failed: ${err.message}`);
|
|
12078
|
+
}
|
|
12079
|
+
this.config.hlc.update({
|
|
12080
|
+
millis: request.clientHlc.millis,
|
|
12081
|
+
counter: request.clientHlc.counter,
|
|
12082
|
+
nodeId: request.clientHlc.nodeId
|
|
12083
|
+
});
|
|
12084
|
+
const response = {
|
|
12085
|
+
serverHlc: this.config.hlc.now()
|
|
12086
|
+
};
|
|
12087
|
+
const errors = [];
|
|
12088
|
+
if (request.operations && request.operations.length > 0) {
|
|
12089
|
+
const ackResults = await this.processOperations(request.operations, principal, errors);
|
|
12090
|
+
if (ackResults.processedCount > 0) {
|
|
12091
|
+
response.ack = {
|
|
12092
|
+
lastId: ackResults.lastId,
|
|
12093
|
+
results: ackResults.results.length > 0 ? ackResults.results : void 0
|
|
12094
|
+
};
|
|
12095
|
+
}
|
|
12096
|
+
}
|
|
12097
|
+
if (request.syncMaps && request.syncMaps.length > 0) {
|
|
12098
|
+
response.deltas = await this.computeDeltas(request.syncMaps, principal, errors);
|
|
12099
|
+
}
|
|
12100
|
+
if (request.queries && request.queries.length > 0) {
|
|
12101
|
+
response.queryResults = await this.executeQueries(request.queries, principal, errors);
|
|
12102
|
+
}
|
|
12103
|
+
if (request.searches && request.searches.length > 0) {
|
|
12104
|
+
response.searchResults = await this.executeSearches(request.searches, principal, errors);
|
|
12105
|
+
}
|
|
12106
|
+
if (errors.length > 0) {
|
|
12107
|
+
response.errors = errors;
|
|
12108
|
+
}
|
|
12109
|
+
response.serverHlc = this.config.hlc.now();
|
|
12110
|
+
return response;
|
|
12111
|
+
}
|
|
12112
|
+
/**
|
|
12113
|
+
* Process a batch of client operations.
|
|
12114
|
+
*/
|
|
12115
|
+
async processOperations(operations, principal, errors) {
|
|
12116
|
+
let lastId = "";
|
|
12117
|
+
let processedCount = 0;
|
|
12118
|
+
const results = [];
|
|
12119
|
+
for (const op of operations) {
|
|
12120
|
+
const opId = op.id || `http-op-${processedCount}`;
|
|
12121
|
+
const isRemove = op.opType === "REMOVE" || op.record && op.record.value === null;
|
|
12122
|
+
const action = isRemove ? "REMOVE" : "PUT";
|
|
12123
|
+
if (!this.config.securityManager.checkPermission(principal, op.mapName, action)) {
|
|
12124
|
+
errors.push({
|
|
12125
|
+
code: 403,
|
|
12126
|
+
message: "Access denied",
|
|
12127
|
+
context: `Operation on ${op.mapName}/${op.key}`
|
|
12128
|
+
});
|
|
12129
|
+
results.push({
|
|
12130
|
+
opId,
|
|
12131
|
+
success: false,
|
|
12132
|
+
achievedLevel: "FIRE_AND_FORGET",
|
|
12133
|
+
error: "Access denied"
|
|
12134
|
+
});
|
|
12135
|
+
continue;
|
|
12136
|
+
}
|
|
12137
|
+
try {
|
|
12138
|
+
const result = await this.config.operationHandler.applyOpToMap(op);
|
|
12139
|
+
if (result.rejected) {
|
|
12140
|
+
results.push({
|
|
12141
|
+
opId,
|
|
12142
|
+
success: false,
|
|
12143
|
+
achievedLevel: "FIRE_AND_FORGET",
|
|
12144
|
+
error: "Operation rejected by conflict resolver"
|
|
12145
|
+
});
|
|
12146
|
+
} else {
|
|
12147
|
+
results.push({
|
|
12148
|
+
opId,
|
|
12149
|
+
success: true,
|
|
12150
|
+
achievedLevel: "MEMORY"
|
|
12151
|
+
});
|
|
12152
|
+
}
|
|
12153
|
+
processedCount++;
|
|
12154
|
+
lastId = opId;
|
|
12155
|
+
} catch (err) {
|
|
12156
|
+
logger.error({ err, opId }, "HTTP sync operation failed");
|
|
12157
|
+
errors.push({
|
|
12158
|
+
code: 500,
|
|
12159
|
+
message: err.message || "Operation failed",
|
|
12160
|
+
context: `Operation ${opId} on ${op.mapName}/${op.key}`
|
|
12161
|
+
});
|
|
12162
|
+
results.push({
|
|
12163
|
+
opId,
|
|
12164
|
+
success: false,
|
|
12165
|
+
achievedLevel: "FIRE_AND_FORGET",
|
|
12166
|
+
error: err.message || "Operation failed"
|
|
12167
|
+
});
|
|
12168
|
+
}
|
|
12169
|
+
}
|
|
12170
|
+
return { processedCount, lastId, results };
|
|
12171
|
+
}
|
|
12172
|
+
/**
|
|
12173
|
+
* Compute deltas by iterating the in-memory LWWMap and filtering records
|
|
12174
|
+
* newer than the client's lastSyncTimestamp using HLC.compare().
|
|
12175
|
+
*/
|
|
12176
|
+
async computeDeltas(syncMaps, principal, errors) {
|
|
12177
|
+
const deltas = [];
|
|
12178
|
+
for (const { mapName, lastSyncTimestamp } of syncMaps) {
|
|
12179
|
+
if (!this.config.securityManager.checkPermission(principal, mapName, "READ")) {
|
|
12180
|
+
errors.push({
|
|
12181
|
+
code: 403,
|
|
12182
|
+
message: "Access denied",
|
|
12183
|
+
context: `Read deltas for ${mapName}`
|
|
12184
|
+
});
|
|
12185
|
+
continue;
|
|
12186
|
+
}
|
|
12187
|
+
try {
|
|
12188
|
+
const map2 = await this.config.storageManager.getMapAsync(mapName);
|
|
12189
|
+
if (!(map2 instanceof import_core25.LWWMap)) {
|
|
12190
|
+
errors.push({
|
|
12191
|
+
code: 400,
|
|
12192
|
+
message: "HTTP sync only supports LWWMap deltas",
|
|
12193
|
+
context: `Map ${mapName} is not an LWWMap`
|
|
12194
|
+
});
|
|
12195
|
+
continue;
|
|
12196
|
+
}
|
|
12197
|
+
const records = [];
|
|
12198
|
+
for (const key of map2.allKeys()) {
|
|
12199
|
+
const record2 = map2.getRecord(key);
|
|
12200
|
+
if (!record2) continue;
|
|
12201
|
+
if (import_core25.HLC.compare(record2.timestamp, lastSyncTimestamp) > 0) {
|
|
12202
|
+
records.push({
|
|
12203
|
+
key,
|
|
12204
|
+
record: record2,
|
|
12205
|
+
eventType: record2.value === null ? "REMOVE" : "PUT"
|
|
12206
|
+
});
|
|
12207
|
+
}
|
|
12208
|
+
}
|
|
12209
|
+
const serverSyncTimestamp = this.config.hlc.now();
|
|
12210
|
+
deltas.push({
|
|
12211
|
+
mapName,
|
|
12212
|
+
records,
|
|
12213
|
+
serverSyncTimestamp
|
|
12214
|
+
});
|
|
12215
|
+
} catch (err) {
|
|
12216
|
+
logger.error({ err, mapName }, "HTTP sync delta computation failed");
|
|
12217
|
+
errors.push({
|
|
12218
|
+
code: 500,
|
|
12219
|
+
message: err.message || "Delta computation failed",
|
|
12220
|
+
context: `Map ${mapName}`
|
|
12221
|
+
});
|
|
12222
|
+
}
|
|
12223
|
+
}
|
|
12224
|
+
return deltas.length > 0 ? deltas : void 0;
|
|
12225
|
+
}
|
|
12226
|
+
/**
|
|
12227
|
+
* Execute one-shot queries.
|
|
12228
|
+
*/
|
|
12229
|
+
async executeQueries(queries, principal, errors) {
|
|
12230
|
+
const results = [];
|
|
12231
|
+
for (const query of queries) {
|
|
12232
|
+
if (!this.config.securityManager.checkPermission(principal, query.mapName, "READ")) {
|
|
12233
|
+
errors.push({
|
|
12234
|
+
code: 403,
|
|
12235
|
+
message: "Access denied",
|
|
12236
|
+
context: `Query ${query.queryId} on ${query.mapName}`
|
|
12237
|
+
});
|
|
12238
|
+
continue;
|
|
12239
|
+
}
|
|
12240
|
+
try {
|
|
12241
|
+
const coreQuery = {
|
|
12242
|
+
where: query.filter,
|
|
12243
|
+
limit: query.limit
|
|
12244
|
+
};
|
|
12245
|
+
const queryResults = await this.config.queryConversionHandler.executeLocalQuery(
|
|
12246
|
+
query.mapName,
|
|
12247
|
+
coreQuery
|
|
12248
|
+
);
|
|
12249
|
+
const hasMore = query.limit ? queryResults.length >= query.limit : false;
|
|
12250
|
+
results.push({
|
|
12251
|
+
queryId: query.queryId,
|
|
12252
|
+
results: queryResults,
|
|
12253
|
+
hasMore
|
|
12254
|
+
});
|
|
12255
|
+
} catch (err) {
|
|
12256
|
+
logger.error({ err, queryId: query.queryId }, "HTTP sync query failed");
|
|
12257
|
+
errors.push({
|
|
12258
|
+
code: 500,
|
|
12259
|
+
message: err.message || "Query failed",
|
|
12260
|
+
context: `Query ${query.queryId} on ${query.mapName}`
|
|
12261
|
+
});
|
|
12262
|
+
}
|
|
12263
|
+
}
|
|
12264
|
+
return results.length > 0 ? results : void 0;
|
|
12265
|
+
}
|
|
12266
|
+
/**
|
|
12267
|
+
* Execute one-shot searches.
|
|
12268
|
+
*/
|
|
12269
|
+
async executeSearches(searches, principal, errors) {
|
|
12270
|
+
const results = [];
|
|
12271
|
+
for (const search of searches) {
|
|
12272
|
+
if (!this.config.securityManager.checkPermission(principal, search.mapName, "READ")) {
|
|
12273
|
+
errors.push({
|
|
12274
|
+
code: 403,
|
|
12275
|
+
message: "Access denied",
|
|
12276
|
+
context: `Search ${search.searchId} on ${search.mapName}`
|
|
12277
|
+
});
|
|
12278
|
+
continue;
|
|
12279
|
+
}
|
|
12280
|
+
try {
|
|
12281
|
+
const searchResult = this.config.searchCoordinator.search(
|
|
12282
|
+
search.mapName,
|
|
12283
|
+
search.query,
|
|
12284
|
+
search.options
|
|
12285
|
+
);
|
|
12286
|
+
results.push({
|
|
12287
|
+
searchId: search.searchId,
|
|
12288
|
+
results: searchResult.results || [],
|
|
12289
|
+
totalCount: searchResult.totalCount
|
|
12290
|
+
});
|
|
12291
|
+
} catch (err) {
|
|
12292
|
+
logger.error({ err, searchId: search.searchId }, "HTTP sync search failed");
|
|
12293
|
+
errors.push({
|
|
12294
|
+
code: 500,
|
|
12295
|
+
message: err.message || "Search failed",
|
|
12296
|
+
context: `Search ${search.searchId} on ${search.mapName}`
|
|
12297
|
+
});
|
|
12298
|
+
}
|
|
12299
|
+
}
|
|
12300
|
+
return results.length > 0 ? results : void 0;
|
|
12301
|
+
}
|
|
12302
|
+
};
|
|
12303
|
+
|
|
12051
12304
|
// src/coordinator/websocket-handler.ts
|
|
12052
12305
|
var crypto2 = __toESM(require("crypto"));
|
|
12053
|
-
var
|
|
12306
|
+
var import_core27 = require("@topgunbuild/core");
|
|
12054
12307
|
|
|
12055
12308
|
// src/utils/CoalescingWriter.ts
|
|
12056
12309
|
var import_ws7 = require("ws");
|
|
12057
|
-
var
|
|
12310
|
+
var import_core26 = require("@topgunbuild/core");
|
|
12058
12311
|
var DEFAULT_OPTIONS = {
|
|
12059
12312
|
maxBatchSize: 100,
|
|
12060
12313
|
maxDelayMs: 5,
|
|
@@ -12095,7 +12348,7 @@ var CoalescingWriter = class {
|
|
|
12095
12348
|
if (this.closed) {
|
|
12096
12349
|
return;
|
|
12097
12350
|
}
|
|
12098
|
-
const data = (0,
|
|
12351
|
+
const data = (0, import_core26.serialize)(message);
|
|
12099
12352
|
this.writeRaw(data, urgent);
|
|
12100
12353
|
}
|
|
12101
12354
|
/**
|
|
@@ -12279,7 +12532,7 @@ var CoalescingWriter = class {
|
|
|
12279
12532
|
offset += msg.data.length;
|
|
12280
12533
|
}
|
|
12281
12534
|
const usedBatch = batch.subarray(0, totalSize);
|
|
12282
|
-
const batchEnvelope = (0,
|
|
12535
|
+
const batchEnvelope = (0, import_core26.serialize)({
|
|
12283
12536
|
type: "BATCH",
|
|
12284
12537
|
count: messages.length,
|
|
12285
12538
|
data: usedBatch
|
|
@@ -12362,7 +12615,7 @@ var WebSocketHandler = class {
|
|
|
12362
12615
|
buf = Buffer.from(message);
|
|
12363
12616
|
}
|
|
12364
12617
|
try {
|
|
12365
|
-
data = (0,
|
|
12618
|
+
data = (0, import_core27.deserialize)(buf);
|
|
12366
12619
|
} catch (e) {
|
|
12367
12620
|
try {
|
|
12368
12621
|
const text = Buffer.isBuffer(buf) ? buf.toString() : new TextDecoder().decode(buf);
|
|
@@ -12380,14 +12633,14 @@ var WebSocketHandler = class {
|
|
|
12380
12633
|
ws.on("close", () => {
|
|
12381
12634
|
this.handleDisconnect(connection);
|
|
12382
12635
|
});
|
|
12383
|
-
ws.send((0,
|
|
12636
|
+
ws.send((0, import_core27.serialize)({ type: "AUTH_REQUIRED" }));
|
|
12384
12637
|
}
|
|
12385
12638
|
/**
|
|
12386
12639
|
* Handle incoming message from client.
|
|
12387
12640
|
* Validates message, handles auth, and routes to appropriate handler.
|
|
12388
12641
|
*/
|
|
12389
12642
|
async handleMessage(client, rawMessage) {
|
|
12390
|
-
const parseResult =
|
|
12643
|
+
const parseResult = import_core27.MessageSchema.safeParse(rawMessage);
|
|
12391
12644
|
if (!parseResult.success) {
|
|
12392
12645
|
this.config.rateLimitedLogger.error(
|
|
12393
12646
|
`invalid-message:${client.id}`,
|
|
@@ -12481,7 +12734,7 @@ var WebSocketHandler = class {
|
|
|
12481
12734
|
};
|
|
12482
12735
|
|
|
12483
12736
|
// src/coordinator/lifecycle-manager.ts
|
|
12484
|
-
var
|
|
12737
|
+
var import_core28 = require("@topgunbuild/core");
|
|
12485
12738
|
var LifecycleManager = class {
|
|
12486
12739
|
constructor(config2) {
|
|
12487
12740
|
this.config = config2;
|
|
@@ -12527,7 +12780,7 @@ var LifecycleManager = class {
|
|
|
12527
12780
|
this.config.metricsService.destroy();
|
|
12528
12781
|
this.config.wss.close();
|
|
12529
12782
|
logger.info(`Closing ${this.config.connectionManager.getClientCount()} client connections...`);
|
|
12530
|
-
const shutdownMsg = (0,
|
|
12783
|
+
const shutdownMsg = (0, import_core28.serialize)({ type: "SHUTDOWN_PENDING", retryAfter: 5e3 });
|
|
12531
12784
|
for (const client of this.config.connectionManager.getClients().values()) {
|
|
12532
12785
|
try {
|
|
12533
12786
|
if (client.socket.readyState === 1) {
|
|
@@ -12679,7 +12932,7 @@ var LifecycleManager = class {
|
|
|
12679
12932
|
};
|
|
12680
12933
|
|
|
12681
12934
|
// src/handlers/CounterHandler.ts
|
|
12682
|
-
var
|
|
12935
|
+
var import_core29 = require("@topgunbuild/core");
|
|
12683
12936
|
var CounterHandler = class {
|
|
12684
12937
|
// counterName -> Set<clientId>
|
|
12685
12938
|
constructor(nodeId = "server") {
|
|
@@ -12693,7 +12946,7 @@ var CounterHandler = class {
|
|
|
12693
12946
|
getOrCreateCounter(name) {
|
|
12694
12947
|
let counter = this.counters.get(name);
|
|
12695
12948
|
if (!counter) {
|
|
12696
|
-
counter = new
|
|
12949
|
+
counter = new import_core29.PNCounterImpl({ nodeId: this.nodeId });
|
|
12697
12950
|
this.counters.set(name, counter);
|
|
12698
12951
|
logger.debug({ name }, "Created new counter");
|
|
12699
12952
|
}
|
|
@@ -12827,10 +13080,10 @@ var CounterHandler = class {
|
|
|
12827
13080
|
};
|
|
12828
13081
|
|
|
12829
13082
|
// src/handlers/EntryProcessorHandler.ts
|
|
12830
|
-
var
|
|
13083
|
+
var import_core31 = require("@topgunbuild/core");
|
|
12831
13084
|
|
|
12832
13085
|
// src/ProcessorSandbox.ts
|
|
12833
|
-
var
|
|
13086
|
+
var import_core30 = require("@topgunbuild/core");
|
|
12834
13087
|
var ivm = null;
|
|
12835
13088
|
try {
|
|
12836
13089
|
ivm = require("isolated-vm");
|
|
@@ -12874,7 +13127,7 @@ var ProcessorSandbox = class {
|
|
|
12874
13127
|
};
|
|
12875
13128
|
}
|
|
12876
13129
|
if (this.config.strictValidation) {
|
|
12877
|
-
const validation = (0,
|
|
13130
|
+
const validation = (0, import_core30.validateProcessorCode)(processor.code);
|
|
12878
13131
|
if (!validation.valid) {
|
|
12879
13132
|
return {
|
|
12880
13133
|
success: false,
|
|
@@ -13089,7 +13342,7 @@ var EntryProcessorHandler = class {
|
|
|
13089
13342
|
* @returns Result with success status, processor result, and new value
|
|
13090
13343
|
*/
|
|
13091
13344
|
async executeOnKey(map2, key, processorDef) {
|
|
13092
|
-
const parseResult =
|
|
13345
|
+
const parseResult = import_core31.EntryProcessorDefSchema.safeParse(processorDef);
|
|
13093
13346
|
if (!parseResult.success) {
|
|
13094
13347
|
logger.warn(
|
|
13095
13348
|
{ key, error: parseResult.error.message },
|
|
@@ -13155,7 +13408,7 @@ var EntryProcessorHandler = class {
|
|
|
13155
13408
|
async executeOnKeys(map2, keys, processorDef) {
|
|
13156
13409
|
const results = /* @__PURE__ */ new Map();
|
|
13157
13410
|
const timestamps = /* @__PURE__ */ new Map();
|
|
13158
|
-
const parseResult =
|
|
13411
|
+
const parseResult = import_core31.EntryProcessorDefSchema.safeParse(processorDef);
|
|
13159
13412
|
if (!parseResult.success) {
|
|
13160
13413
|
const errorResult = {
|
|
13161
13414
|
success: false,
|
|
@@ -13192,7 +13445,7 @@ var EntryProcessorHandler = class {
|
|
|
13192
13445
|
async executeOnEntries(map2, processorDef, predicateCode) {
|
|
13193
13446
|
const results = /* @__PURE__ */ new Map();
|
|
13194
13447
|
const timestamps = /* @__PURE__ */ new Map();
|
|
13195
|
-
const parseResult =
|
|
13448
|
+
const parseResult = import_core31.EntryProcessorDefSchema.safeParse(processorDef);
|
|
13196
13449
|
if (!parseResult.success) {
|
|
13197
13450
|
return { results, timestamps };
|
|
13198
13451
|
}
|
|
@@ -13251,7 +13504,7 @@ var EntryProcessorHandler = class {
|
|
|
13251
13504
|
};
|
|
13252
13505
|
|
|
13253
13506
|
// src/ConflictResolverService.ts
|
|
13254
|
-
var
|
|
13507
|
+
var import_core32 = require("@topgunbuild/core");
|
|
13255
13508
|
var DEFAULT_CONFLICT_RESOLVER_CONFIG = {
|
|
13256
13509
|
maxResolversPerMap: 100,
|
|
13257
13510
|
enableSandboxedResolvers: true,
|
|
@@ -13282,7 +13535,7 @@ var ConflictResolverService = class {
|
|
|
13282
13535
|
throw new Error("ConflictResolverService has been disposed");
|
|
13283
13536
|
}
|
|
13284
13537
|
if (resolver.code) {
|
|
13285
|
-
const parsed =
|
|
13538
|
+
const parsed = import_core32.ConflictResolverDefSchema.safeParse({
|
|
13286
13539
|
name: resolver.name,
|
|
13287
13540
|
code: resolver.code,
|
|
13288
13541
|
priority: resolver.priority,
|
|
@@ -13291,7 +13544,7 @@ var ConflictResolverService = class {
|
|
|
13291
13544
|
if (!parsed.success) {
|
|
13292
13545
|
throw new Error(`Invalid resolver definition: ${parsed.error.message}`);
|
|
13293
13546
|
}
|
|
13294
|
-
const validation = (0,
|
|
13547
|
+
const validation = (0, import_core32.validateResolverCode)(resolver.code);
|
|
13295
13548
|
if (!validation.valid) {
|
|
13296
13549
|
throw new Error(`Invalid resolver code: ${validation.error}`);
|
|
13297
13550
|
}
|
|
@@ -13353,7 +13606,7 @@ var ConflictResolverService = class {
|
|
|
13353
13606
|
const entries = this.resolvers.get(context.mapName) ?? [];
|
|
13354
13607
|
const allEntries = [
|
|
13355
13608
|
...entries,
|
|
13356
|
-
{ resolver:
|
|
13609
|
+
{ resolver: import_core32.BuiltInResolvers.LWW() }
|
|
13357
13610
|
];
|
|
13358
13611
|
for (const entry of allEntries) {
|
|
13359
13612
|
const { resolver } = entry;
|
|
@@ -13809,7 +14062,7 @@ var TopicManager = class {
|
|
|
13809
14062
|
|
|
13810
14063
|
// src/search/SearchCoordinator.ts
|
|
13811
14064
|
var import_events13 = require("events");
|
|
13812
|
-
var
|
|
14065
|
+
var import_core33 = require("@topgunbuild/core");
|
|
13813
14066
|
var SearchCoordinator = class extends import_events13.EventEmitter {
|
|
13814
14067
|
constructor() {
|
|
13815
14068
|
super();
|
|
@@ -13879,7 +14132,7 @@ var SearchCoordinator = class extends import_events13.EventEmitter {
|
|
|
13879
14132
|
logger.warn({ mapName }, "FTS already enabled for map, replacing index");
|
|
13880
14133
|
this.indexes.delete(mapName);
|
|
13881
14134
|
}
|
|
13882
|
-
const index = new
|
|
14135
|
+
const index = new import_core33.FullTextIndex(config2);
|
|
13883
14136
|
this.indexes.set(mapName, index);
|
|
13884
14137
|
this.configs.set(mapName, config2);
|
|
13885
14138
|
logger.info({ mapName, fields: config2.fields }, "FTS enabled for map");
|
|
@@ -14511,7 +14764,7 @@ var SearchCoordinator = class extends import_events13.EventEmitter {
|
|
|
14511
14764
|
|
|
14512
14765
|
// src/search/ClusterSearchCoordinator.ts
|
|
14513
14766
|
var import_events14 = require("events");
|
|
14514
|
-
var
|
|
14767
|
+
var import_core34 = require("@topgunbuild/core");
|
|
14515
14768
|
var DEFAULT_CONFIG6 = {
|
|
14516
14769
|
rrfK: 60,
|
|
14517
14770
|
defaultTimeoutMs: 5e3,
|
|
@@ -14526,7 +14779,7 @@ var ClusterSearchCoordinator = class extends import_events14.EventEmitter {
|
|
|
14526
14779
|
this.partitionService = partitionService;
|
|
14527
14780
|
this.localSearchCoordinator = localSearchCoordinator;
|
|
14528
14781
|
this.config = { ...DEFAULT_CONFIG6, ...config2 };
|
|
14529
|
-
this.rrf = new
|
|
14782
|
+
this.rrf = new import_core34.ReciprocalRankFusion({ k: this.config.rrfK });
|
|
14530
14783
|
this.metricsService = metricsService;
|
|
14531
14784
|
this.clusterManager.on("message", this.handleClusterMessage.bind(this));
|
|
14532
14785
|
}
|
|
@@ -14550,8 +14803,8 @@ var ClusterSearchCoordinator = class extends import_events14.EventEmitter {
|
|
|
14550
14803
|
const perNodeLimit = this.calculatePerNodeLimit(options.limit, options.cursor);
|
|
14551
14804
|
let cursorData = null;
|
|
14552
14805
|
if (options.cursor) {
|
|
14553
|
-
cursorData =
|
|
14554
|
-
if (cursorData && !
|
|
14806
|
+
cursorData = import_core34.SearchCursor.decode(options.cursor);
|
|
14807
|
+
if (cursorData && !import_core34.SearchCursor.isValid(cursorData, query)) {
|
|
14555
14808
|
cursorData = null;
|
|
14556
14809
|
logger.warn({ requestId }, "Invalid or expired cursor, ignoring");
|
|
14557
14810
|
}
|
|
@@ -14617,7 +14870,7 @@ var ClusterSearchCoordinator = class extends import_events14.EventEmitter {
|
|
|
14617
14870
|
async handleSearchRequest(senderId, rawPayload) {
|
|
14618
14871
|
const startTime = performance.now();
|
|
14619
14872
|
const myNodeId = this.clusterManager.config.nodeId;
|
|
14620
|
-
const parsed =
|
|
14873
|
+
const parsed = import_core34.ClusterSearchReqPayloadSchema.safeParse(rawPayload);
|
|
14621
14874
|
if (!parsed.success) {
|
|
14622
14875
|
logger.warn(
|
|
14623
14876
|
{ senderId, error: parsed.error.message },
|
|
@@ -14684,7 +14937,7 @@ var ClusterSearchCoordinator = class extends import_events14.EventEmitter {
|
|
|
14684
14937
|
* Handle search response from a node.
|
|
14685
14938
|
*/
|
|
14686
14939
|
handleSearchResponse(_senderId, rawPayload) {
|
|
14687
|
-
const parsed =
|
|
14940
|
+
const parsed = import_core34.ClusterSearchRespPayloadSchema.safeParse(rawPayload);
|
|
14688
14941
|
if (!parsed.success) {
|
|
14689
14942
|
logger.warn(
|
|
14690
14943
|
{ error: parsed.error.message },
|
|
@@ -14770,7 +15023,7 @@ var ClusterSearchCoordinator = class extends import_events14.EventEmitter {
|
|
|
14770
15023
|
}
|
|
14771
15024
|
let nextCursor;
|
|
14772
15025
|
if (merged.length > limit && cursorResults.length > 0) {
|
|
14773
|
-
nextCursor =
|
|
15026
|
+
nextCursor = import_core34.SearchCursor.fromResults(cursorResults, pending.query);
|
|
14774
15027
|
}
|
|
14775
15028
|
const executionTimeMs = performance.now() - pending.startTime;
|
|
14776
15029
|
if (this.metricsService) {
|
|
@@ -14836,7 +15089,7 @@ var ClusterSearchCoordinator = class extends import_events14.EventEmitter {
|
|
|
14836
15089
|
});
|
|
14837
15090
|
let results = localResult.results;
|
|
14838
15091
|
if (cursorData) {
|
|
14839
|
-
const position =
|
|
15092
|
+
const position = import_core34.SearchCursor.getNodePosition(cursorData, myNodeId);
|
|
14840
15093
|
if (position) {
|
|
14841
15094
|
results = results.filter((r) => {
|
|
14842
15095
|
if (r.score < position.afterScore) {
|
|
@@ -14885,9 +15138,9 @@ var ClusterSearchCoordinator = class extends import_events14.EventEmitter {
|
|
|
14885
15138
|
});
|
|
14886
15139
|
let results = localResult.results;
|
|
14887
15140
|
if (options.cursor) {
|
|
14888
|
-
const cursorData =
|
|
14889
|
-
if (cursorData &&
|
|
14890
|
-
const position =
|
|
15141
|
+
const cursorData = import_core34.SearchCursor.decode(options.cursor);
|
|
15142
|
+
if (cursorData && import_core34.SearchCursor.isValid(cursorData, query)) {
|
|
15143
|
+
const position = import_core34.SearchCursor.getNodePosition(cursorData, myNodeId);
|
|
14891
15144
|
if (position) {
|
|
14892
15145
|
results = results.filter((r) => {
|
|
14893
15146
|
if (r.score < position.afterScore) {
|
|
@@ -14906,7 +15159,7 @@ var ClusterSearchCoordinator = class extends import_events14.EventEmitter {
|
|
|
14906
15159
|
let nextCursor;
|
|
14907
15160
|
if (totalCount > options.limit && results.length > 0) {
|
|
14908
15161
|
const lastResult = results[results.length - 1];
|
|
14909
|
-
nextCursor =
|
|
15162
|
+
nextCursor = import_core34.SearchCursor.fromResults(
|
|
14910
15163
|
[{ key: lastResult.key, score: lastResult.score, nodeId: myNodeId }],
|
|
14911
15164
|
query
|
|
14912
15165
|
);
|
|
@@ -14964,10 +15217,10 @@ var ClusterSearchCoordinator = class extends import_events14.EventEmitter {
|
|
|
14964
15217
|
|
|
14965
15218
|
// src/subscriptions/DistributedSubscriptionCoordinator.ts
|
|
14966
15219
|
var import_events16 = require("events");
|
|
14967
|
-
var
|
|
15220
|
+
var import_core36 = require("@topgunbuild/core");
|
|
14968
15221
|
|
|
14969
15222
|
// src/subscriptions/DistributedSearchCoordinator.ts
|
|
14970
|
-
var
|
|
15223
|
+
var import_core35 = require("@topgunbuild/core");
|
|
14971
15224
|
|
|
14972
15225
|
// src/subscriptions/DistributedSubscriptionBase.ts
|
|
14973
15226
|
var import_events15 = require("events");
|
|
@@ -15291,7 +15544,7 @@ var DistributedSearchCoordinator = class extends DistributedSubscriptionBase {
|
|
|
15291
15544
|
constructor(clusterManager, searchCoordinator, config2, metricsService, options) {
|
|
15292
15545
|
super(clusterManager, config2, metricsService, options);
|
|
15293
15546
|
this.localSearchCoordinator = searchCoordinator;
|
|
15294
|
-
this.rrf = new
|
|
15547
|
+
this.rrf = new import_core35.ReciprocalRankFusion({ k: this.config.rrfK });
|
|
15295
15548
|
this.localSearchCoordinator.on("distributedUpdate", this.handleLocalSearchUpdate.bind(this));
|
|
15296
15549
|
logger.debug("DistributedSearchCoordinator initialized");
|
|
15297
15550
|
}
|
|
@@ -15823,7 +16076,7 @@ var DistributedSubscriptionCoordinator = class extends import_events16.EventEmit
|
|
|
15823
16076
|
handleClusterMessage(msg) {
|
|
15824
16077
|
switch (msg.type) {
|
|
15825
16078
|
case "CLUSTER_SUB_REGISTER": {
|
|
15826
|
-
const parsed =
|
|
16079
|
+
const parsed = import_core36.ClusterSubRegisterPayloadSchema.safeParse(msg.payload);
|
|
15827
16080
|
if (!parsed.success) {
|
|
15828
16081
|
logger.warn(
|
|
15829
16082
|
{ senderId: msg.senderId, error: parsed.error.message },
|
|
@@ -15835,7 +16088,7 @@ var DistributedSubscriptionCoordinator = class extends import_events16.EventEmit
|
|
|
15835
16088
|
break;
|
|
15836
16089
|
}
|
|
15837
16090
|
case "CLUSTER_SUB_ACK": {
|
|
15838
|
-
const parsed =
|
|
16091
|
+
const parsed = import_core36.ClusterSubAckPayloadSchema.safeParse(msg.payload);
|
|
15839
16092
|
if (!parsed.success) {
|
|
15840
16093
|
logger.warn(
|
|
15841
16094
|
{ senderId: msg.senderId, error: parsed.error.message },
|
|
@@ -15847,7 +16100,7 @@ var DistributedSubscriptionCoordinator = class extends import_events16.EventEmit
|
|
|
15847
16100
|
break;
|
|
15848
16101
|
}
|
|
15849
16102
|
case "CLUSTER_SUB_UPDATE": {
|
|
15850
|
-
const parsed =
|
|
16103
|
+
const parsed = import_core36.ClusterSubUpdatePayloadSchema.safeParse(msg.payload);
|
|
15851
16104
|
if (!parsed.success) {
|
|
15852
16105
|
logger.warn(
|
|
15853
16106
|
{ senderId: msg.senderId, error: parsed.error.message },
|
|
@@ -15859,7 +16112,7 @@ var DistributedSubscriptionCoordinator = class extends import_events16.EventEmit
|
|
|
15859
16112
|
break;
|
|
15860
16113
|
}
|
|
15861
16114
|
case "CLUSTER_SUB_UNREGISTER": {
|
|
15862
|
-
const parsed =
|
|
16115
|
+
const parsed = import_core36.ClusterSubUnregisterPayloadSchema.safeParse(msg.payload);
|
|
15863
16116
|
if (!parsed.success) {
|
|
15864
16117
|
logger.warn(
|
|
15865
16118
|
{ senderId: msg.senderId, error: parsed.error.message },
|
|
@@ -15969,9 +16222,9 @@ var DistributedSubscriptionCoordinator = class extends import_events16.EventEmit
|
|
|
15969
16222
|
};
|
|
15970
16223
|
|
|
15971
16224
|
// src/EventJournalService.ts
|
|
15972
|
-
var
|
|
16225
|
+
var import_core37 = require("@topgunbuild/core");
|
|
15973
16226
|
var DEFAULT_JOURNAL_SERVICE_CONFIG = {
|
|
15974
|
-
...
|
|
16227
|
+
...import_core37.DEFAULT_EVENT_JOURNAL_CONFIG,
|
|
15975
16228
|
tableName: "event_journal",
|
|
15976
16229
|
persistBatchSize: 100,
|
|
15977
16230
|
persistIntervalMs: 1e3
|
|
@@ -15984,7 +16237,7 @@ function validateTableName(name) {
|
|
|
15984
16237
|
);
|
|
15985
16238
|
}
|
|
15986
16239
|
}
|
|
15987
|
-
var EventJournalService = class extends
|
|
16240
|
+
var EventJournalService = class extends import_core37.EventJournalImpl {
|
|
15988
16241
|
constructor(config2) {
|
|
15989
16242
|
super(config2);
|
|
15990
16243
|
this.pendingPersist = [];
|
|
@@ -16631,7 +16884,7 @@ function createQueryHandlers(config2, deps, internal) {
|
|
|
16631
16884
|
finalizeClusterQuery: (reqId, timeout) => queryConversionHandler.finalizeClusterQuery(reqId, timeout),
|
|
16632
16885
|
pendingClusterQueries: internal.pendingClusterQueries,
|
|
16633
16886
|
readReplicaHandler: deps.cluster.readReplicaHandler,
|
|
16634
|
-
ConsistencyLevel: { EVENTUAL:
|
|
16887
|
+
ConsistencyLevel: { EVENTUAL: import_core38.ConsistencyLevel.EVENTUAL }
|
|
16635
16888
|
});
|
|
16636
16889
|
return { queryHandler, queryConversionHandler };
|
|
16637
16890
|
}
|
|
@@ -17028,7 +17281,7 @@ function createLifecycleModule(config2, deps) {
|
|
|
17028
17281
|
}
|
|
17029
17282
|
|
|
17030
17283
|
// src/debug/DebugEndpoints.ts
|
|
17031
|
-
var
|
|
17284
|
+
var import_core39 = require("@topgunbuild/core");
|
|
17032
17285
|
var DebugEndpoints = class {
|
|
17033
17286
|
constructor(config2) {
|
|
17034
17287
|
this.config = config2;
|
|
@@ -17116,7 +17369,7 @@ var DebugEndpoints = class {
|
|
|
17116
17369
|
async handleCrdtExport(req, res) {
|
|
17117
17370
|
try {
|
|
17118
17371
|
const body = await this.parseBody(req);
|
|
17119
|
-
const debugger_ = (0,
|
|
17372
|
+
const debugger_ = (0, import_core39.getCRDTDebugger)();
|
|
17120
17373
|
const format = body.format || "json";
|
|
17121
17374
|
const data = debugger_.exportHistory(format);
|
|
17122
17375
|
const contentType = format === "csv" ? "text/csv" : format === "ndjson" ? "application/x-ndjson" : "application/json";
|
|
@@ -17130,7 +17383,7 @@ var DebugEndpoints = class {
|
|
|
17130
17383
|
async handleCrdtStats(req, res) {
|
|
17131
17384
|
try {
|
|
17132
17385
|
const body = await this.parseBody(req);
|
|
17133
|
-
const debugger_ = (0,
|
|
17386
|
+
const debugger_ = (0, import_core39.getCRDTDebugger)();
|
|
17134
17387
|
const stats = debugger_.getStatistics(body.mapId);
|
|
17135
17388
|
res.setHeader("Content-Type", "application/json");
|
|
17136
17389
|
res.end(JSON.stringify(stats, null, 2));
|
|
@@ -17142,7 +17395,7 @@ var DebugEndpoints = class {
|
|
|
17142
17395
|
async handleCrdtConflicts(req, res) {
|
|
17143
17396
|
try {
|
|
17144
17397
|
const body = await this.parseBody(req);
|
|
17145
|
-
const debugger_ = (0,
|
|
17398
|
+
const debugger_ = (0, import_core39.getCRDTDebugger)();
|
|
17146
17399
|
const conflicts = debugger_.getConflicts(body.mapId);
|
|
17147
17400
|
res.setHeader("Content-Type", "application/json");
|
|
17148
17401
|
res.end(JSON.stringify(conflicts, null, 2));
|
|
@@ -17154,7 +17407,7 @@ var DebugEndpoints = class {
|
|
|
17154
17407
|
async handleCrdtOperations(req, res) {
|
|
17155
17408
|
try {
|
|
17156
17409
|
const body = await this.parseBody(req);
|
|
17157
|
-
const debugger_ = (0,
|
|
17410
|
+
const debugger_ = (0, import_core39.getCRDTDebugger)();
|
|
17158
17411
|
const operations = debugger_.getOperations({
|
|
17159
17412
|
mapId: body.mapId,
|
|
17160
17413
|
nodeId: body.nodeId,
|
|
@@ -17171,7 +17424,7 @@ var DebugEndpoints = class {
|
|
|
17171
17424
|
async handleCrdtTimeline(req, res) {
|
|
17172
17425
|
try {
|
|
17173
17426
|
const body = await this.parseBody(req);
|
|
17174
|
-
const debugger_ = (0,
|
|
17427
|
+
const debugger_ = (0, import_core39.getCRDTDebugger)();
|
|
17175
17428
|
const intervalMs = body.intervalMs || 1e3;
|
|
17176
17429
|
const timeline = debugger_.getTimeline(
|
|
17177
17430
|
intervalMs,
|
|
@@ -17190,7 +17443,7 @@ var DebugEndpoints = class {
|
|
|
17190
17443
|
async handleSearchExplain(req, res) {
|
|
17191
17444
|
try {
|
|
17192
17445
|
const body = await this.parseBody(req);
|
|
17193
|
-
const debugger_ = (0,
|
|
17446
|
+
const debugger_ = (0, import_core39.getSearchDebugger)();
|
|
17194
17447
|
if (body.query) {
|
|
17195
17448
|
const lastQuery2 = debugger_.getLastQuery();
|
|
17196
17449
|
if (lastQuery2 && lastQuery2.query === body.query) {
|
|
@@ -17222,7 +17475,7 @@ var DebugEndpoints = class {
|
|
|
17222
17475
|
}
|
|
17223
17476
|
handleSearchStats(res) {
|
|
17224
17477
|
try {
|
|
17225
|
-
const debugger_ = (0,
|
|
17478
|
+
const debugger_ = (0, import_core39.getSearchDebugger)();
|
|
17226
17479
|
const stats = debugger_.getSearchStats();
|
|
17227
17480
|
res.setHeader("Content-Type", "application/json");
|
|
17228
17481
|
res.end(JSON.stringify(stats, null, 2));
|
|
@@ -17234,7 +17487,7 @@ var DebugEndpoints = class {
|
|
|
17234
17487
|
async handleSearchHistory(req, res) {
|
|
17235
17488
|
try {
|
|
17236
17489
|
const body = await this.parseBody(req);
|
|
17237
|
-
const debugger_ = (0,
|
|
17490
|
+
const debugger_ = (0, import_core39.getSearchDebugger)();
|
|
17238
17491
|
let history;
|
|
17239
17492
|
if (body.mapId) {
|
|
17240
17493
|
history = debugger_.getHistoryByMap(body.mapId);
|
|
@@ -17302,7 +17555,7 @@ var fs = __toESM(require("fs"));
|
|
|
17302
17555
|
var path = __toESM(require("path"));
|
|
17303
17556
|
var crypto3 = __toESM(require("crypto"));
|
|
17304
17557
|
var jwt2 = __toESM(require("jsonwebtoken"));
|
|
17305
|
-
var
|
|
17558
|
+
var import_core40 = require("@topgunbuild/core");
|
|
17306
17559
|
var ENV_KEYS = {
|
|
17307
17560
|
AUTO_SETUP: "TOPGUN_AUTO_SETUP",
|
|
17308
17561
|
AUTO_SETUP_STRICT: "TOPGUN_AUTO_SETUP_STRICT",
|
|
@@ -17663,7 +17916,7 @@ var BootstrapController = class {
|
|
|
17663
17916
|
const status = this.getClusterStatus();
|
|
17664
17917
|
const transformedStatus = {
|
|
17665
17918
|
...status,
|
|
17666
|
-
totalPartitions:
|
|
17919
|
+
totalPartitions: import_core40.PARTITION_COUNT,
|
|
17667
17920
|
// Fixed partition count (see PHASE_14D_AUTH_SECURITY.md)
|
|
17668
17921
|
nodes: status.nodes.map((node) => ({
|
|
17669
17922
|
nodeId: node.id,
|
|
@@ -17680,7 +17933,7 @@ var BootstrapController = class {
|
|
|
17680
17933
|
this.sendJson(res, 200, {
|
|
17681
17934
|
nodes: [],
|
|
17682
17935
|
partitions: [],
|
|
17683
|
-
totalPartitions:
|
|
17936
|
+
totalPartitions: import_core40.PARTITION_COUNT,
|
|
17684
17937
|
isRebalancing: false
|
|
17685
17938
|
});
|
|
17686
17939
|
}
|
|
@@ -17982,7 +18235,7 @@ function createBootstrapController(config2) {
|
|
|
17982
18235
|
// src/settings/SettingsController.ts
|
|
17983
18236
|
var fs2 = __toESM(require("fs"));
|
|
17984
18237
|
var jwt3 = __toESM(require("jsonwebtoken"));
|
|
17985
|
-
var
|
|
18238
|
+
var import_core41 = require("@topgunbuild/core");
|
|
17986
18239
|
var HOT_RELOADABLE = /* @__PURE__ */ new Set([
|
|
17987
18240
|
"logLevel",
|
|
17988
18241
|
"metricsEnabled",
|
|
@@ -18095,7 +18348,7 @@ var SettingsController = class {
|
|
|
18095
18348
|
mode: config2?.deploymentMode || process.env.TOPGUN_DEPLOYMENT_MODE || "standalone",
|
|
18096
18349
|
nodeId: process.env.TOPGUN_NODE_ID || "node-1",
|
|
18097
18350
|
peers: [],
|
|
18098
|
-
partitionCount:
|
|
18351
|
+
partitionCount: import_core41.PARTITION_COUNT
|
|
18099
18352
|
},
|
|
18100
18353
|
rateLimits: {
|
|
18101
18354
|
connections: this.runtimeSettings.rateLimits.connections,
|
|
@@ -18567,6 +18820,25 @@ var ServerFactory = class _ServerFactory {
|
|
|
18567
18820
|
journalSubscriptions
|
|
18568
18821
|
}
|
|
18569
18822
|
} = handlers;
|
|
18823
|
+
const httpSyncHandler = new HttpSyncHandler({
|
|
18824
|
+
authHandler,
|
|
18825
|
+
operationHandler,
|
|
18826
|
+
storageManager,
|
|
18827
|
+
queryConversionHandler,
|
|
18828
|
+
searchCoordinator,
|
|
18829
|
+
hlc,
|
|
18830
|
+
securityManager
|
|
18831
|
+
});
|
|
18832
|
+
if (network.setHttpRequestHandler) {
|
|
18833
|
+
network.setHttpRequestHandler((req, res) => {
|
|
18834
|
+
if (req.method === "POST" && req.url === "/sync") {
|
|
18835
|
+
_ServerFactory.handleHttpSync(req, res, httpSyncHandler);
|
|
18836
|
+
return;
|
|
18837
|
+
}
|
|
18838
|
+
res.writeHead(200);
|
|
18839
|
+
res.end(config2.tls?.enabled ? "TopGun Server Running (Secure)" : "TopGun Server Running");
|
|
18840
|
+
});
|
|
18841
|
+
}
|
|
18570
18842
|
const lifecycle = createLifecycleModule(
|
|
18571
18843
|
{ nodeId: config2.nodeId },
|
|
18572
18844
|
{
|
|
@@ -18669,6 +18941,70 @@ var ServerFactory = class _ServerFactory {
|
|
|
18669
18941
|
}
|
|
18670
18942
|
return coordinator;
|
|
18671
18943
|
}
|
|
18944
|
+
/**
|
|
18945
|
+
* Handle an HTTP sync request by parsing the body, validating auth,
|
|
18946
|
+
* delegating to HttpSyncHandler, and serializing the response.
|
|
18947
|
+
*/
|
|
18948
|
+
static handleHttpSync(req, res, handler) {
|
|
18949
|
+
const authHeader = req.headers["authorization"] || "";
|
|
18950
|
+
const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
18951
|
+
if (!token) {
|
|
18952
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
18953
|
+
res.end(JSON.stringify({ error: "Missing Authorization header" }));
|
|
18954
|
+
return;
|
|
18955
|
+
}
|
|
18956
|
+
const chunks = [];
|
|
18957
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
18958
|
+
req.on("end", async () => {
|
|
18959
|
+
try {
|
|
18960
|
+
const body = Buffer.concat(chunks);
|
|
18961
|
+
const contentType = req.headers["content-type"] || "";
|
|
18962
|
+
const isJson = contentType.includes("application/json");
|
|
18963
|
+
let parsed;
|
|
18964
|
+
if (isJson) {
|
|
18965
|
+
parsed = JSON.parse(body.toString("utf-8"));
|
|
18966
|
+
} else {
|
|
18967
|
+
parsed = (0, import_core42.deserialize)(body);
|
|
18968
|
+
}
|
|
18969
|
+
const validation = import_core42.HttpSyncRequestSchema.safeParse(parsed);
|
|
18970
|
+
if (!validation.success) {
|
|
18971
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
18972
|
+
res.end(JSON.stringify({
|
|
18973
|
+
error: "Invalid request body",
|
|
18974
|
+
details: validation.error.issues
|
|
18975
|
+
}));
|
|
18976
|
+
return;
|
|
18977
|
+
}
|
|
18978
|
+
const response = await handler.handleSyncRequest(validation.data, token);
|
|
18979
|
+
if (isJson) {
|
|
18980
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
18981
|
+
res.end(JSON.stringify(response));
|
|
18982
|
+
} else {
|
|
18983
|
+
const responseBytes = (0, import_core42.serialize)(response);
|
|
18984
|
+
res.writeHead(200, { "Content-Type": "application/x-msgpack" });
|
|
18985
|
+
res.end(Buffer.from(responseBytes));
|
|
18986
|
+
}
|
|
18987
|
+
} catch (err) {
|
|
18988
|
+
const message = err.message || "Internal server error";
|
|
18989
|
+
if (message.startsWith("401:")) {
|
|
18990
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
18991
|
+
res.end(JSON.stringify({ error: message.slice(5).trim() }));
|
|
18992
|
+
} else if (message.startsWith("403:")) {
|
|
18993
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
18994
|
+
res.end(JSON.stringify({ error: message.slice(5).trim() }));
|
|
18995
|
+
} else {
|
|
18996
|
+
logger.error({ err }, "HTTP sync request failed");
|
|
18997
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
18998
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
18999
|
+
}
|
|
19000
|
+
}
|
|
19001
|
+
});
|
|
19002
|
+
req.on("error", (err) => {
|
|
19003
|
+
logger.error({ err }, "HTTP sync request stream error");
|
|
19004
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
19005
|
+
res.end(JSON.stringify({ error: "Request stream error" }));
|
|
19006
|
+
});
|
|
19007
|
+
}
|
|
18672
19008
|
static createMetricsServer(bootstrap, settings, debug, metrics) {
|
|
18673
19009
|
const server = (0, import_http.createServer)(async (req, res) => {
|
|
18674
19010
|
const bootstrapHandled = await bootstrap.handle(req, res);
|
|
@@ -18699,7 +19035,7 @@ var ServerFactory = class _ServerFactory {
|
|
|
18699
19035
|
const memberIds = cluster.getMembers();
|
|
18700
19036
|
const nodes = memberIds.map((nodeId) => {
|
|
18701
19037
|
let partitionCount = 0;
|
|
18702
|
-
for (let i = 0; i <
|
|
19038
|
+
for (let i = 0; i < import_core42.PARTITION_COUNT; i++) {
|
|
18703
19039
|
if (partitionService.getPartitionOwner(i) === nodeId) partitionCount++;
|
|
18704
19040
|
}
|
|
18705
19041
|
return {
|
|
@@ -18713,7 +19049,7 @@ var ServerFactory = class _ServerFactory {
|
|
|
18713
19049
|
};
|
|
18714
19050
|
});
|
|
18715
19051
|
const partitions = [];
|
|
18716
|
-
for (let i = 0; i <
|
|
19052
|
+
for (let i = 0; i < import_core42.PARTITION_COUNT; i++) {
|
|
18717
19053
|
const owner = partitionService.getPartitionOwner(i);
|
|
18718
19054
|
const backups = partitionService.getBackups(i);
|
|
18719
19055
|
partitions.push({
|