diff --git a/doc/export-import.md b/doc/export-import.md
index 1c8aabf7..c1b7bb9e 100644
--- a/doc/export-import.md
+++ b/doc/export-import.md
@@ -15,7 +15,8 @@ await index.export(async function(key, data){
Import from folder `/export/` into an `Index` or `Document-Index`:
```js
-const index = new Index({/* keep old config and place it here */});
+// Config is restored automatically from the export payload
+const index = new Index({});
const files = await fs.readdir("./export/");
for(let i = 0; i < files.length; i++){
@@ -24,14 +25,18 @@ for(let i = 0; i < files.length; i++){
}
```
-> You'll need to use the same configuration as you used before the export. Any changes on the configuration needs to be re-indexed.
+The export payload includes a `.cfg` key that carries all index configuration (tokenizer, encoder, resolution, context, score, etc). A plain `new Index({})` or `new Document({})` is sufficient — no need to repeat the original options.
+
+> When a custom encoder or score function is defined **inline** in the original config it is serialized as source text and reconstructed on import. If the function closes over outer variables those bindings **will not** be available after restore — keep any required values inside the function body.
+
+> The feature "fastupdate" is automatically disabled on import.
## Fast-Boot Serialization for Server-Side-Rendering (PHP, Python, Ruby, Rust, Java, Go, Node.js, ...)
> This is an experimental feature with limited support which probably might drop in future release. You're welcome to give some feedback.
-When using Server-Side-Rendering you can create a different export which instantly boot up. Especially when using Server-side rendered content, this could help to restore a __static__ index on page load. Document-Indexes aren't supported yet for this method.
+When using Server-Side-Rendering you can create a different export which instantly boot up. Especially when using Server-side rendered content, this could help to restore a __static__ index on page load.
> When your index is too large you should use the default export/import mechanism.
@@ -39,7 +44,7 @@ You'll need Javascript to create the serialized output. Alternatively just creat
As the first step populate the FlexSearch index with your contents.
-You have two options:
+You have three options:
### 1. Create a function as string
@@ -70,8 +75,6 @@ inject(index);
That's it.
-> You'll need to use the same configuration as you used before the export. Any changes on the configuration needs to be re-indexed.
-
### 2. Create just a function body as string
Alternatively you can use lazy function declaration by passing `false` to the serialize function:
@@ -101,92 +104,144 @@ const index = new Index();
inject(index);
```
+### 3. Self-contained inject (config embedded)
-
+Pass `true` as the second argument to embed the index configuration inside the serialized output. The restored index needs no external config at all:
-## Export / Import (In-Memory)
+```js
+const fn_body = index.serialize(false, true);
+const index2 = new Function("FlexSearch", fn_body)(FlexSearch);
+```
-### Node.js
+This is the recommended approach when the index was built with custom options (custom encoder, score function, tokenizer, etc.) and you cannot guarantee the consumer will supply the same config. The encoder and score functions are serialized as source text — the same caveat about closure variables applies as with export/import.
-> Persistent-Indexes and Worker-Indexes don't support Import/Export.
+
-Export an `Index` or `Document-Index` to the folder `/export/`:
+## Document Fast-Boot Serialization
-```js
-import { promises as fs } from "fs";
+Document indexes can also be serialized for fast-boot on the client side. This works similarly to Index serialization but handles multiple fields, tags, and storage.
-await index.export(async function(key, data){
- await fs.writeFile("./export/" + key, data, "utf8");
-});
+### Serialize a Document Index
+
+```js
+const fn_string = document.serialize();
```
-Import from folder `/export/` into an `Index` or `Document-Index`:
+This produces a function string that looks like:
```js
-const index = new Index({/* keep old config and place it here */});
-
-const files = await fs.readdir("./export/");
-for(let i = 0; i < files.length; i++){
- const data = await fs.readFile("./export/" + files[i], "utf8");
- await index.import(files[i], data);
+function inject(doc){
+ doc.reg = new Set([/* ... */]);
+ doc.index.get("fieldName").map = new Map([/* ... */]);
+ doc.index.get("fieldName").ctx = new Map([/* ... */]);
+ // ... for each field
}
```
-> You'll need to use the same configuration as you used before the export. Any changes on the configuration needs to be re-indexed.
+### Restore the serialized Document
-### Browser
+**Option A — self-contained inject (config embedded):**
```js
-index.export(function(key, data){
-
- // you need to store both the key and the data!
- // e.g. use the key for the filename and save your data
-
- localStorage.setItem(key, data);
-});
+const fn_body = document.serialize(false, false, true);
+const doc = new Function("FlexSearch", fn_body)(FlexSearch);
+
+// Ready to search immediately
+const results = doc.search("your query");
```
-> The size of the export corresponds to the memory consumption of the library. To reduce export size you have to use a configuration which has less memory footprint (use the table at the bottom to get information about configs and its memory allocation).
+Pass `true` as the third argument to embed all field configuration (encoders, tokenizers, score functions, etc.) in the serialized output. `FlexSearch` must be in scope when the function runs.
-When your save routine runs asynchronously you have to use `async/await` or return a promise:
+**Option B — inject into a pre-created document (no config needed):**
```js
-index.export(function(key, data){
-
- return new Promise(function(resolve){
-
- // do the saving as async
+const fn_body = document.serialize(false);
+const inject = new Function("doc", fn_body);
- resolve();
- });
-});
+// A plain new Document({}) is enough — config is read from the serialized data
+const doc = new Document({});
+inject(doc);
+
+// Ready to search
+const results = doc.search("your query");
```
-Before you can import data, you need to create your index first. For document indexes provide the same document descriptor you used when export the data. This configuration isn't stored in the export.
+### Without function wrapper
+
+Get just the body if you want to wrap it differently:
```js
-const index = new Index({/* keep old config and place it here */});
+const fn_body = document.serialize(false);
+const inject = new Function("doc", fn_body);
```
-To import the data just pass a key and data:
+## Bulk Export / Import
+
+Use the bulk export APIs when you want all index data in a single payload for transport or storage:
+```js
+// Export uncompressed (returns JSON string)
+const json = await index.exportIndexBulk();
+
+// Export compressed (returns gzip Uint8Array)
+const compressed = await index.exportIndexBulk(true);
```
-const data = localStorage.getItem(key);
-index.import(key, data);
+
+```js
+// Import uncompressed JSON string
+const restored = new Index({});
+await restored.importIndexBulk(json);
+
+// Import compressed Uint8Array
+const restored2 = new Index({});
+await restored2.importIndexBulk(compressed, true);
```
-You need to import every key! Otherwise, your index does not work. You need to store the keys from the export and use this keys for the import (the order of the keys can differ).
+Same pattern for `Document`:
-> The feature "fastupdate" is automatically disabled on import.
+```js
+const json = await doc.exportDocumentBulk();
+const docRestored = new Document({});
+await docRestored.importDocumentBulk(json);
+```
+
+These methods collect all export data into a Map, serialize to JSON, and optionally compress with gzip. This leverages the same bulk import support and provides a simple, maintainable approach.
-This is just for demonstration and is not recommended, because you might have other keys in your localStorage which aren't supported as an import:
+### Bulk import convenience
+
+`import()` also accepts a full payload map or entries array and loops internally:
```js
-var keys = Object.keys(localStorage);
+const payload = new Map();
+await index.export((key, data) => payload.set(key, data));
-for(let i = 0, key, data; i < keys.length; i++){
- key = keys[i]
- data = localStorage.getItem(key);
- index.import(key, data);
-}
+const index2 = new Index({});
+index2.import(payload); // or index2.import(Array.from(payload.entries()))
```
+
+### Utility helpers for generic strings
+
+`compress()` and `decompress()` stay as convenience helpers for string payloads (for example serialized fast-boot function strings):
+
+```js
+import { compress, decompress } from "flexsearch";
+
+const fnString = index.serialize(false);
+const compressed = await compress(fnString);
+const restored = await decompress(compressed);
+```
+
+#### API
+
+| Function | Signature | Returns |
+|---|---|---|
+| `exportIndexBulk` | `(compressed?: boolean) => Promise` | JSON string (uncompressed) or Uint8Array (compressed) |
+| `importIndexBulk` | `(source: string \| Uint8Array, compressed?: boolean) => Promise` | Restores from bulk payload |
+| `exportDocumentBulk` | `(compressed?: boolean) => Promise` | JSON string (uncompressed) or Uint8Array (compressed) |
+| `importDocumentBulk` | `(source: string \| Uint8Array, compressed?: boolean) => Promise` | Restores from bulk payload |
+| `import` | `(payload: Map \| Array<[string, string]>) => void` | Bulk import convenience |
+| `serialize` | **Index:** `(withFunctionWrapper?: boolean, withCfg?: boolean) => SerializedFunctionString` | **Index:** Fast-boot function string or body |
+| | **Document:** `(withFunctionWrapper?: boolean, withCompression?: boolean, withCfg?: boolean) => SerializedFunctionString \| Promise` | **Document:** Fast-boot function string/body or compressed data |
+| `compress` | `(data: string) => Promise` | Compress string data |
+| `decompress` | `(data: Uint8Array) => Promise` | Decompress to string |
+
diff --git a/index.d.ts b/index.d.ts
index 92edcf33..71668660 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -19,6 +19,9 @@ declare module "flexsearch" {
export type Limit = number;
export type ExportHandler = (key: string, data: string) => void;
export type ExportHandlerAsync = (key: string, data: string) => Promise;
+ export type ExportEntries = Array<[string, string]>;
+ export type ExportMap = Map;
+ export type CompressedSource = Uint8Array | ArrayBuffer | ReadableStream;
export type AsyncCallback = (result?: T) => void;
/************************************/
@@ -148,6 +151,20 @@ declare module "flexsearch" {
LatinDefault: EncoderOptions
};
+ /**
+ * Compress a string using gzip compression
+ * @param data - String data to compress
+ * @returns Promise that resolves to compressed Uint8Array
+ */
+ export function compress(data: string): Promise;
+
+ /**
+ * Decompress gzip-compressed data
+ * @param data - Compressed data as Uint8Array
+ * @returns Promise that resolves to decompressed string
+ */
+ export function decompress(data: Uint8Array): Promise;
+
/**
* These options will determine how the contents will be indexed.
*
@@ -276,8 +293,15 @@ declare module "flexsearch" {
export(handler: ExportHandlerAsync): Promise;
import(key: string, data: string): void;
+ import(payload: ExportMap): void;
+ import(payload: ExportEntries): void;
+
+ exportIndexBulk(compressed?: boolean): Promise;
+ importIndexBulk(source: string | Uint8Array, compressed?: boolean): Promise;
+
serialize(with_function_wrapper?: boolean): SerializedFunctionString;
+ serialize(with_function_wrapper: boolean, with_cfg: boolean): SerializedFunctionString;
// Persistent Index
mount(db: StorageInterface): Promise;
@@ -746,6 +770,15 @@ declare module "flexsearch" {
export(handler: ExportHandlerAsync): Promise;
import(key: string, data: string): void;
+ import(payload: ExportMap): void;
+ import(payload: ExportEntries): void;
+
+ exportDocumentBulk(compressed?: boolean): Promise;
+ importDocumentBulk(source: string | Uint8Array, compressed?: boolean): Promise;
+
+
+ serialize(with_function_wrapper?: boolean, compress?: boolean): SerializedFunctionString | Promise;
+ serialize(with_function_wrapper: boolean, compress: boolean, with_cfg: boolean): SerializedFunctionString | Promise;
// Persistent Index
mount>(db: S): Promise;
diff --git a/src/bundle.js b/src/bundle.js
index 4100f95e..cdfade63 100644
--- a/src/bundle.js
+++ b/src/bundle.js
@@ -13,6 +13,10 @@ import {
import {
SearchOptions,
ContextOptions,
+ SerializedIndexContext,
+ SerializedIndexConfig,
+ SerializedFieldConfig,
+ SerializedDocumentConfig,
DocumentDescriptor,
DocumentSearchOptions,
FieldOptions,
@@ -37,6 +41,7 @@ import Encoder from "./encoder.js";
import IdxDB from "./db/indexeddb/index.js";
import Charset from "./charset.js";
import { KeystoreMap, KeystoreArray, KeystoreSet } from "./keystore.js";
+import { compress, decompress } from "./serialize.js";
/** @export */ Index.prototype.add;
/** @export */ Index.prototype.append;
@@ -55,6 +60,8 @@ import { KeystoreMap, KeystoreArray, KeystoreSet } from "./keystore.js";
/** @export */ Index.prototype.removeAsync;
/** @export */ Index.prototype.export;
/** @export */ Index.prototype.import;
+/** @export */ Index.prototype.exportIndexBulk;
+/** @export */ Index.prototype.importIndexBulk;
/** @export */ Index.prototype.serialize;
/** @export */ Index.prototype.mount;
/** @export */ Index.prototype.commit;
@@ -65,6 +72,11 @@ if(SUPPORT_SERIALIZE || SUPPORT_PERSISTENT){
/** @export */ Index.prototype.reg;
/** @export */ Index.prototype.map;
/** @export */ Index.prototype.ctx;
+/** @export */ Index.prototype.resolution_ctx;
+}
+
+if (SUPPORT_SERIALIZE) {
+/** @export */ Index.prototype._encoderOpt;
}
if(SUPPORT_PERSISTENT){
@@ -108,6 +120,9 @@ if(SUPPORT_PERSISTENT){
/** @export */ Document.prototype.destroy;
/** @export */ Document.prototype.export;
/** @export */ Document.prototype.import;
+/** @export */ Document.prototype.exportDocumentBulk;
+/** @export */ Document.prototype.importDocumentBulk;
+/** @export */ Document.prototype.serialize;
/** @export */ Document.prototype.get;
/** @export */ Document.prototype.set;
@@ -118,6 +133,10 @@ if(SUPPORT_SERIALIZE){
/** @export */ Document.prototype.tag;
/** @export */ Document.prototype.store;
/** @export */ Document.prototype.fastupdate;
+/** @export */ Document.prototype._cfgKey;
+/** @export */ Document.prototype.tree;
+/** @export */ Document.prototype.tagtree;
+/** @export */ Document.prototype.tagfield;
}
/** @export */ Resolver.prototype.limit;
@@ -228,6 +247,34 @@ if(SUPPORT_SERIALIZE){
/** @export */ ContextOptions.bidirectional;
/** @export */ ContextOptions.resolution;
+/** @export */ SerializedIndexContext.depth;
+/** @export */ SerializedIndexContext.bidirectional;
+/** @export */ SerializedIndexContext.resolution;
+
+/** @export */ SerializedIndexConfig.tokenize;
+/** @export */ SerializedIndexConfig.resolution;
+/** @export */ SerializedIndexConfig.context;
+/** @export */ SerializedIndexConfig.rtl;
+/** @export */ SerializedIndexConfig.encoder;
+/** @export */ SerializedIndexConfig.score;
+/** @export */ SerializedIndexConfig.priority;
+/** @export */ SerializedIndexConfig.keystore;
+
+/** @export */ SerializedFieldConfig.field;
+/** @export */ SerializedFieldConfig.tokenize;
+/** @export */ SerializedFieldConfig.resolution;
+/** @export */ SerializedFieldConfig.context;
+/** @export */ SerializedFieldConfig.rtl;
+/** @export */ SerializedFieldConfig.encoder;
+/** @export */ SerializedFieldConfig.score;
+/** @export */ SerializedFieldConfig.priority;
+/** @export */ SerializedFieldConfig.keystore;
+
+/** @export */ SerializedDocumentConfig.id;
+/** @export */ SerializedDocumentConfig.fields;
+/** @export */ SerializedDocumentConfig.tagfields;
+/** @export */ SerializedDocumentConfig.store;
+
/** @export */ DocumentDescriptor.field;
/** @export */ DocumentDescriptor.index;
/** @export */ DocumentDescriptor.tag;
@@ -339,6 +386,8 @@ const FlexSearch = {
"Worker": SUPPORT_WORKER ? WorkerIndex : null,
"Resolver": SUPPORT_RESOLVER ? Resolver : null,
"IndexedDB": SUPPORT_PERSISTENT ? IdxDB : null,
+ "compress": SUPPORT_SERIALIZE ? compress : null,
+ "decompress": SUPPORT_SERIALIZE ? decompress : null,
"Language": {}
};
@@ -386,6 +435,8 @@ export {
Document,
Encoder,
Charset,
+ compress,
+ decompress,
WorkerIndex as Worker,
Resolver,
IdxDB as IndexedDB
diff --git a/src/document.js b/src/document.js
index 325070f5..bc97220d 100644
--- a/src/document.js
+++ b/src/document.js
@@ -38,7 +38,7 @@ import Encoder, { fallback_encoder } from "./encoder.js";
import Cache, { searchCache } from "./cache.js";
import { is_string, is_object, parse_simple } from "./common.js";
import apply_async from "./async.js";
-import { exportDocument, importDocument } from "./serialize.js";
+import { exportDocument, importDocument, serializeDocument, exportDocumentBulk, importDocumentBulk } from "./serialize.js";
import { KeystoreMap, KeystoreSet } from "./keystore.js";
import "./document/add.js";
import "./document/search.js";
@@ -63,9 +63,14 @@ export default function Document(options){
let tmp, keystore;
this.tree = [];
+ // Keep stable public property names for bundled/minified builds.
+ this["tree"] = this.tree;
this.field = [];
this.marker = [];
this.key = ((tmp = document.key || document.id) && parse_tree(tmp, this.marker)) || "id";
+ if(SUPPORT_SERIALIZE){
+ this._cfgKey = document.key || document.id || null;
+ }
keystore = SUPPORT_KEYSTORE && (options.keystore || 0);
keystore && (this.keystore = keystore);
@@ -123,6 +128,8 @@ export default function Document(options){
this.tag = new Map();
this.tagtree = [];
this.tagfield = [];
+ this["tagtree"] = this.tagtree;
+ this["tagfield"] = this.tagfield;
for(let i = 0, params, field; i < tmp.length; i++){
params = tmp[i];
field = params.field || params;
@@ -573,6 +580,9 @@ if(SUPPORT_SERIALIZE){
Document.prototype.export = exportDocument;
Document.prototype.import = importDocument;
+ Document.prototype.exportDocumentBulk = exportDocumentBulk;
+ Document.prototype.importDocumentBulk = importDocumentBulk;
+ Document.prototype.serialize = serializeDocument;
}
if(SUPPORT_ASYNC){
diff --git a/src/index.js b/src/index.js
index d7137c6f..f9125ed0 100644
--- a/src/index.js
+++ b/src/index.js
@@ -29,7 +29,7 @@ import Cache, { searchCache } from "./cache.js";
import Charset from "./charset.js";
import { KeystoreMap, KeystoreSet } from "./keystore.js";
import { is_array, is_string } from "./common.js";
-import { exportIndex, importIndex, serialize } from "./serialize.js";
+import { exportIndex, importIndex, serializeIndex, exportIndexBulk, importIndexBulk } from "./serialize.js";
import { remove_index } from "./index/remove.js";
//import default_encoder from "./charset/latin/default.js";
import apply_preset from "./preset.js";
@@ -88,6 +88,10 @@ export default function Index(options, _register){
)
: { encode: encoder };
+ if(SUPPORT_SERIALIZE){
+ this._encoderOpt = options.encoder || options.encode || null;
+ }
+
if(SUPPORT_COMPRESSION){
this.compress = options.compress || options.compression || false;
}
@@ -281,7 +285,9 @@ if(SUPPORT_SERIALIZE){
Index.prototype.export = exportIndex;
Index.prototype.import = importIndex;
- Index.prototype.serialize = serialize;
+ Index.prototype.exportIndexBulk = exportIndexBulk;
+ Index.prototype.importIndexBulk = importIndexBulk;
+ Index.prototype.serialize = serializeIndex;
}
if(SUPPORT_ASYNC){
diff --git a/src/serialize.js b/src/serialize.js
index 2a280796..fcf1249c 100644
--- a/src/serialize.js
+++ b/src/serialize.js
@@ -2,12 +2,20 @@
import {
SUPPORT_STORE,
SUPPORT_TAGS,
- SUPPORT_WORKER
+ SUPPORT_WORKER,
+ SUPPORT_SERIALIZE,
+ SUPPORT_CHARSET,
+ SUPPORT_ENCODER,
+ SUPPORT_ASYNC,
+ SUPPORT_KEYSTORE
} from "./config.js";
import { IntermediateSearchResults } from "./type.js";
// <-- COMPILER BLOCK
import Index from "./index.js";
import Document from "./document.js";
+import WorkerIndex from "./worker.js";
+import Charset from "./charset.js";
+import Encoder from "./encoder.js";
import { KeystoreMap, KeystoreSet } from "./keystore.js";
import { is_string } from "./common.js";
@@ -15,6 +23,22 @@ const chunk_size_reg = 250000;
const chunk_size_map = 5000;
const chunk_size_ctx = 1000;
+// Runtime config records used for Closure @export protection in bundle builds.
+/** @constructor */ export function IndexContextRecord(){}
+/** @constructor */ export function IndexConfigRecord(){}
+/** @constructor */ export function FieldConfigRecord(){}
+/** @constructor */ export function DocumentConfigRecord(){}
+
+/**
+ * Escape a string for safe embedding in a JS string literal.
+ * Handles backslash, double-quote, single-quote, newlines, and other control chars.
+ * @param {string} str
+ * @return {string}
+ */
+function escape_js_string(str) {
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
+}
+
/**
* @param {Map|KeystoreMap} map
* @param {number=} size
@@ -134,9 +158,252 @@ function json_to_reg(json, reg){
return /** @type {Set} */ (reg);
}
+/**
+ * Find the name of a Charset preset by object reference.
+ * @param {*} encoderOpt
+ * @return {string|null}
+ */
+function find_charset_name(encoderOpt){
+ if(!encoderOpt || typeof encoderOpt === "string") return null;
+ const keys = Object.keys(Charset);
+ for(let i = 0; i < keys.length; i++){
+ if(Charset[keys[i]] === encoderOpt) return keys[i];
+ }
+ return null;
+}
+
+/**
+ * Serialize an encoder option to a string key for JSON export/import.
+ * @param {*} encoderOpt
+ * @return {string|null}
+ */
+function serialize_encoder_to_str(encoderOpt){
+ if(!encoderOpt) return null;
+ if(typeof encoderOpt === "string") return encoderOpt;
+ const name = find_charset_name(encoderOpt);
+ if(name) return name;
+ if(typeof encoderOpt === "function") return encoderOpt.toString();
+ return null;
+}
+
+/**
+ * Serialize an encoder option to a JS expression for inject function bodies.
+ * @param {*} encoderOpt
+ * @param {string} charsetRef - JS variable name for Charset
+ * @return {string|null}
+ */
+function serialize_encoder_to_js(encoderOpt, charsetRef){
+ if(!encoderOpt) return null;
+ if(typeof encoderOpt === "string") return charsetRef + '["' + encoderOpt + '"]';
+ const name = find_charset_name(encoderOpt);
+ if(name) return charsetRef + '["' + name + '"]';
+ if(typeof encoderOpt === "function") return encoderOpt.toString();
+ return null;
+}
+
+/**
+ * Build an Index config as a JS object literal string.
+ * @param {Index|WorkerIndex} index
+ * @param {string} charsetRef
+ * @return {string}
+ */
+function index_config_to_js(index, charsetRef){
+ const parts = [];
+ if(index.tokenize && index.tokenize !== "strict"){
+ parts.push('tokenize:"' + index.tokenize + '"');
+ }
+ if(index.resolution !== undefined && index.resolution !== 9){
+ parts.push("resolution:" + index.resolution);
+ }
+ if(index.depth){
+ const ctxParts = ["depth:" + index.depth];
+ if(!index.bidirectional) ctxParts.push("bidirectional:false");
+ if(index.resolution_ctx !== undefined && index.resolution_ctx !== 3){
+ ctxParts.push("resolution:" + index.resolution_ctx);
+ }
+ parts.push("context:{" + ctxParts.join(",") + "}");
+ }
+ if(index.rtl) parts.push("rtl:true");
+ if(SUPPORT_SERIALIZE && index._encoderOpt){
+ const expr = serialize_encoder_to_js(index._encoderOpt, charsetRef);
+ if(expr) parts.push("encoder:" + expr);
+ }
+ if(index.score) parts.push("score:" + index.score.toString());
+ if(SUPPORT_ASYNC && index.priority && index.priority !== 4) parts.push("priority:" + index.priority);
+ if(SUPPORT_KEYSTORE && index.keystore) parts.push("keystore:" + index.keystore);
+ return "{" + parts.join(",") + "}";
+}
+
+/**
+ * Build an Index config as a plain object for JSON export.
+ * @param {Index|WorkerIndex} index
+ * @return {IndexConfigRecord}
+ */
+function index_config_to_obj(index){
+ const cfg = new IndexConfigRecord();
+ if (index.tokenize && index.tokenize !== "strict") cfg.tokenize = index.tokenize;
+ if (index.resolution !== 9) cfg.resolution = index.resolution;
+ if(index.depth){
+ const ctx = new IndexContextRecord();
+ ctx.depth = index.depth;
+ if (!index.bidirectional) ctx.bidirectional = false;
+ if (index.resolution_ctx !== 3) ctx.resolution = index.resolution_ctx;
+ cfg.context = ctx;
+ }
+ if (index.rtl) cfg.rtl = true;
+ if(SUPPORT_SERIALIZE && index._encoderOpt){
+ const str = serialize_encoder_to_str(index._encoderOpt);
+ if (str) cfg.encoder = str;
+ }
+ if (index.score) cfg.score = index.score.toString();
+ if (SUPPORT_ASYNC && index.priority && index.priority !== 4) cfg.priority = index.priority;
+ if (SUPPORT_KEYSTORE && index.keystore) cfg.keystore = index.keystore;
+ return cfg;
+}
+
+/**
+ * Build a Document config as a JS object literal string for inject functions.
+ * @param {Document} doc
+ * @param {string} charsetRef
+ * @return {string}
+ */
+function document_config_to_js(doc, charsetRef){
+ const idField = (SUPPORT_SERIALIZE && doc._cfgKey) || doc.key || "id";
+ let indexFields = "";
+ for(let i = 0; i < doc.field.length; i++){
+ const fieldName = doc.field[i];
+ const fieldIdx = doc.index.get(fieldName);
+ const inner = fieldIdx ? index_config_to_js(fieldIdx, charsetRef).slice(1, -1) : "";
+ indexFields += (indexFields ? "," : "") + '{field:"' + fieldName + '"' + (inner ? "," + inner : "") + "}";
+ }
+ const parts = ['id:"' + idField + '"'];
+ if(indexFields) parts.push("index:[" + indexFields + "]");
+ if(SUPPORT_TAGS && doc.tagfield && doc.tagfield.length){
+ let tagFields = "";
+ for(let i = 0; i < doc.tagfield.length; i++){
+ tagFields += (tagFields ? "," : "") + '{field:"' + doc.tagfield[i] + '"}';
+ }
+ parts.push("tag:[" + tagFields + "]");
+ }
+ if(SUPPORT_STORE && doc.store !== null) parts.push("store:true");
+ return "{document:{" + parts.join(",") + "}}";
+}
+
+/**
+ * Build a Document config as a plain object for JSON export.
+ * @param {Document} doc
+ * @return {DocumentConfigRecord}
+ */
+function document_config_to_export_obj(doc){
+ const cfg = new DocumentConfigRecord();
+ cfg.id = (SUPPORT_SERIALIZE && doc._cfgKey) || doc.key || "id";
+ cfg.fields = [];
+ for(let i = 0; i < doc.field.length; i++){
+ const fieldName = doc.field[i];
+ const fieldIdx = doc.index.get(fieldName);
+ const fieldCfg = new FieldConfigRecord();
+ fieldCfg.field = fieldName;
+ if(fieldIdx){
+ Object.assign(fieldCfg, index_config_to_obj(fieldIdx));
+ }
+ cfg.fields.push(fieldCfg);
+ }
+ if(SUPPORT_TAGS && doc.tagfield && doc.tagfield.length){
+ cfg.tagfields = doc.tagfield.slice();
+ }
+ if (SUPPORT_STORE && doc.store !== null) cfg.store = true;
+ return cfg;
+}
+
+/**
+ * Apply a serialized config object to an Index instance.
+ * @param {Index} index
+ * @param {IndexConfigRecord|FieldConfigRecord|Object} cfg
+ */
+function apply_index_cfg(index, cfg){
+ if (cfg.tokenize) index.tokenize = cfg.tokenize;
+ if (cfg.resolution !== undefined) index.resolution = cfg.resolution;
+ if (cfg.context) {
+ index.depth = cfg.context.depth || 0;
+ if (cfg.context.bidirectional !== undefined) index.bidirectional = cfg.context.bidirectional;
+ if (cfg.context.resolution !== undefined) index.resolution_ctx = cfg.context.resolution;
+ }
+ if (cfg.rtl !== undefined) index.rtl = cfg.rtl;
+ if (cfg.encoder) {
+ let encoderOpt;
+ const encoderStr = cfg.encoder;
+ if(typeof encoderStr === "string" && SUPPORT_CHARSET && Charset[encoderStr]){
+ encoderOpt = Charset[encoderStr];
+ } else if(typeof encoderStr === "string"){
+ try { encoderOpt = new Function("return (" + encoderStr + ")")(); } catch(e){}
+ }
+ if(encoderOpt){
+ index.encoder = encoderOpt.encode
+ ? encoderOpt
+ : (SUPPORT_ENCODER && typeof encoderOpt === "object"
+ ? new Encoder(encoderOpt)
+ : { encode: encoderOpt });
+ if (SUPPORT_SERIALIZE) index._encoderOpt = cfg.encoder;
+ }
+ }
+ if (cfg.score && typeof cfg.score === "string") {
+ try {
+ const scoreFn = new Function("return (" + cfg.score + ")")();
+ if(typeof scoreFn === "function") index.score = scoreFn;
+ } catch(e){}
+ }
+ if (SUPPORT_ASYNC && cfg.priority !== undefined) index.priority = cfg.priority;
+ if (SUPPORT_KEYSTORE && cfg.keystore) {
+ const ks = cfg.keystore;
+ index.keystore = ks;
+ // Replace empty map/ctx with Keystore variants (populated in subsequent imports)
+ if(!index.map.size) index.map = new KeystoreMap(ks);
+ if(!index.ctx.size) index.ctx = new KeystoreMap(ks);
+ }
+}
+
+/**
+ * Apply a serialized config object to a Document instance.
+ * Initializes fields/tags/store only when the document has no data yet.
+ * @param {Document} doc
+ * @param {DocumentConfigRecord|Object} cfg
+ */
+function apply_document_cfg(doc, cfg){
+ if (cfg.id || cfg.key) doc.key = cfg.id || cfg.key;
+ if (!doc.field.length && cfg.fields && cfg.fields.length) {
+ for (let i = 0; i < cfg.fields.length; i++) {
+ const fc = cfg.fields[i];
+ // Support both old format (string) and new format (object with .field)
+ const fieldName = typeof fc === "string" ? fc : fc.field;
+ doc.field.push(fieldName);
+ // Reconstruct tree entry so new documents can be indexed after import
+ const parts = fieldName.split(":");
+ doc.tree[i] = parts.length > 1 ? parts : parts[0];
+ if(!doc.index.has(fieldName)){
+ const idx = new Index({}, doc.reg);
+ if(typeof fc === "object") apply_index_cfg(idx, fc);
+ doc.index.set(fieldName, idx);
+ }
+ }
+ }
+ if (SUPPORT_TAGS && cfg.tagfields && cfg.tagfields.length && !doc.tag) {
+ if(!doc.tagtree) doc.tagtree = [];
+ doc.tag = new Map();
+ doc.tagfield = cfg.tagfields;
+ for (let i = 0; i < cfg.tagfields.length; i++) {
+ const parts = cfg.tagfields[i].split(":");
+ doc.tagtree[i] = parts.length > 1 ? parts : parts[0];
+ doc.tag.set(cfg.tagfields[i], new Map());
+ }
+ }
+ if (SUPPORT_STORE && cfg.store && !doc.store) {
+ doc.store = new Map();
+ }
+}
+
/**
*
- * @param {function(string, string):Promise|void} callback
+ * @param {function(string, (string|Array