@rljson/io 0.0.69 → 0.0.71
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/README.architecture.md +25 -10
- package/README.public.md +5 -0
- package/dist/README.architecture.md +25 -10
- package/dist/README.public.md +5 -0
- package/dist/io-mem.d.ts +78 -1
- package/dist/io-multi.d.ts +17 -0
- package/dist/io-peer.d.ts +15 -0
- package/dist/io.d.ts +11 -0
- package/dist/io.js +389 -23
- package/dist/io.js.map +1 -1
- package/package.json +1 -1
package/dist/io.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { hip, hsh } from "@rljson/hash";
|
|
2
2
|
import { IsReady } from "@rljson/is-ready";
|
|
3
|
-
import {
|
|
3
|
+
import { equals, copy, merge } from "@rljson/json";
|
|
4
4
|
import { iterateTables, throwOnInvalidTableCfg, validateRljsonAgainstTableCfg, iterateTablesSync } from "@rljson/rljson";
|
|
5
5
|
class IoDbNameMapping {
|
|
6
6
|
// The primary key column is always named '_hash'
|
|
@@ -411,6 +411,27 @@ class IoMem {
|
|
|
411
411
|
readRows(request) {
|
|
412
412
|
return this._readRows(request);
|
|
413
413
|
}
|
|
414
|
+
async readRowsByHashes(request) {
|
|
415
|
+
await this._ioTools.throwWhenTableDoesNotExist(request.table);
|
|
416
|
+
const table = this._mem[request.table];
|
|
417
|
+
const rowIndex = this._rowIndexFor(request.table);
|
|
418
|
+
const seen = /* @__PURE__ */ new Set();
|
|
419
|
+
const rows = [];
|
|
420
|
+
for (const hash of request.hashes) {
|
|
421
|
+
if (seen.has(hash)) continue;
|
|
422
|
+
seen.add(hash);
|
|
423
|
+
const row = rowIndex.get(hash);
|
|
424
|
+
if (row) {
|
|
425
|
+
rows.push(row);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const tableFiltered = {
|
|
429
|
+
_type: table._type,
|
|
430
|
+
_data: rows
|
|
431
|
+
};
|
|
432
|
+
this._ioTools.sortTableDataAndUpdateHash(tableFiltered);
|
|
433
|
+
return { [request.table]: tableFiltered };
|
|
434
|
+
}
|
|
414
435
|
async rowCount(table) {
|
|
415
436
|
const tableData = this._mem[table];
|
|
416
437
|
if (!tableData) {
|
|
@@ -443,8 +464,182 @@ class IoMem {
|
|
|
443
464
|
_isReady = new IsReady();
|
|
444
465
|
_isOpen = false;
|
|
445
466
|
_mem = hip({});
|
|
467
|
+
/**
|
|
468
|
+
* Latest table configuration per table (the one with the most
|
|
469
|
+
* columns). Kept in sync by _createTable/_extendTable so that reads
|
|
470
|
+
* and writes need no repeated scan over all configurations.
|
|
471
|
+
*/
|
|
472
|
+
_latestCfgs = /* @__PURE__ */ new Map();
|
|
473
|
+
/** Column key sets per table, derived from _latestCfgs */
|
|
474
|
+
_columnKeys = /* @__PURE__ */ new Map();
|
|
475
|
+
/**
|
|
476
|
+
* Per-table index of rows by content hash. Rows are only ever added,
|
|
477
|
+
* never removed, so the index cannot go stale.
|
|
478
|
+
*/
|
|
479
|
+
_rowIndex = /* @__PURE__ */ new Map();
|
|
480
|
+
/**
|
|
481
|
+
* Tables whose table hash (and thus the global hash) is outdated
|
|
482
|
+
* after writes. Hashes are refreshed lazily before they become
|
|
483
|
+
* observable (dump, dumpTable, create/extend) instead of after every
|
|
484
|
+
* single write — row hashes themselves are always up to date.
|
|
485
|
+
*/
|
|
486
|
+
_dirtyTableHashes = /* @__PURE__ */ new Set();
|
|
487
|
+
// ...........................................................................
|
|
488
|
+
/**
|
|
489
|
+
* Recomputes the hashes of all dirty tables and the global hash.
|
|
490
|
+
* Produces exactly the state an eager per-write update would have
|
|
491
|
+
* produced (hashes are deterministic over the same data).
|
|
492
|
+
*/
|
|
493
|
+
_refreshHashes() {
|
|
494
|
+
if (this._dirtyTableHashes.size === 0) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
for (const tableKey of this._dirtyTableHashes) {
|
|
498
|
+
const table = this._mem[tableKey];
|
|
499
|
+
table._hash = "";
|
|
500
|
+
hip(table, {
|
|
501
|
+
updateExistingHashes: false,
|
|
502
|
+
throwOnWrongHashes: false
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
this._dirtyTableHashes.clear();
|
|
506
|
+
this._updateGlobalHash();
|
|
507
|
+
}
|
|
508
|
+
// ...........................................................................
|
|
509
|
+
/**
|
|
510
|
+
* Returns the row index of a table, building it lazily from the
|
|
511
|
+
* table data on first access.
|
|
512
|
+
* @param table - The table to index
|
|
513
|
+
*/
|
|
514
|
+
_rowIndexFor(table) {
|
|
515
|
+
let index = this._rowIndex.get(table);
|
|
516
|
+
if (!index) {
|
|
517
|
+
index = /* @__PURE__ */ new Map();
|
|
518
|
+
const tableData = this._mem[table]._data;
|
|
519
|
+
for (const row of tableData) {
|
|
520
|
+
index.set(row._hash, row);
|
|
521
|
+
}
|
|
522
|
+
this._rowIndex.set(table, index);
|
|
523
|
+
}
|
|
524
|
+
return index;
|
|
525
|
+
}
|
|
526
|
+
// ...........................................................................
|
|
527
|
+
/**
|
|
528
|
+
* Inserts a row into hash-sorted table data at its sorted position —
|
|
529
|
+
* preserves the order sortTableDataAndUpdateHash establishes without
|
|
530
|
+
* a full re-sort.
|
|
531
|
+
* @param data - The hash-sorted table data
|
|
532
|
+
* @param row - The row to insert
|
|
533
|
+
*/
|
|
534
|
+
static _insertSortedByHash(data, row) {
|
|
535
|
+
const hash = row._hash;
|
|
536
|
+
let lo = 0;
|
|
537
|
+
let hi = data.length;
|
|
538
|
+
while (lo < hi) {
|
|
539
|
+
const mid = lo + hi >> 1;
|
|
540
|
+
if (data[mid]._hash < hash) {
|
|
541
|
+
lo = mid + 1;
|
|
542
|
+
} else {
|
|
543
|
+
hi = mid;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
data.splice(lo, 0, row);
|
|
547
|
+
}
|
|
548
|
+
// ...........................................................................
|
|
549
|
+
/**
|
|
550
|
+
* The row filter predicate of readRows. Extracted so that the indexed
|
|
551
|
+
* fast path and the full scan share identical semantics.
|
|
552
|
+
* @param row - The row to check
|
|
553
|
+
* @param where - The where clause
|
|
554
|
+
*/
|
|
555
|
+
static _rowMatchesWhere(row, where) {
|
|
556
|
+
for (const column in where) {
|
|
557
|
+
const a = row[column];
|
|
558
|
+
const b = where[column];
|
|
559
|
+
if (b === null && a === void 0) {
|
|
560
|
+
return true;
|
|
561
|
+
}
|
|
562
|
+
if (!equals(a, b)) {
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
// ...........................................................................
|
|
569
|
+
/**
|
|
570
|
+
* Returns the latest table configuration, filling the cache lazily
|
|
571
|
+
* from IoTools (which picks the config with the most columns).
|
|
572
|
+
* @param table - The table to get the configuration for
|
|
573
|
+
*/
|
|
574
|
+
async _latestCfg(table) {
|
|
575
|
+
let cfg = this._latestCfgs.get(table);
|
|
576
|
+
if (!cfg) {
|
|
577
|
+
cfg = await this._ioTools.tableCfg(table);
|
|
578
|
+
this._latestCfgs.set(table, cfg);
|
|
579
|
+
}
|
|
580
|
+
return cfg;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Updates the cached latest configuration of a table
|
|
584
|
+
* @param cfg - The new latest configuration
|
|
585
|
+
*/
|
|
586
|
+
_setLatestCfg(cfg) {
|
|
587
|
+
this._latestCfgs.set(cfg.key, cfg);
|
|
588
|
+
this._columnKeys.set(cfg.key, new Set(cfg.columns.map((c) => c.key)));
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Throws when one of the given columns does not exist in the table.
|
|
592
|
+
* Mirrors IoTools.throwWhenColumnDoesNotExist but uses the cached
|
|
593
|
+
* configuration.
|
|
594
|
+
* @param table - The table to check
|
|
595
|
+
* @param columns - The columns to check
|
|
596
|
+
*/
|
|
597
|
+
async _throwWhenColumnDoesNotExist(table, columns) {
|
|
598
|
+
let columnKeys = this._columnKeys.get(table);
|
|
599
|
+
if (!columnKeys) {
|
|
600
|
+
const cfg = await this._latestCfg(table);
|
|
601
|
+
columnKeys = new Set(cfg.columns.map((c) => c.key));
|
|
602
|
+
this._columnKeys.set(table, columnKeys);
|
|
603
|
+
}
|
|
604
|
+
const missingColumns = columns.filter((column) => !columnKeys.has(column));
|
|
605
|
+
if (missingColumns.length > 0) {
|
|
606
|
+
throw new Error(
|
|
607
|
+
`The following columns do not exist in table "${table}": ${missingColumns.join(
|
|
608
|
+
", "
|
|
609
|
+
)}.`
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Throws when the data does not match the table configurations.
|
|
615
|
+
* Mirrors IoTools.throwWhenTableDataDoesNotMatchCfg but uses the
|
|
616
|
+
* cached configurations.
|
|
617
|
+
* @param data - The data to validate
|
|
618
|
+
*/
|
|
619
|
+
async _throwWhenTableDataDoesNotMatchCfg(data) {
|
|
620
|
+
const errors = [];
|
|
621
|
+
for (const tableKey of Object.keys(data)) {
|
|
622
|
+
const table = data[tableKey];
|
|
623
|
+
if (typeof table !== "object" || !Array.isArray(table?._data)) continue;
|
|
624
|
+
if (table._type === "tableCfgs") continue;
|
|
625
|
+
const tableCfg = await this._latestCfg(tableKey);
|
|
626
|
+
errors.push(...validateRljsonAgainstTableCfg(table._data, tableCfg));
|
|
627
|
+
}
|
|
628
|
+
if (errors.length > 0) {
|
|
629
|
+
throw new Error(
|
|
630
|
+
`Table data does not match the configuration.
|
|
631
|
+
|
|
632
|
+
Errors:
|
|
633
|
+
${errors.map((e) => `- ${e}`).join("\n")}`
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
446
637
|
// ...........................................................................
|
|
447
638
|
async _init() {
|
|
639
|
+
this._refreshHashes();
|
|
640
|
+
this._rowIndex.clear();
|
|
641
|
+
this._latestCfgs.clear();
|
|
642
|
+
this._columnKeys.clear();
|
|
448
643
|
this._ioTools = new IoTools(this);
|
|
449
644
|
this._initTableCfgs();
|
|
450
645
|
this._updateGlobalHash();
|
|
@@ -476,6 +671,7 @@ class IoMem {
|
|
|
476
671
|
}
|
|
477
672
|
// ...........................................................................
|
|
478
673
|
async _createOrExtendTable(request) {
|
|
674
|
+
this._refreshHashes();
|
|
479
675
|
const tableCfg = request.tableCfg;
|
|
480
676
|
await this._ioTools.throwWhenTableIsNotCompatible(tableCfg);
|
|
481
677
|
const { key } = tableCfg;
|
|
@@ -492,6 +688,8 @@ class IoMem {
|
|
|
492
688
|
newConfig = hsh(newConfig);
|
|
493
689
|
this._mem.tableCfgs._data.push(newConfig);
|
|
494
690
|
this._ioTools.sortTableDataAndUpdateHash(this._mem.tableCfgs);
|
|
691
|
+
this._setLatestCfg(newConfig);
|
|
692
|
+
this._rowIndex.get("tableCfgs")?.set(newConfig._hash, newConfig);
|
|
495
693
|
const table = {
|
|
496
694
|
_type: newConfig.type,
|
|
497
695
|
_data: [],
|
|
@@ -509,6 +707,8 @@ class IoMem {
|
|
|
509
707
|
newConfig = hsh(newConfig);
|
|
510
708
|
this._mem.tableCfgs._data.push(newConfig);
|
|
511
709
|
this._ioTools.sortTableDataAndUpdateHash(this._mem.tableCfgs);
|
|
710
|
+
this._setLatestCfg(newConfig);
|
|
711
|
+
this._rowIndex.get("tableCfgs")?.set(newConfig._hash, newConfig);
|
|
512
712
|
const table = this._mem[newConfig.key];
|
|
513
713
|
table._tableCfg = newConfig._hash;
|
|
514
714
|
this._updateTableHash(newConfig.key);
|
|
@@ -516,11 +716,13 @@ class IoMem {
|
|
|
516
716
|
}
|
|
517
717
|
// ...........................................................................
|
|
518
718
|
async _dump() {
|
|
719
|
+
this._refreshHashes();
|
|
519
720
|
return copy(this._mem);
|
|
520
721
|
}
|
|
521
722
|
// ...........................................................................
|
|
522
723
|
async _dumpTable(request) {
|
|
523
724
|
await this._ioTools.throwWhenTableDoesNotExist(request.table);
|
|
725
|
+
this._refreshHashes();
|
|
524
726
|
const table = this._mem[request.table];
|
|
525
727
|
return {
|
|
526
728
|
[request.table]: copy(table)
|
|
@@ -534,49 +736,48 @@ class IoMem {
|
|
|
534
736
|
// ...........................................................................
|
|
535
737
|
async _write(request) {
|
|
536
738
|
const addedData = hsh(request.data);
|
|
537
|
-
this._removeNullValues(addedData);
|
|
739
|
+
const removedNullValues = this._removeNullValues(addedData);
|
|
538
740
|
const tables = Object.keys(addedData);
|
|
539
|
-
|
|
741
|
+
if (removedNullValues) {
|
|
742
|
+
hsh(addedData);
|
|
743
|
+
}
|
|
540
744
|
await this._ioTools.throwWhenTablesDoNotExist(request.data);
|
|
541
|
-
await this.
|
|
745
|
+
await this._throwWhenTableDataDoesNotMatchCfg(request.data);
|
|
542
746
|
for (const table of tables) {
|
|
543
747
|
if (table.startsWith("_")) {
|
|
544
748
|
continue;
|
|
545
749
|
}
|
|
546
750
|
const oldTable = this._mem[table];
|
|
547
751
|
const newTable = addedData[table];
|
|
752
|
+
const rowIndex = this._rowIndexFor(table);
|
|
548
753
|
for (const item of newTable._data) {
|
|
549
754
|
const hash = item._hash;
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
oldTable._data
|
|
755
|
+
if (!rowIndex.has(hash)) {
|
|
756
|
+
rowIndex.set(hash, item);
|
|
757
|
+
IoMem._insertSortedByHash(oldTable._data, item);
|
|
553
758
|
}
|
|
554
759
|
}
|
|
555
|
-
this.
|
|
760
|
+
this._dirtyTableHashes.add(table);
|
|
556
761
|
}
|
|
557
|
-
this._updateGlobalHash();
|
|
558
762
|
}
|
|
559
763
|
// ...........................................................................
|
|
560
764
|
async _readRows(request) {
|
|
561
765
|
await this._ioTools.throwWhenTableDoesNotExist(request.table);
|
|
562
|
-
await this.
|
|
766
|
+
await this._throwWhenColumnDoesNotExist(
|
|
563
767
|
request.table,
|
|
564
768
|
Object.keys(request.where)
|
|
565
769
|
);
|
|
566
770
|
const table = this._mem[request.table];
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
}
|
|
578
|
-
return true;
|
|
579
|
-
});
|
|
771
|
+
const whereHash = request.where["_hash"];
|
|
772
|
+
let tableDataFiltered;
|
|
773
|
+
if (typeof whereHash === "string") {
|
|
774
|
+
const row = this._rowIndexFor(request.table).get(whereHash);
|
|
775
|
+
tableDataFiltered = row && IoMem._rowMatchesWhere(row, request.where) ? [row] : [];
|
|
776
|
+
} else {
|
|
777
|
+
tableDataFiltered = table._data.filter(
|
|
778
|
+
(row) => IoMem._rowMatchesWhere(row, request.where)
|
|
779
|
+
);
|
|
780
|
+
}
|
|
580
781
|
const tableFiltered = {
|
|
581
782
|
_type: table._type,
|
|
582
783
|
_data: tableDataFiltered
|
|
@@ -588,16 +789,19 @@ class IoMem {
|
|
|
588
789
|
return result;
|
|
589
790
|
}
|
|
590
791
|
_removeNullValues(rljson) {
|
|
792
|
+
let removedAny = false;
|
|
591
793
|
iterateTablesSync(rljson, (table) => {
|
|
592
794
|
const data = rljson[table]._data;
|
|
593
795
|
for (const row of data) {
|
|
594
796
|
for (const key in row) {
|
|
595
797
|
if (row[key] === null) {
|
|
596
798
|
delete row[key];
|
|
799
|
+
removedAny = true;
|
|
597
800
|
}
|
|
598
801
|
}
|
|
599
802
|
}
|
|
600
803
|
});
|
|
804
|
+
return removedAny;
|
|
601
805
|
}
|
|
602
806
|
}
|
|
603
807
|
class PeerSocketMock {
|
|
@@ -925,6 +1129,65 @@ class IoPeer {
|
|
|
925
1129
|
);
|
|
926
1130
|
}
|
|
927
1131
|
// ...........................................................................
|
|
1132
|
+
/**
|
|
1133
|
+
* True once the remote side signalled that it does not support batch
|
|
1134
|
+
* reads — all further batch reads then use per-hash readRows.
|
|
1135
|
+
*/
|
|
1136
|
+
_batchReadsUnsupported = false;
|
|
1137
|
+
/**
|
|
1138
|
+
* Batch read over the socket. Falls back to per-hash readRows when
|
|
1139
|
+
* the remote side does not support it (older server) and remembers
|
|
1140
|
+
* the capability for subsequent calls.
|
|
1141
|
+
* @param request - The table and the row hashes to read
|
|
1142
|
+
*/
|
|
1143
|
+
async readRowsByHashes(request) {
|
|
1144
|
+
if (!this._batchReadsUnsupported) {
|
|
1145
|
+
try {
|
|
1146
|
+
return await this._withTimeout(
|
|
1147
|
+
new Promise((resolve, reject) => {
|
|
1148
|
+
this._socket.emit(
|
|
1149
|
+
"readRowsByHashes",
|
|
1150
|
+
request,
|
|
1151
|
+
(result, error) => {
|
|
1152
|
+
if (error) reject(error);
|
|
1153
|
+
resolve(result);
|
|
1154
|
+
}
|
|
1155
|
+
);
|
|
1156
|
+
}),
|
|
1157
|
+
"readRowsByHashes"
|
|
1158
|
+
);
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
const message = String(error.message);
|
|
1161
|
+
const unsupported = message.includes("not found on Io instance") || message.includes("not supported") || message.includes("Timeout after");
|
|
1162
|
+
if (!unsupported) {
|
|
1163
|
+
throw error;
|
|
1164
|
+
}
|
|
1165
|
+
this._batchReadsUnsupported = true;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
const hashes = Array.from(new Set(request.hashes));
|
|
1169
|
+
const results = await Promise.all(
|
|
1170
|
+
hashes.map(
|
|
1171
|
+
(hash) => this.readRows({ table: request.table, where: { _hash: hash } })
|
|
1172
|
+
)
|
|
1173
|
+
);
|
|
1174
|
+
let type = void 0;
|
|
1175
|
+
const rows = [];
|
|
1176
|
+
for (const result of results) {
|
|
1177
|
+
const tableData = result[request.table];
|
|
1178
|
+
type ??= tableData._type;
|
|
1179
|
+
rows.push(...tableData._data);
|
|
1180
|
+
}
|
|
1181
|
+
if (type === void 0) {
|
|
1182
|
+
const empty = await this.readRows({
|
|
1183
|
+
table: request.table,
|
|
1184
|
+
where: { _hash: "__NONE__" }
|
|
1185
|
+
});
|
|
1186
|
+
type = empty[request.table]._type;
|
|
1187
|
+
}
|
|
1188
|
+
return { [request.table]: { _data: rows, _type: type } };
|
|
1189
|
+
}
|
|
1190
|
+
// ...........................................................................
|
|
928
1191
|
/**
|
|
929
1192
|
* Retrieves the number of rows in a specific table.
|
|
930
1193
|
* @param table The name of the table to count rows in.
|
|
@@ -1265,6 +1528,103 @@ class IoMulti {
|
|
|
1265
1528
|
return Promise.resolve(tableData._data.length);
|
|
1266
1529
|
}
|
|
1267
1530
|
// ...........................................................................
|
|
1531
|
+
/**
|
|
1532
|
+
* Batch read with PER-HASH cascade: every hash not found in a
|
|
1533
|
+
* higher-priority readable is looked up in the next one. Readables
|
|
1534
|
+
* without readRowsByHashes are queried per hash via readRows.
|
|
1535
|
+
* @param request - The table and the row hashes to read
|
|
1536
|
+
*/
|
|
1537
|
+
async readRowsByHashes(request) {
|
|
1538
|
+
if (this.readables.length === 0) {
|
|
1539
|
+
throw new Error("No readable Io available");
|
|
1540
|
+
}
|
|
1541
|
+
let tableExistsAny = false;
|
|
1542
|
+
const rows = /* @__PURE__ */ new Map();
|
|
1543
|
+
let type = void 0;
|
|
1544
|
+
let readFrom = "";
|
|
1545
|
+
const errors = [];
|
|
1546
|
+
let remaining = Array.from(new Set(request.hashes));
|
|
1547
|
+
for (const readable of this.readables) {
|
|
1548
|
+
if (remaining.length === 0) break;
|
|
1549
|
+
try {
|
|
1550
|
+
let result;
|
|
1551
|
+
if (readable.io.readRowsByHashes) {
|
|
1552
|
+
result = await readable.io.readRowsByHashes({
|
|
1553
|
+
table: request.table,
|
|
1554
|
+
hashes: remaining
|
|
1555
|
+
});
|
|
1556
|
+
} else {
|
|
1557
|
+
result = await IoMulti._readHashesViaReadRows(
|
|
1558
|
+
readable.io,
|
|
1559
|
+
request.table,
|
|
1560
|
+
remaining
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
const tableData = result[request.table];
|
|
1564
|
+
tableExistsAny = true;
|
|
1565
|
+
type = tableData._type;
|
|
1566
|
+
if (tableData._data.length > 0) {
|
|
1567
|
+
readFrom = readable.id ?? "";
|
|
1568
|
+
for (const tableRow of tableData._data) {
|
|
1569
|
+
rows.set(tableRow._hash, tableRow);
|
|
1570
|
+
}
|
|
1571
|
+
remaining = remaining.filter((hash) => !rows.has(hash));
|
|
1572
|
+
}
|
|
1573
|
+
} catch (e) {
|
|
1574
|
+
errors.push(e);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
if (!tableExistsAny) {
|
|
1578
|
+
if (errors.length === 0) {
|
|
1579
|
+
throw new Error(`Table "${request.table}" not found`);
|
|
1580
|
+
} else {
|
|
1581
|
+
const preciseErrors = errors.filter(
|
|
1582
|
+
(err) => !err.message.includes(`Table "${request.table}" not found`)
|
|
1583
|
+
);
|
|
1584
|
+
if (preciseErrors.length > 0) {
|
|
1585
|
+
throw preciseErrors[0];
|
|
1586
|
+
} else {
|
|
1587
|
+
throw errors[0];
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
const rljson = {
|
|
1592
|
+
[request.table]: hip({ _data: Array.from(rows.values()), _type: type })
|
|
1593
|
+
};
|
|
1594
|
+
if (this.writables.length > 0 && rows.size > 0) {
|
|
1595
|
+
for (const writeable of this.writables) {
|
|
1596
|
+
if (writeable.id === readFrom) {
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
try {
|
|
1600
|
+
await writeable.io.write({ data: rljson });
|
|
1601
|
+
} catch {
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
return rljson;
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Per-hash fallback for readables without readRowsByHashes.
|
|
1610
|
+
* @param io - The readable io
|
|
1611
|
+
* @param table - The table to read from
|
|
1612
|
+
* @param hashes - The row hashes to read
|
|
1613
|
+
*/
|
|
1614
|
+
static async _readHashesViaReadRows(io, table, hashes) {
|
|
1615
|
+
const results = await Promise.all(
|
|
1616
|
+
hashes.map((hash) => io.readRows({ table, where: { _hash: hash } }))
|
|
1617
|
+
);
|
|
1618
|
+
let type = void 0;
|
|
1619
|
+
const rows = [];
|
|
1620
|
+
for (const result of results) {
|
|
1621
|
+
const tableData = result[table];
|
|
1622
|
+
type ??= tableData._type;
|
|
1623
|
+
rows.push(...tableData._data);
|
|
1624
|
+
}
|
|
1625
|
+
return { [table]: { _data: rows, _type: type } };
|
|
1626
|
+
}
|
|
1627
|
+
// ...........................................................................
|
|
1268
1628
|
/**
|
|
1269
1629
|
* Gets the list of underlying readable Io instances, sorted by priority.
|
|
1270
1630
|
*/
|
|
@@ -1364,6 +1724,7 @@ class IoPeerBridge {
|
|
|
1364
1724
|
"createOrExtendTable",
|
|
1365
1725
|
"write",
|
|
1366
1726
|
"readRows",
|
|
1727
|
+
"readRowsByHashes",
|
|
1367
1728
|
"rowCount",
|
|
1368
1729
|
"dumpTable",
|
|
1369
1730
|
"dump",
|
|
@@ -1551,6 +1912,11 @@ class IoServer {
|
|
|
1551
1912
|
rawTableCfgs: () => this._io.rawTableCfgs(),
|
|
1552
1913
|
write: (request) => this._io.write(request),
|
|
1553
1914
|
readRows: (request) => this._io.readRows(request),
|
|
1915
|
+
readRowsByHashes: (request) => this._io.readRowsByHashes ? this._io.readRowsByHashes(request) : Promise.reject(
|
|
1916
|
+
new Error(
|
|
1917
|
+
'Method "readRowsByHashes" not found on Io instance'
|
|
1918
|
+
)
|
|
1919
|
+
),
|
|
1554
1920
|
rowCount: (table) => this._io.rowCount(table)
|
|
1555
1921
|
});
|
|
1556
1922
|
}
|