document-dataply 0.0.2 → 0.0.3-alpha.1

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
@@ -33,14 +33,14 @@ type MyDocument = {
33
33
  }
34
34
 
35
35
  async function main() {
36
- const db = new DocumentDataply<MyDocument>('my-database.db', {
36
+ const db = DocumentDataply.Define<MyDocument>().Options({
37
37
  wal: 'my-database.wal',
38
38
  indices: {
39
39
  name: true, // Index both existing and new data
40
40
  age: false, // Index only new data
41
41
  'tags.0': true // Index the first element of the 'tags' array
42
42
  }
43
- });
43
+ }).Open('my-database.db');
44
44
 
45
45
  // Initialize database
46
46
  await db.init();
@@ -78,7 +78,7 @@ main();
78
78
 
79
79
  ### Indexing Policies
80
80
 
81
- When defining indices in the constructor, you can specify a boolean value.
81
+ When defining indices in the `options`, you can specify a boolean value.
82
82
 
83
83
  - `true`: The library indexes all existing documents for that field during `init()`, and also indexes all subsequent insertions.
84
84
  - `false`: The library only indexes documents inserted after this configuration.
@@ -99,54 +99,32 @@ const ids = await db.insertBatch([
99
99
 
100
100
  ### Querying
101
101
 
102
- `document-dataply` supports various comparison operators.
102
+ `document-dataply` supports powerful search capabilities based on B+Tree indexing.
103
103
 
104
104
  | Operator | Description |
105
105
  | :--- | :--- |
106
- | `lt` | Less than |
107
- | `lte` | Less than or equal to |
108
- | `gt` | Greater than |
109
- | `gte` | Greater than or equal to |
110
- | `equal` | Equal to |
111
- | `notEqual` | Not equal to |
112
- | `like` | SQL-style pattern matching (e.g., `Jo%`) |
113
- | `or` | If any value in the array is satisfied |
114
-
115
- Example of a complex query:
116
- ```typescript
117
- const users = await db.select({
118
- age: { gt: 18, lt: 65 },
119
- 'address.city': 'Seoul',
120
- tags: { or: ['vip', 'premium'] }
121
- }).drain();
122
- ```
106
+ | `lt`, `lte`, `gt`, `gte` | Comparison operations |
107
+ | `equal`, `notEqual` | Equality check |
108
+ | `like` | Pattern matching |
109
+ | `or` | Matching within an array |
123
110
 
124
- > [!IMPORTANT]
125
- > **Query Constraints**: Query conditions (`lt`, `gt`, `equal`, etc.) can only be used on fields explicitly indexed in the constructor.
126
- >
127
- > **If a field in the query is not indexed, that condition will be ignored.**
128
- >
129
- > If you need to filter by unindexed fields, you should first retrieve the documents and then use JavaScript's native `.filter()` method.
130
- ```typescript
131
- const results = await db.select({ /* indexed fields only */ }).drain();
132
- const filtered = results.filter(doc => doc.unindexedField === 'some-value');
133
- ```
111
+ For detailed operator usage, index constraints (including full scans), and sorting methods, see the [Query Guide (QUERY.md)](./docs/QUERY.md).
134
112
 
135
113
  ### Transactions
136
114
 
137
- To ensure the atomicity of multiple operations, use transactions.
115
+ Ensure data integrity with ACID-compliant transactions. Use `commit()` and `rollback()` to process multiple operations atomically.
138
116
 
139
- ```typescript
140
- const tx = db.createTransaction();
141
- try {
142
- await db.insert({ name: 'Alice', age: 25 }, tx);
143
- await db.insert({ name: 'Bob', age: 28 }, tx);
144
- await tx.commit();
145
- } catch (error) {
146
- await tx.rollback();
147
- throw error;
148
- }
149
- ```
117
+ For detailed usage and error handling patterns, see the [Transaction Guide (TRANSACTION.md)](./docs/TRANSACTION.md).
118
+
119
+ ### Updating and Deleting
120
+
121
+ `document-dataply` provides flexible ways to update or delete documents based on query results. All these operations are **Stream-based**, allowing you to handle millions of records without memory concerns.
122
+
123
+ - **Partial Update**: Modify only specific fields or use a function for dynamic updates.
124
+ - **Full Update**: Replace the entire document while preserving the original `_id`.
125
+ - **Delete**: Permanently remove matching documents from both storage and indices.
126
+
127
+ For details on streaming mechanisms and bandwidth optimization tips, see the [Stream Guide (STREAM.md)](./docs/STREAM.md).
150
128
 
151
129
  ## Tips and Advanced Features
152
130
 
@@ -160,25 +138,34 @@ For more information on performance optimization and advanced features, see [TIP
160
138
 
161
139
  ## API Reference
162
140
 
163
- ### `new DocumentDataply<T>(file, options)`
164
- Creates a new database instance. `T` defines the document structure.
165
- `options.indices` is an object where keys are field names and values are booleans indicating whether to index.
141
+ ### `DocumentDataply.Define<T>().Options(options).Open(file)`
142
+ Creates or opens a database instance. `T` defines the document structure.
143
+ `options.indices` is an object where keys are field names and values are booleans indicating the [Indexing Policy](#indexing-policies).
166
144
 
167
145
  ### `db.init()`
168
146
  Initializes the database, sets up internal metadata, and prepares indices.
169
147
 
170
148
  ### `db.insert(document, tx?)`
171
- Inserts a single document. Returns the `_id` (`number`) of the document.
149
+ Inserts a single document. Each document is automatically assigned a unique, immutable `_id` field. The method returns this `_id` (`number`).
172
150
 
173
151
  ### `db.insertBatch(documents, tx?)`
174
152
  Inserts multiple documents efficiently. Returns an array of `_ids` (`number[]`).
175
153
 
176
154
  ### `db.select(query, options?, tx?)`
177
- Searches for documents matching the query.
155
+ Searches for documents matching the query. Passing an empty object (`{}`) as the `query` retrieves all documents.
178
156
  Returns an object `{ stream, drain }`.
179
157
  - `stream`: An async iterator to traverse results one by one.
180
158
  - `drain()`: A promise that resolves to an array of all matching documents.
181
159
 
160
+ ### `db.partialUpdate(query, newFields, tx?)`
161
+ Partially updates documents matching the query. `newFields` can be a partial object or a function that returns a partial object. Returns the number of updated documents.
162
+
163
+ ### `db.fullUpdate(query, newDocument, tx?)`
164
+ Fully replaces documents matching the query while preserving their `_id`. Returns the number of updated documents.
165
+
166
+ ### `db.delete(query, tx?)`
167
+ Deletes documents matching the query. Returns the number of deleted documents.
168
+
182
169
  ### `db.getMetadata(tx?)`
183
170
  Returns physical storage information (number of pages, number of rows, etc.).
184
171
 
package/dist/cjs/index.js CHANGED
@@ -9683,7 +9683,40 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
9683
9683
  await this.update(1, JSON.stringify(metadata), tx);
9684
9684
  }
9685
9685
  };
9686
- var DocumentDataply = class {
9686
+ var DocumentDataply = class _DocumentDataply {
9687
+ /**
9688
+ * Starts the database definition by setting the document type.
9689
+ * This is used to ensure TypeScript type inference works correctly for the document structure.
9690
+ * @template T The structure of the document to be stored.
9691
+ */
9692
+ static Define() {
9693
+ return {
9694
+ /**
9695
+ * Sets the options for the database, such as index configurations and WAL settings.
9696
+ * @template IC The configuration of indices.
9697
+ * @param options The database initialization options.
9698
+ */
9699
+ Options: (options) => _DocumentDataply.Options(options)
9700
+ };
9701
+ }
9702
+ /**
9703
+ * Internal method used by the Define-chain to pass options.
9704
+ */
9705
+ static Options(options) {
9706
+ return {
9707
+ /**
9708
+ * Creates or opens the database instance with the specified file path.
9709
+ * @param file The path to the database file.
9710
+ */
9711
+ Open: (file) => _DocumentDataply.Open(file, options)
9712
+ };
9713
+ }
9714
+ /**
9715
+ * Internal method used to finalize construction and create the instance.
9716
+ */
9717
+ static Open(file, options) {
9718
+ return new _DocumentDataply(file, options);
9719
+ }
9687
9720
  api;
9688
9721
  indexedFields;
9689
9722
  operatorConverters = {
@@ -9869,6 +9902,112 @@ var DocumentDataply = class {
9869
9902
  return ids;
9870
9903
  }, tx));
9871
9904
  }
9905
+ /**
9906
+ * Internal update method used by both fullUpdate and partialUpdate
9907
+ * @param query The query to use
9908
+ * @param computeUpdatedDoc Function that computes the updated document from the original
9909
+ * @param tx The transaction to use
9910
+ * @returns The number of updated documents
9911
+ */
9912
+ async updateInternal(query, computeUpdatedDoc, tx) {
9913
+ const idTree = this.api.trees.get("_id");
9914
+ if (!idTree) {
9915
+ throw new Error("ID tree not found");
9916
+ }
9917
+ const { stream } = this.select(query, {}, tx);
9918
+ let updatedCount = 0;
9919
+ for await (const doc of stream) {
9920
+ const id = doc._id;
9921
+ let pk = null;
9922
+ for await (const [entryPk] of idTree.whereStream({ primaryEqual: { v: id } })) {
9923
+ pk = entryPk;
9924
+ break;
9925
+ }
9926
+ if (pk === null) continue;
9927
+ const updatedDoc = computeUpdatedDoc(doc);
9928
+ const oldFlatDoc = this.api.flattenDocument(doc);
9929
+ const newFlatDoc = this.api.flattenDocument(updatedDoc);
9930
+ for (const [field, tree] of this.api.trees) {
9931
+ const oldV = oldFlatDoc[field];
9932
+ const newV = newFlatDoc[field];
9933
+ if (oldV === newV) continue;
9934
+ if (oldV !== void 0) {
9935
+ await tree.delete(pk, { k: pk, v: oldV });
9936
+ }
9937
+ if (newV !== void 0) {
9938
+ await tree.insert(pk, { k: pk, v: newV });
9939
+ }
9940
+ }
9941
+ await this.api.update(pk, JSON.stringify(updatedDoc), tx);
9942
+ updatedCount++;
9943
+ }
9944
+ return updatedCount;
9945
+ }
9946
+ /**
9947
+ * Fully update documents from the database that match the query
9948
+ * @param query The query to use (only indexed fields + _id allowed)
9949
+ * @param newRecord Complete document to replace with, or function that receives current document and returns new document
9950
+ * @param tx The transaction to use
9951
+ * @returns The number of updated documents
9952
+ */
9953
+ async fullUpdate(query, newRecord, tx) {
9954
+ return this.api.writeLock(() => this.api.runWithDefault(async (tx2) => {
9955
+ return this.updateInternal(query, (doc) => {
9956
+ const newDoc = typeof newRecord === "function" ? newRecord(doc) : newRecord;
9957
+ return { _id: doc._id, ...newDoc };
9958
+ }, tx2);
9959
+ }, tx));
9960
+ }
9961
+ /**
9962
+ * Partially update documents from the database that match the query
9963
+ * @param query The query to use (only indexed fields + _id allowed)
9964
+ * @param newRecord Partial document to merge, or function that receives current document and returns partial update
9965
+ * @param tx The transaction to use
9966
+ * @returns The number of updated documents
9967
+ */
9968
+ async partialUpdate(query, newRecord, tx) {
9969
+ return this.api.writeLock(() => this.api.runWithDefault(async (tx2) => {
9970
+ return this.updateInternal(query, (doc) => {
9971
+ const partialUpdate = typeof newRecord === "function" ? newRecord(doc) : newRecord;
9972
+ delete partialUpdate._id;
9973
+ return { ...doc, ...partialUpdate };
9974
+ }, tx2);
9975
+ }, tx));
9976
+ }
9977
+ /**
9978
+ * Delete documents from the database that match the query
9979
+ * @param query The query to use (only indexed fields + _id allowed)
9980
+ * @param tx The transaction to use
9981
+ * @returns The number of deleted documents
9982
+ */
9983
+ async delete(query, tx) {
9984
+ return this.api.writeLock(() => this.api.runWithDefault(async (tx2) => {
9985
+ const idTree = this.api.trees.get("_id");
9986
+ if (!idTree) {
9987
+ throw new Error("ID tree not found");
9988
+ }
9989
+ const { stream } = this.select(query, {}, tx2);
9990
+ let deletedCount = 0;
9991
+ for await (const doc of stream) {
9992
+ const id = doc._id;
9993
+ let pk = null;
9994
+ for await (const [entryPk] of idTree.whereStream({ primaryEqual: { v: id } })) {
9995
+ pk = entryPk;
9996
+ break;
9997
+ }
9998
+ if (pk === null) continue;
9999
+ const flatDoc = this.api.flattenDocument(doc);
10000
+ for (const [field, tree] of this.api.trees) {
10001
+ const v = flatDoc[field];
10002
+ if (v === void 0) continue;
10003
+ await tree.delete(pk, { k: pk, v });
10004
+ }
10005
+ await this.api.delete(pk, true, tx2);
10006
+ deletedCount++;
10007
+ }
10008
+ return deletedCount;
10009
+ }, tx));
10010
+ }
9872
10011
  /**
9873
10012
  * Select documents from the database
9874
10013
  * @param query The query to use (only indexed fields + _id allowed)
@@ -9883,8 +10022,8 @@ var DocumentDataply = class {
9883
10022
  throw new Error(`Query field "${field}" is not indexed. Available indexed fields: ${Array.from(this.indexedFields).join(", ")}`);
9884
10023
  }
9885
10024
  }
9886
- const orderBy = options.orderBy ?? "_id";
9887
- if (!this.indexedFields.has(orderBy)) {
10025
+ const orderBy = options.orderBy;
10026
+ if (orderBy !== void 0 && !this.indexedFields.has(orderBy)) {
9888
10027
  throw new Error(`orderBy field "${orderBy}" is not indexed. Available indexed fields: ${Array.from(this.indexedFields).join(", ")}`);
9889
10028
  }
9890
10029
  const {
@@ -9896,51 +10035,36 @@ var DocumentDataply = class {
9896
10035
  const isQueryEmpty = Object.keys(query).length === 0;
9897
10036
  const normalizedQuery = isQueryEmpty ? { _id: { gte: 0 } } : query;
9898
10037
  const verbose = self.verboseQuery(normalizedQuery);
9899
- const orderByTree = self.api.trees.get(orderBy);
9900
10038
  const selectivity = await self.getSelectivityCandidate(
9901
10039
  verbose,
9902
- orderByTree ? orderBy : void 0
10040
+ orderBy
9903
10041
  );
9904
10042
  if (!selectivity) return;
9905
10043
  const { driver, others } = selectivity;
9906
- const isDriverOrderByField = orderByTree && driver.field === orderBy;
10044
+ const isDriverOrderByField = orderBy === void 0 || driver.field === orderBy;
9907
10045
  if (isDriverOrderByField) {
9908
- const driverStream = driver.tree.whereStream(driver.condition, limit, sortOrder);
10046
+ let keys = await driver.tree.keys(driver.condition, void 0, sortOrder);
10047
+ for (const { tree, condition } of others) {
10048
+ keys = await tree.keys(condition, keys, sortOrder);
10049
+ }
9909
10050
  let i = 0;
9910
- for await (const [pk, val] of driverStream) {
10051
+ for (const key of keys) {
9911
10052
  if (i >= limit) break;
9912
- let isMatch = true;
9913
- for (const { tree, condition } of others) {
9914
- const targetValue = await tree.get(pk);
9915
- if (targetValue === void 0 || !tree.verify(targetValue, condition)) {
9916
- isMatch = false;
9917
- break;
9918
- }
9919
- }
9920
- if (isMatch) {
9921
- const stringified = await self.api.select(pk, false, tx2);
9922
- if (!stringified) continue;
9923
- yield JSON.parse(stringified);
9924
- i++;
9925
- }
10053
+ const stringified = await self.api.select(key, false, tx2);
10054
+ if (!stringified) continue;
10055
+ yield JSON.parse(stringified);
10056
+ i++;
9926
10057
  }
9927
10058
  } else {
9928
10059
  const results = [];
9929
- const driverStream = driver.tree.whereStream(driver.condition);
9930
- for await (const [pk, val] of driverStream) {
9931
- let isMatch = true;
9932
- for (const { tree, condition } of others) {
9933
- const targetValue = await tree.get(pk);
9934
- if (targetValue === void 0 || !tree.verify(targetValue, condition)) {
9935
- isMatch = false;
9936
- break;
9937
- }
9938
- }
9939
- if (isMatch) {
9940
- const stringified = await self.api.select(pk, false, tx2);
9941
- if (!stringified) continue;
9942
- results.push(JSON.parse(stringified));
9943
- }
10060
+ let keys = await driver.tree.keys(driver.condition, void 0);
10061
+ for (const { tree, condition } of others) {
10062
+ keys = await tree.keys(condition, keys);
10063
+ }
10064
+ for (const key of keys) {
10065
+ const stringified = await self.api.select(key, false, tx2);
10066
+ if (!stringified) continue;
10067
+ results.push(JSON.parse(stringified));
9944
10068
  }
9945
10069
  results.sort((a, b) => {
9946
10070
  const aVal = a[orderBy] ?? a._id;
@@ -2,10 +2,10 @@ import type { DataplyTreeValue, Primitive } from '../../types';
2
2
  import { BPTreeNode, SerializeStrategyAsync, type SerializeStrategyHead } from 'dataply';
3
3
  import { DocumentDataplyAPI } from '../document';
4
4
  export declare class DocumentSerializeStrategyAsync<T extends Primitive> extends SerializeStrategyAsync<number, DataplyTreeValue<T>> {
5
- protected readonly api: DocumentDataplyAPI<any>;
6
- protected readonly txContext: DocumentDataplyAPI<any>['txContext'];
5
+ protected readonly api: DocumentDataplyAPI<any, any>;
6
+ protected readonly txContext: DocumentDataplyAPI<any, any>['txContext'];
7
7
  readonly treeKey: string;
8
- constructor(order: number, api: DocumentDataplyAPI<any>, txContext: DocumentDataplyAPI<any>['txContext'], treeKey: string);
8
+ constructor(order: number, api: DocumentDataplyAPI<any, any>, txContext: DocumentDataplyAPI<any, any>['txContext'], treeKey: string);
9
9
  id(isLeaf: boolean): Promise<string>;
10
10
  read(id: string): Promise<BPTreeNode<number, DataplyTreeValue<T>>>;
11
11
  write(id: string, node: BPTreeNode<number, DataplyTreeValue<T>>): Promise<void>;
@@ -1,7 +1,7 @@
1
- import type { DataplyTreeValue, DocumentDataplyInnerMetadata, DocumentDataplyOptions, DocumentJSON, FlattenedDocumentJSON, Primitive, DocumentDataplyQuery, FinalFlatten, DocumentDataplyCondition, DataplyDocument, DocumentDataplyMetadata, DocumentDataplyQueryOptions, IndexedDocumentDataplyQuery } from '../types';
1
+ import type { DataplyTreeValue, DocumentDataplyInnerMetadata, DocumentDataplyOptions, DocumentJSON, FlattenedDocumentJSON, Primitive, DocumentDataplyQuery, DocumentDataplyIndexedQuery, DocumentDataplyCondition, DataplyDocument, DocumentDataplyMetadata, DocumentDataplyQueryOptions, IndexConfig } from '../types';
2
2
  import { DataplyAPI, Transaction, BPTreeAsync } from 'dataply';
3
3
  import { DocumentValueComparator } from './bptree/documentComparator';
4
- export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyAPI {
4
+ export declare class DocumentDataplyAPI<T extends DocumentJSON, IC extends IndexConfig<T>> extends DataplyAPI {
5
5
  runWithDefault: <T_1>(callback: (tx: Transaction) => Promise<T_1>, tx?: Transaction) => Promise<T_1>;
6
6
  streamWithDefault: <T_1>(callback: (tx: Transaction) => AsyncGenerator<T_1>, tx?: Transaction) => AsyncGenerator<T_1>;
7
7
  indices: DocumentDataplyInnerMetadata['indices'];
@@ -9,7 +9,7 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
9
9
  readonly comparator: DocumentValueComparator<DataplyTreeValue<Primitive>, Primitive>;
10
10
  private pendingBackfillFields;
11
11
  private readonly lock;
12
- constructor(file: string, options: DocumentDataplyOptions<T>);
12
+ constructor(file: string, options: DocumentDataplyOptions<T, IC>);
13
13
  readLock<T>(fn: () => T): Promise<T>;
14
14
  writeLock<T>(fn: () => T): Promise<T>;
15
15
  getDocument(pk: number, tx?: Transaction): Promise<DataplyDocument<T>>;
@@ -34,11 +34,38 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
34
34
  getDocumentInnerMetadata(tx: Transaction): Promise<DocumentDataplyInnerMetadata>;
35
35
  updateDocumentInnerMetadata(metadata: DocumentDataplyInnerMetadata, tx: Transaction): Promise<void>;
36
36
  }
37
- export declare class DocumentDataply<T extends DocumentJSON, I extends string = keyof FinalFlatten<T> & string> {
38
- protected readonly api: DocumentDataplyAPI<T>;
37
+ export declare class DocumentDataply<T extends DocumentJSON, IC extends IndexConfig<T>> {
38
+ /**
39
+ * Starts the database definition by setting the document type.
40
+ * This is used to ensure TypeScript type inference works correctly for the document structure.
41
+ * @template T The structure of the document to be stored.
42
+ */
43
+ static Define<T extends DocumentJSON>(): {
44
+ /**
45
+ * Sets the options for the database, such as index configurations and WAL settings.
46
+ * @template IC The configuration of indices.
47
+ * @param options The database initialization options.
48
+ */
49
+ Options: <IC extends IndexConfig<T>>(options: DocumentDataplyOptions<T, IC>) => {
50
+ /**
51
+ * Creates or opens the database instance with the specified file path.
52
+ * @param file The path to the database file.
53
+ */
54
+ Open: (file: string) => DocumentDataply<T, IC>;
55
+ };
56
+ };
57
+ /**
58
+ * Internal method used by the Define-chain to pass options.
59
+ */
60
+ private static Options;
61
+ /**
62
+ * Internal method used to finalize construction and create the instance.
63
+ */
64
+ private static Open;
65
+ protected readonly api: DocumentDataplyAPI<T, IC>;
39
66
  private readonly indexedFields;
40
67
  private readonly operatorConverters;
41
- constructor(file: string, options?: DocumentDataplyOptions<T>);
68
+ protected constructor(file: string, options?: DocumentDataplyOptions<T, IC>);
42
69
  /**
43
70
  * Initialize the document database
44
71
  */
@@ -58,7 +85,7 @@ export declare class DocumentDataply<T extends DocumentJSON, I extends string =
58
85
  * @param orderByField Optional field name for orderBy optimization
59
86
  * @returns Driver and other candidates for query execution
60
87
  */
61
- getSelectivityCandidate<U extends Partial<IndexedDocumentDataplyQuery<FinalFlatten<DataplyDocument<T>>, I>>, V extends DataplyTreeValue<U>>(query: Partial<DocumentDataplyQuery<V>>, orderByField?: string): Promise<{
88
+ getSelectivityCandidate<U extends Partial<DocumentDataplyIndexedQuery<T, IC>>, V extends DataplyTreeValue<U>>(query: Partial<DocumentDataplyQuery<V>>, orderByField?: string): Promise<{
62
89
  driver: {
63
90
  tree: BPTreeAsync<number, V>;
64
91
  condition: Partial<DocumentDataplyCondition<U>>;
@@ -85,6 +112,37 @@ export declare class DocumentDataply<T extends DocumentJSON, I extends string =
85
112
  * @returns The primary keys of the inserted documents
86
113
  */
87
114
  insertBatch(documents: T[], tx?: Transaction): Promise<number[]>;
115
+ /**
116
+ * Internal update method used by both fullUpdate and partialUpdate
117
+ * @param query The query to use
118
+ * @param computeUpdatedDoc Function that computes the updated document from the original
119
+ * @param tx The transaction to use
120
+ * @returns The number of updated documents
121
+ */
122
+ private updateInternal;
123
+ /**
124
+ * Fully update documents from the database that match the query
125
+ * @param query The query to use (only indexed fields + _id allowed)
126
+ * @param newRecord Complete document to replace with, or function that receives current document and returns new document
127
+ * @param tx The transaction to use
128
+ * @returns The number of updated documents
129
+ */
130
+ fullUpdate(query: Partial<DocumentDataplyIndexedQuery<T, IC>>, newRecord: T | ((document: DataplyDocument<T>) => T), tx?: Transaction): Promise<number>;
131
+ /**
132
+ * Partially update documents from the database that match the query
133
+ * @param query The query to use (only indexed fields + _id allowed)
134
+ * @param newRecord Partial document to merge, or function that receives current document and returns partial update
135
+ * @param tx The transaction to use
136
+ * @returns The number of updated documents
137
+ */
138
+ partialUpdate(query: Partial<DocumentDataplyIndexedQuery<T, IC>>, newRecord: Partial<DataplyDocument<T>> | ((document: DataplyDocument<T>) => Partial<DataplyDocument<T>>), tx?: Transaction): Promise<number>;
139
+ /**
140
+ * Delete documents from the database that match the query
141
+ * @param query The query to use (only indexed fields + _id allowed)
142
+ * @param tx The transaction to use
143
+ * @returns The number of deleted documents
144
+ */
145
+ delete(query: Partial<DocumentDataplyIndexedQuery<T, IC>>, tx?: Transaction): Promise<number>;
88
146
  /**
89
147
  * Select documents from the database
90
148
  * @param query The query to use (only indexed fields + _id allowed)
@@ -93,7 +151,7 @@ export declare class DocumentDataply<T extends DocumentJSON, I extends string =
93
151
  * @returns The documents that match the query
94
152
  * @throws Error if query or orderBy contains non-indexed fields
95
153
  */
96
- select(query: Partial<IndexedDocumentDataplyQuery<FinalFlatten<DataplyDocument<T>>, I>>, options?: DocumentDataplyQueryOptions<FinalFlatten<DataplyDocument<T>>, I>, tx?: Transaction): {
154
+ select(query: Partial<DocumentDataplyIndexedQuery<T, IC>>, options?: DocumentDataplyQueryOptions<T, IC>, tx?: Transaction): {
97
155
  stream: AsyncIterableIterator<DataplyDocument<T>>;
98
156
  drain: () => Promise<DataplyDocument<T>[]>;
99
157
  };
@@ -47,26 +47,26 @@ export type DocumentDataplyCondition<V> = {
47
47
  or?: Partial<V>[];
48
48
  like?: string;
49
49
  };
50
- export type DocumentDataplyQueryOptions<V, I extends string = string> = {
51
- limit?: number;
52
- orderBy?: I | '_id';
53
- sortOrder?: BPTreeOrder;
54
- };
55
50
  export type DocumentDataplyQuery<T> = {
56
51
  [key in keyof T]?: T[key] | DocumentDataplyCondition<T[key]>;
57
52
  } & {
58
53
  [key: string]: any;
59
54
  };
60
55
  /**
61
- * Query type restricted to indexed fields only (+ _id)
56
+ * Query type restricted to indexed fields only
62
57
  */
63
- export type IndexedDocumentDataplyQuery<T, I extends string> = {
64
- [key in (I | '_id')]?: key extends keyof T ? T[key] | DocumentDataplyCondition<T[key]> : never;
58
+ export type DocumentDataplyIndexedQuery<T extends DocumentJSON, IC extends IndexConfig<T>> = {
59
+ [key in keyof IC]: key extends keyof FinalFlatten<DataplyDocument<T>> ? FinalFlatten<DataplyDocument<T>>[key] | DocumentDataplyCondition<FinalFlatten<DataplyDocument<T>>[key]> : never;
65
60
  };
66
61
  export interface DataplyTreeValue<T> {
67
62
  k: number;
68
63
  v: T;
69
64
  }
65
+ export type DocumentDataplyQueryOptions<T extends DocumentJSON, IC extends IndexConfig<T>> = {
66
+ limit?: number;
67
+ orderBy?: ExtractIndexKeys<T, IC> | '_id';
68
+ sortOrder?: BPTreeOrder;
69
+ };
70
70
  /**
71
71
  * T가 객체인지 확인하고, 객체라면 하위 키를 재귀적으로 탐색합니다.
72
72
  */
@@ -75,7 +75,7 @@ type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17
75
75
  * T가 객체인지 확인하고, 객체라면 하위 키를 재귀적으로 탐색합니다.
76
76
  * Depth 제한을 두어 "Type instantiation is excessively deep and possibly infinite" 에러를 방지합니다.
77
77
  */
78
- export type DeepFlattenKeys<T, Prefix extends string = "", D extends number = 12> = [
78
+ export type DeepFlattenKeys<T, Prefix extends string = "", D extends number = 5> = [
79
79
  D
80
80
  ] extends [0] ? never : T extends Primitive ? (Prefix extends `${infer P}.` ? P : never) : T extends readonly any[] ? (DeepFlattenKeys<T[number], `${Prefix}${number}.`, Prev[D]>) : T extends object ? {
81
81
  [K in keyof T & string]: NonNullable<T[K]> extends Primitive ? `${Prefix}${K}` : DeepFlattenKeys<NonNullable<T[K]>, `${Prefix}${K}.`, Prev[D]>;
@@ -88,15 +88,26 @@ type GetTypeByPath<T, Path extends string> = T extends readonly (infer U)[] ? Pa
88
88
  export type FinalFlatten<T> = {
89
89
  [P in DeepFlattenKeys<T>]: GetTypeByPath<T, P & string>;
90
90
  };
91
- export interface DocumentDataplyOptions<T> extends DataplyOptions {
91
+ export type DocumentDataplyIndices<T extends DocumentJSON, IC extends IndexConfig<T>> = {
92
+ [key in keyof IC & keyof FinalFlatten<T>]: GetTypeByPath<T, key>;
93
+ };
94
+ /**
95
+ * Index configuration type - keys are field names, values are boolean
96
+ */
97
+ export type IndexConfig<T> = Partial<{
98
+ [key in keyof FinalFlatten<T>]: boolean;
99
+ }>;
100
+ /**
101
+ * Extract index keys from IndexConfig
102
+ */
103
+ export type ExtractIndexKeys<T extends DocumentJSON, IC extends IndexConfig<T>> = keyof IC & keyof FinalFlatten<DataplyDocument<T>> & string;
104
+ export interface DocumentDataplyOptions<T, IC extends IndexConfig<T> = IndexConfig<T>> extends DataplyOptions {
92
105
  /**
93
- * Indecies to create when initializing the database.
94
- * If not specified, no indecies will be created.
106
+ * Indices to create when initializing the database.
107
+ * If not specified, no indices will be created.
95
108
  * If the value of the index is `true`, the index will be created for the already inserted data.
96
109
  * If the value of the index is `false`, the index will not be created for the already inserted data.
97
110
  */
98
- indices?: Partial<{
99
- [key in keyof FinalFlatten<T>]: boolean;
100
- }>;
111
+ indices?: IC;
101
112
  }
102
113
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-dataply",
3
- "version": "0.0.2",
3
+ "version": "0.0.3-alpha.1",
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>",