albedo-node 0.5.3 โ†’ 0.5.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/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
@@ -48,17 +48,263 @@ export declare const BSON: {
48
48
  serialize: (value: unknown) => Uint8Array;
49
49
  deserialize: <T = unknown>(data: ByteBuffer) => T;
50
50
  };
51
+ /**
52
+ * Wrapper around a native Albedo bucket handle providing
53
+ * methods for CRUD operations, indexing, iteration, and
54
+ * replication support.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * import albedo, { Bucket, BSON } from 'albedo-node';
59
+ *
60
+ * const bucket = Bucket.open('data.db');
61
+ * bucket.insert({ name: 'Alice' });
62
+ *
63
+ * for (const doc of bucket.list({ query: { name: { $eq: 'Alice' } } })) {
64
+ * console.log(doc);
65
+ * }
66
+ *
67
+ * bucket.close();
68
+ * ```
69
+ */
51
70
  export declare class Bucket {
52
71
  private handle;
72
+ /**
73
+ * Create a Bucket instance from an existing native handle.
74
+ * @param handle - opaque bucket handle returned by `albedo.open`
75
+ * @example
76
+ * ```ts
77
+ * const raw = albedo.open('foo.db');
78
+ * const bucket = new Bucket(raw);
79
+ * ```
80
+ */
53
81
  constructor(handle: object);
82
+ /**
83
+ * Open a bucket located at the given filesystem path.
84
+ * @param path - path to the bucket file
85
+ * @returns a new `Bucket` instance
86
+ * @example
87
+ * ```ts
88
+ * const bucket = Bucket.open('data.db');
89
+ * ```
90
+ */
54
91
  static open(path: string): Bucket;
92
+ /**
93
+ * Close the bucket and release native resources.
94
+ * @example
95
+ * ```ts
96
+ * bucket.close();
97
+ * ```
98
+ */
55
99
  close(): void;
100
+ /**
101
+ * Insert a document or raw byte buffer into the bucket.
102
+ * @param doc - object to serialize or pre-serialized buffer
103
+ * @example
104
+ * ```ts
105
+ * bucket.insert({ name: 'Bob' });
106
+ * ```
107
+ */
56
108
  insert(doc: object | ByteBuffer): void;
109
+ /**
110
+ * Delete documents matching the query. If no query is provided,
111
+ * all documents will be removed.
112
+ * @param query - filter object or `Query` instance
113
+ * @example
114
+ * ```ts
115
+ * bucket.delete({ name: { $eq: 'Bob' } });
116
+ * // or using Query builder
117
+ * bucket.delete(new Query().where('name', { $eq: 'Bob' }));
118
+ * ```
119
+ */
120
+ delete(query?: object | Query): void;
121
+ /**
122
+ * Retrieve information about all indexes defined on the bucket.
123
+ * @example
124
+ * ```ts
125
+ * console.log(bucket.indexes);
126
+ * ```
127
+ */
57
128
  get indexes(): Record<string, IndexInfo>;
129
+ /**
130
+ * Create or update an index on a field.
131
+ * @param name - index name (field path)
132
+ * @param options - index configuration
133
+ * @example
134
+ * ```ts
135
+ * bucket.ensureIndex('name', { unique: false, sparse: false, reverse: false });
136
+ * ```
137
+ */
58
138
  ensureIndex(name: string, options: IndexOptions): void;
139
+ /**
140
+ * Remove an index by name.
141
+ * @example
142
+ * ```ts
143
+ * bucket.dropIndex('name');
144
+ * ```
145
+ */
59
146
  dropIndex(name: string): void;
60
- list<T>(query: object): Generator<T>;
61
- transformIterator<T>(query: object): Generator<T, undefined, null | object>;
147
+ /**
148
+ * Iterate over documents matching the optional query.
149
+ * @param query - filter or `Query` object
150
+ * @yields each document deserialized from the bucket
151
+ * @example
152
+ * ```ts
153
+ * for (const doc of bucket.list({ query: { age: { $gt: 30 } } })) {
154
+ * console.log(doc);
155
+ * }
156
+ * ```
157
+ */
158
+ list<T>(query?: object | Query): Generator<T>;
159
+ /**
160
+ * Normalize a query argument to a plain object, unpacking
161
+ * `Query` instances.
162
+ * @example
163
+ * ```ts
164
+ * Bucket.convertToQuery(new Query().where('x', { $eq: 1 }));
165
+ * Bucket.convertToQuery({ foo: { $exists: true } });
166
+ * ```
167
+ */
168
+ static convertToQuery(query?: object | Query): object;
169
+ /**
170
+ * Generator that allows reading and optionally modifying each
171
+ * document matching the query.
172
+ * @param query - filter or `Query` instance
173
+ * @yields the current document; the caller may send back an updated
174
+ * document or `null` to delete it.
175
+ * @example
176
+ * ```ts
177
+ * for (const doc of bucket.transformIterator({ query: { count: { $lt: 5 } } })) {
178
+ * if (doc.count < 2) {
179
+ * // update in-place
180
+ * yield { ...doc, count: doc.count + 1 };
181
+ * }
182
+ * }
183
+ * ```
184
+ */
185
+ transformIterator<T>(query?: object | Query): Generator<T, undefined, null | object>;
186
+ /**
187
+ * Apply a transformation function to each document matching the
188
+ * provided query. The predicate receives the current document and
189
+ * should return the modified document, or `null` to remove it.
190
+ *
191
+ * This is a helper built on top of `transformIterator` and mirrors its
192
+ * behavior but uses a simple callback API instead of a generator.
193
+ *
194
+ * @param query - filter or `Query` object
195
+ * @param fn - transformation function
196
+ * @example
197
+ * ```ts
198
+ * bucket.transform(where('active', { $eq: true }), doc => {
199
+ * if (doc.count > 10) return null; // delete
200
+ * return { ...doc, count: doc.count + 1 };
201
+ * });
202
+ * ```
203
+ */
204
+ transform<T extends object>(query: object | Query | undefined, fn: (doc: T) => T | null): void;
205
+ /**
206
+ * Register a callback to receive replication data produced by the
207
+ * bucket.
208
+ * @param callback - invoked with raw replication bytes
209
+ * @example
210
+ * ```ts
211
+ * bucket.setReplicationCallback(bytes => {
212
+ * console.log('got replication', bytes.length);
213
+ * });
214
+ * ```
215
+ */
62
216
  setReplicationCallback(callback: (data: Uint8Array) => void): void;
217
+ /**
218
+ * Apply a batch of replication operations to this bucket.
219
+ * @param data - bytes produced by another bucket's replication
220
+ * @example
221
+ * ```ts
222
+ * bucket.applyReplicationBatch(remoteBytes);
223
+ * ```
224
+ */
63
225
  applyReplicationBatch(data: Uint8Array): void;
64
226
  }
227
+ type BSONValue = any;
228
+ type FilterOperators = {
229
+ $eq: BSONValue;
230
+ } | {
231
+ $ne: BSONValue;
232
+ } | {
233
+ $lt: BSONValue;
234
+ } | {
235
+ $lte: BSONValue;
236
+ } | {
237
+ $gt: BSONValue;
238
+ } | {
239
+ $gte: BSONValue;
240
+ } | {
241
+ $in: BSONValue[];
242
+ } | {
243
+ $between: [BSONValue, BSONValue];
244
+ } | {
245
+ $startsWith: string;
246
+ } | {
247
+ $endsWith: string;
248
+ } | {
249
+ $exists: boolean;
250
+ } | {
251
+ $notExists: boolean;
252
+ };
253
+ /**
254
+ * Builder for query objects that can be used with bucket
255
+ * operations like `list`, `delete`, and `transform`.
256
+ *
257
+ * The class supports chaining to construct filters, sorting,
258
+ * and pagination (offset/limit).
259
+ */
260
+ export declare class Query {
261
+ private _query;
262
+ /**
263
+ * Return the raw query object to pass to the native layer.
264
+ */
265
+ get query(): object;
266
+ /**
267
+ * Add a filter condition for the specified field.
268
+ * @param field - dot-separated path to the document field
269
+ * @param filter - comparison operator object
270
+ * @returns the same `Query` for chaining
271
+ * @example
272
+ * ```ts
273
+ * const q = new Query().where('age', { $gt: 18 });
274
+ * ```
275
+ */
276
+ where(field: string, filter: FilterOperators): this;
277
+ /**
278
+ * Specify sorting for the result set.
279
+ * @param field - field to sort by
280
+ * @param direction - `asc` or `desc` (defaults to `asc`)
281
+ * @example
282
+ * ```ts
283
+ * const q = new Query().sortBy('name', 'desc');
284
+ * ```
285
+ */
286
+ sortBy(field: string, direction?: "asc" | "desc"): this;
287
+ /**
288
+ * Set an offset and limit for pagination.
289
+ * @param offset - number of documents to skip
290
+ * @param limit - maximum number of documents to return
291
+ * @example
292
+ * ```ts
293
+ * const q = new Query().sector(10, 5);
294
+ * ```
295
+ */
296
+ sector(offset?: number, limit?: number): this;
297
+ }
298
+ /**
299
+ * Shortcut helper that creates a new `Query` with a single
300
+ * `where` clause applied.
301
+ *
302
+ * @param field - field name to filter on
303
+ * @param filter - filter operator object
304
+ * @returns a `Query` instance ready to use
305
+ * @example
306
+ * ```ts
307
+ * bucket.list(where('age', { $lt: 30 }));
308
+ * ```
309
+ */
310
+ 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.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,32 +12,132 @@ exports.BSON = {
11
12
  serialize: albedo.serialize,
12
13
  deserialize: albedo.deserialize,
13
14
  };
15
+ /**
16
+ * Wrapper around a native Albedo bucket handle providing
17
+ * methods for CRUD operations, indexing, iteration, and
18
+ * replication support.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import albedo, { Bucket, BSON } from 'albedo-node';
23
+ *
24
+ * const bucket = Bucket.open('data.db');
25
+ * bucket.insert({ name: 'Alice' });
26
+ *
27
+ * for (const doc of bucket.list({ query: { name: { $eq: 'Alice' } } })) {
28
+ * console.log(doc);
29
+ * }
30
+ *
31
+ * bucket.close();
32
+ * ```
33
+ */
14
34
  class Bucket {
15
35
  handle;
36
+ /**
37
+ * Create a Bucket instance from an existing native handle.
38
+ * @param handle - opaque bucket handle returned by `albedo.open`
39
+ * @example
40
+ * ```ts
41
+ * const raw = albedo.open('foo.db');
42
+ * const bucket = new Bucket(raw);
43
+ * ```
44
+ */
16
45
  constructor(handle) {
17
46
  this.handle = handle;
18
47
  }
48
+ /**
49
+ * Open a bucket located at the given filesystem path.
50
+ * @param path - path to the bucket file
51
+ * @returns a new `Bucket` instance
52
+ * @example
53
+ * ```ts
54
+ * const bucket = Bucket.open('data.db');
55
+ * ```
56
+ */
19
57
  static open(path) {
20
58
  const handle = albedo.open(path);
21
59
  return new Bucket(handle);
22
60
  }
61
+ /**
62
+ * Close the bucket and release native resources.
63
+ * @example
64
+ * ```ts
65
+ * bucket.close();
66
+ * ```
67
+ */
23
68
  close() {
24
69
  albedo.close(this.handle);
25
70
  }
71
+ /**
72
+ * Insert a document or raw byte buffer into the bucket.
73
+ * @param doc - object to serialize or pre-serialized buffer
74
+ * @example
75
+ * ```ts
76
+ * bucket.insert({ name: 'Bob' });
77
+ * ```
78
+ */
26
79
  insert(doc) {
27
80
  albedo.insert(this.handle, doc);
28
81
  }
82
+ /**
83
+ * Delete documents matching the query. If no query is provided,
84
+ * all documents will be removed.
85
+ * @param query - filter object or `Query` instance
86
+ * @example
87
+ * ```ts
88
+ * bucket.delete({ name: { $eq: 'Bob' } });
89
+ * // or using Query builder
90
+ * bucket.delete(new Query().where('name', { $eq: 'Bob' }));
91
+ * ```
92
+ */
93
+ delete(query) {
94
+ albedo.delete(this.handle, Bucket.convertToQuery(query));
95
+ }
96
+ /**
97
+ * Retrieve information about all indexes defined on the bucket.
98
+ * @example
99
+ * ```ts
100
+ * console.log(bucket.indexes);
101
+ * ```
102
+ */
29
103
  get indexes() {
30
104
  return albedo.listIndexes(this.handle);
31
105
  }
106
+ /**
107
+ * Create or update an index on a field.
108
+ * @param name - index name (field path)
109
+ * @param options - index configuration
110
+ * @example
111
+ * ```ts
112
+ * bucket.ensureIndex('name', { unique: false, sparse: false, reverse: false });
113
+ * ```
114
+ */
32
115
  ensureIndex(name, options) {
33
116
  albedo.ensureIndex(this.handle, name, options);
34
117
  }
118
+ /**
119
+ * Remove an index by name.
120
+ * @example
121
+ * ```ts
122
+ * bucket.dropIndex('name');
123
+ * ```
124
+ */
35
125
  dropIndex(name) {
36
126
  albedo.dropIndex(this.handle, name);
37
127
  }
128
+ /**
129
+ * Iterate over documents matching the optional query.
130
+ * @param query - filter or `Query` object
131
+ * @yields each document deserialized from the bucket
132
+ * @example
133
+ * ```ts
134
+ * for (const doc of bucket.list({ query: { age: { $gt: 30 } } })) {
135
+ * console.log(doc);
136
+ * }
137
+ * ```
138
+ */
38
139
  *list(query) {
39
- const cursor = albedo.list(this.handle, query);
140
+ const cursor = albedo.list(this.handle, Bucket.convertToQuery(query));
40
141
  try {
41
142
  let data;
42
143
  while ((data = albedo.listData(cursor)) !== null) {
@@ -47,11 +148,43 @@ class Bucket {
47
148
  albedo.listClose(cursor);
48
149
  }
49
150
  }
151
+ /**
152
+ * Normalize a query argument to a plain object, unpacking
153
+ * `Query` instances.
154
+ * @example
155
+ * ```ts
156
+ * Bucket.convertToQuery(new Query().where('x', { $eq: 1 }));
157
+ * Bucket.convertToQuery({ foo: { $exists: true } });
158
+ * ```
159
+ */
160
+ static convertToQuery(query) {
161
+ if (query instanceof Query) {
162
+ return query.query;
163
+ }
164
+ return query || {};
165
+ }
166
+ /**
167
+ * Generator that allows reading and optionally modifying each
168
+ * document matching the query.
169
+ * @param query - filter or `Query` instance
170
+ * @yields the current document; the caller may send back an updated
171
+ * document or `null` to delete it.
172
+ * @example
173
+ * ```ts
174
+ * for (const doc of bucket.transformIterator({ query: { count: { $lt: 5 } } })) {
175
+ * if (doc.count < 2) {
176
+ * // update in-place
177
+ * yield { ...doc, count: doc.count + 1 };
178
+ * }
179
+ * }
180
+ * ```
181
+ */
50
182
  *transformIterator(query) {
51
- const iter = albedo.transform(this.handle, query);
183
+ const queryObj = Bucket.convertToQuery(query);
184
+ const iter = albedo.transform(this.handle, queryObj);
52
185
  try {
53
186
  let data;
54
- while ((data = albedo.transformData(iter)) !== null) {
187
+ while ((data = albedo.transformData(iter)) !== undefined) {
55
188
  const newDoc = yield data;
56
189
  albedo.transformApply(iter, newDoc);
57
190
  }
@@ -60,11 +193,136 @@ class Bucket {
60
193
  albedo.transformClose(iter);
61
194
  }
62
195
  }
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(query, fn) {
215
+ const queryObj = Bucket.convertToQuery(query);
216
+ const iter = albedo.transform(this.handle, queryObj);
217
+ try {
218
+ let data;
219
+ while ((data = albedo.transformData(iter)) !== undefined) {
220
+ albedo.transformApply(iter, fn(data));
221
+ }
222
+ }
223
+ finally {
224
+ albedo.transformClose(iter);
225
+ }
226
+ }
227
+ /**
228
+ * Register a callback to receive replication data produced by the
229
+ * bucket.
230
+ * @param callback - invoked with raw replication bytes
231
+ * @example
232
+ * ```ts
233
+ * bucket.setReplicationCallback(bytes => {
234
+ * console.log('got replication', bytes.length);
235
+ * });
236
+ * ```
237
+ */
63
238
  setReplicationCallback(callback) {
64
239
  albedo.setReplicationCallback(this.handle, callback);
65
240
  }
241
+ /**
242
+ * Apply a batch of replication operations to this bucket.
243
+ * @param data - bytes produced by another bucket's replication
244
+ * @example
245
+ * ```ts
246
+ * bucket.applyReplicationBatch(remoteBytes);
247
+ * ```
248
+ */
66
249
  applyReplicationBatch(data) {
67
250
  albedo.applyReplicationBatch(this.handle, data);
68
251
  }
69
252
  }
70
253
  exports.Bucket = Bucket;
254
+ /**
255
+ * Builder for query objects that can be used with bucket
256
+ * operations like `list`, `delete`, and `transform`.
257
+ *
258
+ * The class supports chaining to construct filters, sorting,
259
+ * and pagination (offset/limit).
260
+ */
261
+ class Query {
262
+ _query = {};
263
+ /**
264
+ * Return the raw query object to pass to the native layer.
265
+ */
266
+ get query() {
267
+ return this._query;
268
+ }
269
+ /**
270
+ * Add a filter condition for the specified field.
271
+ * @param field - dot-separated path to the document field
272
+ * @param filter - comparison operator object
273
+ * @returns the same `Query` for chaining
274
+ * @example
275
+ * ```ts
276
+ * const q = new Query().where('age', { $gt: 18 });
277
+ * ```
278
+ */
279
+ where(field, filter) {
280
+ if (!this._query.query) {
281
+ this._query.query = {};
282
+ }
283
+ this._query.query[field] = filter;
284
+ return this;
285
+ }
286
+ /**
287
+ * Specify sorting for the result set.
288
+ * @param field - field to sort by
289
+ * @param direction - `asc` or `desc` (defaults to `asc`)
290
+ * @example
291
+ * ```ts
292
+ * const q = new Query().sortBy('name', 'desc');
293
+ * ```
294
+ */
295
+ sortBy(field, direction = "asc") {
296
+ this._query.sort = direction === "asc" ? { asc: field } : { desc: field };
297
+ return this;
298
+ }
299
+ /**
300
+ * Set an offset and limit for pagination.
301
+ * @param offset - number of documents to skip
302
+ * @param limit - maximum number of documents to return
303
+ * @example
304
+ * ```ts
305
+ * const q = new Query().sector(10, 5);
306
+ * ```
307
+ */
308
+ sector(offset, limit) {
309
+ this._query.sector = { offset, limit };
310
+ return this;
311
+ }
312
+ }
313
+ exports.Query = Query;
314
+ /**
315
+ * Shortcut helper that creates a new `Query` with a single
316
+ * `where` clause applied.
317
+ *
318
+ * @param field - field name to filter on
319
+ * @param filter - filter operator object
320
+ * @returns a `Query` instance ready to use
321
+ * @example
322
+ * ```ts
323
+ * bucket.list(where('age', { $lt: 30 }));
324
+ * ```
325
+ */
326
+ function where(field, filter) {
327
+ return new Query().where(field, filter);
328
+ }
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,6 +1,6 @@
1
1
  {
2
2
  "name": "albedo-node",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "Albedo DB native bindings",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/bun.lock DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "workspaces": {
4
- "": {
5
- "name": "albedo-node",
6
- "devDependencies": {
7
- "@types/node": "^24.0.0",
8
- "typescript": "^5.9.3",
9
- },
10
- },
11
- },
12
- "packages": {
13
- "@types/node": ["@types/node@24.10.13", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg=="],
14
-
15
- "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
16
-
17
- "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
18
- }
19
- }