datafolder 1.0.20 → 1.0.22

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.md CHANGED
@@ -4,33 +4,7 @@ datafolder is a lightning fluid JSON database for high speed SSDs, featuring an
4
4
 
5
5
  ### Changelog
6
6
 
7
- 1.0.20 Add HNSW support.
8
-
9
- 1.0.19 Add DocumentDB test case (test/testQyDocumentDB.js)
10
-
11
- 1.0.18 Add Embedding Storage Support. Related APIs: dataFolder.insertEmbedding() and dataFolder.delEmbedding(). TODO: Add HNSW support and vector query.
12
-
13
- 1.0.17 Add Northwind sample data test (test/testQyNorthwind.js)
14
-
15
- 1.0.16 Add GraphQL sample data test (test/testQyGraphQL.js) to show how to write complex queries.
16
-
17
- 1.0.15 Use [Workers](https://nodejs.org/docs/latest/api/worker_threads.html) to implement snapshot compactor, expire service, outdated data cleaner etc.
18
-
19
- 1.0.14 Expire supports specifying milliseconds.
20
-
21
- 1.0.13 Added expire support to delete a dir in a specific future time.
22
-
23
- 1.0.12 Added expire support to expire a file or some properties of the JSON data in future.
24
-
25
- 1.0.11 Added [SKILL.md](https://github.com/datafolderdev/datafolder/blob/main/SKILL.md) for feeding to AI
26
-
27
- 1.0.10 Added document [datafolder for high speed SSDs](https://github.com/datafolderdev/datafolder/wiki/datafolder-for-high-speed-SSDs)
28
-
29
- 1.0.9 Added Typescript type declarations.
30
-
31
- 1.0.7 Added trigger editor and [Internals of datafolder](https://github.com/datafolderdev/datafolder/wiki/Internals-of-datafolder)
32
-
33
- 1.0.6 Added code mirror as the query editor.
7
+ [ChangeLog] (ChangeLog.md)
34
8
 
35
9
  ## Installation
36
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datafolder",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "author": {
5
5
  "name": "lysh",
6
6
  "email": "datafolderdev@outlook.com"
@@ -1344,10 +1344,18 @@
1344
1344
  /**
1345
1345
  The upper boundary of the range.
1346
1346
  */
1347
- to, flags) {
1347
+ to, flags,
1348
+ /**
1349
+ The goal column (stored vertical offset) associated with a
1350
+ cursor. This is used to preserve the vertical position when
1351
+ [moving](https://codemirror.net/6/docs/ref/#view.EditorView.moveVertically) across
1352
+ lines of different length.
1353
+ */
1354
+ goalColumn) {
1348
1355
  this.from = from;
1349
1356
  this.to = to;
1350
1357
  this.flags = flags;
1358
+ this.goalColumn = goalColumn;
1351
1359
  }
1352
1360
  /**
1353
1361
  The anchor of the range—the side that doesn't move when you
@@ -1371,6 +1379,16 @@
1371
1379
  */
1372
1380
  get assoc() { return this.flags & 8 /* RangeFlag.AssocBefore */ ? -1 : this.flags & 16 /* RangeFlag.AssocAfter */ ? 1 : 0; }
1373
1381
  /**
1382
+ A flag that, when set, makes some selection-extending commands
1383
+ treat the range's head and anchor as exchangeable, so that for
1384
+ example Shift-ArrowUp will make the lower side of the selection
1385
+ the anchor, even if that was the head before. Used to implement
1386
+ MacOS-style undirectional selections.
1387
+ */
1388
+ get undirectional() {
1389
+ return (this.flags & 64 /* RangeFlag.Undirectional */) > 0;
1390
+ }
1391
+ /**
1374
1392
  The bidirectional text level associated with this cursor, if
1375
1393
  any.
1376
1394
  */
@@ -1379,16 +1397,6 @@
1379
1397
  return level == 7 ? null : level;
1380
1398
  }
1381
1399
  /**
1382
- The goal column (stored vertical offset) associated with a
1383
- cursor. This is used to preserve the vertical position when
1384
- [moving](https://codemirror.net/6/docs/ref/#view.EditorView.moveVertically) across
1385
- lines of different length.
1386
- */
1387
- get goalColumn() {
1388
- let value = this.flags >> 6 /* RangeFlag.GoalColumnOffset */;
1389
- return value == 16777215 /* RangeFlag.NoGoalColumn */ ? undefined : value;
1390
- }
1391
- /**
1392
1400
  Map this range through a change, producing a valid range in the
1393
1401
  updated document.
1394
1402
  */
@@ -1401,7 +1409,7 @@
1401
1409
  from = change.mapPos(this.from, 1);
1402
1410
  to = change.mapPos(this.to, -1);
1403
1411
  }
1404
- return from == this.from && to == this.to ? this : new SelectionRange(from, to, this.flags);
1412
+ return from == this.from && to == this.to ? this : new SelectionRange(from, to, this.flags, this.goalColumn);
1405
1413
  }
1406
1414
  /**
1407
1415
  Extend this range to cover at least `from` to `to`.
@@ -1435,8 +1443,8 @@
1435
1443
  /**
1436
1444
  @internal
1437
1445
  */
1438
- static create(from, to, flags) {
1439
- return new SelectionRange(from, to, flags);
1446
+ static create(from, to, flags, goalColumn) {
1447
+ return new SelectionRange(from, to, flags, goalColumn);
1440
1448
  }
1441
1449
  }
1442
1450
  /**
@@ -1551,19 +1559,26 @@
1551
1559
  */
1552
1560
  static cursor(pos, assoc = 0, bidiLevel, goalColumn) {
1553
1561
  return SelectionRange.create(pos, pos, (assoc == 0 ? 0 : assoc < 0 ? 8 /* RangeFlag.AssocBefore */ : 16 /* RangeFlag.AssocAfter */) |
1554
- (bidiLevel == null ? 7 : Math.min(6, bidiLevel)) |
1555
- ((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 16777215 /* RangeFlag.NoGoalColumn */) << 6 /* RangeFlag.GoalColumnOffset */));
1562
+ (bidiLevel == null ? 7 : Math.min(6, bidiLevel)), goalColumn);
1556
1563
  }
1557
1564
  /**
1558
1565
  Create a selection range.
1559
1566
  */
1560
1567
  static range(anchor, head, goalColumn, bidiLevel, assoc) {
1561
- let flags = ((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 16777215 /* RangeFlag.NoGoalColumn */) << 6 /* RangeFlag.GoalColumnOffset */) |
1562
- (bidiLevel == null ? 7 : Math.min(6, bidiLevel));
1568
+ let flags = bidiLevel == null ? 7 : Math.min(6, bidiLevel);
1563
1569
  if (!assoc && anchor != head)
1564
1570
  assoc = head < anchor ? 1 : -1;
1565
- return head < anchor ? SelectionRange.create(head, anchor, 32 /* RangeFlag.Inverted */ | 16 /* RangeFlag.AssocAfter */ | flags)
1566
- : SelectionRange.create(anchor, head, (!assoc ? 0 : assoc < 0 ? 8 /* RangeFlag.AssocBefore */ : 16 /* RangeFlag.AssocAfter */) | flags);
1571
+ if (assoc)
1572
+ flags |= assoc < 0 ? 8 /* RangeFlag.AssocBefore */ : 16 /* RangeFlag.AssocAfter */;
1573
+ return head < anchor ? SelectionRange.create(head, anchor, flags | 32 /* RangeFlag.Inverted */, goalColumn)
1574
+ : SelectionRange.create(anchor, head, flags, goalColumn);
1575
+ }
1576
+ /**
1577
+ Create an [undirectional](https://codemirror.net/6/docs/ref/#state.SelectionRange.undirectional)
1578
+ selection range.
1579
+ */
1580
+ static undirectionalRange(from, to) {
1581
+ return SelectionRange.create(from, to, 64 /* RangeFlag.Undirectional */, undefined);
1567
1582
  }
1568
1583
  /**
1569
1584
  @internal
@@ -7070,8 +7085,9 @@
7070
7085
  }
7071
7086
  }
7072
7087
  });
7073
- b.addLineStartIfNotCovered(pendingLineAttrs);
7074
7088
  this.openWidget = openEnd > markCount;
7089
+ if (!this.openWidget)
7090
+ b.addLineStartIfNotCovered(pendingLineAttrs);
7075
7091
  this.openMarks = openEnd;
7076
7092
  }
7077
7093
  forward(from, to, side = 1) {
@@ -7896,7 +7912,7 @@
7896
7912
  break;
7897
7913
  to = next;
7898
7914
  }
7899
- return EditorSelection.range(from + line.from, to + line.from);
7915
+ return EditorSelection.undirectionalRange(from + line.from, to + line.from);
7900
7916
  }
7901
7917
  function posAtCoordsImprecise(view, contentRect, block, x, y) {
7902
7918
  let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
@@ -8033,8 +8049,12 @@
8033
8049
  else {
8034
8050
  let from = skipAtomicRanges(atoms, range.from, -1);
8035
8051
  let to = skipAtomicRanges(atoms, range.to, 1);
8036
- if (from != range.from || to != range.to)
8037
- updated = EditorSelection.range(range.from == range.anchor ? from : to, range.from == range.head ? from : to);
8052
+ if (from != range.from || to != range.to) {
8053
+ if (range.undirectional)
8054
+ updated = EditorSelection.undirectionalRange(range.from, range.to);
8055
+ else
8056
+ updated = EditorSelection.range(range.from == range.anchor ? from : to, range.from == range.head ? from : to);
8057
+ }
8038
8058
  }
8039
8059
  if (updated) {
8040
8060
  if (!ranges)
@@ -8440,7 +8460,8 @@
8440
8460
  // Chrome will put the selection *inside* them, confusing
8441
8461
  // posFromDOM
8442
8462
  let vp = view.viewport;
8443
- if ((browser.ios || browser.chrome) && curSel.main.empty && head != anchor &&
8463
+ if ((browser.ios || browser.chrome) && head != anchor &&
8464
+ Math.min(head, anchor) <= curSel.main.from && Math.max(head, anchor) >= curSel.main.to &&
8444
8465
  (vp.from > 0 || vp.to < view.state.doc.length)) {
8445
8466
  let from = Math.min(head, anchor), to = Math.max(head, anchor);
8446
8467
  let offFrom = vp.from - from, offTo = vp.to - to;
@@ -8761,13 +8782,11 @@
8761
8782
  // (after which we retroactively handle them and reset the DOM) to
8762
8783
  // avoid messing up the virtual keyboard state.
8763
8784
  this.pendingIOSKey = undefined;
8764
- /**
8765
- When enabled (>-1), tab presses are not given to key handlers,
8766
- leaving the browser's default behavior. If >0, the mode expires
8767
- at that timestamp, and any other keypress clears it.
8768
- Esc enables temporary tab focus mode for two seconds when not
8769
- otherwise handled.
8770
- */
8785
+ // When enabled (>-1), tab presses are not given to key handlers,
8786
+ // leaving the browser's default behavior. If >0, the mode expires
8787
+ // at that timestamp, and any other keypress clears it.
8788
+ // Esc enables temporary tab focus mode for two seconds when not
8789
+ // otherwise handled.
8771
8790
  this.tabFocusMode = -1;
8772
8791
  this.lastSelectionOrigin = null;
8773
8792
  this.lastSelectionTime = 0;
@@ -8875,11 +8894,16 @@
8875
8894
  // state. So we let it go through, and then, in
8876
8895
  // applyDOMChange, notify key handlers of it and reset to
8877
8896
  // the state they produce.
8878
- let pending;
8879
- if (browser.ios && !event.synthetic && !event.altKey && !event.metaKey && !event.shiftKey &&
8880
- ((pending = PendingKeys.find(key => key.keyCode == event.keyCode)) && !event.ctrlKey ||
8897
+ if (browser.ios && !event.synthetic && !event.altKey && !event.metaKey &&
8898
+ (PendingKeys.some(key => key.keyCode == event.keyCode) && !event.ctrlKey ||
8881
8899
  EmacsyPendingKeys.indexOf(event.key) > -1 && event.ctrlKey)) {
8882
- this.pendingIOSKey = pending || event;
8900
+ let mods = { ctrlKey: event.ctrlKey, altKey: event.altKey, metaKey: event.metaKey, shiftKey: event.shiftKey };
8901
+ // On iOS with autocapitalize, drop the shift modifier for these
8902
+ // keys, since it will be set at the start of every sentence.
8903
+ if (mods.shiftKey && browser.ios && !/^(off|none)$/.test(this.view.contentDOM.autocapitalize) &&
8904
+ iosVirtualKeyboardOpen(this.view.win))
8905
+ mods.shiftKey = false;
8906
+ this.pendingIOSKey = { key: event.key, keyCode: event.keyCode, mods };
8883
8907
  setTimeout(() => this.flushIOSKey(), 250);
8884
8908
  return true;
8885
8909
  }
@@ -8895,7 +8919,7 @@
8895
8919
  if (key.key == "Enter" && change && change.from < change.to && /^\S+$/.test(change.insert.toString()))
8896
8920
  return false;
8897
8921
  this.pendingIOSKey = undefined;
8898
- return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key instanceof KeyboardEvent ? key : undefined);
8922
+ return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key.mods);
8899
8923
  }
8900
8924
  ignoreDuringComposition(event) {
8901
8925
  if (!/^key/.test(event.type) || event.synthetic)
@@ -8933,6 +8957,11 @@
8933
8957
  this.mouseSelection.destroy();
8934
8958
  }
8935
8959
  }
8960
+ function iosVirtualKeyboardOpen(win) {
8961
+ if (!win.visualViewport)
8962
+ return false;
8963
+ return win.visualViewport.height * win.visualViewport.scale / win.document.documentElement.clientHeight < 0.85;
8964
+ }
8936
8965
  function bindHandler(plugin, handler) {
8937
8966
  return (view, event) => {
8938
8967
  try {
@@ -9252,7 +9281,7 @@
9252
9281
  let from = visual ? visual.posAtStart : line.from, to = visual ? visual.posAtEnd : line.to;
9253
9282
  if (to < view.state.doc.length && to == line.to)
9254
9283
  to++;
9255
- return EditorSelection.range(from, to);
9284
+ return EditorSelection.undirectionalRange(from, to);
9256
9285
  }
9257
9286
  }
9258
9287
  const BadMouseDetail = browser.ie && browser.ie_version <= 11;
@@ -9310,7 +9339,7 @@
9310
9339
  if (tile && tile.isWidget()) {
9311
9340
  let from = tile.posAtStart, to = from + tile.length;
9312
9341
  if (from >= range.to || to <= range.from)
9313
- range = EditorSelection.range(from, to);
9342
+ range = EditorSelection.undirectionalRange(from, to);
9314
9343
  }
9315
9344
  }
9316
9345
  let { inputState } = view;
@@ -11935,7 +11964,6 @@
11935
11964
  for (let event in this.handlers)
11936
11965
  context.addEventListener(event, this.handlers[event]);
11937
11966
  this.measureReq = { read: view => {
11938
- this.editContext.updateControlBounds(view.contentDOM.getBoundingClientRect());
11939
11967
  let sel = getSelection(view.root);
11940
11968
  if (sel && sel.rangeCount)
11941
11969
  this.editContext.updateSelectionBounds(sel.getRangeAt(0).getBoundingClientRect());
@@ -18739,8 +18767,7 @@
18739
18767
  }
18740
18768
  /**
18741
18769
  A subclass of [`Language`](https://codemirror.net/6/docs/ref/#language.Language) for use with Lezer
18742
- [LR parsers](https://lezer.codemirror.net/docs/ref#lr.LRParser)
18743
- parsers.
18770
+ [LR parsers](https://lezer.codemirror.net/docs/ref#lr.LRParser).
18744
18771
  */
18745
18772
  class LRLanguage extends Language {
18746
18773
  constructor(data, parser, name) {
@@ -19732,18 +19759,25 @@
19732
19759
  if (tr.isUserEvent("delete"))
19733
19760
  tr.changes.iterChangedRanges((fromA, toA) => folded = clearTouchedFolds(folded, fromA, toA));
19734
19761
  folded = folded.map(tr.changes);
19762
+ let rangesToFold = [];
19735
19763
  for (let e of tr.effects) {
19736
19764
  if (e.is(foldEffect) && !foldExists(folded, e.value.from, e.value.to)) {
19737
- let { preparePlaceholder } = tr.state.facet(foldConfig);
19738
- let widget = !preparePlaceholder ? foldWidget :
19739
- Decoration.replace({ widget: new PreparedFoldWidget(preparePlaceholder(tr.state, e.value)) });
19740
- folded = folded.update({ add: [widget.range(e.value.from, e.value.to)] });
19765
+ rangesToFold.push(e.value);
19741
19766
  }
19742
19767
  else if (e.is(unfoldEffect)) {
19743
19768
  folded = folded.update({ filter: (from, to) => e.value.from != from || e.value.to != to,
19744
19769
  filterFrom: e.value.from, filterTo: e.value.to });
19745
19770
  }
19746
19771
  }
19772
+ if (rangesToFold.length) {
19773
+ let { preparePlaceholder } = tr.state.facet(foldConfig);
19774
+ let decorations = rangesToFold.map(value => {
19775
+ let widget = !preparePlaceholder ? foldWidget :
19776
+ Decoration.replace({ widget: new PreparedFoldWidget(preparePlaceholder(tr.state, value)) });
19777
+ return widget.range(value.from, value.to);
19778
+ });
19779
+ folded = folded.update({ add: decorations });
19780
+ }
19747
19781
  // Clear folded ranges that cover the selection head
19748
19782
  if (tr.selection)
19749
19783
  folded = clearTouchedFolds(folded, tr.selection.main.head);
@@ -20059,7 +20093,7 @@
20059
20093
  or array of tags in their `tag` property, and either a single
20060
20094
  `class` property providing a static CSS class (for highlighter
20061
20095
  that rely on external styling), or a
20062
- [`style-mod`](https://github.com/marijnh/style-mod#documentation)-style
20096
+ [`style-mod`](https://code.haverbeke.berlin/marijn/style-mod#documentation)-style
20063
20097
  set of CSS properties (which define the styling for those tags).
20064
20098
 
20065
20099
  The CSS rules created for a highlighter will be emitted in the
@@ -21146,8 +21180,10 @@
21146
21180
  on, if any.
21147
21181
  */
21148
21182
  const cursorMatchingBracket = ({ state, dispatch }) => toMatchingBracket(state, dispatch);
21149
- function extendSel(target, how) {
21183
+ function extendSel(target, forward, how) {
21150
21184
  let selection = updateSel(target.state.selection, range => {
21185
+ if (range.undirectional && (range.head >= range.anchor) != forward)
21186
+ range = EditorSelection.range(range.head, range.anchor);
21151
21187
  let head = how(range);
21152
21188
  return EditorSelection.range(range.anchor, head.head, head.goalColumn, head.bidiLevel || undefined, head.assoc);
21153
21189
  });
@@ -21157,7 +21193,7 @@
21157
21193
  return true;
21158
21194
  }
21159
21195
  function selectByChar(view, forward) {
21160
- return extendSel(view, range => view.moveByChar(range, forward));
21196
+ return extendSel(view, forward, range => view.moveByChar(range, forward));
21161
21197
  }
21162
21198
  /**
21163
21199
  Move the selection head one character to the left, while leaving
@@ -21169,7 +21205,7 @@
21169
21205
  */
21170
21206
  const selectCharRight = view => selectByChar(view, ltrAtCursor(view));
21171
21207
  function selectByGroup(view, forward) {
21172
- return extendSel(view, range => view.moveByGroup(range, forward));
21208
+ return extendSel(view, forward, range => view.moveByGroup(range, forward));
21173
21209
  }
21174
21210
  /**
21175
21211
  Move the selection head one [group](https://codemirror.net/6/docs/ref/#commands.cursorGroupLeft) to
@@ -21183,13 +21219,19 @@
21183
21219
  /**
21184
21220
  Move the selection head over the next syntactic element to the left.
21185
21221
  */
21186
- const selectSyntaxLeft = view => extendSel(view, range => moveBySyntax(view.state, range, !ltrAtCursor(view)));
21222
+ const selectSyntaxLeft = view => {
21223
+ let forward = !ltrAtCursor(view);
21224
+ return extendSel(view, forward, range => moveBySyntax(view.state, range, forward));
21225
+ };
21187
21226
  /**
21188
21227
  Move the selection head over the next syntactic element to the right.
21189
21228
  */
21190
- const selectSyntaxRight = view => extendSel(view, range => moveBySyntax(view.state, range, ltrAtCursor(view)));
21229
+ const selectSyntaxRight = view => {
21230
+ let forward = ltrAtCursor(view);
21231
+ return extendSel(view, forward, range => moveBySyntax(view.state, range, forward));
21232
+ };
21191
21233
  function selectByLine(view, forward) {
21192
- return extendSel(view, range => view.moveVertically(range, forward));
21234
+ return extendSel(view, forward, range => view.moveVertically(range, forward));
21193
21235
  }
21194
21236
  /**
21195
21237
  Move the selection head one line up.
@@ -21200,7 +21242,7 @@
21200
21242
  */
21201
21243
  const selectLineDown = view => selectByLine(view, true);
21202
21244
  function selectByPage(view, forward) {
21203
- return extendSel(view, range => view.moveVertically(range, forward, pageInfo(view).height));
21245
+ return extendSel(view, forward, range => view.moveVertically(range, forward, pageInfo(view).height));
21204
21246
  }
21205
21247
  /**
21206
21248
  Move the selection head one page up.
@@ -21213,27 +21255,33 @@
21213
21255
  /**
21214
21256
  Move the selection head to the next line boundary.
21215
21257
  */
21216
- const selectLineBoundaryForward = view => extendSel(view, range => moveByLineBoundary(view, range, true));
21258
+ const selectLineBoundaryForward = view => extendSel(view, true, range => moveByLineBoundary(view, range, true));
21217
21259
  /**
21218
21260
  Move the selection head to the previous line boundary.
21219
21261
  */
21220
- const selectLineBoundaryBackward = view => extendSel(view, range => moveByLineBoundary(view, range, false));
21262
+ const selectLineBoundaryBackward = view => extendSel(view, false, range => moveByLineBoundary(view, range, false));
21221
21263
  /**
21222
21264
  Move the selection head one line boundary to the left.
21223
21265
  */
21224
- const selectLineBoundaryLeft = view => extendSel(view, range => moveByLineBoundary(view, range, !ltrAtCursor(view)));
21266
+ const selectLineBoundaryLeft = view => {
21267
+ let forward = !ltrAtCursor(view);
21268
+ return extendSel(view, forward, range => moveByLineBoundary(view, range, forward));
21269
+ };
21225
21270
  /**
21226
21271
  Move the selection head one line boundary to the right.
21227
21272
  */
21228
- const selectLineBoundaryRight = view => extendSel(view, range => moveByLineBoundary(view, range, ltrAtCursor(view)));
21273
+ const selectLineBoundaryRight = view => {
21274
+ let forward = ltrAtCursor(view);
21275
+ return extendSel(view, forward, range => moveByLineBoundary(view, range, forward));
21276
+ };
21229
21277
  /**
21230
21278
  Move the selection head to the start of the line.
21231
21279
  */
21232
- const selectLineStart = view => extendSel(view, range => EditorSelection.cursor(view.lineBlockAt(range.head).from));
21280
+ const selectLineStart = view => extendSel(view, false, range => EditorSelection.cursor(view.lineBlockAt(range.head).from));
21233
21281
  /**
21234
21282
  Move the selection head to the end of the line.
21235
21283
  */
21236
- const selectLineEnd = view => extendSel(view, range => EditorSelection.cursor(view.lineBlockAt(range.head).to));
21284
+ const selectLineEnd = view => extendSel(view, true, range => EditorSelection.cursor(view.lineBlockAt(range.head).to));
21237
21285
  /**
21238
21286
  Move the selection to the start of the document.
21239
21287
  */
@@ -22811,6 +22859,7 @@
22811
22859
  else if (next.from == from && next.to == to) {
22812
22860
  replacement = state.toText(query.getReplacement(next));
22813
22861
  changes.push({ from: next.from, to: next.to, insert: replacement });
22862
+ next = query.nextMatch(state, next.from, next.to);
22814
22863
  effects.push(EditorView.announce.of(state.phrase("replaced match on line $", state.doc.lineAt(from).number) + "."));
22815
22864
  }
22816
22865
  let changeSet = view.state.changes(changes);
@@ -0,0 +1,451 @@
1
+ import test from "node:test";
2
+ import { deepEqual } from "node:assert/strict";
3
+ import { QyDB } from "../src/QyDB.js";
4
+ import { logger } from "../src/QyLogger.js";
5
+ logger.level = "warn";
6
+ // FOR doc IN employees
7
+ // COLLECT department = doc.department
8
+ // AGGREGATE count = COUNT(doc)
9
+ // RETURN {department: department, employeeCount: count}
10
+ function query1(qyDB) {
11
+ deepEqual(Object.fromEntries(qyDB
12
+ .indexDir("employees", "department")
13
+ .fileList.map((file) => [file.name, Object.keys(file.fileContent).length])), { sales: 4, product: 2, "R&D": 2 });
14
+ }
15
+ // FOR doc IN employees
16
+ // LET isOlder = doc.age > 30
17
+ // LET isSales = doc.department == "sales"
18
+ // FILTER isOlder && isSales
19
+ // RETURN doc
20
+ function query2(qyDB) {
21
+ deepEqual(Object.fromEntries(qyDB.queryFiles("employees", { department: "sales", age: { $gt: 30 } }).map((x) => [x.name, x.view()])), {
22
+ employee_1_1: { age: 31, department: "sales" },
23
+ employee_1_5: { age: 36, department: "sales" },
24
+ employee_1_6: { age: 36, department: "sales" },
25
+ });
26
+ }
27
+ function query3(qyDB) {
28
+ deepEqual(Object.fromEntries(qyDB
29
+ .queryFiles("employees", { $and: [{ department: "sales" }, { $and: [{ age: { $gt: 30, $lt: 60 } }] }] })
30
+ .map((x) => [x.name, x.view()])), {
31
+ employee_1_1: { age: 31, department: "sales" },
32
+ employee_1_5: { age: 36, department: "sales" },
33
+ employee_1_6: { age: 36, department: "sales" },
34
+ });
35
+ }
36
+ function query4(qyDB) {
37
+ const result = {};
38
+ qyDB.fetch
39
+ .cd("employees")
40
+ .queryFiles(".", { department: "sales", age: { $gt: 30 } })
41
+ .run()[0]
42
+ .forEach((x) => (result[x.name] = x.view()));
43
+ deepEqual(result, {
44
+ employee_1_1: { age: 31, department: "sales" },
45
+ employee_1_5: { age: 36, department: "sales" },
46
+ employee_1_6: { age: 36, department: "sales" },
47
+ });
48
+ }
49
+ function query5(qyDB) {
50
+ const result = {};
51
+ qyDB.fetch
52
+ .queryFiles("employees", {
53
+ department: (x) => x == "sales",
54
+ age: (x) => x > 30,
55
+ })
56
+ .run()[0]
57
+ .forEach((x) => (result[x.name] = x.view()));
58
+ deepEqual(result, {
59
+ employee_1_1: { age: 31, department: "sales" },
60
+ employee_1_5: { age: 36, department: "sales" },
61
+ employee_1_6: { age: 36, department: "sales" },
62
+ });
63
+ }
64
+ function query6(qyDB) {
65
+ const result = qyDB
66
+ .queryFiles("employees", {
67
+ department: (x) => x != "sales",
68
+ age: (x) => x > 30,
69
+ })
70
+ .reduce((map, x) => {
71
+ map[x.name] = x.view();
72
+ return map;
73
+ }, {});
74
+ deepEqual(result, {
75
+ employee_1_9: { age: 46, department: "R&D" },
76
+ employee_1_2: { age: 38, department: "product" },
77
+ employee_1_7: { age: 36, department: "product" },
78
+ });
79
+ }
80
+ function query7(qyDB) {
81
+ const result = {};
82
+ const queryObj = {
83
+ department: (x) => x == "sales",
84
+ age: (x) => x > 30,
85
+ };
86
+ ["employees", "employees/*/*"].forEach((folderPath) => {
87
+ qyDB.fetch
88
+ .queryFiles(folderPath, queryObj)
89
+ .run()[0]
90
+ .forEach((x) => (result[x.fullPath] = x.view()));
91
+ });
92
+ deepEqual(result, {
93
+ "/employees/2024/12/employee_3_1": { department: "sales", age: 31 },
94
+ "/employees/2025/03/employee_2_1": { department: "sales", age: 31 },
95
+ "/employees/employee_1_1": { department: "sales", age: 31 },
96
+ "/employees/2024/12/employee_3_5": { department: "sales", age: 36 },
97
+ "/employees/2025/03/employee_2_5": { department: "sales", age: 36 },
98
+ "/employees/employee_1_5": { department: "sales", age: 36 },
99
+ "/employees/2024/12/employee_3_6": { department: "sales", age: 36 },
100
+ "/employees/2025/03/employee_2_6": { department: "sales", age: 36 },
101
+ "/employees/employee_1_6": { department: "sales", age: 36 },
102
+ });
103
+ }
104
+ function query8(qyDB) {
105
+ const result = ["employees", ["employees", "*", "*"]]
106
+ .map((folderPath) => qyDB.queryFiles(folderPath, {
107
+ department: (x) => x != "sales",
108
+ age: (x) => x > 30,
109
+ }))
110
+ .flat()
111
+ .reduce((map, x) => {
112
+ map[x.fullPath] = x.view();
113
+ return map;
114
+ }, {});
115
+ deepEqual(result, {
116
+ "/employees/2024/12/employee_3_2": { department: "product", age: 38 },
117
+ "/employees/2025/03/employee_2_2": { department: "product", age: 38 },
118
+ "/employees/employee_1_2": { department: "product", age: 38 },
119
+ "/employees/2024/12/employee_3_7": { department: "product", age: 36 },
120
+ "/employees/2025/03/employee_2_7": { department: "product", age: 36 },
121
+ "/employees/employee_1_7": { department: "product", age: 36 },
122
+ "/employees/2024/12/employee_3_9": { department: "R&D", age: 46 },
123
+ "/employees/2025/03/employee_2_9": { department: "R&D", age: 46 },
124
+ "/employees/employee_1_9": { department: "R&D", age: 46 },
125
+ });
126
+ }
127
+ function query9(qyDB) {
128
+ const queryObj = { $and: [{ department: (x) => x != "sales" }, { age: (x) => x > 30 }] };
129
+ deepEqual(qyDB.fetch
130
+ .cd("employees")
131
+ .queryFiles("2024/12", queryObj)
132
+ .queryFiles("2025/03", queryObj)
133
+ .queryFiles(".", queryObj)
134
+ .run()
135
+ .map((list) => list.reduce((map, x) => {
136
+ map[x.fullPath] = x.view();
137
+ return map;
138
+ }, {})), [
139
+ {
140
+ "/employees/2024/12/employee_3_2": { department: "product", age: 38 },
141
+ "/employees/2024/12/employee_3_7": { department: "product", age: 36 },
142
+ "/employees/2024/12/employee_3_9": { department: "R&D", age: 46 },
143
+ },
144
+ {
145
+ "/employees/2025/03/employee_2_2": { department: "product", age: 38 },
146
+ "/employees/2025/03/employee_2_7": { department: "product", age: 36 },
147
+ "/employees/2025/03/employee_2_9": { department: "R&D", age: 46 },
148
+ },
149
+ {
150
+ "/employees/employee_1_2": { department: "product", age: 38 },
151
+ "/employees/employee_1_7": { department: "product", age: 36 },
152
+ "/employees/employee_1_9": { department: "R&D", age: 46 },
153
+ },
154
+ ]);
155
+ }
156
+ function query10(qyDB) {
157
+ const queryObj = {
158
+ $or: [{ department: (x) => x != "sales", age: (x) => x > 36 }, { age: (x) => x > 40 }],
159
+ };
160
+ deepEqual(qyDB.fetch
161
+ .cd("employees")
162
+ .queryFiles("*/*", queryObj)
163
+ .queryFiles(".", queryObj)
164
+ .run()
165
+ .map((list) => list.reduce((map, x) => {
166
+ map[x.fullPath] = x.view();
167
+ return map;
168
+ }, {})), [
169
+ {
170
+ "/employees/2024/12/employee_3_2": { department: "product", age: 38 },
171
+ "/employees/2024/12/employee_3_9": { department: "R&D", age: 46 },
172
+ "/employees/2025/03/employee_2_2": { department: "product", age: 38 },
173
+ "/employees/2025/03/employee_2_9": { department: "R&D", age: 46 },
174
+ },
175
+ {
176
+ "/employees/employee_1_2": { department: "product", age: 38 },
177
+ "/employees/employee_1_9": { department: "R&D", age: 46 },
178
+ },
179
+ ]);
180
+ }
181
+ function query11(qyDB) {
182
+ const queryObj = {
183
+ $and: [
184
+ {
185
+ $or: [{ department: (x) => x != "sales", age: (x) => x > 36 }],
186
+ },
187
+ { age: (x) => x > 40 },
188
+ ],
189
+ };
190
+ deepEqual(qyDB.fetch
191
+ .cd("employees")
192
+ .queryFiles("*/*", queryObj)
193
+ .queryFiles(".", queryObj)
194
+ .run()
195
+ .map((list) => list.reduce((map, x) => {
196
+ map[x.fullPath] = x.view();
197
+ return map;
198
+ }, {})), [
199
+ {
200
+ "/employees/2024/12/employee_3_9": { department: "R&D", age: 46 },
201
+ "/employees/2025/03/employee_2_9": { department: "R&D", age: 46 },
202
+ },
203
+ { "/employees/employee_1_9": { department: "R&D", age: 46 } },
204
+ ]);
205
+ }
206
+ function query12(qyDB) {
207
+ const queryObj = {
208
+ $and: { department: (x) => x != "sales", age: (x) => x > 36 },
209
+ age: (x) => x > 40,
210
+ };
211
+ deepEqual(qyDB.fetch
212
+ .cd("employees")
213
+ .queryFiles([(x) => x >= 2024 && x <= 2025, (x) => x != 10], queryObj, 2)
214
+ .queryFiles(".", queryObj, 2)
215
+ .run()
216
+ .map((list) => list.reduce((map, x) => {
217
+ map[x.fullPath] = x.view();
218
+ return map;
219
+ }, {})), [
220
+ {
221
+ "/employees/2024/12/employee_3_9": { department: "R&D", age: 46 },
222
+ "/employees/2025/03/employee_2_9": { age: 46, department: "R&D" },
223
+ },
224
+ { "/employees/employee_1_9": { department: "R&D", age: 46 } },
225
+ ]);
226
+ }
227
+ function query13(qyDB) {
228
+ const queryObj = { $and: { department: (x) => x != "sales", age: (x) => x > 36 }, age: (x) => x > 40 };
229
+ deepEqual(qyDB.fetch
230
+ .cd("employees")
231
+ .queryFiles([(x) => x >= 2024 && x <= 2025, (x) => x != 10], queryObj, 1)
232
+ .queryFiles(".", queryObj, 1)
233
+ .run()
234
+ .map((list) => list.reduce((map, x) => {
235
+ map[x.fullPath] = x.view();
236
+ return map;
237
+ }, {})), [
238
+ { "/employees/2024/12/employee_3_9": { department: "R&D", age: 46 } },
239
+ { "/employees/employee_1_9": { department: "R&D", age: 46 } },
240
+ ]);
241
+ }
242
+ function query14(qyDB) {
243
+ const queryObj = { $and: { department: (x) => x != "sales", age: (x) => x > 36 }, age: (x) => x > 40 };
244
+ deepEqual(qyDB.fetch
245
+ .cd("employees")
246
+ .queryFiles([(x) => x >= 2024 && x <= 2025, (x) => x != 10], queryObj, 1, 1)
247
+ .queryFiles(".", queryObj, 1, 1)
248
+ .run()
249
+ .map((list) => list.reduce((map, x) => {
250
+ map[x.fullPath] = x.view();
251
+ return map;
252
+ }, {})), [{ "/employees/2025/03/employee_2_9": { age: 46, department: "R&D" } }, {}]);
253
+ deepEqual(qyDB.fetch
254
+ .cd("employees")
255
+ .queryFiles([{ $gte: 2024, $lte: 2025 }, (x) => x != 10], queryObj, 1, 1)
256
+ .queryFiles(".", queryObj, 1, 1)
257
+ .run()
258
+ .map((list) => list.reduce((map, x) => {
259
+ map[x.fullPath] = x.view();
260
+ return map;
261
+ }, {})), [{ "/employees/2025/03/employee_2_9": { age: 46, department: "R&D" } }, {}]);
262
+ deepEqual(qyDB.fetch
263
+ .cd("employees")
264
+ .queryFiles([{ $gt: 2023, $gte: 2024, $lte: 2025, $lt: 2026 }, (x) => x != 10], queryObj, 1, 1)
265
+ .queryFiles(".", queryObj, 1, 1)
266
+ .run()
267
+ .map((list) => list.reduce((map, x) => {
268
+ map[x.fullPath] = x.view();
269
+ return map;
270
+ }, {})), [{ "/employees/2025/03/employee_2_9": { age: 46, department: "R&D" } }, {}]);
271
+ deepEqual(qyDB.fetch
272
+ .cd("employees")
273
+ .queryFiles([{ $gt: 2023, $gte: 2024, $lte: 2025, $lt: 2025 }, (x) => x != 10], queryObj, 1, 1)
274
+ .queryFiles(".", queryObj, 1, 1)
275
+ .run(), [[], []]);
276
+ deepEqual(qyDB.fetch
277
+ .cd("employees")
278
+ .queryFiles([{ $gt: 2024, $gte: 2024, $lte: 2025, $lt: 2026 }, (x) => x != 10], queryObj, 1, 1)
279
+ .queryFiles(".", queryObj, 1, 1)
280
+ .run(), [[], []]);
281
+ deepEqual(qyDB.fetch
282
+ .cd("employees")
283
+ .queryFiles([{ $or: [2024, 2025] }, (x) => x != 10], queryObj, 1, 1)
284
+ .queryFiles(".", queryObj, 1, 1)
285
+ .run()
286
+ .map((list) => list.reduce((map, x) => {
287
+ map[x.fullPath] = x.view();
288
+ return map;
289
+ }, {})), [{ "/employees/2025/03/employee_2_9": { age: 46, department: "R&D" } }, {}]);
290
+ deepEqual(qyDB.fetch
291
+ .cd("employees")
292
+ .queryFiles([{ $in: [2023, 2024, 2025] }, (x) => x != 10], queryObj, 1, 1)
293
+ .queryFiles(".", queryObj, 1, 1)
294
+ .run()
295
+ .map((list) => list.reduce((map, x) => {
296
+ map[x.fullPath] = x.view();
297
+ return map;
298
+ }, {})), [{ "/employees/2025/03/employee_2_9": { age: 46, department: "R&D" } }, {}]);
299
+ }
300
+ function query15(qyDB) {
301
+ const queryObj = { $and: { department: (x) => x != "sales", age: (x) => x > 36 }, age: (x) => x > 40 };
302
+ deepEqual(qyDB.fetch
303
+ .cd("employees")
304
+ .queryFiles({
305
+ $or: [".", [(x) => x >= 2024 && x <= 2025, (x) => x != 10]],
306
+ }, queryObj, 10, 0)
307
+ .run()
308
+ .map((list) => list.reduce((map, x) => {
309
+ map[x.fullPath] = x.view();
310
+ return map;
311
+ }, {})), [
312
+ {
313
+ "/employees/2024/12/employee_3_9": { age: 46, department: "R&D" },
314
+ "/employees/2025/03/employee_2_9": { age: 46, department: "R&D" },
315
+ "/employees/employee_1_9": { age: 46, department: "R&D" },
316
+ },
317
+ ]);
318
+ }
319
+ function query16(qyDB) {
320
+ const queryObj = {
321
+ $and: { department: (x) => x != "sales", age: (x) => x > 36 },
322
+ age: (x) => x > 40,
323
+ };
324
+ deepEqual(qyDB.fetch
325
+ .cd("employees")
326
+ .queryFilesMulti([["./**", queryObj]])
327
+ .run()
328
+ .map((list) => list.reduce((map, x) => {
329
+ map[x.fullPath] = x.view();
330
+ return map;
331
+ }, {})), [
332
+ {
333
+ "/employees/2025/03/employee_2_9": { age: 46, department: "R&D" },
334
+ "/employees/2024/12/employee_3_9": { age: 46, department: "R&D" },
335
+ "/employees/employee_1_9": { age: 46, department: "R&D" },
336
+ },
337
+ ]);
338
+ deepEqual(qyDB.fetch
339
+ .cd("employees")
340
+ .queryFilesMulti([["./**", queryObj]], 1, 1)
341
+ .run()
342
+ .map((list) => list.reduce((map, x) => {
343
+ map[x.fullPath] = x.view();
344
+ return map;
345
+ }, {})), [{ "/employees/2024/12/employee_3_9": { age: 46, department: "R&D" } }]);
346
+ }
347
+ function query17(qyDB) {
348
+ const result = {};
349
+ qyDB.fetch
350
+ .queryFiles("employees", {
351
+ department: { $or: ["sales", "R&D", "UNEXISTED"] },
352
+ age: (x) => x > 30,
353
+ })
354
+ .run()[0]
355
+ .forEach((x) => (result[x.name] = x.view()));
356
+ deepEqual(result, {
357
+ employee_1_1: { age: 31, department: "sales" },
358
+ employee_1_5: { age: 36, department: "sales" },
359
+ employee_1_6: { age: 36, department: "sales" },
360
+ employee_1_9: { age: 46, department: "R&D" },
361
+ });
362
+ }
363
+ function query18(qyDB) {
364
+ const result = {};
365
+ qyDB.fetch
366
+ .queryFiles("employees", "employee_1_1")
367
+ .run()[0]
368
+ .forEach((x) => (result[x.name] = x.view()));
369
+ deepEqual(result, {
370
+ employee_1_1: { age: 31, department: "sales" },
371
+ });
372
+ const result2 = {};
373
+ qyDB.queryFiles("**", "employee_1_1").forEach((x) => (result2[x.name] = x.view()));
374
+ deepEqual(result2, {
375
+ employee_1_1: { age: 31, department: "sales" },
376
+ });
377
+ }
378
+ function query19(qyDB) {
379
+ const result = {};
380
+ qyDB.fetch
381
+ .cd("employees")
382
+ .queryFiles("**", "employee_3_2")
383
+ .run()[0]
384
+ .forEach((x) => (result[x.name] = x.view()));
385
+ deepEqual(result, {
386
+ employee_3_2: { age: 38, department: "product" },
387
+ });
388
+ const result2 = {};
389
+ qyDB.queryFiles("**", "employee_2_1").forEach((x) => (result2[x.name] = x.view()));
390
+ deepEqual(result2, {
391
+ employee_2_1: { age: 31, department: "sales" },
392
+ });
393
+ }
394
+ let count = 0;
395
+ function getEmployeeData() {
396
+ ++count;
397
+ return {
398
+ [`employee_${count}_1`]: { department: "sales", age: 31 },
399
+ [`employee_${count}_2`]: { department: "product", age: 38 },
400
+ [`employee_${count}_3`]: { department: "R&D", age: 26 },
401
+ [`employee_${count}_4`]: { department: "sales", age: 25 },
402
+ [`employee_${count}_5`]: { department: "sales", age: 36 },
403
+ [`employee_${count}_6`]: { department: "sales", age: 36 },
404
+ [`employee_${count}_7`]: { department: "product", age: 36 },
405
+ [`employee_${count}_9`]: { department: "R&D", age: 46 },
406
+ };
407
+ }
408
+ function insertData(qyDB, dirPath) {
409
+ const { batch } = qyDB;
410
+ batch.cd(dirPath);
411
+ const employeeData = getEmployeeData();
412
+ for (const key in employeeData) {
413
+ batch.insert(key, employeeData[key]);
414
+ }
415
+ batch.run();
416
+ }
417
+ test("test QyAQL", { skip: false }, async (context) => {
418
+ context.after(async (t) => {
419
+ await qyDB.stop();
420
+ });
421
+ const dataFolder = "./tmp/QyAQL/1";
422
+ const qyDB = new QyDB(dataFolder, {});
423
+ await qyDB.start();
424
+ qyDB.delDir("employees");
425
+ qyDB.createIndex("employees", "department");
426
+ insertData(qyDB, "employees");
427
+ query1(qyDB);
428
+ insertData(qyDB, ["employees", "2025", "03"]);
429
+ insertData(qyDB, ["employees", "2024", "12"]);
430
+ await qyDB.stop();
431
+ await qyDB.start();
432
+ query1(qyDB);
433
+ query2(qyDB);
434
+ query3(qyDB);
435
+ query4(qyDB);
436
+ query5(qyDB);
437
+ query6(qyDB);
438
+ query7(qyDB);
439
+ query8(qyDB);
440
+ query9(qyDB);
441
+ query10(qyDB);
442
+ query11(qyDB);
443
+ query12(qyDB);
444
+ query13(qyDB);
445
+ query14(qyDB);
446
+ query15(qyDB);
447
+ query16(qyDB);
448
+ query17(qyDB);
449
+ query18(qyDB);
450
+ query19(qyDB);
451
+ });