kitcn 0.18.0 → 0.20.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/aggregate/index.d.ts +7 -1
- package/dist/aggregate/index.js +1 -1
- package/dist/auth/generated/index.d.ts +1 -1
- package/dist/auth/index.d.ts +14 -14
- package/dist/auth/index.js +1 -1
- package/dist/cli.mjs +4 -2
- package/dist/{generated-contract-disabled-LOAfma7_.d.ts → generated-contract-disabled-BbPCefQM.d.ts} +12 -12
- package/dist/{local-env-DykABjCt.mjs → local-env-yFKub75x.mjs} +5 -1
- package/dist/orm/index.d.ts +1 -1
- package/dist/orm/index.js +548 -171
- package/dist/{query-context-CJE_hYA3.js → query-context-BzihIpnM.js} +208 -102
- package/dist/{runtime-DmVSOe24.js → runtime-6DJxpDfb.js} +73 -37
- package/dist/watcher.mjs +1 -1
- package/dist/{where-clause-compiler-B1weGkP4.d.ts → where-clause-compiler-BGBNBNit.d.ts} +46 -35
- package/package.json +1 -1
- package/skills/kitcn/references/features/aggregates.md +10 -1
- package/skills/kitcn/references/features/orm.md +1 -1
|
@@ -528,11 +528,19 @@ function getIndexFields(table, index, schema) {
|
|
|
528
528
|
fields.push("_id");
|
|
529
529
|
return fields;
|
|
530
530
|
}
|
|
531
|
-
|
|
531
|
+
/**
|
|
532
|
+
* Read the index key out of a document.
|
|
533
|
+
*
|
|
534
|
+
* Takes field paths already split on `.` rather than the field names: the field
|
|
535
|
+
* list is fixed for the life of a query, so splitting it once at `withIndex`
|
|
536
|
+
* time saves an array allocation per field per document pulled through the
|
|
537
|
+
* stream.
|
|
538
|
+
*/
|
|
539
|
+
function getIndexKey(doc, indexFieldPaths) {
|
|
532
540
|
const key = [];
|
|
533
|
-
for (const
|
|
541
|
+
for (const path of indexFieldPaths) {
|
|
534
542
|
let obj = doc;
|
|
535
|
-
for (const subfield of
|
|
543
|
+
for (const subfield of path) obj = obj[subfield];
|
|
536
544
|
key.push(obj);
|
|
537
545
|
}
|
|
538
546
|
return key;
|
|
@@ -729,17 +737,26 @@ var QueryStream = class {
|
|
|
729
737
|
}
|
|
730
738
|
[Symbol.asyncIterator]() {
|
|
731
739
|
const iterator = this.iterWithKeys()[Symbol.asyncIterator]();
|
|
732
|
-
return {
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
done
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
740
|
+
return {
|
|
741
|
+
async next() {
|
|
742
|
+
const result = await iterator.next();
|
|
743
|
+
if (result.done) return {
|
|
744
|
+
done: true,
|
|
745
|
+
value: void 0
|
|
746
|
+
};
|
|
747
|
+
return {
|
|
748
|
+
done: false,
|
|
749
|
+
value: result.value[0]
|
|
750
|
+
};
|
|
751
|
+
},
|
|
752
|
+
async return() {
|
|
753
|
+
await iterator.return?.();
|
|
754
|
+
return {
|
|
755
|
+
done: true,
|
|
756
|
+
value: void 0
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
};
|
|
743
760
|
}
|
|
744
761
|
};
|
|
745
762
|
var StreamDatabaseReader = class {
|
|
@@ -858,6 +875,7 @@ var OrderedStreamQuery = class extends StreamableQuery {
|
|
|
858
875
|
table: this.parent.parent.table,
|
|
859
876
|
index: this.parent.index,
|
|
860
877
|
indexFields: this.parent.q.indexFields,
|
|
878
|
+
indexFieldPaths: this.parent.q.indexFieldPaths,
|
|
861
879
|
order: this.order,
|
|
862
880
|
bounds: {
|
|
863
881
|
lowerBound: this.parent.q.lowerBoundIndexKey ?? [],
|
|
@@ -876,7 +894,7 @@ var OrderedStreamQuery = class extends StreamableQuery {
|
|
|
876
894
|
return db.query(table).withIndex(index, indexRange).order(order);
|
|
877
895
|
}
|
|
878
896
|
iterWithKeys() {
|
|
879
|
-
const {
|
|
897
|
+
const { indexFieldPaths } = this.reflect();
|
|
880
898
|
const iterable = this.inner();
|
|
881
899
|
return { [Symbol.asyncIterator]() {
|
|
882
900
|
const iterator = iterable[Symbol.asyncIterator]();
|
|
@@ -888,7 +906,7 @@ var OrderedStreamQuery = class extends StreamableQuery {
|
|
|
888
906
|
};
|
|
889
907
|
return {
|
|
890
908
|
done: false,
|
|
891
|
-
value: [result.value, getIndexKey(result.value,
|
|
909
|
+
value: [result.value, getIndexKey(result.value, indexFieldPaths)]
|
|
892
910
|
};
|
|
893
911
|
} };
|
|
894
912
|
} };
|
|
@@ -950,8 +968,11 @@ var ReflectIndexRange = class {
|
|
|
950
968
|
upperBoundInclusive = true;
|
|
951
969
|
equalityIndexFilter = [];
|
|
952
970
|
indexFields;
|
|
971
|
+
/** `indexFields`, each split on `.` once, for `getIndexKey`. */
|
|
972
|
+
indexFieldPaths;
|
|
953
973
|
constructor(indexFields) {
|
|
954
974
|
this.indexFields = indexFields;
|
|
975
|
+
this.indexFieldPaths = indexFields.map((field) => field.split("."));
|
|
955
976
|
}
|
|
956
977
|
eq(field, value) {
|
|
957
978
|
if (!this.#canLowerBound(field) || !this.#canUpperBound(field)) throw new Error(`Cannot use eq on field '${field}'`);
|
|
@@ -1043,16 +1064,24 @@ var ReflectIndexRange = class {
|
|
|
1043
1064
|
function mergedStream(streams, orderByIndexFields) {
|
|
1044
1065
|
return new MergedStream(streams, orderByIndexFields);
|
|
1045
1066
|
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Marks sources that are already `OrderByStream`s, so `narrow` reuses them
|
|
1069
|
+
* instead of adding a redundant delegation layer per call.
|
|
1070
|
+
*
|
|
1071
|
+
* Module-private on purpose: wrapping is also where the ordering invariant is
|
|
1072
|
+
* checked, so no caller outside this file can skip it.
|
|
1073
|
+
*/
|
|
1074
|
+
const PRE_ORDERED_STREAMS = Symbol("preOrderedStreams");
|
|
1046
1075
|
var MergedStream = class MergedStream extends QueryStream {
|
|
1047
1076
|
#order;
|
|
1048
1077
|
#streams;
|
|
1049
1078
|
#equalityIndexFilter;
|
|
1050
1079
|
#indexFields;
|
|
1051
|
-
constructor(streams, orderByIndexFields) {
|
|
1080
|
+
constructor(streams, orderByIndexFields, preOrdered) {
|
|
1052
1081
|
super();
|
|
1053
1082
|
if (streams.length === 0) throw new Error("Cannot union empty array of streams");
|
|
1054
1083
|
this.#order = allSame(streams.map((stream) => stream.getOrder()), "Cannot merge streams with different orders");
|
|
1055
|
-
this.#streams = streams.map((stream) => new OrderByStream(stream, orderByIndexFields));
|
|
1084
|
+
this.#streams = preOrdered === PRE_ORDERED_STREAMS ? streams : streams.map((stream) => new OrderByStream(stream, orderByIndexFields.slice()));
|
|
1056
1085
|
this.#indexFields = allSame(this.#streams.map((stream) => stream.getIndexFields()), "Cannot merge streams with different index fields. Consider using .orderBy()");
|
|
1057
1086
|
this.#equalityIndexFilter = commonPrefix(this.#streams.map((stream) => stream.getEqualityIndexFilter()));
|
|
1058
1087
|
}
|
|
@@ -1065,40 +1094,60 @@ var MergedStream = class MergedStream extends QueryStream {
|
|
|
1065
1094
|
done: false,
|
|
1066
1095
|
value: void 0
|
|
1067
1096
|
}));
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1097
|
+
let started = false;
|
|
1098
|
+
let refillIndex;
|
|
1099
|
+
return {
|
|
1100
|
+
async next() {
|
|
1101
|
+
if (started) {
|
|
1102
|
+
if (refillIndex !== void 0) {
|
|
1103
|
+
results[refillIndex] = await iterators[refillIndex].next();
|
|
1104
|
+
refillIndex = void 0;
|
|
1105
|
+
}
|
|
1106
|
+
} else {
|
|
1107
|
+
started = true;
|
|
1108
|
+
await Promise.all(iterators.map(async (iterator, i) => {
|
|
1109
|
+
results[i] = await iterator.next();
|
|
1110
|
+
}));
|
|
1080
1111
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1112
|
+
let minIndexKeyAndIndex;
|
|
1113
|
+
for (let i = 0; i < results.length; i++) {
|
|
1114
|
+
const result = results[i];
|
|
1115
|
+
if (result.done || !result.value) continue;
|
|
1116
|
+
const [_, resultIndexKey] = result.value;
|
|
1117
|
+
if (minIndexKeyAndIndex === void 0) {
|
|
1118
|
+
minIndexKeyAndIndex = [resultIndexKey, i];
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
const [prevMin, _prevMinIndex] = minIndexKeyAndIndex;
|
|
1122
|
+
if (compareKeys({
|
|
1123
|
+
value: resultIndexKey,
|
|
1124
|
+
kind: "exact"
|
|
1125
|
+
}, {
|
|
1126
|
+
value: prevMin,
|
|
1127
|
+
kind: "exact"
|
|
1128
|
+
}) * comparisonInversion < 0) minIndexKeyAndIndex = [resultIndexKey, i];
|
|
1129
|
+
}
|
|
1130
|
+
if (minIndexKeyAndIndex === void 0) return {
|
|
1131
|
+
done: true,
|
|
1132
|
+
value: void 0
|
|
1133
|
+
};
|
|
1134
|
+
const [_, minIndex] = minIndexKeyAndIndex;
|
|
1135
|
+
const result = results[minIndex].value;
|
|
1136
|
+
results[minIndex].value = void 0;
|
|
1137
|
+
refillIndex = minIndex;
|
|
1138
|
+
return {
|
|
1139
|
+
done: false,
|
|
1140
|
+
value: result
|
|
1141
|
+
};
|
|
1142
|
+
},
|
|
1143
|
+
async return() {
|
|
1144
|
+
await Promise.all(iterators.map((iterator) => iterator.return?.()));
|
|
1145
|
+
return {
|
|
1146
|
+
done: true,
|
|
1147
|
+
value: void 0
|
|
1148
|
+
};
|
|
1089
1149
|
}
|
|
1090
|
-
|
|
1091
|
-
done: true,
|
|
1092
|
-
value: void 0
|
|
1093
|
-
};
|
|
1094
|
-
const [_, minIndex] = minIndexKeyAndIndex;
|
|
1095
|
-
const result = results[minIndex].value;
|
|
1096
|
-
results[minIndex].value = void 0;
|
|
1097
|
-
return {
|
|
1098
|
-
done: false,
|
|
1099
|
-
value: result
|
|
1100
|
-
};
|
|
1101
|
-
} };
|
|
1150
|
+
};
|
|
1102
1151
|
} };
|
|
1103
1152
|
}
|
|
1104
1153
|
getOrder() {
|
|
@@ -1111,7 +1160,7 @@ var MergedStream = class MergedStream extends QueryStream {
|
|
|
1111
1160
|
return this.#indexFields;
|
|
1112
1161
|
}
|
|
1113
1162
|
narrow(indexBounds) {
|
|
1114
|
-
return new MergedStream(this.#streams.map((stream) => stream.narrow(indexBounds)), this.#indexFields);
|
|
1163
|
+
return new MergedStream(this.#streams.map((stream) => stream.narrow(indexBounds)), this.#indexFields, PRE_ORDERED_STREAMS);
|
|
1115
1164
|
}
|
|
1116
1165
|
};
|
|
1117
1166
|
function allSame(values, errorMessage) {
|
|
@@ -1159,16 +1208,22 @@ var ConcatStreams = class ConcatStreams extends QueryStream {
|
|
|
1159
1208
|
this.#equalityIndexFilter = commonPrefix(streams.map((stream) => stream.getEqualityIndexFilter()));
|
|
1160
1209
|
}
|
|
1161
1210
|
iterWithKeys() {
|
|
1162
|
-
const
|
|
1211
|
+
const streams = this.#streams;
|
|
1163
1212
|
const comparisonInversion = this.#order === "asc" ? 1 : -1;
|
|
1164
|
-
let previousIndexKey;
|
|
1165
1213
|
return { [Symbol.asyncIterator]() {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1214
|
+
let index = 0;
|
|
1215
|
+
let iterator;
|
|
1216
|
+
let previousIndexKey;
|
|
1217
|
+
return {
|
|
1218
|
+
async next() {
|
|
1219
|
+
while (index < streams.length) {
|
|
1220
|
+
iterator ??= streams[index].iterWithKeys()[Symbol.asyncIterator]();
|
|
1221
|
+
const result = await iterator.next();
|
|
1222
|
+
if (result.done) {
|
|
1223
|
+
index++;
|
|
1224
|
+
iterator = void 0;
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1172
1227
|
const [_, indexKey] = result.value;
|
|
1173
1228
|
if (previousIndexKey !== void 0 && compareKeys({
|
|
1174
1229
|
value: previousIndexKey,
|
|
@@ -1180,12 +1235,21 @@ var ConcatStreams = class ConcatStreams extends QueryStream {
|
|
|
1180
1235
|
previousIndexKey = indexKey;
|
|
1181
1236
|
return result;
|
|
1182
1237
|
}
|
|
1238
|
+
return {
|
|
1239
|
+
done: true,
|
|
1240
|
+
value: void 0
|
|
1241
|
+
};
|
|
1242
|
+
},
|
|
1243
|
+
async return() {
|
|
1244
|
+
await iterator?.return?.();
|
|
1245
|
+
index = streams.length;
|
|
1246
|
+
iterator = void 0;
|
|
1247
|
+
return {
|
|
1248
|
+
done: true,
|
|
1249
|
+
value: void 0
|
|
1250
|
+
};
|
|
1183
1251
|
}
|
|
1184
|
-
|
|
1185
|
-
done: true,
|
|
1186
|
-
value: void 0
|
|
1187
|
-
};
|
|
1188
|
-
} };
|
|
1252
|
+
};
|
|
1189
1253
|
} };
|
|
1190
1254
|
}
|
|
1191
1255
|
getOrder() {
|
|
@@ -1258,6 +1322,18 @@ var FlatMapStreamIterator = class {
|
|
|
1258
1322
|
value: [u, [...this.#currentOuterItem.indexKey, ...indexKey]]
|
|
1259
1323
|
};
|
|
1260
1324
|
}
|
|
1325
|
+
async return() {
|
|
1326
|
+
try {
|
|
1327
|
+
await this.#currentOuterItem?.innerIterator.return?.();
|
|
1328
|
+
} finally {
|
|
1329
|
+
this.#currentOuterItem = null;
|
|
1330
|
+
await this.#outerIterator.return?.();
|
|
1331
|
+
}
|
|
1332
|
+
return {
|
|
1333
|
+
done: true,
|
|
1334
|
+
value: void 0
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1261
1337
|
};
|
|
1262
1338
|
var FlatMapStream = class FlatMapStream extends QueryStream {
|
|
1263
1339
|
#stream;
|
|
@@ -1477,15 +1553,24 @@ var OrderByStream = class OrderByStream extends QueryStream {
|
|
|
1477
1553
|
const staticFilter = this.#staticFilter;
|
|
1478
1554
|
return { [Symbol.asyncIterator]() {
|
|
1479
1555
|
const iterator = iterable[Symbol.asyncIterator]();
|
|
1480
|
-
return {
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1556
|
+
return {
|
|
1557
|
+
async next() {
|
|
1558
|
+
const result = await iterator.next();
|
|
1559
|
+
if (result.done) return result;
|
|
1560
|
+
const [doc, indexKey] = result.value;
|
|
1561
|
+
return {
|
|
1562
|
+
done: false,
|
|
1563
|
+
value: [doc, indexKey.slice(staticFilter.length)]
|
|
1564
|
+
};
|
|
1565
|
+
},
|
|
1566
|
+
async return() {
|
|
1567
|
+
await iterator.return?.();
|
|
1568
|
+
return {
|
|
1569
|
+
done: true,
|
|
1570
|
+
value: void 0
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1489
1574
|
} };
|
|
1490
1575
|
}
|
|
1491
1576
|
narrow(indexBounds) {
|
|
@@ -1516,36 +1601,57 @@ var DistinctStream = class DistinctStream extends QueryStream {
|
|
|
1516
1601
|
iterWithKeys() {
|
|
1517
1602
|
const stream = this.#stream;
|
|
1518
1603
|
const distinctIndexFieldsLength = this.#distinctIndexFieldsLength;
|
|
1604
|
+
const order = stream.getOrder();
|
|
1605
|
+
const comparisonInversion = order === "asc" ? 1 : -1;
|
|
1519
1606
|
return { [Symbol.asyncIterator]() {
|
|
1520
|
-
let
|
|
1521
|
-
let
|
|
1522
|
-
return {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
done
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1607
|
+
let currentIterator = stream.iterWithKeys()[Symbol.asyncIterator]();
|
|
1608
|
+
let previousDistinctIndexKey;
|
|
1609
|
+
return {
|
|
1610
|
+
async next() {
|
|
1611
|
+
const result = await currentIterator.next();
|
|
1612
|
+
if (result.done) return {
|
|
1613
|
+
done: true,
|
|
1614
|
+
value: void 0
|
|
1615
|
+
};
|
|
1616
|
+
const [doc, indexKey] = result.value;
|
|
1617
|
+
if (doc === null) return {
|
|
1618
|
+
done: false,
|
|
1619
|
+
value: [null, indexKey]
|
|
1620
|
+
};
|
|
1621
|
+
const distinctIndexKey = indexKey.slice(0, distinctIndexFieldsLength);
|
|
1622
|
+
if (previousDistinctIndexKey !== void 0 && compareKeys({
|
|
1623
|
+
value: previousDistinctIndexKey,
|
|
1624
|
+
kind: "exact"
|
|
1625
|
+
}, {
|
|
1626
|
+
value: distinctIndexKey,
|
|
1627
|
+
kind: "exact"
|
|
1628
|
+
}) * comparisonInversion >= 0) throw new Error(`DistinctStream in wrong order: ${JSON.stringify(previousDistinctIndexKey)}, ${JSON.stringify(distinctIndexKey)}`);
|
|
1629
|
+
previousDistinctIndexKey = distinctIndexKey;
|
|
1630
|
+
let narrowed;
|
|
1631
|
+
if (order === "asc") narrowed = stream.narrow({
|
|
1632
|
+
lowerBound: distinctIndexKey,
|
|
1633
|
+
lowerBoundInclusive: false,
|
|
1634
|
+
upperBound: [],
|
|
1635
|
+
upperBoundInclusive: true
|
|
1636
|
+
});
|
|
1637
|
+
else narrowed = stream.narrow({
|
|
1638
|
+
lowerBound: [],
|
|
1639
|
+
lowerBoundInclusive: true,
|
|
1640
|
+
upperBound: distinctIndexKey,
|
|
1641
|
+
upperBoundInclusive: false
|
|
1642
|
+
});
|
|
1643
|
+
await currentIterator.return?.();
|
|
1644
|
+
currentIterator = narrowed.iterWithKeys()[Symbol.asyncIterator]();
|
|
1645
|
+
return result;
|
|
1646
|
+
},
|
|
1647
|
+
async return() {
|
|
1648
|
+
await currentIterator.return?.();
|
|
1649
|
+
return {
|
|
1650
|
+
done: true,
|
|
1651
|
+
value: void 0
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
};
|
|
1549
1655
|
} };
|
|
1550
1656
|
}
|
|
1551
1657
|
narrow(indexBounds) {
|
|
@@ -1512,7 +1512,7 @@ function aggregateNameFromNamespace(namespace) {
|
|
|
1512
1512
|
async function insertHandler(ctx, args) {
|
|
1513
1513
|
const tree = await getOrCreateTree(ctx.db, args.namespace, DEFAULT_MAX_NODE_SIZE, true);
|
|
1514
1514
|
const summand = args.summand ?? 0;
|
|
1515
|
-
const pushUp = await insertIntoNode(ctx,
|
|
1515
|
+
const pushUp = await insertIntoNode(ctx, tree.maxNodeSize, tree.root, {
|
|
1516
1516
|
k: args.key,
|
|
1517
1517
|
v: args.value,
|
|
1518
1518
|
s: summand
|
|
@@ -1529,7 +1529,7 @@ async function insertHandler(ctx, args) {
|
|
|
1529
1529
|
}
|
|
1530
1530
|
async function deleteHandler(ctx, args) {
|
|
1531
1531
|
const tree = await getOrCreateTree(ctx.db, args.namespace, DEFAULT_MAX_NODE_SIZE, true);
|
|
1532
|
-
await deleteFromNode(ctx,
|
|
1532
|
+
await deleteFromNode(ctx, tree.maxNodeSize, tree.root, args.key);
|
|
1533
1533
|
const root = await ctx.db.get(tree.root);
|
|
1534
1534
|
if (root.items.length === 0 && root.subtrees.length === 1) {
|
|
1535
1535
|
log(`collapsing root ${root._id} because its only child is ${root.subtrees[0]}`);
|
|
@@ -1538,13 +1538,12 @@ async function deleteHandler(ctx, args) {
|
|
|
1538
1538
|
await ctx.db.delete(root._id);
|
|
1539
1539
|
}
|
|
1540
1540
|
}
|
|
1541
|
-
|
|
1542
|
-
|
|
1541
|
+
function assertMaxNodeSize(maxNodeSize) {
|
|
1542
|
+
if (maxNodeSize % 2 !== 0 || maxNodeSize < 4) throw new Error("MAX_NODE_SIZE must be even and at least 4");
|
|
1543
1543
|
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
return max / 2;
|
|
1544
|
+
function minNodeSizeFor(maxNodeSize) {
|
|
1545
|
+
assertMaxNodeSize(maxNodeSize);
|
|
1546
|
+
return maxNodeSize / 2;
|
|
1548
1547
|
}
|
|
1549
1548
|
async function aggregateBetweenHandler(ctx, args) {
|
|
1550
1549
|
const tree = await getTree(ctx.db, args.namespace);
|
|
@@ -1554,8 +1553,8 @@ async function aggregateBetweenHandler(ctx, args) {
|
|
|
1554
1553
|
};
|
|
1555
1554
|
return await aggregateBetweenInNode(ctx.db, tree.root, args.k1, args.k2);
|
|
1556
1555
|
}
|
|
1557
|
-
async function filterBetween(db, node, k1, k2) {
|
|
1558
|
-
const n = await db.get(node);
|
|
1556
|
+
async function filterBetween(db, node, k1, k2, preloaded) {
|
|
1557
|
+
const n = preloaded ?? await db.get(node);
|
|
1559
1558
|
const included = [];
|
|
1560
1559
|
function includeSubtree(i, unboundedRight) {
|
|
1561
1560
|
const unboundedLeft = k1 === void 0 || included.length > 0;
|
|
@@ -1623,7 +1622,7 @@ async function offsetUntilHandler(ctx, args) {
|
|
|
1623
1622
|
namespace: args.namespace
|
|
1624
1623
|
})).count;
|
|
1625
1624
|
}
|
|
1626
|
-
async function deleteFromNode(ctx,
|
|
1625
|
+
async function deleteFromNode(ctx, maxNodeSize, node, key) {
|
|
1627
1626
|
let n = await ctx.db.get(node);
|
|
1628
1627
|
let foundItem = null;
|
|
1629
1628
|
let i = 0;
|
|
@@ -1659,13 +1658,13 @@ async function deleteFromNode(ctx, namespace, node, key) {
|
|
|
1659
1658
|
code: "DELETE_MISSING_KEY",
|
|
1660
1659
|
message: `key ${p(key)} not found in node ${n._id}`
|
|
1661
1660
|
});
|
|
1662
|
-
const deleted = await deleteFromNode(ctx,
|
|
1661
|
+
const deleted = await deleteFromNode(ctx, maxNodeSize, n.subtrees[i], key);
|
|
1663
1662
|
if (!deleted) return null;
|
|
1664
1663
|
if (!foundItem) foundItem = deleted;
|
|
1665
1664
|
const newAggregate = n.aggregate && sub(n.aggregate, itemAggregate(deleted));
|
|
1666
1665
|
if (newAggregate) await ctx.db.patch(node, { aggregate: newAggregate });
|
|
1667
1666
|
const deficientSubtree = await ctx.db.get(n.subtrees[i]);
|
|
1668
|
-
const minNodeSize =
|
|
1667
|
+
const minNodeSize = minNodeSizeFor(maxNodeSize);
|
|
1669
1668
|
if (deficientSubtree.items.length < minNodeSize) {
|
|
1670
1669
|
log(`deficient subtree ${deficientSubtree._id}`);
|
|
1671
1670
|
if (i > 0) {
|
|
@@ -1751,26 +1750,28 @@ async function mergeNodes(db, parent, leftIndex) {
|
|
|
1751
1750
|
});
|
|
1752
1751
|
await db.delete(right._id);
|
|
1753
1752
|
}
|
|
1754
|
-
async function negativeOffsetInNode(db, node, index, k1, k2) {
|
|
1755
|
-
const filtered = await filterBetween(db, node, k1, k2);
|
|
1753
|
+
async function negativeOffsetInNode(db, node, index, k1, k2, preloaded) {
|
|
1754
|
+
const filtered = await filterBetween(db, node, k1, k2, preloaded);
|
|
1756
1755
|
for (const included of filtered.reverse()) if (included.type === "item") {
|
|
1757
1756
|
if (index === 0) return included.item;
|
|
1758
1757
|
index -= 1;
|
|
1759
1758
|
} else {
|
|
1760
|
-
const
|
|
1761
|
-
|
|
1759
|
+
const subtree = await db.get(included.subtree);
|
|
1760
|
+
const subtreeCount = (await nodeAggregate(db, subtree)).count;
|
|
1761
|
+
if (index < subtreeCount) return await negativeOffsetInNode(db, included.subtree, index, void 0, void 0, subtree);
|
|
1762
1762
|
index -= subtreeCount;
|
|
1763
1763
|
}
|
|
1764
1764
|
throw new ConvexError(`negative offset exceeded count by ${index} (in node ${node})`);
|
|
1765
1765
|
}
|
|
1766
|
-
async function atOffsetInNode(db, node, index, k1, k2) {
|
|
1767
|
-
const filtered = await filterBetween(db, node, k1, k2);
|
|
1766
|
+
async function atOffsetInNode(db, node, index, k1, k2, preloaded) {
|
|
1767
|
+
const filtered = await filterBetween(db, node, k1, k2, preloaded);
|
|
1768
1768
|
for (const included of filtered) if (included.type === "item") {
|
|
1769
1769
|
if (index === 0) return included.item;
|
|
1770
1770
|
index -= 1;
|
|
1771
1771
|
} else {
|
|
1772
|
-
const
|
|
1773
|
-
|
|
1772
|
+
const subtree = await db.get(included.subtree);
|
|
1773
|
+
const subtreeCount = (await nodeAggregate(db, subtree)).count;
|
|
1774
|
+
if (index < subtreeCount) return await atOffsetInNode(db, included.subtree, index, void 0, void 0, subtree);
|
|
1774
1775
|
index -= subtreeCount;
|
|
1775
1776
|
}
|
|
1776
1777
|
throw new ConvexError(`offset exceeded count by ${index} (in node ${node})`);
|
|
@@ -1812,7 +1813,7 @@ function accumulate(nums) {
|
|
|
1812
1813
|
sum: 0
|
|
1813
1814
|
});
|
|
1814
1815
|
}
|
|
1815
|
-
async function insertIntoNode(ctx,
|
|
1816
|
+
async function insertIntoNode(ctx, maxNodeSize, node, item) {
|
|
1816
1817
|
const n = await ctx.db.get(node);
|
|
1817
1818
|
let i = 0;
|
|
1818
1819
|
for (; i < n.items.length; i++) {
|
|
@@ -1820,31 +1821,41 @@ async function insertIntoNode(ctx, namespace, node, item) {
|
|
|
1820
1821
|
if (compare === -1) break;
|
|
1821
1822
|
if (compare === 0) throw new ConvexError(`key ${p(item.k)} already exists in node ${n._id}`);
|
|
1822
1823
|
}
|
|
1824
|
+
let nextItems = n.items;
|
|
1825
|
+
let nextSubtrees = n.subtrees;
|
|
1823
1826
|
if (n.subtrees.length > 0) {
|
|
1824
|
-
const pushUp = await insertIntoNode(ctx,
|
|
1825
|
-
if (pushUp)
|
|
1826
|
-
|
|
1827
|
+
const pushUp = await insertIntoNode(ctx, maxNodeSize, n.subtrees[i], item);
|
|
1828
|
+
if (pushUp) {
|
|
1829
|
+
nextItems = [
|
|
1827
1830
|
...n.items.slice(0, i),
|
|
1828
1831
|
pushUp.item,
|
|
1829
1832
|
...n.items.slice(i)
|
|
1830
|
-
]
|
|
1831
|
-
|
|
1833
|
+
];
|
|
1834
|
+
nextSubtrees = [
|
|
1832
1835
|
...n.subtrees.slice(0, i),
|
|
1833
1836
|
pushUp.leftSubtree,
|
|
1834
1837
|
pushUp.rightSubtree,
|
|
1835
1838
|
...n.subtrees.slice(i + 1)
|
|
1836
|
-
]
|
|
1837
|
-
}
|
|
1838
|
-
} else
|
|
1839
|
+
];
|
|
1840
|
+
}
|
|
1841
|
+
} else nextItems = [
|
|
1839
1842
|
...n.items.slice(0, i),
|
|
1840
1843
|
item,
|
|
1841
1844
|
...n.items.slice(i)
|
|
1842
|
-
]
|
|
1845
|
+
];
|
|
1843
1846
|
const newAggregate = n.aggregate && add(n.aggregate, itemAggregate(item));
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1847
|
+
const nodePatch = {};
|
|
1848
|
+
if (nextItems !== n.items) nodePatch.items = nextItems;
|
|
1849
|
+
if (nextSubtrees !== n.subtrees) nodePatch.subtrees = nextSubtrees;
|
|
1850
|
+
if (newAggregate) nodePatch.aggregate = newAggregate;
|
|
1851
|
+
if (Object.keys(nodePatch).length > 0) await ctx.db.patch(node, nodePatch);
|
|
1852
|
+
const newN = {
|
|
1853
|
+
...n,
|
|
1854
|
+
items: nextItems,
|
|
1855
|
+
subtrees: nextSubtrees,
|
|
1856
|
+
aggregate: newAggregate ?? n.aggregate
|
|
1857
|
+
};
|
|
1858
|
+
const minNodeSize = minNodeSizeFor(maxNodeSize);
|
|
1848
1859
|
if (newN.items.length > maxNodeSize) {
|
|
1849
1860
|
if (newN.items.length !== maxNodeSize + 1 || newN.items.length !== 2 * minNodeSize + 1) throw new Error(`bad ${newN.items.length}`);
|
|
1850
1861
|
log(`splitting node ${newN._id} at ${newN.items[minNodeSize].k}`);
|
|
@@ -1889,6 +1900,7 @@ async function getOrCreateTree(db, namespace, maxNodeSize, rootLazy) {
|
|
|
1889
1900
|
const originalTree = await getTree(db, namespace);
|
|
1890
1901
|
const aggregateName = aggregateNameFromNamespace(namespace);
|
|
1891
1902
|
if (originalTree) {
|
|
1903
|
+
assertMaxNodeSize(originalTree.maxNodeSize);
|
|
1892
1904
|
if (originalTree.aggregateName !== aggregateName) {
|
|
1893
1905
|
await db.patch(originalTree._id, { aggregateName });
|
|
1894
1906
|
return {
|
|
@@ -1906,7 +1918,7 @@ async function getOrCreateTree(db, namespace, maxNodeSize, rootLazy) {
|
|
|
1906
1918
|
sum: 0
|
|
1907
1919
|
}
|
|
1908
1920
|
});
|
|
1909
|
-
const effectiveMaxNodeSize = maxNodeSize ?? await
|
|
1921
|
+
const effectiveMaxNodeSize = maxNodeSize ?? (await mustGetTree(db, void 0)).maxNodeSize;
|
|
1910
1922
|
const effectiveRootLazy = rootLazy ?? await isRootLazy(db, void 0) ?? true;
|
|
1911
1923
|
const id = await db.insert(AGGREGATE_TREE_TABLE, {
|
|
1912
1924
|
aggregateName,
|
|
@@ -1915,7 +1927,7 @@ async function getOrCreateTree(db, namespace, maxNodeSize, rootLazy) {
|
|
|
1915
1927
|
namespace
|
|
1916
1928
|
});
|
|
1917
1929
|
const newTree = await db.get(id);
|
|
1918
|
-
|
|
1930
|
+
assertMaxNodeSize(effectiveMaxNodeSize);
|
|
1919
1931
|
if (effectiveRootLazy) await db.patch(root, { aggregate: void 0 });
|
|
1920
1932
|
return newTree;
|
|
1921
1933
|
}
|
|
@@ -1930,6 +1942,19 @@ async function deleteTreeNodes(db, node) {
|
|
|
1930
1942
|
for (const subtree of current.subtrees) await deleteTreeNodes(db, subtree);
|
|
1931
1943
|
await db.delete(node);
|
|
1932
1944
|
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Deletes up to `limit` namespace trees belonging to one aggregate, returning
|
|
1947
|
+
* true once none are left. Unlike `clearTree` this does not recreate the tree,
|
|
1948
|
+
* so a caller can drain every namespace across several transactions.
|
|
1949
|
+
*/
|
|
1950
|
+
async function deleteTreesHandler(ctx, args) {
|
|
1951
|
+
const trees = await ctx.db.query(AGGREGATE_TREE_TABLE).withIndex("by_aggregate_name", (q) => q.eq("aggregateName", args.aggregateName)).take(args.limit);
|
|
1952
|
+
for (const tree of trees) {
|
|
1953
|
+
await deleteTreeNodes(ctx.db, tree.root);
|
|
1954
|
+
await ctx.db.delete(tree._id);
|
|
1955
|
+
}
|
|
1956
|
+
return trees.length === 0;
|
|
1957
|
+
}
|
|
1933
1958
|
async function clearTree(db, args) {
|
|
1934
1959
|
const tree = await getTree(db, args.namespace);
|
|
1935
1960
|
let existingRootLazy = true;
|
|
@@ -2319,6 +2344,17 @@ var Aggregate = class {
|
|
|
2319
2344
|
rootLazy: opts[0]?.rootLazy
|
|
2320
2345
|
});
|
|
2321
2346
|
}
|
|
2347
|
+
/**
|
|
2348
|
+
* Deletes up to `limit` namespace trees without recreating them. Returns true
|
|
2349
|
+
* once this aggregate owns no trees, so callers can drain every namespace
|
|
2350
|
+
* across several mutations instead of walking them all in one.
|
|
2351
|
+
*/
|
|
2352
|
+
async deleteTrees(ctx, limit) {
|
|
2353
|
+
return deleteTreesHandler({ db: ctx.db }, {
|
|
2354
|
+
aggregateName: this.aggregateName,
|
|
2355
|
+
limit
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2322
2358
|
async makeRootLazy(ctx, namespace) {
|
|
2323
2359
|
const tree = await getOrCreateTree(ctx.db, namespaceForArg(this.aggregateName, { namespace }));
|
|
2324
2360
|
await ctx.db.patch(tree.root, { aggregate: void 0 });
|
package/dist/watcher.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as generateMeta, c as PARSE_SNAPSHOT_SUFFIX, i as resolveConfiguredBackend, n as withLocalCodegenEnv, o as getConvexConfig, r as loadCliConfig, s as logger } from "./local-env-
|
|
2
|
+
import { a as generateMeta, c as PARSE_SNAPSHOT_SUFFIX, i as resolveConfiguredBackend, n as withLocalCodegenEnv, o as getConvexConfig, r as loadCliConfig, s as logger } from "./local-env-yFKub75x.mjs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|