albedo-node 0.5.4 โ†’ 0.5.6

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 ADDED
@@ -0,0 +1,183 @@
1
+ # albedo-node
2
+
3
+ Native Node.js/Bun bindings for the [Albedo](https://github.com/klirix/albedo) embedded document database.
4
+
5
+ This package wraps the core database engine with a concise TypeScript API, ships prebuilt binaries for common platforms, and exposes BSON helpers plus replication utilities so you can embed Albedo buckets directly in your applications.
6
+
7
+ ## Features
8
+
9
+ - ๐Ÿงฑ Access to the full Albedo bucket API from Node.js or Bun
10
+ - ๐Ÿ“ฆ Precompiled native modules for Linux (glibc & musl), macOS, and arm64/x64
11
+ - ๐Ÿ” Generator-based iterators for queries and in-place document transforms
12
+ - ๐Ÿ”„ Built-in replication callback and apply mechanisms
13
+ - ๐Ÿงช TypeScript typings and Bun-based test suite
14
+
15
+ ## Installation
16
+
17
+ Install with your preferred package manager (Node.js 18+ or Bun 1.0+ recommended):
18
+
19
+ ```bash
20
+ npm install albedo-node
21
+ ```
22
+
23
+ ```bash
24
+ pnpm add albedo-node
25
+ ```
26
+
27
+ ```bash
28
+ bun add albedo-node
29
+ ```
30
+
31
+ The published package includes native binaries under `native/`. When running on an unsupported platform, or if you prefer to build locally, install [Zig 0.15.x](https://ziglang.org/download/) and run `bun run build` to compile the binding.
32
+
33
+ ## Quick start
34
+
35
+ ```ts
36
+ import albedo, { BSON, Bucket, where } from "albedo-node";
37
+
38
+ // Open or create a bucket on disk
39
+ const bucket = Bucket.open("./example.bucket");
40
+
41
+ // Insert JavaScript objects or pre-serialized BSON buffers
42
+ bucket.insert({ name: "Ada", skills: ["math", "computing"] });
43
+
44
+ const serialized = BSON.serialize({ name: "Alan", skills: ["logic", "cryptanalysis"] });
45
+ bucket.insert(serialized);
46
+
47
+ // List documents using a generator with the fluent `where` helper
48
+ for (const doc of bucket.list(where("name", { $eq: "Ada" }))) {
49
+ console.log(doc);
50
+ }
51
+
52
+ bucket.close();
53
+ ```
54
+
55
+ ### Fluent query helpers
56
+
57
+ Use the fluent `Query` builder when you need more than a single-field filter:
58
+
59
+ ```ts
60
+ import { Bucket, Query } from "albedo-node";
61
+
62
+ const bucket = Bucket.open("./example.bucket");
63
+
64
+ const adults = new Query()
65
+ .where("age", { $gte: 21 })
66
+ .sortBy("_id", "asc")
67
+ .sector(0, 50);
68
+
69
+ for (const doc of bucket.list(adults)) {
70
+ console.log(doc);
71
+ }
72
+
73
+ bucket.close();
74
+ ```
75
+
76
+ You can chain multiple `where` calls or combine them with `sortBy` and `sector`. For quick one-off predicates, the standalone `where(field, filter)` helper returns a `Query` instance that slots into any method accepting a query.
77
+
78
+ ### Transforming documents in place
79
+
80
+ ```ts
81
+ const iter = bucket.transformIterator({ query: { name: "Ada" } });
82
+ let step = iter.next();
83
+ while (!step.done) {
84
+ const doc = step.value as { name: string; skills: string[] };
85
+ step = iter.next({ ...doc, skills: [...doc.skills, "analytics"] });
86
+ }
87
+ ```
88
+
89
+ ### Replication
90
+
91
+ ```ts
92
+ const primary = Bucket.open("./primary.bucket");
93
+ const replica = Bucket.open("./replica.bucket");
94
+
95
+ const batches: Uint8Array[] = [];
96
+ primary.setReplicationCallback((data) => {
97
+ batches.push(data);
98
+ });
99
+
100
+ primary.insert({ name: "Replica", version: 1 });
101
+ primary.close();
102
+
103
+ // Apply the first batch to the replica bucket
104
+ if (batches.length > 0) {
105
+ replica.applyReplicationBatch(batches[0]);
106
+ }
107
+
108
+ replica.close();
109
+ ```
110
+
111
+ ## Query objects and BSON payloads
112
+
113
+ Most bucket operations accept either:
114
+
115
+ - Plain JavaScript objects that will be converted to BSON automatically, or
116
+ - Raw `Uint8Array` buffers containing BSON documents that you have serialized yourself, or
117
+ - Instances of the fluent `Query` builder (including values produced by the `where()` helper).
118
+
119
+ Regardless of which form you use, the structure mirrors the underlying Albedo query language. Common patterns include:
120
+
121
+ - `{ query: { field: value } }` โ€” equality filters
122
+ - `{ query: { age: { "$gt": 40 } } }` โ€” comparison operators
123
+ - `{ query: { _id: someId }, sector: { limit: 10, offset: 0 } }` โ€” pagination controls
124
+
125
+ For the exhaustive list of operators and document shapes, consult the [Albedo Query reference](https://github.com/klirix/albedo#readme). Whatever BSON document the core engine accepts can be provided here either as a plain object or as prebuilt BSON bytes.
126
+
127
+ ### ObjectId support
128
+
129
+ The module exposes `albedo.ObjectId`, compatible with the BSON 12-byte identifier:
130
+
131
+ ```ts
132
+ const id = new albedo.ObjectId();
133
+ const hex = id.toString();
134
+ const parsed = albedo.ObjectId.fromString(hex);
135
+ ```
136
+
137
+ Serialized documents that contain `_id` fields with a BSON ObjectId will be revived as `ObjectId` instances when deserialized.
138
+
139
+ ## API reference
140
+
141
+ - `default` export โ€” raw Albedo native module
142
+ - `ObjectId`, `serialize`, `deserialize`, `open`, `close`, `insert`, `delete`, `list`, `transform`, `setReplicationCallback`, etc.
143
+ - `BSON.serialize(value)` / `BSON.deserialize(bytes)` โ€” helper wrappers
144
+ - `Bucket`
145
+ - `static open(path: string): Bucket`
146
+ - `insert(doc: object | Uint8Array)`
147
+ - `delete(query?: object | Query)`
148
+ - `list<T>(query?: object | Query): Generator<T>`
149
+ - `transformIterator<T>(query?: object | Query): Generator<T, undefined, object | null>`
150
+ - `ensureIndex(name: string, options: { unique: boolean; sparse: boolean; reverse: boolean })`
151
+ - `dropIndex(name: string)`
152
+ - `indexes: Record<string, { name: string; unique: boolean; sparse: boolean; reverse: boolean }>`
153
+ - `setReplicationCallback(cb: (data: Uint8Array) => void)`
154
+ - `applyReplicationBatch(batch: Uint8Array)`
155
+ - accepts raw BSON `Uint8Array` payloads anywhere a query or document object is expected
156
+
157
+ - `Query`
158
+ - `where(field, filter)` chains field predicates (e.g. `{ $eq: value }`, `{ $gt: 10 }`)
159
+ - `sortBy(field, direction?)` sets sort order
160
+ - `sector(offset?, limit?)` applies pagination window
161
+
162
+ - `where(field, filter): Query` โ€” convenience helper that creates a single-field `Query`
163
+
164
+ ## Building from source
165
+
166
+ ```bash
167
+ bun install
168
+ bun run build # zig build + tsc emit
169
+ ```
170
+
171
+ The build step compiles the Zig binding for the current host and writes the `.node` artifact into `native/`, then emits the TypeScript declaration files to `dist/`.
172
+
173
+ ## Running tests
174
+
175
+ ```bash
176
+ bun test
177
+ ```
178
+
179
+ The test suite exercises insertion, querying, indexing, transforms, replication, and BSON round-trips using Bunโ€™s test runner.
180
+
181
+ ## License
182
+
183
+ MIT. See the accompanying `LICENSE` file (or the upstream [Albedo](https://github.com/klirix/albedo) repository) for details.
package/dist/index.d.ts CHANGED
@@ -19,7 +19,7 @@ interface ObjectIdInstance {
19
19
  }
20
20
  interface ObjectIdConstructor {
21
21
  new (buffer?: ByteBuffer): ObjectIdInstance;
22
- fromString(hex: string): ObjectIdInstance;
22
+ fromString(str: string): ObjectIdInstance;
23
23
  }
24
24
  interface AlbedoModule {
25
25
  ObjectId: ObjectIdConstructor;
@@ -48,18 +48,273 @@ export declare const BSON: {
48
48
  serialize: (value: unknown) => Uint8Array;
49
49
  deserialize: <T = unknown>(data: ByteBuffer) => T;
50
50
  };
51
+ /**
52
+ * Native ObjectId class constructor.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const id = new ObjectId();
57
+ * const parsed = ObjectId.fromString(id.toString());
58
+ * ```
59
+ */
60
+ export declare const ObjectId: ObjectIdConstructor;
61
+ /**
62
+ * Wrapper around a native Albedo bucket handle providing
63
+ * methods for CRUD operations, indexing, iteration, and
64
+ * replication support.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * import albedo, { Bucket, BSON } from 'albedo-node';
69
+ *
70
+ * const bucket = Bucket.open('data.db');
71
+ * bucket.insert({ name: 'Alice' });
72
+ *
73
+ * for (const doc of bucket.list({ query: { name: { $eq: 'Alice' } } })) {
74
+ * console.log(doc);
75
+ * }
76
+ *
77
+ * bucket.close();
78
+ * ```
79
+ */
51
80
  export declare class Bucket {
52
81
  private handle;
82
+ /**
83
+ * Create a Bucket instance from an existing native handle.
84
+ * @param handle - opaque bucket handle returned by `albedo.open`
85
+ * @example
86
+ * ```ts
87
+ * const raw = albedo.open('foo.db');
88
+ * const bucket = new Bucket(raw);
89
+ * ```
90
+ */
53
91
  constructor(handle: object);
92
+ /**
93
+ * Open a bucket located at the given filesystem path.
94
+ * @param path - path to the bucket file
95
+ * @returns a new `Bucket` instance
96
+ * @example
97
+ * ```ts
98
+ * const bucket = Bucket.open('data.db');
99
+ * ```
100
+ */
54
101
  static open(path: string): Bucket;
102
+ /**
103
+ * Close the bucket and release native resources.
104
+ * @example
105
+ * ```ts
106
+ * bucket.close();
107
+ * ```
108
+ */
55
109
  close(): void;
110
+ /**
111
+ * Insert a document or raw byte buffer into the bucket.
112
+ * @param doc - object to serialize or pre-serialized buffer
113
+ * @example
114
+ * ```ts
115
+ * bucket.insert({ name: 'Bob' });
116
+ * ```
117
+ */
56
118
  insert(doc: object | ByteBuffer): void;
57
- delete(query: object): void;
119
+ /**
120
+ * Delete documents matching the query. If no query is provided,
121
+ * all documents will be removed.
122
+ * @param query - filter object or `Query` instance
123
+ * @example
124
+ * ```ts
125
+ * bucket.delete({ name: { $eq: 'Bob' } });
126
+ * // or using Query builder
127
+ * bucket.delete(new Query().where('name', { $eq: 'Bob' }));
128
+ * ```
129
+ */
130
+ delete(query?: object | Query): void;
131
+ /**
132
+ * Retrieve information about all indexes defined on the bucket.
133
+ * @example
134
+ * ```ts
135
+ * console.log(bucket.indexes);
136
+ * ```
137
+ */
58
138
  get indexes(): Record<string, IndexInfo>;
139
+ /**
140
+ * Create or update an index on a field.
141
+ * @param name - index name (field path)
142
+ * @param options - index configuration
143
+ * @example
144
+ * ```ts
145
+ * bucket.ensureIndex('name', { unique: false, sparse: false, reverse: false });
146
+ * ```
147
+ */
59
148
  ensureIndex(name: string, options: IndexOptions): void;
149
+ /**
150
+ * Remove an index by name.
151
+ * @example
152
+ * ```ts
153
+ * bucket.dropIndex('name');
154
+ * ```
155
+ */
60
156
  dropIndex(name: string): void;
61
- list<T>(query: object): Generator<T>;
62
- transformIterator<T>(query: object): Generator<T, undefined, null | object>;
157
+ /**
158
+ * Iterate over documents matching the optional query.
159
+ * @param query - filter or `Query` object
160
+ * @yields each document deserialized from the bucket
161
+ * @example
162
+ * ```ts
163
+ * for (const doc of bucket.list({ query: { age: { $gt: 30 } } })) {
164
+ * console.log(doc);
165
+ * }
166
+ * ```
167
+ */
168
+ list<T>(query?: object | Query): Generator<T>;
169
+ /**
170
+ * Normalize a query argument to a plain object, unpacking
171
+ * `Query` instances.
172
+ * @example
173
+ * ```ts
174
+ * Bucket.convertToQuery(new Query().where('x', { $eq: 1 }));
175
+ * Bucket.convertToQuery({ foo: { $exists: true } });
176
+ * ```
177
+ */
178
+ static convertToQuery(query?: object | Query): object;
179
+ /**
180
+ * Generator that allows reading and optionally modifying each
181
+ * document matching the query.
182
+ * @param query - filter or `Query` instance
183
+ * @yields the current document; the caller may send back an updated
184
+ * document or `null` to delete it.
185
+ * @example
186
+ * ```ts
187
+ * for (const doc of bucket.transformIterator({ query: { count: { $lt: 5 } } })) {
188
+ * if (doc.count < 2) {
189
+ * // update in-place
190
+ * yield { ...doc, count: doc.count + 1 };
191
+ * }
192
+ * }
193
+ * ```
194
+ */
195
+ transformIterator<T>(query?: object | Query): Generator<T, undefined, null | object>;
196
+ /**
197
+ * Apply a transformation function to each document matching the
198
+ * provided query. The predicate receives the current document and
199
+ * should return the modified document, or `null` to remove it.
200
+ *
201
+ * This is a helper built on top of `transformIterator` and mirrors its
202
+ * behavior but uses a simple callback API instead of a generator.
203
+ *
204
+ * @param query - filter or `Query` object
205
+ * @param fn - transformation function
206
+ * @example
207
+ * ```ts
208
+ * bucket.transform(where('active', { $eq: true }), doc => {
209
+ * if (doc.count > 10) return null; // delete
210
+ * return { ...doc, count: doc.count + 1 };
211
+ * });
212
+ * ```
213
+ */
214
+ transform<T extends object>(query: object | Query | undefined, fn: (doc: T) => T | null): void;
215
+ /**
216
+ * Register a callback to receive replication data produced by the
217
+ * bucket.
218
+ * @param callback - invoked with raw replication bytes
219
+ * @example
220
+ * ```ts
221
+ * bucket.setReplicationCallback(bytes => {
222
+ * console.log('got replication', bytes.length);
223
+ * });
224
+ * ```
225
+ */
63
226
  setReplicationCallback(callback: (data: Uint8Array) => void): void;
227
+ /**
228
+ * Apply a batch of replication operations to this bucket.
229
+ * @param data - bytes produced by another bucket's replication
230
+ * @example
231
+ * ```ts
232
+ * bucket.applyReplicationBatch(remoteBytes);
233
+ * ```
234
+ */
64
235
  applyReplicationBatch(data: Uint8Array): void;
65
236
  }
237
+ type BSONValue = any;
238
+ type FilterOperators = {
239
+ $eq: BSONValue;
240
+ } | {
241
+ $ne: BSONValue;
242
+ } | {
243
+ $lt: BSONValue;
244
+ } | {
245
+ $lte: BSONValue;
246
+ } | {
247
+ $gt: BSONValue;
248
+ } | {
249
+ $gte: BSONValue;
250
+ } | {
251
+ $in: BSONValue[];
252
+ } | {
253
+ $between: [BSONValue, BSONValue];
254
+ } | {
255
+ $startsWith: string;
256
+ } | {
257
+ $endsWith: string;
258
+ } | {
259
+ $exists: boolean;
260
+ } | {
261
+ $notExists: boolean;
262
+ };
263
+ /**
264
+ * Builder for query objects that can be used with bucket
265
+ * operations like `list`, `delete`, and `transform`.
266
+ *
267
+ * The class supports chaining to construct filters, sorting,
268
+ * and pagination (offset/limit).
269
+ */
270
+ export declare class Query {
271
+ private _query;
272
+ /**
273
+ * Return the raw query object to pass to the native layer.
274
+ */
275
+ get query(): object;
276
+ /**
277
+ * Add a filter condition for the specified field.
278
+ * @param field - dot-separated path to the document field
279
+ * @param filter - comparison operator object
280
+ * @returns the same `Query` for chaining
281
+ * @example
282
+ * ```ts
283
+ * const q = new Query().where('age', { $gt: 18 });
284
+ * ```
285
+ */
286
+ where(field: string, filter: FilterOperators): this;
287
+ /**
288
+ * Specify sorting for the result set.
289
+ * @param field - field to sort by
290
+ * @param direction - `asc` or `desc` (defaults to `asc`)
291
+ * @example
292
+ * ```ts
293
+ * const q = new Query().sortBy('name', 'desc');
294
+ * ```
295
+ */
296
+ sortBy(field: string, direction?: "asc" | "desc"): this;
297
+ /**
298
+ * Set an offset and limit for pagination.
299
+ * @param offset - number of documents to skip
300
+ * @param limit - maximum number of documents to return
301
+ * @example
302
+ * ```ts
303
+ * const q = new Query().sector(10, 5);
304
+ * ```
305
+ */
306
+ sector(offset?: number, limit?: number): this;
307
+ }
308
+ /**
309
+ * Shortcut helper that creates a new `Query` with a single
310
+ * `where` clause applied.
311
+ *
312
+ * @param field - field name to filter on
313
+ * @param filter - filter operator object
314
+ * @returns a `Query` instance ready to use
315
+ * @example
316
+ * ```ts
317
+ * bucket.list(where('age', { $lt: 30 }));
318
+ * ```
319
+ */
320
+ export declare function where(field: string, filter: FilterOperators): Query;
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Bucket = exports.BSON = void 0;
3
+ exports.Query = exports.Bucket = exports.ObjectId = exports.BSON = void 0;
4
+ exports.where = where;
4
5
  const platformSuffix = process.platform == "darwin" ? "macos" : process.platform === "win32" ? "windows" : process.platform;
5
6
  const archSuffix = process.arch === "x64" ? "x86_64" : process.arch === "arm64" ? "aarch64" : process.arch;
6
7
  const isMusl = process.versions.libc && process.versions.libc.includes("musl");
@@ -11,35 +12,142 @@ exports.BSON = {
11
12
  serialize: albedo.serialize,
12
13
  deserialize: albedo.deserialize,
13
14
  };
15
+ /**
16
+ * Native ObjectId class constructor.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const id = new ObjectId();
21
+ * const parsed = ObjectId.fromString(id.toString());
22
+ * ```
23
+ */
24
+ exports.ObjectId = albedo.ObjectId;
25
+ /**
26
+ * Wrapper around a native Albedo bucket handle providing
27
+ * methods for CRUD operations, indexing, iteration, and
28
+ * replication support.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import albedo, { Bucket, BSON } from 'albedo-node';
33
+ *
34
+ * const bucket = Bucket.open('data.db');
35
+ * bucket.insert({ name: 'Alice' });
36
+ *
37
+ * for (const doc of bucket.list({ query: { name: { $eq: 'Alice' } } })) {
38
+ * console.log(doc);
39
+ * }
40
+ *
41
+ * bucket.close();
42
+ * ```
43
+ */
14
44
  class Bucket {
15
45
  handle;
46
+ /**
47
+ * Create a Bucket instance from an existing native handle.
48
+ * @param handle - opaque bucket handle returned by `albedo.open`
49
+ * @example
50
+ * ```ts
51
+ * const raw = albedo.open('foo.db');
52
+ * const bucket = new Bucket(raw);
53
+ * ```
54
+ */
16
55
  constructor(handle) {
17
56
  this.handle = handle;
18
57
  }
58
+ /**
59
+ * Open a bucket located at the given filesystem path.
60
+ * @param path - path to the bucket file
61
+ * @returns a new `Bucket` instance
62
+ * @example
63
+ * ```ts
64
+ * const bucket = Bucket.open('data.db');
65
+ * ```
66
+ */
19
67
  static open(path) {
20
68
  const handle = albedo.open(path);
21
69
  return new Bucket(handle);
22
70
  }
71
+ /**
72
+ * Close the bucket and release native resources.
73
+ * @example
74
+ * ```ts
75
+ * bucket.close();
76
+ * ```
77
+ */
23
78
  close() {
24
79
  albedo.close(this.handle);
25
80
  }
81
+ /**
82
+ * Insert a document or raw byte buffer into the bucket.
83
+ * @param doc - object to serialize or pre-serialized buffer
84
+ * @example
85
+ * ```ts
86
+ * bucket.insert({ name: 'Bob' });
87
+ * ```
88
+ */
26
89
  insert(doc) {
27
90
  albedo.insert(this.handle, doc);
28
91
  }
92
+ /**
93
+ * Delete documents matching the query. If no query is provided,
94
+ * all documents will be removed.
95
+ * @param query - filter object or `Query` instance
96
+ * @example
97
+ * ```ts
98
+ * bucket.delete({ name: { $eq: 'Bob' } });
99
+ * // or using Query builder
100
+ * bucket.delete(new Query().where('name', { $eq: 'Bob' }));
101
+ * ```
102
+ */
29
103
  delete(query) {
30
- albedo.delete(this.handle, query);
104
+ albedo.delete(this.handle, Bucket.convertToQuery(query));
31
105
  }
106
+ /**
107
+ * Retrieve information about all indexes defined on the bucket.
108
+ * @example
109
+ * ```ts
110
+ * console.log(bucket.indexes);
111
+ * ```
112
+ */
32
113
  get indexes() {
33
114
  return albedo.listIndexes(this.handle);
34
115
  }
116
+ /**
117
+ * Create or update an index on a field.
118
+ * @param name - index name (field path)
119
+ * @param options - index configuration
120
+ * @example
121
+ * ```ts
122
+ * bucket.ensureIndex('name', { unique: false, sparse: false, reverse: false });
123
+ * ```
124
+ */
35
125
  ensureIndex(name, options) {
36
126
  albedo.ensureIndex(this.handle, name, options);
37
127
  }
128
+ /**
129
+ * Remove an index by name.
130
+ * @example
131
+ * ```ts
132
+ * bucket.dropIndex('name');
133
+ * ```
134
+ */
38
135
  dropIndex(name) {
39
136
  albedo.dropIndex(this.handle, name);
40
137
  }
138
+ /**
139
+ * Iterate over documents matching the optional query.
140
+ * @param query - filter or `Query` object
141
+ * @yields each document deserialized from the bucket
142
+ * @example
143
+ * ```ts
144
+ * for (const doc of bucket.list({ query: { age: { $gt: 30 } } })) {
145
+ * console.log(doc);
146
+ * }
147
+ * ```
148
+ */
41
149
  *list(query) {
42
- const cursor = albedo.list(this.handle, query);
150
+ const cursor = albedo.list(this.handle, Bucket.convertToQuery(query));
43
151
  try {
44
152
  let data;
45
153
  while ((data = albedo.listData(cursor)) !== null) {
@@ -50,11 +158,43 @@ class Bucket {
50
158
  albedo.listClose(cursor);
51
159
  }
52
160
  }
161
+ /**
162
+ * Normalize a query argument to a plain object, unpacking
163
+ * `Query` instances.
164
+ * @example
165
+ * ```ts
166
+ * Bucket.convertToQuery(new Query().where('x', { $eq: 1 }));
167
+ * Bucket.convertToQuery({ foo: { $exists: true } });
168
+ * ```
169
+ */
170
+ static convertToQuery(query) {
171
+ if (query instanceof Query) {
172
+ return query.query;
173
+ }
174
+ return query || {};
175
+ }
176
+ /**
177
+ * Generator that allows reading and optionally modifying each
178
+ * document matching the query.
179
+ * @param query - filter or `Query` instance
180
+ * @yields the current document; the caller may send back an updated
181
+ * document or `null` to delete it.
182
+ * @example
183
+ * ```ts
184
+ * for (const doc of bucket.transformIterator({ query: { count: { $lt: 5 } } })) {
185
+ * if (doc.count < 2) {
186
+ * // update in-place
187
+ * yield { ...doc, count: doc.count + 1 };
188
+ * }
189
+ * }
190
+ * ```
191
+ */
53
192
  *transformIterator(query) {
54
- const iter = albedo.transform(this.handle, query);
193
+ const queryObj = Bucket.convertToQuery(query);
194
+ const iter = albedo.transform(this.handle, queryObj);
55
195
  try {
56
196
  let data;
57
- while ((data = albedo.transformData(iter)) !== null) {
197
+ while ((data = albedo.transformData(iter)) !== undefined) {
58
198
  const newDoc = yield data;
59
199
  albedo.transformApply(iter, newDoc);
60
200
  }
@@ -63,11 +203,136 @@ class Bucket {
63
203
  albedo.transformClose(iter);
64
204
  }
65
205
  }
206
+ /**
207
+ * Apply a transformation function to each document matching the
208
+ * provided query. The predicate receives the current document and
209
+ * should return the modified document, or `null` to remove it.
210
+ *
211
+ * This is a helper built on top of `transformIterator` and mirrors its
212
+ * behavior but uses a simple callback API instead of a generator.
213
+ *
214
+ * @param query - filter or `Query` object
215
+ * @param fn - transformation function
216
+ * @example
217
+ * ```ts
218
+ * bucket.transform(where('active', { $eq: true }), doc => {
219
+ * if (doc.count > 10) return null; // delete
220
+ * return { ...doc, count: doc.count + 1 };
221
+ * });
222
+ * ```
223
+ */
224
+ transform(query, fn) {
225
+ const queryObj = Bucket.convertToQuery(query);
226
+ const iter = albedo.transform(this.handle, queryObj);
227
+ try {
228
+ let data;
229
+ while ((data = albedo.transformData(iter)) !== undefined) {
230
+ albedo.transformApply(iter, fn(data));
231
+ }
232
+ }
233
+ finally {
234
+ albedo.transformClose(iter);
235
+ }
236
+ }
237
+ /**
238
+ * Register a callback to receive replication data produced by the
239
+ * bucket.
240
+ * @param callback - invoked with raw replication bytes
241
+ * @example
242
+ * ```ts
243
+ * bucket.setReplicationCallback(bytes => {
244
+ * console.log('got replication', bytes.length);
245
+ * });
246
+ * ```
247
+ */
66
248
  setReplicationCallback(callback) {
67
249
  albedo.setReplicationCallback(this.handle, callback);
68
250
  }
251
+ /**
252
+ * Apply a batch of replication operations to this bucket.
253
+ * @param data - bytes produced by another bucket's replication
254
+ * @example
255
+ * ```ts
256
+ * bucket.applyReplicationBatch(remoteBytes);
257
+ * ```
258
+ */
69
259
  applyReplicationBatch(data) {
70
260
  albedo.applyReplicationBatch(this.handle, data);
71
261
  }
72
262
  }
73
263
  exports.Bucket = Bucket;
264
+ /**
265
+ * Builder for query objects that can be used with bucket
266
+ * operations like `list`, `delete`, and `transform`.
267
+ *
268
+ * The class supports chaining to construct filters, sorting,
269
+ * and pagination (offset/limit).
270
+ */
271
+ class Query {
272
+ _query = {};
273
+ /**
274
+ * Return the raw query object to pass to the native layer.
275
+ */
276
+ get query() {
277
+ return this._query;
278
+ }
279
+ /**
280
+ * Add a filter condition for the specified field.
281
+ * @param field - dot-separated path to the document field
282
+ * @param filter - comparison operator object
283
+ * @returns the same `Query` for chaining
284
+ * @example
285
+ * ```ts
286
+ * const q = new Query().where('age', { $gt: 18 });
287
+ * ```
288
+ */
289
+ where(field, filter) {
290
+ if (!this._query.query) {
291
+ this._query.query = {};
292
+ }
293
+ this._query.query[field] = filter;
294
+ return this;
295
+ }
296
+ /**
297
+ * Specify sorting for the result set.
298
+ * @param field - field to sort by
299
+ * @param direction - `asc` or `desc` (defaults to `asc`)
300
+ * @example
301
+ * ```ts
302
+ * const q = new Query().sortBy('name', 'desc');
303
+ * ```
304
+ */
305
+ sortBy(field, direction = "asc") {
306
+ this._query.sort = direction === "asc" ? { asc: field } : { desc: field };
307
+ return this;
308
+ }
309
+ /**
310
+ * Set an offset and limit for pagination.
311
+ * @param offset - number of documents to skip
312
+ * @param limit - maximum number of documents to return
313
+ * @example
314
+ * ```ts
315
+ * const q = new Query().sector(10, 5);
316
+ * ```
317
+ */
318
+ sector(offset, limit) {
319
+ this._query.sector = { offset, limit };
320
+ return this;
321
+ }
322
+ }
323
+ exports.Query = Query;
324
+ /**
325
+ * Shortcut helper that creates a new `Query` with a single
326
+ * `where` clause applied.
327
+ *
328
+ * @param field - field name to filter on
329
+ * @param filter - filter operator object
330
+ * @returns a `Query` instance ready to use
331
+ * @example
332
+ * ```ts
333
+ * bucket.list(where('age', { $lt: 30 }));
334
+ * ```
335
+ */
336
+ function where(field, filter) {
337
+ return new Query().where(field, filter);
338
+ }
package/example/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { BSON, Bucket } from "albedo-node";
1
+ import { BSON, Bucket, where } from "albedo-node";
2
2
  import Database from "bun:sqlite";
3
3
  import { unlinkSync } from "fs";
4
4
 
@@ -81,10 +81,7 @@ console.time("Query index x 100");
81
81
  for (let i = 0; i < 100; i++) {
82
82
 
83
83
  bucket
84
- .list({
85
- query: { _id: Math.floor(Math.random() * docNum) },
86
- sector: { limit: 100 },
87
- })
84
+ .list(where("_id", { $eq: Math.floor(Math.random() * docNum) }).sector(0, 100))
88
85
  .next();
89
86
  }
90
87
  console.timeEnd("Query index x 100");
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,9 +1,25 @@
1
1
  {
2
2
  "name": "albedo-node",
3
- "version": "0.5.4",
4
- "description": "Albedo DB native bindings",
3
+ "version": "0.5.6",
4
+ "description": "High-performance embedded database for Node.js with native bindings, BSON support, indexing, and replication",
5
+ "keywords": [
6
+ "albedo",
7
+ "database",
8
+ "embedded-database",
9
+ "bson",
10
+ "native",
11
+ "node-addon"
12
+ ],
5
13
  "main": "dist/index.js",
6
14
  "types": "dist/index.d.ts",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/klirix/albedo-node.git"
18
+ },
19
+ "homepage": "https://github.com/klirix/albedo-node#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/klirix/albedo-node/issues"
22
+ },
7
23
  "scripts": {
8
24
  "build:binding": "zig build",
9
25
  "build:ts": "bun run tsc -p tsconfig.json",