network-ai 3.6.2 → 3.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/blackboard-backend-crdt.d.ts +156 -0
- package/dist/lib/blackboard-backend-crdt.d.ts.map +1 -0
- package/dist/lib/blackboard-backend-crdt.js +251 -0
- package/dist/lib/blackboard-backend-crdt.js.map +1 -0
- package/dist/lib/consistency.d.ts +194 -0
- package/dist/lib/consistency.d.ts.map +1 -0
- package/dist/lib/consistency.js +274 -0
- package/dist/lib/consistency.js.map +1 -0
- package/dist/lib/crdt.d.ts +106 -0
- package/dist/lib/crdt.d.ts.map +1 -0
- package/dist/lib/crdt.js +141 -0
- package/dist/lib/crdt.js.map +1 -0
- package/dist/run-tests.d.ts +9 -0
- package/dist/run-tests.d.ts.map +1 -0
- package/dist/run-tests.js +75 -0
- package/dist/run-tests.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CRDT Blackboard Backend
|
|
4
|
+
*
|
|
5
|
+
* Provides a `CrdtBackend` implementation of `BlackboardBackend` that uses
|
|
6
|
+
* vector clocks and conflict-free merge semantics to synchronize state across
|
|
7
|
+
* multiple nodes (processes or machines) without requiring coordination.
|
|
8
|
+
*
|
|
9
|
+
* Each `CrdtBackend` instance represents one "node" in the distributed system.
|
|
10
|
+
* Nodes exchange `CrdtEntry` records and merge them using `mergeEntry()` to
|
|
11
|
+
* converge to the same state — even after concurrent, offline writes.
|
|
12
|
+
*
|
|
13
|
+
* Architecture:
|
|
14
|
+
* - Each node has a unique `nodeId` and a local `VectorClock`.
|
|
15
|
+
* - Writes increment the node's own clock counter and tag the entry.
|
|
16
|
+
* - Deletes record tombstones (`deleted: true`) so deletions propagate.
|
|
17
|
+
* - `merge(entries)` applies conflict-free resolution for each key.
|
|
18
|
+
* - `sync(other)` performs a full bidirectional merge with another node.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { CrdtBackend } from 'network-ai';
|
|
23
|
+
*
|
|
24
|
+
* const nodeA = new CrdtBackend('node-a');
|
|
25
|
+
* const nodeB = new CrdtBackend('node-b');
|
|
26
|
+
*
|
|
27
|
+
* nodeA.write('status', 'idle', 'agent-1');
|
|
28
|
+
* nodeB.write('status', 'busy', 'agent-2');
|
|
29
|
+
*
|
|
30
|
+
* nodeA.sync(nodeB); // bidirectional merge
|
|
31
|
+
*
|
|
32
|
+
* // Both nodes now converge on the deterministic winner for 'status'
|
|
33
|
+
* console.log(nodeA.read('status')?.value === nodeB.read('status')?.value); // true
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @module BlackboardBackendCrdt
|
|
37
|
+
* @version 1.0.0
|
|
38
|
+
* @license MIT
|
|
39
|
+
*/
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.CrdtBackend = void 0;
|
|
42
|
+
const crdt_1 = require("./crdt");
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// CRDT BACKEND
|
|
45
|
+
// ============================================================================
|
|
46
|
+
/**
|
|
47
|
+
* CRDT-based `BlackboardBackend` for distributed multi-node agent coordination.
|
|
48
|
+
*
|
|
49
|
+
* Fully satisfies the synchronous `BlackboardBackend` interface while adding
|
|
50
|
+
* vector-clock-based merge semantics for deterministic convergence across nodes.
|
|
51
|
+
*
|
|
52
|
+
* All reads and writes are O(1). `merge()` is O(n) in the number of incoming
|
|
53
|
+
* entries. `sync()` is O(n + m) in the combined store sizes.
|
|
54
|
+
*/
|
|
55
|
+
class CrdtBackend {
|
|
56
|
+
_nodeId;
|
|
57
|
+
_clock = {};
|
|
58
|
+
_store = new Map();
|
|
59
|
+
/**
|
|
60
|
+
* Create a new `CrdtBackend` node.
|
|
61
|
+
*
|
|
62
|
+
* @param nodeId Unique node identifier. Defaults to a random string.
|
|
63
|
+
* @param options Additional options.
|
|
64
|
+
*/
|
|
65
|
+
constructor(nodeId, options) {
|
|
66
|
+
// nodeId param takes precedence; fall back to options.nodeId, then random
|
|
67
|
+
this._nodeId = nodeId ?? options?.nodeId ?? `node-${Math.random().toString(36).slice(2, 10)}`;
|
|
68
|
+
}
|
|
69
|
+
// --------------------------------------------------------------------------
|
|
70
|
+
// BlackboardBackend interface
|
|
71
|
+
// --------------------------------------------------------------------------
|
|
72
|
+
/**
|
|
73
|
+
* Read an entry. Returns `null` if not found, expired, or tombstoned.
|
|
74
|
+
*/
|
|
75
|
+
read(key) {
|
|
76
|
+
const entry = this._store.get(key);
|
|
77
|
+
if (!entry)
|
|
78
|
+
return null;
|
|
79
|
+
if (entry.deleted)
|
|
80
|
+
return null;
|
|
81
|
+
if (this._isExpired(entry))
|
|
82
|
+
return null;
|
|
83
|
+
return entry;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Write a value to this node, incrementing the local vector clock.
|
|
87
|
+
* The entry is tagged with the current clock and this node's id.
|
|
88
|
+
*/
|
|
89
|
+
write(key, value, sourceAgent, ttl) {
|
|
90
|
+
this._clock = (0, crdt_1.tickClock)(this._clock, this._nodeId);
|
|
91
|
+
const existing = this._store.get(key);
|
|
92
|
+
const version = (existing && !existing.deleted) ? existing.version + 1 : 1;
|
|
93
|
+
const entry = {
|
|
94
|
+
key,
|
|
95
|
+
value,
|
|
96
|
+
source_agent: sourceAgent,
|
|
97
|
+
timestamp: new Date().toISOString(),
|
|
98
|
+
ttl: ttl ?? null,
|
|
99
|
+
version,
|
|
100
|
+
vectorClock: { ...this._clock },
|
|
101
|
+
nodeId: this._nodeId,
|
|
102
|
+
};
|
|
103
|
+
this._store.set(key, entry);
|
|
104
|
+
return entry;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Delete an entry by recording a tombstone so the deletion propagates on
|
|
108
|
+
* sync to other nodes.
|
|
109
|
+
*
|
|
110
|
+
* Returns `true` if the key existed and was not already deleted.
|
|
111
|
+
*/
|
|
112
|
+
delete(key) {
|
|
113
|
+
const existing = this._store.get(key);
|
|
114
|
+
if (!existing || existing.deleted)
|
|
115
|
+
return false;
|
|
116
|
+
this._clock = (0, crdt_1.tickClock)(this._clock, this._nodeId);
|
|
117
|
+
const tombstone = {
|
|
118
|
+
...existing,
|
|
119
|
+
value: null,
|
|
120
|
+
deleted: true,
|
|
121
|
+
timestamp: new Date().toISOString(),
|
|
122
|
+
vectorClock: { ...this._clock },
|
|
123
|
+
nodeId: this._nodeId,
|
|
124
|
+
};
|
|
125
|
+
this._store.set(key, tombstone);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Return all non-expired, non-deleted keys.
|
|
130
|
+
*/
|
|
131
|
+
listKeys() {
|
|
132
|
+
const keys = [];
|
|
133
|
+
for (const [key, entry] of this._store.entries()) {
|
|
134
|
+
if (!entry.deleted && !this._isExpired(entry)) {
|
|
135
|
+
keys.push(key);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return keys;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Return a snapshot of all non-expired, non-deleted entries.
|
|
142
|
+
*/
|
|
143
|
+
getSnapshot() {
|
|
144
|
+
const result = {};
|
|
145
|
+
for (const [key, entry] of this._store.entries()) {
|
|
146
|
+
if (!entry.deleted && !this._isExpired(entry)) {
|
|
147
|
+
result[key] = entry;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
// --------------------------------------------------------------------------
|
|
153
|
+
// CRDT-specific API
|
|
154
|
+
// --------------------------------------------------------------------------
|
|
155
|
+
/**
|
|
156
|
+
* The unique node identifier for this backend instance.
|
|
157
|
+
*/
|
|
158
|
+
get nodeId() {
|
|
159
|
+
return this._nodeId;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Return a copy of the current vector clock for this node.
|
|
163
|
+
*/
|
|
164
|
+
getVectorClock() {
|
|
165
|
+
return { ...this._clock };
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Return the raw `CrdtEntry` for a key, including tombstones and expired
|
|
169
|
+
* entries. Use `read()` for normal agent access.
|
|
170
|
+
*
|
|
171
|
+
* @returns The stored `CrdtEntry`, or `null` if the key has never been written.
|
|
172
|
+
*/
|
|
173
|
+
getCrdtEntry(key) {
|
|
174
|
+
return this._store.get(key) ?? null;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Return a snapshot of all raw `CrdtEntry` records in this node's store,
|
|
178
|
+
* including tombstones and expired entries.
|
|
179
|
+
*
|
|
180
|
+
* Use this to obtain the payload for sending to another node's `merge()`.
|
|
181
|
+
*/
|
|
182
|
+
getCrdtSnapshot() {
|
|
183
|
+
const result = {};
|
|
184
|
+
for (const [key, entry] of this._store.entries()) {
|
|
185
|
+
result[key] = entry;
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Merge an array of `CrdtEntry` records from another node into this store.
|
|
191
|
+
*
|
|
192
|
+
* For each incoming entry:
|
|
193
|
+
* - If this node has no entry for the key, store it directly.
|
|
194
|
+
* - Otherwise, apply `mergeEntry()` to pick the winner deterministically.
|
|
195
|
+
*
|
|
196
|
+
* The local vector clock is advanced to the component-wise max of the
|
|
197
|
+
* current clock and all clocks carried in the incoming entries.
|
|
198
|
+
*
|
|
199
|
+
* @param entries Entries from another node (obtained via `getCrdtSnapshot()`).
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```typescript
|
|
203
|
+
* nodeA.merge(Object.values(nodeB.getCrdtSnapshot()));
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
merge(entries) {
|
|
207
|
+
for (const incoming of entries) {
|
|
208
|
+
// Advance our clock to acknowledge the remote writes
|
|
209
|
+
this._clock = (0, crdt_1.mergeClock)(this._clock, incoming.vectorClock);
|
|
210
|
+
const local = this._store.get(incoming.key);
|
|
211
|
+
if (!local) {
|
|
212
|
+
this._store.set(incoming.key, incoming);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
this._store.set(incoming.key, (0, crdt_1.mergeEntry)(local, incoming));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Bidirectionally synchronise this node with `other`.
|
|
221
|
+
*
|
|
222
|
+
* After `sync()`, both nodes will have merged each other's entire store and
|
|
223
|
+
* converge to the same state for every key.
|
|
224
|
+
*
|
|
225
|
+
* Equivalent to:
|
|
226
|
+
* ```typescript
|
|
227
|
+
* const mine = Object.values(this.getCrdtSnapshot());
|
|
228
|
+
* const theirs = Object.values(other.getCrdtSnapshot());
|
|
229
|
+
* this.merge(theirs);
|
|
230
|
+
* other.merge(mine);
|
|
231
|
+
* ```
|
|
232
|
+
*
|
|
233
|
+
* @param other The remote `CrdtBackend` node to sync with.
|
|
234
|
+
*/
|
|
235
|
+
sync(other) {
|
|
236
|
+
const myEntries = Object.values(this.getCrdtSnapshot());
|
|
237
|
+
const theirEntries = Object.values(other.getCrdtSnapshot());
|
|
238
|
+
this.merge(theirEntries);
|
|
239
|
+
other.merge(myEntries);
|
|
240
|
+
}
|
|
241
|
+
// --------------------------------------------------------------------------
|
|
242
|
+
// Private helpers
|
|
243
|
+
// --------------------------------------------------------------------------
|
|
244
|
+
_isExpired(entry) {
|
|
245
|
+
if (!entry.ttl)
|
|
246
|
+
return false;
|
|
247
|
+
return Date.now() > new Date(entry.timestamp).getTime() + entry.ttl * 1000;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
exports.CrdtBackend = CrdtBackend;
|
|
251
|
+
//# sourceMappingURL=blackboard-backend-crdt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blackboard-backend-crdt.js","sourceRoot":"","sources":["../../lib/blackboard-backend-crdt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configurable Consistency Levels for Blackboard Backends
|
|
3
|
+
*
|
|
4
|
+
* Wraps any `BlackboardBackend` with one of three consistency guarantees:
|
|
5
|
+
*
|
|
6
|
+
* - `'eventual'` (default) — writes return immediately; async replication
|
|
7
|
+
* happens in the background. Highest throughput.
|
|
8
|
+
* - `'session'` — read-your-writes guarantee. Every write made
|
|
9
|
+
* by this instance is immediately visible to
|
|
10
|
+
* subsequent reads, even before it propagates to
|
|
11
|
+
* other nodes. Tracked in a local session cache.
|
|
12
|
+
* - `'strong'` — writes are flushed to the backend before the
|
|
13
|
+
* async `writeAsync()` resolves. For backends
|
|
14
|
+
* that implement `FlushableBackend` (e.g.
|
|
15
|
+
* `RedisBackend`, `CrdtBackend`) this guarantees
|
|
16
|
+
* durability before the caller continues.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { MemoryBackend } from './blackboard-backend';
|
|
21
|
+
* import { ConsistentBackend } from './consistency';
|
|
22
|
+
*
|
|
23
|
+
* const backend = new ConsistentBackend(new MemoryBackend(), 'session');
|
|
24
|
+
* backend.write('k', 'v', 'agent-1');
|
|
25
|
+
* backend.read('k'); // always returns 'v' in this session
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Integration with `getBlackboard()`:
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const board = orchestrator.getBlackboard('live', {
|
|
31
|
+
* backend: new RedisBackend(client),
|
|
32
|
+
* consistency: 'strong',
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @module Consistency
|
|
37
|
+
* @version 1.0.0
|
|
38
|
+
* @license MIT
|
|
39
|
+
*/
|
|
40
|
+
import type { BlackboardBackend, BlackboardEntry } from './blackboard-backend';
|
|
41
|
+
/**
|
|
42
|
+
* The three supported consistency levels.
|
|
43
|
+
*
|
|
44
|
+
* | Level | Read guarantee | Write latency | Best for |
|
|
45
|
+
* |------------|-----------------------|---------------|---------------------------------|
|
|
46
|
+
* | `eventual` | Stale reads possible | Lowest | High-throughput, async pipelines|
|
|
47
|
+
* | `session` | Read-your-writes | Low | Single-node agent sessions |
|
|
48
|
+
* | `strong` | Durable after `writeAsync` | Higher | Critical state, audit trails |
|
|
49
|
+
*/
|
|
50
|
+
export type ConsistencyLevel = 'eventual' | 'session' | 'strong';
|
|
51
|
+
/**
|
|
52
|
+
* Optional extension of `BlackboardBackend` for backends that support an
|
|
53
|
+
* explicit flush operation (e.g. `RedisBackend`, `CrdtBackend`).
|
|
54
|
+
*
|
|
55
|
+
* `ConsistentBackend` automatically detects this interface at runtime and
|
|
56
|
+
* uses it for `'strong'` consistency writes via `writeAsync()`.
|
|
57
|
+
*/
|
|
58
|
+
export interface FlushableBackend extends BlackboardBackend {
|
|
59
|
+
/**
|
|
60
|
+
* Flush all pending writes to the durable store.
|
|
61
|
+
* Called by `ConsistentBackend` after each write under `'strong'` consistency.
|
|
62
|
+
*/
|
|
63
|
+
flush(): Promise<void>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Type guard — returns `true` if `backend` implements `FlushableBackend`.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* if (isFlushable(backend)) {
|
|
71
|
+
* await backend.flush();
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export declare function isFlushable(backend: BlackboardBackend): backend is FlushableBackend;
|
|
76
|
+
/**
|
|
77
|
+
* A `BlackboardBackend` wrapper that adds configurable consistency semantics.
|
|
78
|
+
*
|
|
79
|
+
* Fully satisfies the synchronous `BlackboardBackend` interface so it can be
|
|
80
|
+
* used anywhere a plain backend is accepted. For `'strong'` consistency, use
|
|
81
|
+
* `writeAsync()` to await durability confirmation.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* // Session consistency — read-your-writes
|
|
86
|
+
* const backend = new ConsistentBackend(new MemoryBackend(), 'session');
|
|
87
|
+
* backend.write('task', 'pending', 'agent-1');
|
|
88
|
+
* backend.read('task'); // → 'pending' (guaranteed, even before replication)
|
|
89
|
+
*
|
|
90
|
+
* // Strong consistency with Redis
|
|
91
|
+
* const backend = new ConsistentBackend(new RedisBackend(client), 'strong');
|
|
92
|
+
* await backend.writeAsync('result', data, 'agent-1');
|
|
93
|
+
* // Redis has confirmed the write
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export declare class ConsistentBackend implements BlackboardBackend {
|
|
97
|
+
private readonly _backend;
|
|
98
|
+
private readonly _level;
|
|
99
|
+
/**
|
|
100
|
+
* Session write cache.
|
|
101
|
+
*
|
|
102
|
+
* - `BlackboardEntry` → written in this session (overrides backend reads)
|
|
103
|
+
* - `null` → deleted in this session (hides backend reads)
|
|
104
|
+
*
|
|
105
|
+
* Only populated for `'session'` consistency.
|
|
106
|
+
*/
|
|
107
|
+
private readonly _session;
|
|
108
|
+
/**
|
|
109
|
+
* Create a new `ConsistentBackend`.
|
|
110
|
+
*
|
|
111
|
+
* @param backend The underlying storage backend to wrap.
|
|
112
|
+
* @param level Consistency level. Defaults to `'eventual'`.
|
|
113
|
+
*/
|
|
114
|
+
constructor(backend: BlackboardBackend, level?: ConsistencyLevel);
|
|
115
|
+
/**
|
|
116
|
+
* Read an entry.
|
|
117
|
+
*
|
|
118
|
+
* - `eventual` / `strong`: delegates directly to the underlying backend.
|
|
119
|
+
* - `session`: returns the session-cached entry if present; falls back to
|
|
120
|
+
* the backend. Returns `null` for session-deleted keys even if the backend
|
|
121
|
+
* still has them.
|
|
122
|
+
*/
|
|
123
|
+
read(key: string): BlackboardEntry | null;
|
|
124
|
+
/**
|
|
125
|
+
* Write a value synchronously.
|
|
126
|
+
*
|
|
127
|
+
* For all consistency levels this writes immediately to the underlying
|
|
128
|
+
* backend. For `'session'` the result is also cached locally so subsequent
|
|
129
|
+
* `read()` calls see it without waiting for replication.
|
|
130
|
+
*
|
|
131
|
+
* For `'strong'` consistency, prefer `writeAsync()` to await durability.
|
|
132
|
+
*/
|
|
133
|
+
write(key: string, value: unknown, sourceAgent: string, ttl?: number): BlackboardEntry;
|
|
134
|
+
/**
|
|
135
|
+
* Delete an entry.
|
|
136
|
+
*
|
|
137
|
+
* For `'session'` consistency, also records a session-tombstone so the key
|
|
138
|
+
* appears deleted to subsequent `read()` calls in this session.
|
|
139
|
+
*/
|
|
140
|
+
delete(key: string): boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Return all non-expired, non-deleted keys.
|
|
143
|
+
*
|
|
144
|
+
* For `'session'` consistency, session-written keys are included even if
|
|
145
|
+
* not yet visible in the backend; session-deleted keys are excluded.
|
|
146
|
+
*/
|
|
147
|
+
listKeys(): string[];
|
|
148
|
+
/**
|
|
149
|
+
* Return a snapshot of all non-expired, non-deleted entries.
|
|
150
|
+
*
|
|
151
|
+
* For `'session'` consistency, session writes overlay the backend snapshot.
|
|
152
|
+
*/
|
|
153
|
+
getSnapshot(): Record<string, BlackboardEntry>;
|
|
154
|
+
/**
|
|
155
|
+
* Write a value and, for `'strong'` consistency, await durability
|
|
156
|
+
* confirmation from the backend.
|
|
157
|
+
*
|
|
158
|
+
* - `eventual` / `session`: behaves identically to `write()` but returns
|
|
159
|
+
* a `Promise` for API uniformity.
|
|
160
|
+
* - `strong` + `FlushableBackend`: calls `backend.flush()` after the write
|
|
161
|
+
* and resolves only after the flush completes.
|
|
162
|
+
* - `strong` + non-flushable backend: writes synchronously and resolves
|
|
163
|
+
* immediately (backend writes are already synchronous/durable).
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* const entry = await backend.writeAsync('checkpoint', data, 'agent-1');
|
|
168
|
+
* // Under 'strong' + RedisBackend: Redis has persisted the entry by here
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
writeAsync(key: string, value: unknown, sourceAgent: string, ttl?: number): Promise<BlackboardEntry>;
|
|
172
|
+
/**
|
|
173
|
+
* The configured consistency level.
|
|
174
|
+
*/
|
|
175
|
+
get consistencyLevel(): ConsistencyLevel;
|
|
176
|
+
/**
|
|
177
|
+
* The underlying backend being wrapped.
|
|
178
|
+
*/
|
|
179
|
+
get backend(): BlackboardBackend;
|
|
180
|
+
/**
|
|
181
|
+
* Number of entries currently in the session cache (live + tombstones).
|
|
182
|
+
* Always `0` for `'eventual'` and `'strong'` levels.
|
|
183
|
+
*/
|
|
184
|
+
get sessionSize(): number;
|
|
185
|
+
/**
|
|
186
|
+
* Clear the session write cache without modifying the underlying backend.
|
|
187
|
+
*
|
|
188
|
+
* After calling this, `read()` will reflect the backend state directly.
|
|
189
|
+
* Only meaningful for `'session'` consistency.
|
|
190
|
+
*/
|
|
191
|
+
clearSession(): void;
|
|
192
|
+
private _isExpired;
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=consistency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consistency.d.ts","sourceRoot":"","sources":["../../lib/consistency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAM/E;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAMjE;;;;;;GAMG;AACH,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD;;;OAGG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,IAAI,gBAAgB,CAEnF;AAMD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,iBAAkB,YAAW,iBAAiB;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAE1C;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkD;IAE3E;;;;;OAKG;gBACS,OAAO,EAAE,iBAAiB,EAAE,KAAK,GAAE,gBAA6B;IAS5E;;;;;;;OAOG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAoBzC;;;;;;;;OAQG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,eAAe;IAQtF;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAQ5B;;;;;OAKG;IACH,QAAQ,IAAI,MAAM,EAAE;IAqBpB;;;;OAIG;IACH,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC;IAwB9C;;;;;;;;;;;;;;;;OAgBG;IACG,UAAU,CACd,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,MAAM,EACnB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,eAAe,CAAC;IAQ3B;;OAEG;IACH,IAAI,gBAAgB,IAAI,gBAAgB,CAEvC;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,iBAAiB,CAE/B;IAED;;;OAGG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;;;;OAKG;IACH,YAAY,IAAI,IAAI;IAQpB,OAAO,CAAC,UAAU;CAInB"}
|