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 +183 -0
- package/dist/index.d.ts +248 -2
- package/dist/index.js +262 -4
- package/example/index.js +2 -5
- package/native/albedo.aarch64_linux_musl.node +0 -0
- package/native/albedo.aarch64_macos.node +0 -0
- package/native/albedo.x86_64_linux.node +0 -0
- package/native/albedo.x86_64_linux_musl.node +0 -0
- package/native/albedo.x86_64_macos.node +0 -0
- package/package.json +1 -1
- package/bun.lock +0 -19
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
|
-
|
|
61
|
-
|
|
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
|
|
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)) !==
|
|
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
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
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
|
-
}
|