@topgunbuild/server 0.4.0 → 0.5.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/index.d.mts +186 -2
- package/dist/index.d.ts +186 -2
- package/dist/index.js +508 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +511 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { createServer as createHttpServer } from "http";
|
|
|
10
10
|
import { createServer as createHttpsServer } from "https";
|
|
11
11
|
import { readFileSync as readFileSync2 } from "fs";
|
|
12
12
|
import { WebSocketServer as WebSocketServer2, WebSocket as WebSocket3 } from "ws";
|
|
13
|
-
import { HLC as HLC2, LWWMap as LWWMap3, ORMap as ORMap2, serialize as serialize4, deserialize, MessageSchema, WriteConcern as WriteConcern2, ConsistencyLevel as ConsistencyLevel2, DEFAULT_REPLICATION_CONFIG as DEFAULT_REPLICATION_CONFIG2 } from "@topgunbuild/core";
|
|
13
|
+
import { HLC as HLC2, LWWMap as LWWMap3, ORMap as ORMap2, serialize as serialize4, deserialize, MessageSchema, WriteConcern as WriteConcern2, ConsistencyLevel as ConsistencyLevel2, DEFAULT_REPLICATION_CONFIG as DEFAULT_REPLICATION_CONFIG2, IndexedLWWMap as IndexedLWWMap2, IndexedORMap as IndexedORMap2 } from "@topgunbuild/core";
|
|
14
14
|
import * as jwt from "jsonwebtoken";
|
|
15
15
|
import * as crypto from "crypto";
|
|
16
16
|
|
|
@@ -101,7 +101,7 @@ function executeQuery(records, query) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// src/query/QueryRegistry.ts
|
|
104
|
-
import { serialize } from "@topgunbuild/core";
|
|
104
|
+
import { serialize, IndexedLWWMap } from "@topgunbuild/core";
|
|
105
105
|
|
|
106
106
|
// src/utils/logger.ts
|
|
107
107
|
import pino from "pino";
|
|
@@ -362,14 +362,70 @@ var QueryRegistry = class {
|
|
|
362
362
|
/**
|
|
363
363
|
* Processes a record change for all relevant subscriptions.
|
|
364
364
|
* Calculates diffs and sends updates.
|
|
365
|
+
*
|
|
366
|
+
* For IndexedLWWMap: Uses StandingQueryRegistry for O(1) affected query detection.
|
|
367
|
+
* For regular maps: Falls back to ReverseQueryIndex.
|
|
365
368
|
*/
|
|
366
369
|
processChange(mapName, map, changeKey, changeRecord, oldRecord) {
|
|
367
370
|
const index = this.indexes.get(mapName);
|
|
368
371
|
if (!index) return;
|
|
369
372
|
const newVal = this.extractValue(changeRecord);
|
|
370
373
|
const oldVal = this.extractValue(oldRecord);
|
|
374
|
+
if (map instanceof IndexedLWWMap) {
|
|
375
|
+
this.processChangeWithStandingQuery(mapName, map, changeKey, newVal, oldVal);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
this.processChangeWithReverseIndex(mapName, map, changeKey, newVal, oldVal, index);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Process change using IndexedLWWMap's StandingQueryRegistry.
|
|
382
|
+
* O(1) detection of affected queries.
|
|
383
|
+
*/
|
|
384
|
+
processChangeWithStandingQuery(mapName, map, changeKey, newVal, oldVal) {
|
|
385
|
+
const subs = this.subscriptions.get(mapName);
|
|
386
|
+
if (!subs || subs.size === 0) return;
|
|
387
|
+
const subsByQueryId = /* @__PURE__ */ new Map();
|
|
388
|
+
for (const sub of subs) {
|
|
389
|
+
subsByQueryId.set(sub.id, sub);
|
|
390
|
+
}
|
|
391
|
+
const standingRegistry = map.getStandingQueryRegistry();
|
|
392
|
+
let changes;
|
|
393
|
+
if (oldVal === null || oldVal === void 0) {
|
|
394
|
+
if (newVal !== null && newVal !== void 0) {
|
|
395
|
+
changes = standingRegistry.onRecordAdded(changeKey, newVal);
|
|
396
|
+
} else {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
} else if (newVal === null || newVal === void 0) {
|
|
400
|
+
changes = standingRegistry.onRecordRemoved(changeKey, oldVal);
|
|
401
|
+
} else {
|
|
402
|
+
changes = standingRegistry.onRecordUpdated(changeKey, oldVal, newVal);
|
|
403
|
+
}
|
|
404
|
+
for (const sub of subs) {
|
|
405
|
+
const coreQuery = this.convertToCoreQuery(sub.query);
|
|
406
|
+
if (!coreQuery) {
|
|
407
|
+
this.processSubscriptionFallback(sub, map, changeKey, newVal);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const queryHash = this.hashCoreQuery(coreQuery);
|
|
411
|
+
const change = changes.get(queryHash);
|
|
412
|
+
if (change === "added") {
|
|
413
|
+
sub.previousResultKeys.add(changeKey);
|
|
414
|
+
this.sendUpdate(sub, changeKey, newVal, "UPDATE");
|
|
415
|
+
} else if (change === "removed") {
|
|
416
|
+
sub.previousResultKeys.delete(changeKey);
|
|
417
|
+
this.sendUpdate(sub, changeKey, null, "REMOVE");
|
|
418
|
+
} else if (change === "updated") {
|
|
419
|
+
this.sendUpdate(sub, changeKey, newVal, "UPDATE");
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Process change using legacy ReverseQueryIndex.
|
|
425
|
+
*/
|
|
426
|
+
processChangeWithReverseIndex(mapName, map, changeKey, newVal, oldVal, index) {
|
|
371
427
|
const changedFields = this.getChangedFields(oldVal, newVal);
|
|
372
|
-
if (changedFields !== "ALL" && changedFields.size === 0 &&
|
|
428
|
+
if (changedFields !== "ALL" && changedFields.size === 0 && oldVal && newVal) {
|
|
373
429
|
return;
|
|
374
430
|
}
|
|
375
431
|
const candidates = index.getCandidates(changedFields, oldVal, newVal);
|
|
@@ -411,6 +467,103 @@ var QueryRegistry = class {
|
|
|
411
467
|
sub.previousResultKeys = newResultKeys;
|
|
412
468
|
}
|
|
413
469
|
}
|
|
470
|
+
/**
|
|
471
|
+
* Fallback processing for subscriptions that can't use StandingQueryRegistry.
|
|
472
|
+
*/
|
|
473
|
+
processSubscriptionFallback(sub, map, changeKey, newVal) {
|
|
474
|
+
const dummyRecord = {
|
|
475
|
+
value: newVal,
|
|
476
|
+
timestamp: { millis: 0, counter: 0, nodeId: "" }
|
|
477
|
+
};
|
|
478
|
+
const isMatch = newVal !== null && matchesQuery(dummyRecord, sub.query);
|
|
479
|
+
const wasInResult = sub.previousResultKeys.has(changeKey);
|
|
480
|
+
if (isMatch && !wasInResult) {
|
|
481
|
+
sub.previousResultKeys.add(changeKey);
|
|
482
|
+
this.sendUpdate(sub, changeKey, newVal, "UPDATE");
|
|
483
|
+
} else if (!isMatch && wasInResult) {
|
|
484
|
+
sub.previousResultKeys.delete(changeKey);
|
|
485
|
+
this.sendUpdate(sub, changeKey, null, "REMOVE");
|
|
486
|
+
} else if (isMatch && wasInResult) {
|
|
487
|
+
this.sendUpdate(sub, changeKey, newVal, "UPDATE");
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Convert server Query format to core Query format.
|
|
492
|
+
*/
|
|
493
|
+
convertToCoreQuery(query) {
|
|
494
|
+
if (query.predicate) {
|
|
495
|
+
return this.predicateToCoreQuery(query.predicate);
|
|
496
|
+
}
|
|
497
|
+
if (query.where) {
|
|
498
|
+
const conditions = [];
|
|
499
|
+
for (const [attribute, condition] of Object.entries(query.where)) {
|
|
500
|
+
if (typeof condition !== "object" || condition === null) {
|
|
501
|
+
conditions.push({ type: "eq", attribute, value: condition });
|
|
502
|
+
} else {
|
|
503
|
+
for (const [op, value] of Object.entries(condition)) {
|
|
504
|
+
const coreOp = this.convertOperator(op);
|
|
505
|
+
if (coreOp) {
|
|
506
|
+
conditions.push({ type: coreOp, attribute, value });
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (conditions.length === 0) return null;
|
|
512
|
+
if (conditions.length === 1) return conditions[0];
|
|
513
|
+
return { type: "and", children: conditions };
|
|
514
|
+
}
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
predicateToCoreQuery(predicate) {
|
|
518
|
+
if (!predicate || !predicate.op) return null;
|
|
519
|
+
switch (predicate.op) {
|
|
520
|
+
case "eq":
|
|
521
|
+
case "neq":
|
|
522
|
+
case "gt":
|
|
523
|
+
case "gte":
|
|
524
|
+
case "lt":
|
|
525
|
+
case "lte":
|
|
526
|
+
return {
|
|
527
|
+
type: predicate.op,
|
|
528
|
+
attribute: predicate.attribute,
|
|
529
|
+
value: predicate.value
|
|
530
|
+
};
|
|
531
|
+
case "and":
|
|
532
|
+
case "or":
|
|
533
|
+
if (predicate.children && Array.isArray(predicate.children)) {
|
|
534
|
+
const children = predicate.children.map((c) => this.predicateToCoreQuery(c)).filter((c) => c !== null);
|
|
535
|
+
if (children.length === 0) return null;
|
|
536
|
+
if (children.length === 1) return children[0];
|
|
537
|
+
return { type: predicate.op, children };
|
|
538
|
+
}
|
|
539
|
+
return null;
|
|
540
|
+
case "not":
|
|
541
|
+
if (predicate.children && predicate.children[0]) {
|
|
542
|
+
const child = this.predicateToCoreQuery(predicate.children[0]);
|
|
543
|
+
if (child) {
|
|
544
|
+
return { type: "not", child };
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return null;
|
|
548
|
+
default:
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
convertOperator(op) {
|
|
553
|
+
const mapping = {
|
|
554
|
+
"$eq": "eq",
|
|
555
|
+
"$ne": "neq",
|
|
556
|
+
"$neq": "neq",
|
|
557
|
+
"$gt": "gt",
|
|
558
|
+
"$gte": "gte",
|
|
559
|
+
"$lt": "lt",
|
|
560
|
+
"$lte": "lte"
|
|
561
|
+
};
|
|
562
|
+
return mapping[op] || null;
|
|
563
|
+
}
|
|
564
|
+
hashCoreQuery(query) {
|
|
565
|
+
return JSON.stringify(query);
|
|
566
|
+
}
|
|
414
567
|
extractValue(record) {
|
|
415
568
|
if (!record) return null;
|
|
416
569
|
if (Array.isArray(record)) {
|
|
@@ -9603,6 +9756,26 @@ var ServerCoordinator = class {
|
|
|
9603
9756
|
}
|
|
9604
9757
|
async executeLocalQuery(mapName, query) {
|
|
9605
9758
|
const map = await this.getMapAsync(mapName);
|
|
9759
|
+
const localQuery = { ...query };
|
|
9760
|
+
delete localQuery.offset;
|
|
9761
|
+
delete localQuery.limit;
|
|
9762
|
+
if (map instanceof IndexedLWWMap2) {
|
|
9763
|
+
const coreQuery = this.convertToCoreQuery(localQuery);
|
|
9764
|
+
if (coreQuery) {
|
|
9765
|
+
const entries = map.queryEntries(coreQuery);
|
|
9766
|
+
return entries.map(([key, value]) => {
|
|
9767
|
+
const record = map.getRecord(key);
|
|
9768
|
+
return { key, value, timestamp: record?.timestamp };
|
|
9769
|
+
});
|
|
9770
|
+
}
|
|
9771
|
+
}
|
|
9772
|
+
if (map instanceof IndexedORMap2) {
|
|
9773
|
+
const coreQuery = this.convertToCoreQuery(localQuery);
|
|
9774
|
+
if (coreQuery) {
|
|
9775
|
+
const results = map.query(coreQuery);
|
|
9776
|
+
return results.map(({ key, value }) => ({ key, value }));
|
|
9777
|
+
}
|
|
9778
|
+
}
|
|
9606
9779
|
const records = /* @__PURE__ */ new Map();
|
|
9607
9780
|
if (map instanceof LWWMap3) {
|
|
9608
9781
|
for (const key of map.allKeys()) {
|
|
@@ -9620,11 +9793,89 @@ var ServerCoordinator = class {
|
|
|
9620
9793
|
}
|
|
9621
9794
|
}
|
|
9622
9795
|
}
|
|
9623
|
-
const localQuery = { ...query };
|
|
9624
|
-
delete localQuery.offset;
|
|
9625
|
-
delete localQuery.limit;
|
|
9626
9796
|
return executeQuery(records, localQuery);
|
|
9627
9797
|
}
|
|
9798
|
+
/**
|
|
9799
|
+
* Convert server Query format to core Query format for indexed execution.
|
|
9800
|
+
* Returns null if conversion is not possible (complex queries).
|
|
9801
|
+
*/
|
|
9802
|
+
convertToCoreQuery(query) {
|
|
9803
|
+
if (query.predicate) {
|
|
9804
|
+
return this.predicateToCoreQuery(query.predicate);
|
|
9805
|
+
}
|
|
9806
|
+
if (query.where) {
|
|
9807
|
+
const conditions = [];
|
|
9808
|
+
for (const [attribute, condition] of Object.entries(query.where)) {
|
|
9809
|
+
if (typeof condition !== "object" || condition === null) {
|
|
9810
|
+
conditions.push({ type: "eq", attribute, value: condition });
|
|
9811
|
+
} else {
|
|
9812
|
+
for (const [op, value] of Object.entries(condition)) {
|
|
9813
|
+
const coreOp = this.convertOperator(op);
|
|
9814
|
+
if (coreOp) {
|
|
9815
|
+
conditions.push({ type: coreOp, attribute, value });
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
}
|
|
9819
|
+
}
|
|
9820
|
+
if (conditions.length === 0) return null;
|
|
9821
|
+
if (conditions.length === 1) return conditions[0];
|
|
9822
|
+
return { type: "and", children: conditions };
|
|
9823
|
+
}
|
|
9824
|
+
return null;
|
|
9825
|
+
}
|
|
9826
|
+
/**
|
|
9827
|
+
* Convert predicate node to core Query format.
|
|
9828
|
+
*/
|
|
9829
|
+
predicateToCoreQuery(predicate) {
|
|
9830
|
+
if (!predicate || !predicate.op) return null;
|
|
9831
|
+
switch (predicate.op) {
|
|
9832
|
+
case "eq":
|
|
9833
|
+
case "neq":
|
|
9834
|
+
case "gt":
|
|
9835
|
+
case "gte":
|
|
9836
|
+
case "lt":
|
|
9837
|
+
case "lte":
|
|
9838
|
+
return {
|
|
9839
|
+
type: predicate.op,
|
|
9840
|
+
attribute: predicate.attribute,
|
|
9841
|
+
value: predicate.value
|
|
9842
|
+
};
|
|
9843
|
+
case "and":
|
|
9844
|
+
case "or":
|
|
9845
|
+
if (predicate.children && Array.isArray(predicate.children)) {
|
|
9846
|
+
const children = predicate.children.map((c) => this.predicateToCoreQuery(c)).filter((c) => c !== null);
|
|
9847
|
+
if (children.length === 0) return null;
|
|
9848
|
+
if (children.length === 1) return children[0];
|
|
9849
|
+
return { type: predicate.op, children };
|
|
9850
|
+
}
|
|
9851
|
+
return null;
|
|
9852
|
+
case "not":
|
|
9853
|
+
if (predicate.children && predicate.children[0]) {
|
|
9854
|
+
const child = this.predicateToCoreQuery(predicate.children[0]);
|
|
9855
|
+
if (child) {
|
|
9856
|
+
return { type: "not", child };
|
|
9857
|
+
}
|
|
9858
|
+
}
|
|
9859
|
+
return null;
|
|
9860
|
+
default:
|
|
9861
|
+
return null;
|
|
9862
|
+
}
|
|
9863
|
+
}
|
|
9864
|
+
/**
|
|
9865
|
+
* Convert server operator to core query type.
|
|
9866
|
+
*/
|
|
9867
|
+
convertOperator(op) {
|
|
9868
|
+
const mapping = {
|
|
9869
|
+
"$eq": "eq",
|
|
9870
|
+
"$ne": "neq",
|
|
9871
|
+
"$neq": "neq",
|
|
9872
|
+
"$gt": "gt",
|
|
9873
|
+
"$gte": "gte",
|
|
9874
|
+
"$lt": "lt",
|
|
9875
|
+
"$lte": "lte"
|
|
9876
|
+
};
|
|
9877
|
+
return mapping[op] || null;
|
|
9878
|
+
}
|
|
9628
9879
|
finalizeClusterQuery(requestId, timeout = false) {
|
|
9629
9880
|
const pending = this.pendingClusterQueries.get(requestId);
|
|
9630
9881
|
if (!pending) return;
|
|
@@ -11555,6 +11806,255 @@ var MapWithResolver = class {
|
|
|
11555
11806
|
return this.map.prune(olderThan);
|
|
11556
11807
|
}
|
|
11557
11808
|
};
|
|
11809
|
+
|
|
11810
|
+
// src/config/IndexConfig.ts
|
|
11811
|
+
var DEFAULT_INDEX_CONFIG = {
|
|
11812
|
+
autoIndex: false,
|
|
11813
|
+
maxAutoIndexesPerMap: 10,
|
|
11814
|
+
maps: [],
|
|
11815
|
+
logStats: false,
|
|
11816
|
+
statsLogInterval: 6e4
|
|
11817
|
+
};
|
|
11818
|
+
function validateIndexConfig(config) {
|
|
11819
|
+
const errors = [];
|
|
11820
|
+
if (config.maxAutoIndexesPerMap !== void 0) {
|
|
11821
|
+
if (typeof config.maxAutoIndexesPerMap !== "number" || config.maxAutoIndexesPerMap < 1) {
|
|
11822
|
+
errors.push("maxAutoIndexesPerMap must be a positive number");
|
|
11823
|
+
}
|
|
11824
|
+
}
|
|
11825
|
+
if (config.statsLogInterval !== void 0) {
|
|
11826
|
+
if (typeof config.statsLogInterval !== "number" || config.statsLogInterval < 1e3) {
|
|
11827
|
+
errors.push("statsLogInterval must be at least 1000ms");
|
|
11828
|
+
}
|
|
11829
|
+
}
|
|
11830
|
+
if (config.maps) {
|
|
11831
|
+
const mapNames = /* @__PURE__ */ new Set();
|
|
11832
|
+
for (const mapConfig of config.maps) {
|
|
11833
|
+
if (!mapConfig.mapName || typeof mapConfig.mapName !== "string") {
|
|
11834
|
+
errors.push("Each map config must have a valid mapName");
|
|
11835
|
+
continue;
|
|
11836
|
+
}
|
|
11837
|
+
if (mapNames.has(mapConfig.mapName)) {
|
|
11838
|
+
errors.push(`Duplicate map config for: ${mapConfig.mapName}`);
|
|
11839
|
+
}
|
|
11840
|
+
mapNames.add(mapConfig.mapName);
|
|
11841
|
+
if (!Array.isArray(mapConfig.indexes)) {
|
|
11842
|
+
errors.push(`Map ${mapConfig.mapName}: indexes must be an array`);
|
|
11843
|
+
continue;
|
|
11844
|
+
}
|
|
11845
|
+
const attrNames = /* @__PURE__ */ new Set();
|
|
11846
|
+
for (const indexDef of mapConfig.indexes) {
|
|
11847
|
+
if (!indexDef.attribute || typeof indexDef.attribute !== "string") {
|
|
11848
|
+
errors.push(`Map ${mapConfig.mapName}: index must have valid attribute`);
|
|
11849
|
+
continue;
|
|
11850
|
+
}
|
|
11851
|
+
if (!["hash", "navigable"].includes(indexDef.type)) {
|
|
11852
|
+
errors.push(
|
|
11853
|
+
`Map ${mapConfig.mapName}: index type must be 'hash' or 'navigable'`
|
|
11854
|
+
);
|
|
11855
|
+
}
|
|
11856
|
+
if (indexDef.comparator && !["number", "string", "date"].includes(indexDef.comparator)) {
|
|
11857
|
+
errors.push(
|
|
11858
|
+
`Map ${mapConfig.mapName}: comparator must be 'number', 'string', or 'date'`
|
|
11859
|
+
);
|
|
11860
|
+
}
|
|
11861
|
+
const key = `${indexDef.attribute}:${indexDef.type}`;
|
|
11862
|
+
if (attrNames.has(key)) {
|
|
11863
|
+
errors.push(
|
|
11864
|
+
`Map ${mapConfig.mapName}: duplicate ${indexDef.type} index on ${indexDef.attribute}`
|
|
11865
|
+
);
|
|
11866
|
+
}
|
|
11867
|
+
attrNames.add(key);
|
|
11868
|
+
}
|
|
11869
|
+
}
|
|
11870
|
+
}
|
|
11871
|
+
return errors;
|
|
11872
|
+
}
|
|
11873
|
+
function mergeWithDefaults(userConfig) {
|
|
11874
|
+
return {
|
|
11875
|
+
...DEFAULT_INDEX_CONFIG,
|
|
11876
|
+
...userConfig,
|
|
11877
|
+
maps: userConfig.maps ?? DEFAULT_INDEX_CONFIG.maps
|
|
11878
|
+
};
|
|
11879
|
+
}
|
|
11880
|
+
|
|
11881
|
+
// src/config/MapFactory.ts
|
|
11882
|
+
import {
|
|
11883
|
+
LWWMap as LWWMap5,
|
|
11884
|
+
ORMap as ORMap3,
|
|
11885
|
+
IndexedLWWMap as IndexedLWWMap3,
|
|
11886
|
+
IndexedORMap as IndexedORMap3,
|
|
11887
|
+
simpleAttribute
|
|
11888
|
+
} from "@topgunbuild/core";
|
|
11889
|
+
var MapFactory = class {
|
|
11890
|
+
/**
|
|
11891
|
+
* Create a MapFactory.
|
|
11892
|
+
*
|
|
11893
|
+
* @param config - Server index configuration
|
|
11894
|
+
*/
|
|
11895
|
+
constructor(config) {
|
|
11896
|
+
this.config = mergeWithDefaults(config ?? {});
|
|
11897
|
+
this.mapConfigs = /* @__PURE__ */ new Map();
|
|
11898
|
+
for (const mapConfig of this.config.maps ?? []) {
|
|
11899
|
+
this.mapConfigs.set(mapConfig.mapName, mapConfig);
|
|
11900
|
+
}
|
|
11901
|
+
}
|
|
11902
|
+
/**
|
|
11903
|
+
* Create an LWWMap or IndexedLWWMap based on configuration.
|
|
11904
|
+
*
|
|
11905
|
+
* @param mapName - Name of the map
|
|
11906
|
+
* @param hlc - Hybrid Logical Clock instance
|
|
11907
|
+
* @returns LWWMap or IndexedLWWMap depending on configuration
|
|
11908
|
+
*/
|
|
11909
|
+
createLWWMap(mapName, hlc) {
|
|
11910
|
+
const mapConfig = this.mapConfigs.get(mapName);
|
|
11911
|
+
if (!mapConfig || mapConfig.indexes.length === 0) {
|
|
11912
|
+
return new LWWMap5(hlc);
|
|
11913
|
+
}
|
|
11914
|
+
const map = new IndexedLWWMap3(hlc);
|
|
11915
|
+
for (const indexDef of mapConfig.indexes) {
|
|
11916
|
+
this.addIndexToLWWMap(map, indexDef);
|
|
11917
|
+
}
|
|
11918
|
+
return map;
|
|
11919
|
+
}
|
|
11920
|
+
/**
|
|
11921
|
+
* Create an ORMap or IndexedORMap based on configuration.
|
|
11922
|
+
*
|
|
11923
|
+
* @param mapName - Name of the map
|
|
11924
|
+
* @param hlc - Hybrid Logical Clock instance
|
|
11925
|
+
* @returns ORMap or IndexedORMap depending on configuration
|
|
11926
|
+
*/
|
|
11927
|
+
createORMap(mapName, hlc) {
|
|
11928
|
+
const mapConfig = this.mapConfigs.get(mapName);
|
|
11929
|
+
if (!mapConfig || mapConfig.indexes.length === 0) {
|
|
11930
|
+
return new ORMap3(hlc);
|
|
11931
|
+
}
|
|
11932
|
+
const map = new IndexedORMap3(hlc);
|
|
11933
|
+
for (const indexDef of mapConfig.indexes) {
|
|
11934
|
+
this.addIndexToORMap(map, indexDef);
|
|
11935
|
+
}
|
|
11936
|
+
return map;
|
|
11937
|
+
}
|
|
11938
|
+
/**
|
|
11939
|
+
* Add an index to an IndexedLWWMap based on definition.
|
|
11940
|
+
*/
|
|
11941
|
+
addIndexToLWWMap(map, indexDef) {
|
|
11942
|
+
const attribute = this.createAttribute(indexDef.attribute);
|
|
11943
|
+
if (indexDef.type === "hash") {
|
|
11944
|
+
map.addHashIndex(attribute);
|
|
11945
|
+
} else if (indexDef.type === "navigable") {
|
|
11946
|
+
const navAttribute = attribute;
|
|
11947
|
+
const comparator = this.createComparator(indexDef.comparator);
|
|
11948
|
+
map.addNavigableIndex(navAttribute, comparator);
|
|
11949
|
+
}
|
|
11950
|
+
}
|
|
11951
|
+
/**
|
|
11952
|
+
* Add an index to an IndexedORMap based on definition.
|
|
11953
|
+
*/
|
|
11954
|
+
addIndexToORMap(map, indexDef) {
|
|
11955
|
+
const attribute = this.createAttribute(indexDef.attribute);
|
|
11956
|
+
if (indexDef.type === "hash") {
|
|
11957
|
+
map.addHashIndex(attribute);
|
|
11958
|
+
} else if (indexDef.type === "navigable") {
|
|
11959
|
+
const navAttribute = attribute;
|
|
11960
|
+
const comparator = this.createComparator(indexDef.comparator);
|
|
11961
|
+
map.addNavigableIndex(navAttribute, comparator);
|
|
11962
|
+
}
|
|
11963
|
+
}
|
|
11964
|
+
/**
|
|
11965
|
+
* Create an Attribute for extracting values from records.
|
|
11966
|
+
* Supports dot notation for nested paths.
|
|
11967
|
+
*/
|
|
11968
|
+
createAttribute(path) {
|
|
11969
|
+
return simpleAttribute(path, (record) => {
|
|
11970
|
+
return this.getNestedValue(record, path);
|
|
11971
|
+
});
|
|
11972
|
+
}
|
|
11973
|
+
/**
|
|
11974
|
+
* Get a nested value from an object using dot notation.
|
|
11975
|
+
*
|
|
11976
|
+
* @param obj - Object to extract value from
|
|
11977
|
+
* @param path - Dot-notation path (e.g., "user.email")
|
|
11978
|
+
* @returns Value at the path or undefined
|
|
11979
|
+
*/
|
|
11980
|
+
getNestedValue(obj, path) {
|
|
11981
|
+
if (obj === null || obj === void 0) {
|
|
11982
|
+
return void 0;
|
|
11983
|
+
}
|
|
11984
|
+
const parts = path.split(".");
|
|
11985
|
+
let current = obj;
|
|
11986
|
+
for (const part of parts) {
|
|
11987
|
+
if (current === void 0 || current === null) {
|
|
11988
|
+
return void 0;
|
|
11989
|
+
}
|
|
11990
|
+
if (typeof current !== "object") {
|
|
11991
|
+
return void 0;
|
|
11992
|
+
}
|
|
11993
|
+
current = current[part];
|
|
11994
|
+
}
|
|
11995
|
+
return current;
|
|
11996
|
+
}
|
|
11997
|
+
/**
|
|
11998
|
+
* Create a comparator function for navigable indexes.
|
|
11999
|
+
*/
|
|
12000
|
+
createComparator(type) {
|
|
12001
|
+
switch (type) {
|
|
12002
|
+
case "number":
|
|
12003
|
+
return (a, b) => {
|
|
12004
|
+
const numA = typeof a === "number" ? a : parseFloat(String(a));
|
|
12005
|
+
const numB = typeof b === "number" ? b : parseFloat(String(b));
|
|
12006
|
+
return numA - numB;
|
|
12007
|
+
};
|
|
12008
|
+
case "date":
|
|
12009
|
+
return (a, b) => {
|
|
12010
|
+
const dateA = new Date(a).getTime();
|
|
12011
|
+
const dateB = new Date(b).getTime();
|
|
12012
|
+
return dateA - dateB;
|
|
12013
|
+
};
|
|
12014
|
+
case "string":
|
|
12015
|
+
return (a, b) => {
|
|
12016
|
+
const strA = String(a);
|
|
12017
|
+
const strB = String(b);
|
|
12018
|
+
return strA.localeCompare(strB);
|
|
12019
|
+
};
|
|
12020
|
+
default:
|
|
12021
|
+
return void 0;
|
|
12022
|
+
}
|
|
12023
|
+
}
|
|
12024
|
+
/**
|
|
12025
|
+
* Check if a map should be indexed based on configuration.
|
|
12026
|
+
*
|
|
12027
|
+
* @param mapName - Name of the map
|
|
12028
|
+
* @returns true if map has index configuration
|
|
12029
|
+
*/
|
|
12030
|
+
hasIndexConfig(mapName) {
|
|
12031
|
+
const config = this.mapConfigs.get(mapName);
|
|
12032
|
+
return config !== void 0 && config.indexes.length > 0;
|
|
12033
|
+
}
|
|
12034
|
+
/**
|
|
12035
|
+
* Get index configuration for a map.
|
|
12036
|
+
*
|
|
12037
|
+
* @param mapName - Name of the map
|
|
12038
|
+
* @returns Map index config or undefined
|
|
12039
|
+
*/
|
|
12040
|
+
getMapConfig(mapName) {
|
|
12041
|
+
return this.mapConfigs.get(mapName);
|
|
12042
|
+
}
|
|
12043
|
+
/**
|
|
12044
|
+
* Get all configured map names.
|
|
12045
|
+
*
|
|
12046
|
+
* @returns Array of map names with index configuration
|
|
12047
|
+
*/
|
|
12048
|
+
getConfiguredMaps() {
|
|
12049
|
+
return Array.from(this.mapConfigs.keys());
|
|
12050
|
+
}
|
|
12051
|
+
/**
|
|
12052
|
+
* Get the full server index configuration.
|
|
12053
|
+
*/
|
|
12054
|
+
getConfig() {
|
|
12055
|
+
return this.config;
|
|
12056
|
+
}
|
|
12057
|
+
};
|
|
11558
12058
|
export {
|
|
11559
12059
|
BufferPool,
|
|
11560
12060
|
ClusterCoordinator,
|
|
@@ -11564,6 +12064,7 @@ export {
|
|
|
11564
12064
|
ConnectionRateLimiter,
|
|
11565
12065
|
DEFAULT_CLUSTER_COORDINATOR_CONFIG,
|
|
11566
12066
|
DEFAULT_CONFLICT_RESOLVER_CONFIG,
|
|
12067
|
+
DEFAULT_INDEX_CONFIG,
|
|
11567
12068
|
DEFAULT_JOURNAL_SERVICE_CONFIG,
|
|
11568
12069
|
DEFAULT_LAG_TRACKER_CONFIG,
|
|
11569
12070
|
DEFAULT_SANDBOX_CONFIG,
|
|
@@ -11574,6 +12075,7 @@ export {
|
|
|
11574
12075
|
IteratorTasklet,
|
|
11575
12076
|
LagTracker,
|
|
11576
12077
|
LockManager,
|
|
12078
|
+
MapFactory,
|
|
11577
12079
|
MapTasklet,
|
|
11578
12080
|
MapWithResolver,
|
|
11579
12081
|
MemoryServerAdapter,
|
|
@@ -11604,10 +12106,12 @@ export {
|
|
|
11604
12106
|
getNativeStats,
|
|
11605
12107
|
logNativeStatus,
|
|
11606
12108
|
logger,
|
|
12109
|
+
mergeWithDefaults,
|
|
11607
12110
|
setGlobalBufferPool,
|
|
11608
12111
|
setGlobalEventPayloadPool,
|
|
11609
12112
|
setGlobalMessagePool,
|
|
11610
12113
|
setGlobalRecordPool,
|
|
11611
|
-
setGlobalTimestampPool
|
|
12114
|
+
setGlobalTimestampPool,
|
|
12115
|
+
validateIndexConfig
|
|
11612
12116
|
};
|
|
11613
12117
|
//# sourceMappingURL=index.mjs.map
|