document-dataply 0.0.9-alpha.3 → 0.0.9-alpha.5

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/cjs/index.js CHANGED
@@ -10081,9 +10081,24 @@ function compareValue(a, b) {
10081
10081
  }
10082
10082
  return aList.length - bList.length;
10083
10083
  }
10084
+ function comparePrimaryValue(a, b) {
10085
+ const aArr = Array.isArray(a);
10086
+ const bArr = Array.isArray(b);
10087
+ if (!aArr && !bArr) {
10088
+ return comparePrimitive(a, b);
10089
+ }
10090
+ const aList = aArr ? a : [a];
10091
+ const bList = bArr ? b : [b];
10092
+ const len = Math.min(aList.length, bList.length);
10093
+ for (let i = 0; i < len; i++) {
10094
+ const diff = comparePrimitive(aList[i], bList[i]);
10095
+ if (diff !== 0) return diff;
10096
+ }
10097
+ return 0;
10098
+ }
10084
10099
  var DocumentValueComparator = class extends import_dataply2.ValueComparator {
10085
10100
  primaryAsc(a, b) {
10086
- return compareValue(a.v, b.v);
10101
+ return comparePrimaryValue(a.v, b.v);
10087
10102
  }
10088
10103
  asc(a, b) {
10089
10104
  const diff = compareValue(a.v, b.v);
@@ -10417,19 +10432,39 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10417
10432
  * Convert CreateIndexOption to IndexMetaConfig for metadata storage.
10418
10433
  */
10419
10434
  toIndexMetaConfig(option) {
10435
+ if (!option || typeof option !== "object") {
10436
+ throw new Error("Index option must be a non-null object");
10437
+ }
10438
+ if (!option.type) {
10439
+ throw new Error('Index option must have a "type" property ("btree" or "fts")');
10440
+ }
10420
10441
  if (option.type === "btree") {
10442
+ if (!Array.isArray(option.fields) || option.fields.length === 0) {
10443
+ throw new Error('btree index requires a non-empty "fields" array');
10444
+ }
10445
+ for (let i = 0; i < option.fields.length; i++) {
10446
+ if (typeof option.fields[i] !== "string" || option.fields[i].length === 0) {
10447
+ throw new Error(`btree index "fields[${i}]" must be a non-empty string, got: ${JSON.stringify(option.fields[i])}`);
10448
+ }
10449
+ }
10421
10450
  return {
10422
10451
  type: "btree",
10423
10452
  fields: option.fields
10424
10453
  };
10425
10454
  }
10426
10455
  if (option.type === "fts") {
10456
+ if (typeof option.fields !== "string" || option.fields.length === 0) {
10457
+ throw new Error(`fts index requires a non-empty string "fields", got: ${JSON.stringify(option.fields)}`);
10458
+ }
10427
10459
  if (option.tokenizer === "ngram") {
10460
+ if (typeof option.gramSize !== "number" || option.gramSize < 1) {
10461
+ throw new Error(`fts ngram index requires a positive "gramSize" number, got: ${JSON.stringify(option.gramSize)}`);
10462
+ }
10428
10463
  return {
10429
10464
  type: "fts",
10430
10465
  fields: option.fields,
10431
10466
  tokenizer: "ngram",
10432
- gramSize: option.ngram
10467
+ gramSize: option.gramSize
10433
10468
  };
10434
10469
  }
10435
10470
  return {
@@ -10537,8 +10572,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10537
10572
  if (metadata.lastId === 0) {
10538
10573
  return 0;
10539
10574
  }
10540
- const indexTxMap = {};
10541
- const indexEntryMap = /* @__PURE__ */ new Map();
10575
+ let indexTxMap = {};
10542
10576
  for (const indexName of backfillTargets) {
10543
10577
  const tree = this.trees.get(indexName);
10544
10578
  if (tree && indexName !== "_id") {
@@ -10546,6 +10580,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10546
10580
  }
10547
10581
  }
10548
10582
  let backfilledCount = 0;
10583
+ let chunkCount = 0;
10584
+ const CHUNK_SIZE = 1e3;
10549
10585
  const idTree = this.trees.get("_id");
10550
10586
  if (!idTree) {
10551
10587
  throw new Error("ID tree not found");
@@ -10574,10 +10610,6 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10574
10610
  const keyToInsert = this.getTokenKey(k, token);
10575
10611
  const entry = { k, v: token };
10576
10612
  batchInsertData.push([keyToInsert, entry]);
10577
- if (!indexEntryMap.has(btx)) {
10578
- indexEntryMap.set(btx, []);
10579
- }
10580
- indexEntryMap.get(btx).push({ k: keyToInsert, v: entry });
10581
10613
  }
10582
10614
  await btx.batchInsert(batchInsertData);
10583
10615
  } else {
@@ -10585,34 +10617,42 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10585
10617
  if (indexVal === void 0) continue;
10586
10618
  const entry = { k, v: indexVal };
10587
10619
  const batchInsertData = [[k, entry]];
10588
- if (!indexEntryMap.has(btx)) {
10589
- indexEntryMap.set(btx, []);
10590
- }
10591
- indexEntryMap.get(btx).push(entry);
10592
10620
  await btx.batchInsert(batchInsertData);
10593
10621
  }
10594
10622
  }
10595
10623
  backfilledCount++;
10596
- }
10597
- const btxs = Object.values(indexTxMap);
10598
- const success = [];
10599
- try {
10600
- for (const btx of btxs) {
10601
- await btx.commit();
10602
- success.push(btx);
10603
- }
10604
- } catch (err) {
10605
- for (const btx of btxs) {
10606
- await btx.rollback();
10624
+ chunkCount++;
10625
+ if (chunkCount >= CHUNK_SIZE) {
10626
+ try {
10627
+ for (const btx of Object.values(indexTxMap)) {
10628
+ await btx.commit();
10629
+ }
10630
+ } catch (err) {
10631
+ for (const btx of Object.values(indexTxMap)) {
10632
+ await btx.rollback();
10633
+ }
10634
+ throw err;
10635
+ }
10636
+ for (const indexName of backfillTargets) {
10637
+ const tree = this.trees.get(indexName);
10638
+ if (tree && indexName !== "_id") {
10639
+ indexTxMap[indexName] = await tree.createTransaction();
10640
+ }
10641
+ }
10642
+ chunkCount = 0;
10607
10643
  }
10608
- for (const btx of success) {
10609
- const entries = indexEntryMap.get(btx);
10610
- if (!entries) continue;
10611
- for (const entry of entries) {
10612
- await btx.delete(entry.k, entry);
10644
+ }
10645
+ if (chunkCount > 0) {
10646
+ try {
10647
+ for (const btx of Object.values(indexTxMap)) {
10648
+ await btx.commit();
10613
10649
  }
10650
+ } catch (err) {
10651
+ for (const btx of Object.values(indexTxMap)) {
10652
+ await btx.rollback();
10653
+ }
10654
+ throw err;
10614
10655
  }
10615
- throw err;
10616
10656
  }
10617
10657
  this.pendingBackfillFields = [];
10618
10658
  return backfilledCount;
@@ -10706,9 +10746,10 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10706
10746
  const currentVersion = innerMetadata.schemeVersion ?? 0;
10707
10747
  if (currentVersion < version) {
10708
10748
  await callback(tx2);
10709
- innerMetadata.schemeVersion = version;
10710
- innerMetadata.updatedAt = Date.now();
10711
- await this.updateDocumentInnerMetadata(innerMetadata, tx2);
10749
+ const freshMetadata = await this.getDocumentInnerMetadata(tx2);
10750
+ freshMetadata.schemeVersion = version;
10751
+ freshMetadata.updatedAt = Date.now();
10752
+ await this.updateDocumentInnerMetadata(freshMetadata, tx2);
10712
10753
  }
10713
10754
  }, tx);
10714
10755
  }
@@ -10769,27 +10810,61 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10769
10810
  const condition = query[primaryField];
10770
10811
  const treeTx = await tree.createTransaction();
10771
10812
  let score = 0;
10772
- const coveredFields = config.fields.filter((f) => queryFields.has(f));
10773
- score += coveredFields.length;
10774
- if (condition) {
10775
- if (typeof condition !== "object" || condition === null) {
10776
- score += 100;
10777
- } else if ("primaryEqual" in condition || "equal" in condition) {
10778
- score += 100;
10779
- } else if ("primaryGte" in condition || "primaryLte" in condition || "primaryGt" in condition || "primaryLt" in condition || "gte" in condition || "lte" in condition || "gt" in condition || "lt" in condition) {
10780
- score += 50;
10781
- } else if ("primaryOr" in condition || "or" in condition) {
10782
- score += 20;
10783
- } else if ("like" in condition) {
10784
- score += 15;
10785
- } else {
10786
- score += 10;
10813
+ let isConsecutive = true;
10814
+ const coveredFields = [];
10815
+ const compositeVerifyFields = [];
10816
+ for (const field of config.fields) {
10817
+ if (!queryFields.has(field)) {
10818
+ isConsecutive = false;
10819
+ continue;
10820
+ }
10821
+ coveredFields.push(field);
10822
+ if (field !== primaryField) {
10823
+ compositeVerifyFields.push(field);
10824
+ }
10825
+ score += 1;
10826
+ if (isConsecutive) {
10827
+ const cond = query[field];
10828
+ if (cond !== void 0) {
10829
+ if (typeof cond !== "object" || cond === null) {
10830
+ score += 100;
10831
+ } else if ("primaryEqual" in cond || "equal" in cond) {
10832
+ score += 100;
10833
+ } else if ("primaryGte" in cond || "primaryLte" in cond || "primaryGt" in cond || "primaryLt" in cond || "gte" in cond || "lte" in cond || "gt" in cond || "lt" in cond) {
10834
+ score += 50;
10835
+ isConsecutive = false;
10836
+ } else if ("primaryOr" in cond || "or" in cond) {
10837
+ score += 20;
10838
+ isConsecutive = false;
10839
+ } else if ("like" in cond) {
10840
+ score += 15;
10841
+ isConsecutive = false;
10842
+ } else {
10843
+ score += 10;
10844
+ isConsecutive = false;
10845
+ }
10846
+ }
10787
10847
  }
10788
10848
  }
10789
- if (orderByField && primaryField === orderByField) {
10790
- score += 200;
10849
+ let isIndexOrderSupported = false;
10850
+ if (orderByField) {
10851
+ for (const field of config.fields) {
10852
+ if (field === orderByField) {
10853
+ isIndexOrderSupported = true;
10854
+ break;
10855
+ }
10856
+ const cond = query[field];
10857
+ let isExactMatch = false;
10858
+ if (cond !== void 0) {
10859
+ if (typeof cond !== "object" || cond === null) isExactMatch = true;
10860
+ else if ("primaryEqual" in cond || "equal" in cond) isExactMatch = true;
10861
+ }
10862
+ if (!isExactMatch) break;
10863
+ }
10864
+ if (isIndexOrderSupported) {
10865
+ score += 200;
10866
+ }
10791
10867
  }
10792
- const compositeVerifyFields = coveredFields.filter((f) => f !== primaryField);
10793
10868
  candidates.push({
10794
10869
  tree: treeTx,
10795
10870
  condition,
@@ -10797,7 +10872,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10797
10872
  indexName,
10798
10873
  isFtsMatch: false,
10799
10874
  score,
10800
- compositeVerifyFields
10875
+ compositeVerifyFields,
10876
+ isIndexOrderSupported
10801
10877
  });
10802
10878
  } else if (config.type === "fts") {
10803
10879
  const field = config.fields;
@@ -10815,7 +10891,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10815
10891
  isFtsMatch: true,
10816
10892
  matchTokens,
10817
10893
  score: 90,
10818
- compositeVerifyFields: []
10894
+ compositeVerifyFields: [],
10895
+ isIndexOrderSupported: false
10819
10896
  });
10820
10897
  }
10821
10898
  }
@@ -10858,33 +10935,30 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10858
10935
  getTokenKey(pk, token) {
10859
10936
  return pk + ":" + token;
10860
10937
  }
10861
- async applyCandidateByFTS(candidate, matchedTokens, filterValues, order) {
10938
+ async *applyCandidateByFTSStream(candidate, matchedTokens, filterValues, order) {
10862
10939
  const keys = /* @__PURE__ */ new Set();
10863
10940
  for (let i = 0, len = matchedTokens.length; i < len; i++) {
10864
10941
  const token = matchedTokens[i];
10865
- const pairs = await candidate.tree.where(
10942
+ for await (const pair of candidate.tree.whereStream(
10866
10943
  { primaryEqual: { v: token } },
10867
- {
10868
- order
10944
+ { order }
10945
+ )) {
10946
+ const pk = pair[1].k;
10947
+ if (filterValues && !filterValues.has(pk)) continue;
10948
+ if (!keys.has(pk)) {
10949
+ keys.add(pk);
10950
+ yield pk;
10869
10951
  }
10870
- );
10871
- for (const pair of pairs.values()) {
10872
- if (filterValues && !filterValues.has(pair.k)) continue;
10873
- keys.add(pair.k);
10874
10952
  }
10875
10953
  }
10876
- return keys;
10877
10954
  }
10878
10955
  /**
10879
10956
  * 특정 인덱스 후보를 조회하여 PK 집합을 필터링합니다.
10880
10957
  */
10881
- async applyCandidate(candidate, filterValues, order) {
10882
- return await candidate.tree.keys(
10958
+ applyCandidateStream(candidate, filterValues, order) {
10959
+ return candidate.tree.keysStream(
10883
10960
  candidate.condition,
10884
- {
10885
- filterValues,
10886
- order
10887
- }
10961
+ { filterValues, order }
10888
10962
  );
10889
10963
  }
10890
10964
  /**
@@ -10900,30 +10974,34 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10900
10974
  );
10901
10975
  if (!selectivity) return new Float64Array(0);
10902
10976
  const { driver, others, rollback } = selectivity;
10903
- const useIndexOrder = orderBy === void 0 || driver.field === orderBy;
10977
+ const useIndexOrder = orderBy === void 0 || driver.isIndexOrderSupported;
10904
10978
  const candidates = [driver, ...others];
10905
10979
  let keys = void 0;
10906
10980
  for (let i = 0, len = candidates.length; i < len; i++) {
10907
10981
  const candidate = candidates[i];
10908
10982
  const currentOrder = useIndexOrder ? sortOrder : void 0;
10909
10983
  if (candidate.isFtsMatch && candidate.matchTokens && candidate.matchTokens.length > 0) {
10910
- keys = await this.applyCandidateByFTS(
10984
+ const stream = this.applyCandidateByFTSStream(
10911
10985
  candidate,
10912
10986
  candidate.matchTokens,
10913
10987
  keys,
10914
10988
  currentOrder
10915
10989
  );
10990
+ keys = /* @__PURE__ */ new Set();
10991
+ for await (const pk of stream) keys.add(pk);
10916
10992
  } else {
10917
- keys = await this.applyCandidate(candidate, keys, currentOrder);
10993
+ const stream = this.applyCandidateStream(candidate, keys, currentOrder);
10994
+ keys = /* @__PURE__ */ new Set();
10995
+ for await (const pk of stream) keys.add(pk);
10918
10996
  }
10919
10997
  }
10920
10998
  rollback();
10921
10999
  return new Float64Array(Array.from(keys || []));
10922
11000
  }
10923
11001
  /**
10924
- * 드라이버 인덱스만으로 PK 가져옵니다. (교집합 없이)
11002
+ * 드라이버 인덱스만으로 PK 스트림을 가져옵니다. (교집합 없이)
10925
11003
  * selectDocuments에서 사용하며, 나머지 조건(others)은 스트리밍 중 tree.verify()로 검증합니다.
10926
- * @returns 드라이버 키 배열, others 후보 목록, rollback 함수. 또는 null.
11004
+ * @returns 드라이버 키 스트림, others 후보 목록, rollback 함수. 또는 null.
10927
11005
  */
10928
11006
  async getDriverKeys(query, orderBy, sortOrder = "asc") {
10929
11007
  const isQueryEmpty = Object.keys(query).length === 0;
@@ -10934,21 +11012,21 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
10934
11012
  );
10935
11013
  if (!selectivity) return null;
10936
11014
  const { driver, others, compositeVerifyConditions, rollback } = selectivity;
10937
- const useIndexOrder = orderBy === void 0 || driver.field === orderBy;
11015
+ const useIndexOrder = orderBy === void 0 || driver.isIndexOrderSupported;
10938
11016
  const currentOrder = useIndexOrder ? sortOrder : void 0;
10939
- let keys;
11017
+ let keysStream;
10940
11018
  if (driver.isFtsMatch && driver.matchTokens && driver.matchTokens.length > 0) {
10941
- keys = await this.applyCandidateByFTS(
11019
+ keysStream = this.applyCandidateByFTSStream(
10942
11020
  driver,
10943
11021
  driver.matchTokens,
10944
11022
  void 0,
10945
11023
  currentOrder
10946
11024
  );
10947
11025
  } else {
10948
- keys = await this.applyCandidate(driver, void 0, currentOrder);
11026
+ keysStream = this.applyCandidateStream(driver, void 0, currentOrder);
10949
11027
  }
10950
11028
  return {
10951
- keys: new Float64Array(Array.from(keys)),
11029
+ keysStream,
10952
11030
  others,
10953
11031
  compositeVerifyConditions,
10954
11032
  isDriverOrderByField: useIndexOrder,
@@ -11298,28 +11376,17 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11298
11376
  return currentChunkSize;
11299
11377
  }
11300
11378
  /**
11301
- * Prefetch 방식으로 키 배열을 청크 단위로 조회하여 문서를 순회합니다.
11379
+ * Prefetch 방식으로 키 스트림을 청크 단위로 조회하여 문서를 순회합니다.
11302
11380
  * FTS 검증, 복합 인덱스 검증, others 후보에 대한 tree.verify() 검증을 통과한 문서만 yield 합니다.
11303
11381
  */
11304
- async *processChunkedKeysWithVerify(keys, startIdx, initialChunkSize, ftsConditions, compositeVerifyConditions, others, tx) {
11382
+ async *processChunkedKeysWithVerify(keysStream, startIdx, initialChunkSize, ftsConditions, compositeVerifyConditions, others, tx) {
11305
11383
  const verifyOthers = others.filter((o) => !o.isFtsMatch);
11306
- let i = startIdx;
11307
- const totalKeys = keys.length;
11308
11384
  let currentChunkSize = initialChunkSize;
11309
- let nextChunkPromise = null;
11310
- if (i < totalKeys) {
11311
- const endIdx = Math.min(i + currentChunkSize, totalKeys);
11312
- nextChunkPromise = this.selectMany(keys.subarray(i, endIdx), false, tx);
11313
- i = endIdx;
11314
- }
11315
- while (nextChunkPromise) {
11316
- const rawResults = await nextChunkPromise;
11317
- nextChunkPromise = null;
11318
- if (i < totalKeys) {
11319
- const endIdx = Math.min(i + currentChunkSize, totalKeys);
11320
- nextChunkPromise = this.selectMany(keys.subarray(i, endIdx), false, tx);
11321
- i = endIdx;
11322
- }
11385
+ let chunk = [];
11386
+ let dropped = 0;
11387
+ const processChunk = async (pks) => {
11388
+ const docs = [];
11389
+ const rawResults = await this.selectMany(new Float64Array(pks), false, tx);
11323
11390
  let chunkTotalSize = 0;
11324
11391
  for (let j = 0, len = rawResults.length; j < len; j++) {
11325
11392
  const s = rawResults[j];
@@ -11346,9 +11413,26 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11346
11413
  }
11347
11414
  if (!passed) continue;
11348
11415
  }
11349
- yield doc;
11416
+ docs.push(doc);
11350
11417
  }
11351
11418
  currentChunkSize = this.adjustChunkSize(currentChunkSize, chunkTotalSize);
11419
+ return docs;
11420
+ };
11421
+ for await (const pk of keysStream) {
11422
+ if (dropped < startIdx) {
11423
+ dropped++;
11424
+ continue;
11425
+ }
11426
+ chunk.push(pk);
11427
+ if (chunk.length >= currentChunkSize) {
11428
+ const docs = await processChunk(chunk);
11429
+ for (let j = 0; j < docs.length; j++) yield docs[j];
11430
+ chunk = [];
11431
+ }
11432
+ }
11433
+ if (chunk.length > 0) {
11434
+ const docs = await processChunk(chunk);
11435
+ for (let j = 0; j < docs.length; j++) yield docs[j];
11352
11436
  }
11353
11437
  }
11354
11438
  /**
@@ -11396,11 +11480,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11396
11480
  }
11397
11481
  const driverResult = await self.getDriverKeys(query, orderByField, sortOrder);
11398
11482
  if (!driverResult) return;
11399
- const { keys, others, compositeVerifyConditions, isDriverOrderByField, rollback } = driverResult;
11400
- if (keys.length === 0) {
11401
- rollback();
11402
- return;
11403
- }
11483
+ const { keysStream, others, compositeVerifyConditions, isDriverOrderByField, rollback } = driverResult;
11404
11484
  try {
11405
11485
  if (!isDriverOrderByField && orderByField) {
11406
11486
  const topK = limit === Infinity ? Infinity : offset + limit;
@@ -11415,7 +11495,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11415
11495
  }
11416
11496
  const results = [];
11417
11497
  for await (const doc of self.processChunkedKeysWithVerify(
11418
- keys,
11498
+ keysStream,
11419
11499
  0,
11420
11500
  self.options.pageSize,
11421
11501
  ftsConditions,
@@ -11451,16 +11531,23 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
11451
11531
  yield limitedResults[j];
11452
11532
  }
11453
11533
  } else {
11534
+ const hasFilters = ftsConditions.length > 0 || compositeVerifyConditions.length > 0 || others.length > 0;
11535
+ const startIdx = hasFilters ? 0 : offset;
11454
11536
  let yieldedCount = 0;
11537
+ let skippedCount = hasFilters ? 0 : offset;
11455
11538
  for await (const doc of self.processChunkedKeysWithVerify(
11456
- keys,
11457
- offset,
11539
+ keysStream,
11540
+ startIdx,
11458
11541
  self.options.pageSize,
11459
11542
  ftsConditions,
11460
11543
  compositeVerifyConditions,
11461
11544
  others,
11462
11545
  tx2
11463
11546
  )) {
11547
+ if (skippedCount < offset) {
11548
+ skippedCount++;
11549
+ continue;
11550
+ }
11464
11551
  if (yieldedCount >= limit) break;
11465
11552
  yield doc;
11466
11553
  yieldedCount++;
@@ -127,6 +127,7 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
127
127
  field: string;
128
128
  indexName: string;
129
129
  isFtsMatch: false;
130
+ isIndexOrderSupported: boolean;
130
131
  } | {
131
132
  tree: BPTreeAsync<string, V>;
132
133
  condition: Partial<DocumentDataplyCondition<U>>;
@@ -134,6 +135,7 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
134
135
  indexName: string;
135
136
  isFtsMatch: true;
136
137
  matchTokens: string[];
138
+ isIndexOrderSupported: boolean;
137
139
  });
138
140
  others: ({
139
141
  tree: BPTreeAsync<number, V>;
@@ -141,6 +143,7 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
141
143
  field: string;
142
144
  indexName: string;
143
145
  isFtsMatch: false;
146
+ isIndexOrderSupported: boolean;
144
147
  } | {
145
148
  tree: BPTreeAsync<string, V>;
146
149
  condition: Partial<DocumentDataplyCondition<U>>;
@@ -148,6 +151,7 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
148
151
  indexName: string;
149
152
  isFtsMatch: true;
150
153
  matchTokens: string[];
154
+ isIndexOrderSupported: boolean;
151
155
  })[];
152
156
  compositeVerifyConditions: {
153
157
  field: string;
@@ -164,20 +168,20 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
164
168
  smallChunkSize: number;
165
169
  };
166
170
  private getTokenKey;
167
- private applyCandidateByFTS;
171
+ private applyCandidateByFTSStream;
168
172
  /**
169
173
  * 특정 인덱스 후보를 조회하여 PK 집합을 필터링합니다.
170
174
  */
171
- private applyCandidate;
175
+ private applyCandidateStream;
172
176
  /**
173
177
  * 쿼리와 인덱스 선택을 기반으로 기본 키(Primary Keys)를 가져옵니다.
174
178
  * 쿼리 최적화를 통합하기 위한 내부 공통 메서드입니다.
175
179
  */
176
180
  getKeys(query: Partial<DocumentDataplyQuery<T>>, orderBy?: string, sortOrder?: 'asc' | 'desc'): Promise<Float64Array>;
177
181
  /**
178
- * 드라이버 인덱스만으로 PK 가져옵니다. (교집합 없이)
182
+ * 드라이버 인덱스만으로 PK 스트림을 가져옵니다. (교집합 없이)
179
183
  * selectDocuments에서 사용하며, 나머지 조건(others)은 스트리밍 중 tree.verify()로 검증합니다.
180
- * @returns 드라이버 키 배열, others 후보 목록, rollback 함수. 또는 null.
184
+ * @returns 드라이버 키 스트림, others 후보 목록, rollback 함수. 또는 null.
181
185
  */
182
186
  private getDriverKeys;
183
187
  private insertDocumentInternal;
@@ -250,7 +254,7 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
250
254
  */
251
255
  private adjustChunkSize;
252
256
  /**
253
- * Prefetch 방식으로 키 배열을 청크 단위로 조회하여 문서를 순회합니다.
257
+ * Prefetch 방식으로 키 스트림을 청크 단위로 조회하여 문서를 순회합니다.
254
258
  * FTS 검증, 복합 인덱스 검증, others 후보에 대한 tree.verify() 검증을 통과한 문서만 yield 합니다.
255
259
  */
256
260
  private processChunkedKeysWithVerify;
@@ -151,7 +151,7 @@ export type CreateIndexFTSOption<T extends DocumentJSON> = {
151
151
  type: 'fts';
152
152
  fields: DeepFlattenKeys<DataplyDocument<T>> & string;
153
153
  tokenizer: 'ngram';
154
- ngram: number;
154
+ gramSize: number;
155
155
  };
156
156
  export type CreateIndexOption<T extends DocumentJSON> = CreateIndexBTreeOption<T> | CreateIndexFTSOption<T>;
157
157
  export interface DocumentDataplyOptions extends DataplyOptions {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-dataply",
3
- "version": "0.0.9-alpha.3",
3
+ "version": "0.0.9-alpha.5",
4
4
  "description": "Simple and powerful JSON document database supporting complex queries and flexible indexing policies.",
5
5
  "license": "MIT",
6
6
  "author": "izure <admin@izure.org>",
@@ -42,7 +42,7 @@
42
42
  "dataply"
43
43
  ],
44
44
  "dependencies": {
45
- "dataply": "^0.0.24-alpha.0"
45
+ "dataply": "^0.0.24-alpha.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/jest": "^30.0.0",
package/readme.md CHANGED
@@ -54,16 +54,21 @@ async function main() {
54
54
  .Options({ wal: 'my-database.wal' })
55
55
  .Open('my-database.db');
56
56
 
57
- // Register indices before init (Recommended)
58
- await db.createIndex('name', { type: 'btree', fields: ['name'] });
59
- await db.createIndex('tags_0', { type: 'btree', fields: ['tags.0'] });
60
-
61
- // Composite Index support
62
- await db.createIndex('idx_name_age', { type: 'btree', fields: ['name', 'age'] });
63
-
64
57
  // Initialize database
65
58
  await db.init();
66
59
 
60
+ // Register indices
61
+ // use transaction to ensure atomicity
62
+ await db.migration(1, async (tx) => {
63
+ await db.createIndex('name', { type: 'btree', fields: ['name'] }, tx);
64
+ await db.createIndex('tags_0', { type: 'btree', fields: ['tags.0'] }, tx);
65
+
66
+ // Composite Index support
67
+ await db.createIndex('idx_name_age', { type: 'btree', fields: ['name', 'age'] }, tx);
68
+
69
+ console.log('Migration completed successfully');
70
+ });
71
+
67
72
  // Insert document
68
73
  const id = await db.insert({
69
74
  name: 'John Doe',