albedo-node 0.6.2 โ 0.7.2
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 +51 -1
- package/dist/index.d.ts +64 -18
- package/dist/index.js +19 -17
- package/native/albedo.aarch64_linux_gnu.node +0 -0
- package/native/albedo.aarch64_linux_musl.node +0 -0
- package/native/albedo.aarch64_macos.node +0 -0
- package/native/albedo.x86_64_linux_gnu.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/README.md
CHANGED
|
@@ -8,7 +8,7 @@ This package wraps the core database engine with a concise TypeScript API, ships
|
|
|
8
8
|
|
|
9
9
|
- ๐งฑ Access to the full Albedo bucket API from Node.js or Bun
|
|
10
10
|
- ๐ฆ Precompiled native modules for Linux (glibc & musl), macOS, and arm64/x64
|
|
11
|
-
- ๐ Generator-based iterators for queries and
|
|
11
|
+
- ๐ Generator-based iterators for queries, transforms, and live subscriptions
|
|
12
12
|
- ๐ Built-in replication callback and apply mechanisms
|
|
13
13
|
- ๐งช TypeScript typings and Bun-based test suite
|
|
14
14
|
|
|
@@ -86,6 +86,48 @@ while (!step.done) {
|
|
|
86
86
|
}
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
### Subscribing to change events
|
|
90
|
+
|
|
91
|
+
`Bucket.subscribe()` exposes the new oplog-backed subscription API as an async
|
|
92
|
+
generator. It yields individual change events rather than re-scanned documents.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const bucket = Bucket.open("./example.bucket", {
|
|
96
|
+
wal: true,
|
|
97
|
+
write_durability: "all",
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
for await (const event of bucket.subscribe<{ name: string }>(
|
|
101
|
+
where("name", { $eq: "Ada" }),
|
|
102
|
+
{ pollingTimeout: 50, batchSize: 64 },
|
|
103
|
+
)) {
|
|
104
|
+
console.log(event.op, event.seqno, event.doc);
|
|
105
|
+
|
|
106
|
+
if (event.op === "delete") {
|
|
107
|
+
console.log("deleted", event.doc_id.toString());
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Each yielded event has this shape:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
type SubscriptionEvent<T = unknown> = {
|
|
116
|
+
seqno: bigint;
|
|
117
|
+
op: "insert" | "update" | "delete";
|
|
118
|
+
doc_id: ObjectId;
|
|
119
|
+
ts: bigint;
|
|
120
|
+
doc?: T;
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Notes:
|
|
125
|
+
|
|
126
|
+
- Subscriptions require WAL mode to be active.
|
|
127
|
+
- `doc` is present for inline insert and update payloads.
|
|
128
|
+
- Delete events still include `doc_id`, even when `doc` is absent.
|
|
129
|
+
- The generator closes the native subscription automatically when iteration stops.
|
|
130
|
+
|
|
89
131
|
### Replication
|
|
90
132
|
|
|
91
133
|
```ts
|
|
@@ -146,6 +188,7 @@ Serialized documents that contain `_id` fields with a BSON ObjectId will be revi
|
|
|
146
188
|
- `insert(doc: object | Uint8Array)`
|
|
147
189
|
- `delete(query?: object | Query)`
|
|
148
190
|
- `list<T>(query?: object | Query): Generator<T>`
|
|
191
|
+
- `subscribe<T>(query?: object | Query, options?: { pollingTimeout?: number; batchSize?: number }): AsyncGenerator<SubscriptionEvent<T>>`
|
|
149
192
|
- `transformIterator<T>(query?: object | Query): Generator<T, undefined, object | null>`
|
|
150
193
|
- `ensureIndex(name: string, options: { unique: boolean; sparse: boolean; reverse: boolean })`
|
|
151
194
|
- `dropIndex(name: string)`
|
|
@@ -154,6 +197,13 @@ Serialized documents that contain `_id` fields with a BSON ObjectId will be revi
|
|
|
154
197
|
- `applyReplicationBatch(batch: Uint8Array)`
|
|
155
198
|
- accepts raw BSON `Uint8Array` payloads anywhere a query or document object is expected
|
|
156
199
|
|
|
200
|
+
- `SubscriptionEvent<T>`
|
|
201
|
+
- `seqno: bigint`
|
|
202
|
+
- `op: "insert" | "update" | "delete"`
|
|
203
|
+
- `doc_id: ObjectId`
|
|
204
|
+
- `ts: bigint`
|
|
205
|
+
- `doc?: T`
|
|
206
|
+
|
|
157
207
|
- `Query`
|
|
158
208
|
- `where(field, filter)` chains field predicates (e.g. `{ $eq: value }`, `{ $gt: 10 }`)
|
|
159
209
|
- `sortBy(field, direction?)` sets sort order
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
type ByteBuffer = Uint8Array;
|
|
2
|
+
type BucketHandle = object;
|
|
3
|
+
type ListIteratorHandle = object;
|
|
4
|
+
type TransformIteratorHandle = object;
|
|
5
|
+
type SubscriptionHandle = object;
|
|
2
6
|
interface IndexOptions {
|
|
3
7
|
unique: boolean;
|
|
4
8
|
sparse: boolean;
|
|
5
9
|
reverse: boolean;
|
|
6
10
|
}
|
|
11
|
+
interface IndexInfo {
|
|
12
|
+
name: string;
|
|
13
|
+
unique: boolean;
|
|
14
|
+
sparse: boolean;
|
|
15
|
+
reverse: boolean;
|
|
16
|
+
}
|
|
7
17
|
interface ObjectIdInstance {
|
|
8
18
|
buffer: ByteBuffer;
|
|
9
19
|
toString(): string;
|
|
@@ -12,6 +22,20 @@ interface ObjectIdConstructor {
|
|
|
12
22
|
new (buffer?: ByteBuffer): ObjectIdInstance;
|
|
13
23
|
fromString(str: string): ObjectIdInstance;
|
|
14
24
|
}
|
|
25
|
+
export interface SubscriptionEvent<T = unknown> {
|
|
26
|
+
seqno: bigint;
|
|
27
|
+
op: "insert" | "update" | "delete";
|
|
28
|
+
doc_id: ObjectIdInstance;
|
|
29
|
+
ts: bigint;
|
|
30
|
+
doc?: T;
|
|
31
|
+
}
|
|
32
|
+
interface SubscriptionBatch<T = unknown> {
|
|
33
|
+
batch: Array<SubscriptionEvent<T>>;
|
|
34
|
+
}
|
|
35
|
+
export interface SubscribeOptions {
|
|
36
|
+
pollingTimeout?: number;
|
|
37
|
+
batchSize?: number;
|
|
38
|
+
}
|
|
15
39
|
/**
|
|
16
40
|
* Controls when fsync is called to guarantee write durability.
|
|
17
41
|
*/
|
|
@@ -34,11 +58,36 @@ interface OpenBucketOptions {
|
|
|
34
58
|
write_durability?: WriteDurability;
|
|
35
59
|
read_durability?: ReadDurability;
|
|
36
60
|
}
|
|
37
|
-
|
|
61
|
+
interface AlbedoModule {
|
|
62
|
+
ObjectId: ObjectIdConstructor;
|
|
63
|
+
serialize(value: unknown): Uint8Array;
|
|
64
|
+
deserialize<T = unknown>(data: ByteBuffer): T;
|
|
65
|
+
open(path: string): BucketHandle;
|
|
66
|
+
open_with_options(path: string, options: OpenBucketOptions): BucketHandle;
|
|
67
|
+
close(bucket: BucketHandle): void;
|
|
68
|
+
list(bucket: BucketHandle, query: object): ListIteratorHandle;
|
|
69
|
+
listClose(cursor: ListIteratorHandle): void;
|
|
70
|
+
listData(cursor: ListIteratorHandle): unknown | null;
|
|
71
|
+
insert(bucket: BucketHandle, doc: ByteBuffer | object): void;
|
|
72
|
+
ensureIndex(bucket: BucketHandle, name: string, options: IndexOptions): void;
|
|
73
|
+
listIndexes(bucket: BucketHandle): Record<string, IndexInfo>;
|
|
74
|
+
dropIndex(bucket: BucketHandle, name: string): void;
|
|
75
|
+
delete(bucket: BucketHandle, query: object): void;
|
|
76
|
+
transform(bucket: BucketHandle, query: object): TransformIteratorHandle;
|
|
77
|
+
transformClose(iter: TransformIteratorHandle): void;
|
|
78
|
+
transformData(iter: TransformIteratorHandle): unknown | null;
|
|
79
|
+
transformApply(iter: TransformIteratorHandle, replace: ByteBuffer | object | null): void;
|
|
80
|
+
subscribe(bucket: BucketHandle, query: object): SubscriptionHandle;
|
|
81
|
+
subscribePoll<T = unknown>(sub: SubscriptionHandle, maxEvents: number): SubscriptionBatch<T> | null;
|
|
82
|
+
subscribeClose(sub: SubscriptionHandle): void;
|
|
83
|
+
setReplicationCallback(bucket: BucketHandle, callback: (data: Uint8Array) => void): void;
|
|
84
|
+
applyReplicationBatch(bucket: BucketHandle, data: ByteBuffer): void;
|
|
85
|
+
}
|
|
86
|
+
export declare const albedo: AlbedoModule;
|
|
38
87
|
export default albedo;
|
|
39
88
|
export declare const BSON: {
|
|
40
|
-
serialize:
|
|
41
|
-
deserialize:
|
|
89
|
+
serialize: (value: unknown) => Uint8Array;
|
|
90
|
+
deserialize: <T = unknown>(data: ByteBuffer) => T;
|
|
42
91
|
};
|
|
43
92
|
/**
|
|
44
93
|
* Native ObjectId class constructor.
|
|
@@ -127,7 +176,7 @@ export declare class Bucket {
|
|
|
127
176
|
* console.log(bucket.indexes);
|
|
128
177
|
* ```
|
|
129
178
|
*/
|
|
130
|
-
get indexes():
|
|
179
|
+
get indexes(): Record<string, IndexInfo>;
|
|
131
180
|
/**
|
|
132
181
|
* Create or update an index on a field.
|
|
133
182
|
* @param name - index name (field path)
|
|
@@ -159,29 +208,26 @@ export declare class Bucket {
|
|
|
159
208
|
*/
|
|
160
209
|
list<T>(query?: object | Query): Generator<T>;
|
|
161
210
|
/**
|
|
162
|
-
* Async iterator that continuously polls
|
|
163
|
-
* optional query. Unlike `list`, when there are no more results the
|
|
164
|
-
* iterator waits for `pollingTimeout` milliseconds and retries,
|
|
165
|
-
* making it suitable for watching a bucket for new data.
|
|
211
|
+
* Async iterator that continuously polls a change subscription.
|
|
166
212
|
*
|
|
167
|
-
* The
|
|
168
|
-
*
|
|
213
|
+
* The generator yields individual oplog events rather than rescanned
|
|
214
|
+
* documents, and automatically closes the native subscription when the
|
|
215
|
+
* consumer stops iterating.
|
|
169
216
|
*
|
|
170
217
|
* @param query - filter or `Query` object
|
|
171
218
|
* @param options - polling configuration
|
|
172
|
-
* @param options.pollingTimeout - ms to wait before retrying when
|
|
173
|
-
*
|
|
174
|
-
* @
|
|
219
|
+
* @param options.pollingTimeout - ms to wait before retrying when the
|
|
220
|
+
* subscription is idle (default `50`)
|
|
221
|
+
* @param options.batchSize - maximum number of change events to pull per
|
|
222
|
+
* native poll (default `64`)
|
|
175
223
|
* @example
|
|
176
224
|
* ```ts
|
|
177
|
-
* for await (const
|
|
178
|
-
* console.log(
|
|
225
|
+
* for await (const event of bucket.subscribe<User>(where('active', { $eq: true }))) {
|
|
226
|
+
* console.log(event.op, event.doc);
|
|
179
227
|
* }
|
|
180
228
|
* ```
|
|
181
229
|
*/
|
|
182
|
-
|
|
183
|
-
pollingTimeout?: number;
|
|
184
|
-
}): AsyncGenerator<T>;
|
|
230
|
+
subscribe<T>(query?: object | Query, options?: SubscribeOptions): AsyncGenerator<SubscriptionEvent<T>>;
|
|
185
231
|
/**
|
|
186
232
|
* Collect all documents matching the optional query into an array.
|
|
187
233
|
* @param query - filter or `Query` object
|
package/dist/index.js
CHANGED
|
@@ -184,34 +184,36 @@ class Bucket {
|
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
/**
|
|
187
|
-
* Async iterator that continuously polls
|
|
188
|
-
* optional query. Unlike `list`, when there are no more results the
|
|
189
|
-
* iterator waits for `pollingTimeout` milliseconds and retries,
|
|
190
|
-
* making it suitable for watching a bucket for new data.
|
|
187
|
+
* Async iterator that continuously polls a change subscription.
|
|
191
188
|
*
|
|
192
|
-
* The
|
|
193
|
-
*
|
|
189
|
+
* The generator yields individual oplog events rather than rescanned
|
|
190
|
+
* documents, and automatically closes the native subscription when the
|
|
191
|
+
* consumer stops iterating.
|
|
194
192
|
*
|
|
195
193
|
* @param query - filter or `Query` object
|
|
196
194
|
* @param options - polling configuration
|
|
197
|
-
* @param options.pollingTimeout - ms to wait before retrying when
|
|
198
|
-
*
|
|
199
|
-
* @
|
|
195
|
+
* @param options.pollingTimeout - ms to wait before retrying when the
|
|
196
|
+
* subscription is idle (default `50`)
|
|
197
|
+
* @param options.batchSize - maximum number of change events to pull per
|
|
198
|
+
* native poll (default `64`)
|
|
200
199
|
* @example
|
|
201
200
|
* ```ts
|
|
202
|
-
* for await (const
|
|
203
|
-
* console.log(
|
|
201
|
+
* for await (const event of bucket.subscribe<User>(where('active', { $eq: true }))) {
|
|
202
|
+
* console.log(event.op, event.doc);
|
|
204
203
|
* }
|
|
205
204
|
* ```
|
|
206
205
|
*/
|
|
207
|
-
async *
|
|
206
|
+
async *subscribe(query, options) {
|
|
208
207
|
const pollingTimeout = options?.pollingTimeout ?? 50;
|
|
209
|
-
const
|
|
208
|
+
const batchSize = options?.batchSize ?? 64;
|
|
209
|
+
const subscription = exports.albedo.subscribe(this.handle, Bucket.convertToQuery(query));
|
|
210
210
|
try {
|
|
211
211
|
while (true) {
|
|
212
|
-
const
|
|
213
|
-
if (
|
|
214
|
-
|
|
212
|
+
const batch = exports.albedo.subscribePoll(subscription, batchSize);
|
|
213
|
+
if (batch !== null) {
|
|
214
|
+
for (const event of batch.batch) {
|
|
215
|
+
yield event;
|
|
216
|
+
}
|
|
215
217
|
}
|
|
216
218
|
else {
|
|
217
219
|
await new Promise((r) => setTimeout(r, pollingTimeout));
|
|
@@ -219,7 +221,7 @@ class Bucket {
|
|
|
219
221
|
}
|
|
220
222
|
}
|
|
221
223
|
finally {
|
|
222
|
-
exports.albedo.
|
|
224
|
+
exports.albedo.subscribeClose(subscription);
|
|
223
225
|
}
|
|
224
226
|
}
|
|
225
227
|
/**
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|