@fabric-harness/sdk 1.14.0 → 2.0.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/dist/action.d.ts +69 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/action.js +119 -0
- package/dist/action.js.map +1 -0
- package/dist/attachment-store-contract.d.ts +34 -0
- package/dist/attachment-store-contract.d.ts.map +1 -0
- package/dist/attachment-store-contract.js +0 -0
- package/dist/attachment-store-contract.js.map +1 -0
- package/dist/attachment-store.d.ts +107 -0
- package/dist/attachment-store.d.ts.map +1 -0
- package/dist/attachment-store.js +164 -0
- package/dist/attachment-store.js.map +1 -0
- package/dist/channel.d.ts +8 -1
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js.map +1 -1
- package/dist/conversation-projection.d.ts +52 -0
- package/dist/conversation-projection.d.ts.map +1 -0
- package/dist/conversation-projection.js +207 -0
- package/dist/conversation-projection.js.map +1 -0
- package/dist/conversation-stream-contract.d.ts +37 -0
- package/dist/conversation-stream-contract.d.ts.map +1 -0
- package/dist/conversation-stream-contract.js +453 -0
- package/dist/conversation-stream-contract.js.map +1 -0
- package/dist/conversation-stream.d.ts +141 -0
- package/dist/conversation-stream.d.ts.map +1 -0
- package/dist/conversation-stream.js +159 -0
- package/dist/conversation-stream.js.map +1 -0
- package/dist/delivered-message.d.ts +90 -0
- package/dist/delivered-message.d.ts.map +1 -0
- package/dist/delivered-message.js +154 -0
- package/dist/delivered-message.js.map +1 -0
- package/dist/dispatch.d.ts +16 -3
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +5 -1
- package/dist/dispatch.js.map +1 -1
- package/dist/history.d.ts +27 -0
- package/dist/history.d.ts.map +1 -1
- package/dist/history.js +94 -0
- package/dist/history.js.map +1 -1
- package/dist/in-memory-submission-store.d.ts +53 -0
- package/dist/in-memory-submission-store.d.ts.map +1 -0
- package/dist/in-memory-submission-store.js +340 -0
- package/dist/in-memory-submission-store.js.map +1 -0
- package/dist/index.d.ts +24 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/persistent-agent.d.ts +6 -0
- package/dist/persistent-agent.d.ts.map +1 -1
- package/dist/persistent-agent.js +6 -0
- package/dist/persistent-agent.js.map +1 -1
- package/dist/session.d.ts +13 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +78 -2
- package/dist/session.js.map +1 -1
- package/dist/strict.d.ts +24 -3
- package/dist/strict.d.ts.map +1 -1
- package/dist/strict.js +13 -2
- package/dist/strict.js.map +1 -1
- package/dist/submission-context.d.ts +26 -0
- package/dist/submission-context.d.ts.map +1 -0
- package/dist/submission-context.js +20 -0
- package/dist/submission-context.js.map +1 -0
- package/dist/submission-runner.d.ts +150 -0
- package/dist/submission-runner.d.ts.map +1 -0
- package/dist/submission-runner.js +767 -0
- package/dist/submission-runner.js.map +1 -0
- package/dist/submission-state.d.ts +50 -0
- package/dist/submission-state.d.ts.map +1 -0
- package/dist/submission-state.js +82 -0
- package/dist/submission-state.js.map +1 -0
- package/dist/submission-store-contract.d.ts +31 -0
- package/dist/submission-store-contract.d.ts.map +1 -0
- package/dist/submission-store-contract.js +730 -0
- package/dist/submission-store-contract.js.map +1 -0
- package/dist/submission-store.d.ts +365 -0
- package/dist/submission-store.d.ts.map +1 -0
- package/dist/submission-store.js +191 -0
- package/dist/submission-store.js.map +1 -0
- package/dist/submission-telemetry.d.ts +56 -0
- package/dist/submission-telemetry.d.ts.map +1 -0
- package/dist/submission-telemetry.js +36 -0
- package/dist/submission-telemetry.js.map +1 -0
- package/dist/testing-contracts.d.ts +17 -0
- package/dist/testing-contracts.d.ts.map +1 -0
- package/dist/testing-contracts.js +14 -0
- package/dist/testing-contracts.js.map +1 -0
- package/dist/types.d.ts +28 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ConversationStreamStore } from './conversation-stream.js';
|
|
2
|
+
import type { SessionData, SessionEntry, SessionStore } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Wrap a {@link SessionStore} so active-path appends are mirrored into an
|
|
5
|
+
* append-only {@link ConversationStreamStore} projection (v2 A4).
|
|
6
|
+
*
|
|
7
|
+
* The SessionEntry DAG stays the single source of truth; the stream gives
|
|
8
|
+
* clients offset-based catch-up + live tail. Two rules keep them coherent:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Idempotent by entryId** — a crash between the DAG write and the
|
|
11
|
+
* stream write is repaired on the next append: the projector diffs the
|
|
12
|
+
* stream tail against the active path and re-emits anything missing.
|
|
13
|
+
* 2. **Truncation is explicit** — the DAG can branch (fork/replay/
|
|
14
|
+
* checkpoint-restore rewrite `leafId`); the stream cannot. When `save()`
|
|
15
|
+
* rewinds the active path, the projector appends a `truncated` record
|
|
16
|
+
* and re-acquires the producer (epoch bump), so tailing clients see the
|
|
17
|
+
* branch switch instead of silently reading a stale path.
|
|
18
|
+
*
|
|
19
|
+
* Projection failures are logged and swallowed — the conversation stream is
|
|
20
|
+
* an observability surface and must never fail the session write itself;
|
|
21
|
+
* the diff-repair on the next append restores consistency.
|
|
22
|
+
*/
|
|
23
|
+
export declare function withConversationProjection(store: SessionStore, streams: ConversationStreamStore, options?: {
|
|
24
|
+
producerId?: string;
|
|
25
|
+
onError?: (error: unknown) => void;
|
|
26
|
+
}): SessionStore;
|
|
27
|
+
export declare class ConversationProjector {
|
|
28
|
+
private readonly streams;
|
|
29
|
+
private readonly producerId;
|
|
30
|
+
private readonly onError;
|
|
31
|
+
private readonly producers;
|
|
32
|
+
constructor(streams: ConversationStreamStore, options?: {
|
|
33
|
+
producerId?: string;
|
|
34
|
+
onError?: (error: unknown) => void;
|
|
35
|
+
});
|
|
36
|
+
/** Mirror one appended entry, repairing any missed backlog first. */
|
|
37
|
+
projectAppend(store: SessionStore, sessionId: string, entry: SessionEntry): Promise<void>;
|
|
38
|
+
/** Handle whole-document saves (fork/replay/truncation rewrite the path). */
|
|
39
|
+
projectSave(data: SessionData): Promise<void>;
|
|
40
|
+
deleteStream(sessionId: string): Promise<void>;
|
|
41
|
+
private ensureProducer;
|
|
42
|
+
/** Fold the stream into the currently-projected entry ids (truncations rewind). */
|
|
43
|
+
private readProjectedEntryIds;
|
|
44
|
+
/**
|
|
45
|
+
* Diff the projected tail against the current active path. A pure
|
|
46
|
+
* extension appends the missing suffix; a divergence appends a
|
|
47
|
+
* `truncated` record (with a fresh producer epoch) then the new suffix.
|
|
48
|
+
*/
|
|
49
|
+
private reproject;
|
|
50
|
+
private appendRecords;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=conversation-projection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-projection.d.ts","sourceRoot":"","sources":["../src/conversation-projection.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,uBAAuB,EACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,uBAAuB,EAChC,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAAO,GACxE,YAAY,CAyBd;AAUD,qBAAa,qBAAqB;IAM9B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2B;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoC;gBAG3C,OAAO,EAAE,uBAAuB,EACjD,OAAO,GAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;KAAO;IAQ3E,qEAAqE;IAC/D,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB/F,6EAA6E;IACvE,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAS7C,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAStC,cAAc;IAmB5B,mFAAmF;YACrE,qBAAqB;IAoBnC;;;;OAIG;YACW,SAAS;YAgCT,aAAa;CA6B5B"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { ConversationStreamStoreError, conversationStreamPath, } from './conversation-stream.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wrap a {@link SessionStore} so active-path appends are mirrored into an
|
|
4
|
+
* append-only {@link ConversationStreamStore} projection (v2 A4).
|
|
5
|
+
*
|
|
6
|
+
* The SessionEntry DAG stays the single source of truth; the stream gives
|
|
7
|
+
* clients offset-based catch-up + live tail. Two rules keep them coherent:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Idempotent by entryId** — a crash between the DAG write and the
|
|
10
|
+
* stream write is repaired on the next append: the projector diffs the
|
|
11
|
+
* stream tail against the active path and re-emits anything missing.
|
|
12
|
+
* 2. **Truncation is explicit** — the DAG can branch (fork/replay/
|
|
13
|
+
* checkpoint-restore rewrite `leafId`); the stream cannot. When `save()`
|
|
14
|
+
* rewinds the active path, the projector appends a `truncated` record
|
|
15
|
+
* and re-acquires the producer (epoch bump), so tailing clients see the
|
|
16
|
+
* branch switch instead of silently reading a stale path.
|
|
17
|
+
*
|
|
18
|
+
* Projection failures are logged and swallowed — the conversation stream is
|
|
19
|
+
* an observability surface and must never fail the session write itself;
|
|
20
|
+
* the diff-repair on the next append restores consistency.
|
|
21
|
+
*/
|
|
22
|
+
export function withConversationProjection(store, streams, options = {}) {
|
|
23
|
+
const projector = new ConversationProjector(streams, options);
|
|
24
|
+
const wrapped = {
|
|
25
|
+
...store,
|
|
26
|
+
load: (id) => store.load(id),
|
|
27
|
+
save: async (data) => {
|
|
28
|
+
await store.save(data);
|
|
29
|
+
await projector.projectSave(data);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
if (store.appendEntry) {
|
|
33
|
+
wrapped.appendEntry = async (sessionId, entry) => {
|
|
34
|
+
await store.appendEntry?.(sessionId, entry);
|
|
35
|
+
await projector.projectAppend(store, sessionId, entry);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (store.delete) {
|
|
39
|
+
wrapped.delete = async (sessionId) => {
|
|
40
|
+
await store.delete?.(sessionId);
|
|
41
|
+
await projector.deleteStream(sessionId);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return wrapped;
|
|
45
|
+
}
|
|
46
|
+
export class ConversationProjector {
|
|
47
|
+
streams;
|
|
48
|
+
producerId;
|
|
49
|
+
onError;
|
|
50
|
+
producers = new Map();
|
|
51
|
+
constructor(streams, options = {}) {
|
|
52
|
+
this.streams = streams;
|
|
53
|
+
this.producerId = options.producerId ?? `projector-${Math.random().toString(16).slice(2)}`;
|
|
54
|
+
this.onError = options.onError ?? ((error) => {
|
|
55
|
+
console.error('[fabric-harness:conversation-projection] projection failed:', error);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** Mirror one appended entry, repairing any missed backlog first. */
|
|
59
|
+
async projectAppend(store, sessionId, entry) {
|
|
60
|
+
try {
|
|
61
|
+
const producer = await this.ensureProducer(sessionId);
|
|
62
|
+
// Fast path: this entry directly extends the projected tail.
|
|
63
|
+
const last = producer.projectedEntryIds[producer.projectedEntryIds.length - 1];
|
|
64
|
+
if (entry.parentId && entry.parentId === last) {
|
|
65
|
+
await this.appendRecords(sessionId, producer, [{ kind: 'entry', entry }], [entry.id]);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Slow path: reproject from the session's current active path (covers
|
|
69
|
+
// missed appends, first projection of an existing session, and forks).
|
|
70
|
+
const data = await store.load(sessionId);
|
|
71
|
+
if (!data)
|
|
72
|
+
return;
|
|
73
|
+
await this.reproject(sessionId, producer, data);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
this.onError(error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Handle whole-document saves (fork/replay/truncation rewrite the path). */
|
|
80
|
+
async projectSave(data) {
|
|
81
|
+
try {
|
|
82
|
+
const producer = await this.ensureProducer(data.id);
|
|
83
|
+
await this.reproject(data.id, producer, data);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
this.onError(error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async deleteStream(sessionId) {
|
|
90
|
+
try {
|
|
91
|
+
await this.streams.delete(conversationStreamPath(sessionId));
|
|
92
|
+
this.producers.delete(sessionId);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
this.onError(error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async ensureProducer(sessionId) {
|
|
99
|
+
const existing = this.producers.get(sessionId);
|
|
100
|
+
if (existing)
|
|
101
|
+
return existing;
|
|
102
|
+
const path = conversationStreamPath(sessionId);
|
|
103
|
+
await this.streams.createStream(path, { storeSessionId: sessionId });
|
|
104
|
+
const claim = await this.streams.acquireProducer(path, this.producerId);
|
|
105
|
+
const state = {
|
|
106
|
+
producerId: claim.producerId,
|
|
107
|
+
producerEpoch: claim.producerEpoch,
|
|
108
|
+
nextProducerSequence: claim.nextProducerSequence,
|
|
109
|
+
// Durable stream stores outlive this process: rebuild what has already
|
|
110
|
+
// been projected from the stream itself, otherwise the first
|
|
111
|
+
// post-restart diff would re-emit the entire path as duplicates.
|
|
112
|
+
projectedEntryIds: await this.readProjectedEntryIds(path),
|
|
113
|
+
};
|
|
114
|
+
this.producers.set(sessionId, state);
|
|
115
|
+
return state;
|
|
116
|
+
}
|
|
117
|
+
/** Fold the stream into the currently-projected entry ids (truncations rewind). */
|
|
118
|
+
async readProjectedEntryIds(path) {
|
|
119
|
+
const projected = [];
|
|
120
|
+
let offset;
|
|
121
|
+
for (;;) {
|
|
122
|
+
const result = await this.streams.read(path, { ...(offset !== undefined ? { offset } : {}), limit: 1000 });
|
|
123
|
+
for (const batch of result.batches) {
|
|
124
|
+
for (const record of batch.records) {
|
|
125
|
+
if (record.kind === 'entry') {
|
|
126
|
+
projected.push(record.entry.id);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const keep = record.rewoundTo ? projected.indexOf(record.rewoundTo) + 1 : 0;
|
|
130
|
+
projected.length = keep;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (result.upToDate)
|
|
135
|
+
return projected;
|
|
136
|
+
offset = result.nextOffset;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Diff the projected tail against the current active path. A pure
|
|
141
|
+
* extension appends the missing suffix; a divergence appends a
|
|
142
|
+
* `truncated` record (with a fresh producer epoch) then the new suffix.
|
|
143
|
+
*/
|
|
144
|
+
async reproject(sessionId, producer, data) {
|
|
145
|
+
const path = activePath(data);
|
|
146
|
+
const pathIds = path.map((entry) => entry.id);
|
|
147
|
+
const projected = producer.projectedEntryIds;
|
|
148
|
+
let common = 0;
|
|
149
|
+
while (common < projected.length && common < pathIds.length && projected[common] === pathIds[common]) {
|
|
150
|
+
common += 1;
|
|
151
|
+
}
|
|
152
|
+
const records = [];
|
|
153
|
+
if (common < projected.length) {
|
|
154
|
+
// The active path diverged from what we already emitted — branch switch.
|
|
155
|
+
const newLeaf = pathIds[pathIds.length - 1] ?? '';
|
|
156
|
+
const rewoundTo = common > 0 ? projected[common - 1] ?? '' : '';
|
|
157
|
+
records.push({ kind: 'truncated', rewoundTo, newLeafId: newLeaf, reason: 'active-path-rewound' });
|
|
158
|
+
const claim = await this.streams.acquireProducer(conversationStreamPath(sessionId), this.producerId);
|
|
159
|
+
producer.producerEpoch = claim.producerEpoch;
|
|
160
|
+
producer.nextProducerSequence = claim.nextProducerSequence;
|
|
161
|
+
}
|
|
162
|
+
for (const entry of path.slice(common)) {
|
|
163
|
+
records.push({ kind: 'entry', entry });
|
|
164
|
+
}
|
|
165
|
+
if (records.length === 0)
|
|
166
|
+
return;
|
|
167
|
+
await this.appendRecords(sessionId, producer, records, pathIds);
|
|
168
|
+
}
|
|
169
|
+
async appendRecords(sessionId, producer, records, projectedIds) {
|
|
170
|
+
try {
|
|
171
|
+
await this.streams.append({
|
|
172
|
+
path: conversationStreamPath(sessionId),
|
|
173
|
+
producerId: producer.producerId,
|
|
174
|
+
producerEpoch: producer.producerEpoch,
|
|
175
|
+
producerSequence: producer.nextProducerSequence,
|
|
176
|
+
records,
|
|
177
|
+
});
|
|
178
|
+
producer.nextProducerSequence += 1;
|
|
179
|
+
producer.projectedEntryIds =
|
|
180
|
+
records.length === 1 && records[0]?.kind === 'entry'
|
|
181
|
+
? [...producer.projectedEntryIds, ...projectedIds.slice(-1)]
|
|
182
|
+
: projectedIds;
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (error instanceof ConversationStreamStoreError && error.code === 'PRODUCER_FENCED') {
|
|
186
|
+
// Another projector took over (multi-writer window). Drop local
|
|
187
|
+
// state; the next append re-acquires and diff-repairs.
|
|
188
|
+
this.producers.delete(sessionId);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/** Compute the active (leaf-rooted) path of a SessionData snapshot. */
|
|
196
|
+
function activePath(data) {
|
|
197
|
+
const entries = data.entries ?? [];
|
|
198
|
+
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
199
|
+
const path = [];
|
|
200
|
+
let current = data.leafId ? byId.get(data.leafId) : entries[entries.length - 1];
|
|
201
|
+
while (current) {
|
|
202
|
+
path.push(current);
|
|
203
|
+
current = current.parentId ? byId.get(current.parentId) : undefined;
|
|
204
|
+
}
|
|
205
|
+
return path.reverse();
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=conversation-projection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-projection.js","sourceRoot":"","sources":["../src/conversation-projection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared contract tests for {@link ConversationStreamStore} implementations.
|
|
3
|
+
*
|
|
4
|
+
* Store packages call {@link defineConversationStreamStoreContractTests} with
|
|
5
|
+
* a factory that creates their backend. The tests exercise every method on
|
|
6
|
+
* `ConversationStreamStore` with identical behavioral assertions regardless of
|
|
7
|
+
* the underlying storage engine — batch atomicity, producer fencing via
|
|
8
|
+
* epochs, idempotent redelivery, and offset catch-up reads.
|
|
9
|
+
*
|
|
10
|
+
* `subscribe` is a process-local notification seam: the suite only asserts
|
|
11
|
+
* that appends made through the same store instance notify local listeners
|
|
12
|
+
* and that unsubscribing stops further notifications. Cross-process fan-out
|
|
13
|
+
* is deliberately out of contract — backends may no-op it.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { defineConversationStreamStoreContractTests } from '@fabric-harness/sdk/testing/contracts';
|
|
18
|
+
*
|
|
19
|
+
* defineConversationStreamStoreContractTests('My backend', async () => ({
|
|
20
|
+
* store: await createMyStreamStore(),
|
|
21
|
+
* close: async () => myBackend.close(),
|
|
22
|
+
* }));
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import type { ConversationStreamStore } from './conversation-stream.js';
|
|
26
|
+
export interface ConversationStreamStoreContractHandle {
|
|
27
|
+
/** The fresh store under test. */
|
|
28
|
+
store: ConversationStreamStore;
|
|
29
|
+
/** Optional cleanup after the test (close connections, delete temp files). */
|
|
30
|
+
close?: () => Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Register the standard {@link ConversationStreamStore} contract tests under
|
|
34
|
+
* the given describe label. Each test gets a fresh store from `factory()`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function defineConversationStreamStoreContractTests(name: string, factory: () => Promise<ConversationStreamStoreContractHandle>): void;
|
|
37
|
+
//# sourceMappingURL=conversation-stream-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-stream-contract.d.ts","sourceRoot":"","sources":["../src/conversation-stream-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAEV,uBAAuB,EAExB,MAAM,0BAA0B,CAAC;AAoClC,MAAM,WAAW,qCAAqC;IACpD,kCAAkC;IAClC,KAAK,EAAE,uBAAuB,CAAC;IAC/B,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,0CAA0C,CACxD,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,OAAO,CAAC,qCAAqC,CAAC,GAC5D,IAAI,CAmdN"}
|