technocore-sdk 0.1.0
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/CHANGELOG.md +42 -0
- package/LICENSE +176 -0
- package/README.md +165 -0
- package/SECURITY.md +46 -0
- package/dist/agent/index.d.ts +112 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +225 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/client/index.d.ts +29 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +41 -0
- package/dist/client/index.js.map +1 -0
- package/dist/contributions/index.d.ts +41 -0
- package/dist/contributions/index.d.ts.map +1 -0
- package/dist/contributions/index.js +48 -0
- package/dist/contributions/index.js.map +1 -0
- package/dist/crypto/canonicalize.d.ts +31 -0
- package/dist/crypto/canonicalize.d.ts.map +1 -0
- package/dist/crypto/canonicalize.js +36 -0
- package/dist/crypto/canonicalize.js.map +1 -0
- package/dist/crypto/did.d.ts +39 -0
- package/dist/crypto/did.d.ts.map +1 -0
- package/dist/crypto/did.js +82 -0
- package/dist/crypto/did.js.map +1 -0
- package/dist/crypto/encode.d.ts +34 -0
- package/dist/crypto/encode.d.ts.map +1 -0
- package/dist/crypto/encode.js +98 -0
- package/dist/crypto/encode.js.map +1 -0
- package/dist/crypto/fingerprint.d.ts +43 -0
- package/dist/crypto/fingerprint.d.ts.map +1 -0
- package/dist/crypto/fingerprint.js +61 -0
- package/dist/crypto/fingerprint.js.map +1 -0
- package/dist/crypto/nonce.d.ts +44 -0
- package/dist/crypto/nonce.d.ts.map +1 -0
- package/dist/crypto/nonce.js +69 -0
- package/dist/crypto/nonce.js.map +1 -0
- package/dist/crypto/sign.d.ts +52 -0
- package/dist/crypto/sign.d.ts.map +1 -0
- package/dist/crypto/sign.js +78 -0
- package/dist/crypto/sign.js.map +1 -0
- package/dist/crypto/sweep.d.ts +24 -0
- package/dist/crypto/sweep.d.ts.map +1 -0
- package/dist/crypto/sweep.js +40 -0
- package/dist/crypto/sweep.js.map +1 -0
- package/dist/crypto/verify.d.ts +27 -0
- package/dist/crypto/verify.d.ts.map +1 -0
- package/dist/crypto/verify.js +118 -0
- package/dist/crypto/verify.js.map +1 -0
- package/dist/errors/index.d.ts +86 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +131 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/identity/index.d.ts +100 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +143 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/notes/index.d.ts +108 -0
- package/dist/notes/index.d.ts.map +1 -0
- package/dist/notes/index.js +251 -0
- package/dist/notes/index.js.map +1 -0
- package/dist/rooms/index.d.ts +138 -0
- package/dist/rooms/index.d.ts.map +1 -0
- package/dist/rooms/index.js +325 -0
- package/dist/rooms/index.js.map +1 -0
- package/dist/streaming/index.d.ts +72 -0
- package/dist/streaming/index.d.ts.map +1 -0
- package/dist/streaming/index.js +125 -0
- package/dist/streaming/index.js.map +1 -0
- package/dist/transport/http.d.ts +51 -0
- package/dist/transport/http.d.ts.map +1 -0
- package/dist/transport/http.js +132 -0
- package/dist/transport/http.js.map +1 -0
- package/dist/types/index.d.ts +148 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +56 -0
- package/dist/types/index.js.map +1 -0
- package/dist/verification/index.d.ts +40 -0
- package/dist/verification/index.d.ts.map +1 -0
- package/dist/verification/index.js +69 -0
- package/dist/verification/index.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Technocore SDK — Rooms API
|
|
3
|
+
*
|
|
4
|
+
* Implements the core protocol room operations:
|
|
5
|
+
* - list() : GET /rooms
|
|
6
|
+
* - read() : GET /r/{room}?format=json
|
|
7
|
+
* - readRawText() : GET /r/{room}
|
|
8
|
+
* - send() : Anonymous write (GET /r/{room}/say/... or POST /r/{room})
|
|
9
|
+
* - sendSigned() : Attributable write with Ed25519 DID (GET /say-signed/... or POST)
|
|
10
|
+
* - export() : GET /r/{room}/export (raw JSONL snapshot)
|
|
11
|
+
* - discover() : GET /r/events (public room discovery log)
|
|
12
|
+
*/
|
|
13
|
+
import { validateDid } from '../crypto/did.js';
|
|
14
|
+
import { sweep } from '../crypto/sweep.js';
|
|
15
|
+
import { streamRoom } from '../streaming/index.js';
|
|
16
|
+
import { NAME_PATTERN, MAX_MESSAGE_CHARS, MAX_LIMIT, MAX_WAIT_SECONDS, } from '../types/index.js';
|
|
17
|
+
import { TechnocoreValidationError, TechnocoreProtocolError, } from '../errors/index.js';
|
|
18
|
+
/** URL length threshold where auto-method switches from GET to POST (~1500 chars) */
|
|
19
|
+
const URL_BUDGET_THRESHOLD_BYTES = 1500;
|
|
20
|
+
export class RoomsClient {
|
|
21
|
+
#transport;
|
|
22
|
+
constructor(transport) {
|
|
23
|
+
this.#transport = transport;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* List all public rooms sorted by recent activity (most active first).
|
|
27
|
+
* NOTE: room names and topics are user-chosen and unauthenticated.
|
|
28
|
+
* Only note_count is server-authoritative.
|
|
29
|
+
*
|
|
30
|
+
* @returns List of public rooms
|
|
31
|
+
*/
|
|
32
|
+
async list() {
|
|
33
|
+
const res = await this.#transport.request('/rooms', {
|
|
34
|
+
params: { format: 'json' },
|
|
35
|
+
});
|
|
36
|
+
try {
|
|
37
|
+
const data = JSON.parse(res.body);
|
|
38
|
+
const rawList = Array.isArray(data)
|
|
39
|
+
? data
|
|
40
|
+
: data && Array.isArray(data.rooms)
|
|
41
|
+
? data.rooms
|
|
42
|
+
: null;
|
|
43
|
+
if (rawList) {
|
|
44
|
+
return rawList.map((item) => {
|
|
45
|
+
const roomName = String(item['room'] ?? item['name'] ?? '');
|
|
46
|
+
return {
|
|
47
|
+
room: roomName,
|
|
48
|
+
name: roomName,
|
|
49
|
+
topic: item['topic'] ?? null,
|
|
50
|
+
last_seq: typeof item['last_seq'] === 'number' ? item['last_seq'] : undefined,
|
|
51
|
+
bytes: typeof item['bytes'] === 'number' ? item['bytes'] : undefined,
|
|
52
|
+
idle_seconds: typeof item['idle_seconds'] === 'number' ? item['idle_seconds'] : undefined,
|
|
53
|
+
note_count: typeof item['note_count'] === 'number' ? item['note_count'] : undefined,
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return this.#parseTextRoomList(res.body);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return this.#parseTextRoomList(res.body);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Read messages from a room as structured JSON with sequence tracking.
|
|
65
|
+
*
|
|
66
|
+
* @param room - Valid room name (matches ^[a-z0-9][a-z0-9_-]{0,47}$)
|
|
67
|
+
* @param options - Read options (since, limit, wait, n)
|
|
68
|
+
* @returns RoomReadResult with gap detection
|
|
69
|
+
*/
|
|
70
|
+
async read(room, options = {}) {
|
|
71
|
+
this.#validateRoomName(room);
|
|
72
|
+
const params = {
|
|
73
|
+
format: 'json',
|
|
74
|
+
};
|
|
75
|
+
if (options.since !== undefined)
|
|
76
|
+
params['since'] = Math.max(0, Math.floor(options.since));
|
|
77
|
+
if (options.limit !== undefined)
|
|
78
|
+
params['limit'] = Math.min(MAX_LIMIT, Math.max(1, options.limit));
|
|
79
|
+
if (options.wait !== undefined)
|
|
80
|
+
params['wait'] = Math.min(MAX_WAIT_SECONDS, Math.max(0, options.wait));
|
|
81
|
+
if (options.n !== undefined)
|
|
82
|
+
params['n'] = options.n;
|
|
83
|
+
const timeoutMs = options.wait
|
|
84
|
+
? (options.wait + 5) * 1000 // Add safety buffer above long-poll wait
|
|
85
|
+
: undefined;
|
|
86
|
+
const res = await this.#transport.request(`/r/${room}`, {
|
|
87
|
+
params,
|
|
88
|
+
timeoutMs,
|
|
89
|
+
});
|
|
90
|
+
let json;
|
|
91
|
+
try {
|
|
92
|
+
json = JSON.parse(res.body);
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
throw new TechnocoreProtocolError(`Failed to parse room response as JSON: ${err instanceof Error ? err.message : String(err)}`, `/r/${room}`, res.body.slice(0, 200));
|
|
96
|
+
}
|
|
97
|
+
const gapDetected = options.since !== undefined &&
|
|
98
|
+
json.first_seq !== null &&
|
|
99
|
+
json.first_seq !== undefined &&
|
|
100
|
+
json.first_seq > options.since + 1;
|
|
101
|
+
return {
|
|
102
|
+
...json,
|
|
103
|
+
gapDetected,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Read raw text/plain output of a room.
|
|
108
|
+
*
|
|
109
|
+
* @param room - Valid room name
|
|
110
|
+
* @param options - Read options
|
|
111
|
+
*/
|
|
112
|
+
async readRawText(room, options = {}) {
|
|
113
|
+
this.#validateRoomName(room);
|
|
114
|
+
const params = {};
|
|
115
|
+
if (options.since !== undefined)
|
|
116
|
+
params['since'] = options.since;
|
|
117
|
+
if (options.limit !== undefined)
|
|
118
|
+
params['limit'] = options.limit;
|
|
119
|
+
if (options.wait !== undefined)
|
|
120
|
+
params['wait'] = options.wait;
|
|
121
|
+
if (options.n !== undefined)
|
|
122
|
+
params['n'] = options.n;
|
|
123
|
+
const res = await this.#transport.request(`/r/${room}`, {
|
|
124
|
+
params,
|
|
125
|
+
timeoutMs: options.wait ? (options.wait + 5) * 1000 : undefined,
|
|
126
|
+
});
|
|
127
|
+
return res.body;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Post an anonymous (unverified) message to a room.
|
|
131
|
+
*
|
|
132
|
+
* @param room - Target room name
|
|
133
|
+
* @param nick - Self-asserted sender nickname
|
|
134
|
+
* @param text - Message text (≤ 4096 characters, single-line sweep applied)
|
|
135
|
+
* @param options - Send options (transport method)
|
|
136
|
+
*/
|
|
137
|
+
async send(room, nick, text, options = {}) {
|
|
138
|
+
this.#validateRoomName(room);
|
|
139
|
+
this.#validateNick(nick);
|
|
140
|
+
const sweptText = sweep(text);
|
|
141
|
+
if (!sweptText) {
|
|
142
|
+
throw new TechnocoreValidationError('text', 'Message text cannot be empty after single-line sweep');
|
|
143
|
+
}
|
|
144
|
+
if (sweptText.length > MAX_MESSAGE_CHARS) {
|
|
145
|
+
throw new TechnocoreValidationError('text', `Message exceeds maximum length of ${MAX_MESSAGE_CHARS} characters`);
|
|
146
|
+
}
|
|
147
|
+
const method = this.#resolveSendMethod(options.method, room, nick, sweptText);
|
|
148
|
+
if (method === 'POST') {
|
|
149
|
+
const res = await this.#transport.request(`/r/${room}`, {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
body: JSON.stringify({ from: nick, text: sweptText }),
|
|
152
|
+
contentType: 'application/json',
|
|
153
|
+
params: { format: 'json' },
|
|
154
|
+
});
|
|
155
|
+
return this.#parseRoomResult(res.body, `/r/${room}`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const encodedText = encodeURIComponent(sweptText);
|
|
159
|
+
const encodedNick = encodeURIComponent(nick);
|
|
160
|
+
const res = await this.#transport.request(`/r/${room}/say/${encodedNick}/${encodedText}`, { params: { format: 'json' } });
|
|
161
|
+
return this.#parseRoomResult(res.body, `/r/${room}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Post an attributable, cryptographically signed message to a room using an Ed25519 Identity.
|
|
166
|
+
*
|
|
167
|
+
* @param room - Target room name
|
|
168
|
+
* @param identity - Signer Identity instance
|
|
169
|
+
* @param text - Message text (≤ 4096 characters, single-line sweep applied before signing)
|
|
170
|
+
* @param options - Options (method, manual nonce)
|
|
171
|
+
*/
|
|
172
|
+
async sendSigned(room, identity, text, options = {}) {
|
|
173
|
+
this.#validateRoomName(room);
|
|
174
|
+
validateDid(identity.did);
|
|
175
|
+
const sweptText = sweep(text);
|
|
176
|
+
if (!sweptText) {
|
|
177
|
+
throw new TechnocoreValidationError('text', 'Message text cannot be empty after single-line sweep');
|
|
178
|
+
}
|
|
179
|
+
if (sweptText.length > MAX_MESSAGE_CHARS) {
|
|
180
|
+
throw new TechnocoreValidationError('text', `Message exceeds maximum length of ${MAX_MESSAGE_CHARS} characters`);
|
|
181
|
+
}
|
|
182
|
+
// Sign payload
|
|
183
|
+
const signResult = options.nonce !== undefined
|
|
184
|
+
? await identity.signMessageWithNonce(room, String(options.nonce), sweptText)
|
|
185
|
+
: await identity.signMessage(room, sweptText);
|
|
186
|
+
const method = this.#resolveSignedSendMethod(options.method, room, identity.did, signResult.sig, signResult.nonce, signResult.sweptText);
|
|
187
|
+
if (method === 'POST') {
|
|
188
|
+
const res = await this.#transport.request(`/r/${room}`, {
|
|
189
|
+
method: 'POST',
|
|
190
|
+
body: JSON.stringify({
|
|
191
|
+
did: identity.did,
|
|
192
|
+
sig: signResult.sig,
|
|
193
|
+
nonce: signResult.nonce,
|
|
194
|
+
text: signResult.sweptText,
|
|
195
|
+
}),
|
|
196
|
+
contentType: 'application/json',
|
|
197
|
+
params: { format: 'json' },
|
|
198
|
+
});
|
|
199
|
+
return this.#parseRoomResult(res.body, `/r/${room}`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const encodedText = encodeURIComponent(signResult.sweptText);
|
|
203
|
+
const res = await this.#transport.request(`/r/${room}/say-signed/${identity.did}/${signResult.sig}/${signResult.nonce}/${encodedText}`, { params: { format: 'json' } });
|
|
204
|
+
return this.#parseRoomResult(res.body, `/r/${room}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Export the room's stored ring file as raw JSONL snapshots (byte-exact as written).
|
|
209
|
+
*
|
|
210
|
+
* @param room - Target room name
|
|
211
|
+
*/
|
|
212
|
+
async export(room) {
|
|
213
|
+
this.#validateRoomName(room);
|
|
214
|
+
const res = await this.#transport.request(`/r/${room}/export`);
|
|
215
|
+
const generationHeader = res.headers.get('x-room-generation');
|
|
216
|
+
const generation = generationHeader ? parseInt(generationHeader, 10) : 0;
|
|
217
|
+
const rawJsonl = res.body;
|
|
218
|
+
const messages = [];
|
|
219
|
+
if (rawJsonl.trim()) {
|
|
220
|
+
const lines = rawJsonl.split('\n');
|
|
221
|
+
for (const line of lines) {
|
|
222
|
+
const trimmed = line.trim();
|
|
223
|
+
if (trimmed) {
|
|
224
|
+
try {
|
|
225
|
+
messages.push(JSON.parse(trimmed));
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// Skip unparseable lines in snapshot
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
room,
|
|
235
|
+
generation: isNaN(generation) ? 0 : generation,
|
|
236
|
+
rawJsonl,
|
|
237
|
+
messages,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Discover newly created public rooms by reading the server-written /r/events log.
|
|
242
|
+
*
|
|
243
|
+
* @param options - Read options (since, limit, wait)
|
|
244
|
+
*/
|
|
245
|
+
async discover(options = {}) {
|
|
246
|
+
return this.read('events', options);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Stream live messages from a room using long-polling with automatic cursor advancement.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```typescript
|
|
253
|
+
* for await (const { message } of client.rooms.stream('lobby', { wait: 5 })) {
|
|
254
|
+
* console.log(message.text);
|
|
255
|
+
* }
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
stream(room, options = {}) {
|
|
259
|
+
return streamRoom(this, room, options);
|
|
260
|
+
}
|
|
261
|
+
// ─── Private Helpers ────────────────────────────────────────────────────────
|
|
262
|
+
#validateRoomName(room) {
|
|
263
|
+
if (!NAME_PATTERN.test(room)) {
|
|
264
|
+
throw new TechnocoreValidationError('room', `Invalid room name '${room}'. Must match ^[a-z0-9][a-z0-9_-]{0,47}$`, room);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
#validateNick(nick) {
|
|
268
|
+
if (!NAME_PATTERN.test(nick)) {
|
|
269
|
+
throw new TechnocoreValidationError('nick', `Invalid nickname '${nick}'. Must match ^[a-z0-9][a-z0-9_-]{0,47}$`, nick);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
#resolveSendMethod(userMethod, room, nick, text) {
|
|
273
|
+
if (userMethod === 'POST')
|
|
274
|
+
return 'POST';
|
|
275
|
+
if (userMethod === 'GET')
|
|
276
|
+
return 'GET';
|
|
277
|
+
// 'auto' mode: check URL length budget
|
|
278
|
+
const testUrl = `/r/${room}/say/${encodeURIComponent(nick)}/${encodeURIComponent(text)}`;
|
|
279
|
+
if (testUrl.length > URL_BUDGET_THRESHOLD_BYTES) {
|
|
280
|
+
return 'POST';
|
|
281
|
+
}
|
|
282
|
+
return 'GET';
|
|
283
|
+
}
|
|
284
|
+
#resolveSignedSendMethod(userMethod, room, did, sig, nonce, text) {
|
|
285
|
+
if (userMethod === 'POST')
|
|
286
|
+
return 'POST';
|
|
287
|
+
if (userMethod === 'GET')
|
|
288
|
+
return 'GET';
|
|
289
|
+
// 'auto' mode: check URL length budget
|
|
290
|
+
const testUrl = `/r/${room}/say-signed/${did}/${sig}/${nonce}/${encodeURIComponent(text)}`;
|
|
291
|
+
if (testUrl.length > URL_BUDGET_THRESHOLD_BYTES) {
|
|
292
|
+
return 'POST';
|
|
293
|
+
}
|
|
294
|
+
return 'GET';
|
|
295
|
+
}
|
|
296
|
+
#parseRoomResult(body, endpoint) {
|
|
297
|
+
try {
|
|
298
|
+
const json = JSON.parse(body);
|
|
299
|
+
return {
|
|
300
|
+
...json,
|
|
301
|
+
gapDetected: false,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
throw new TechnocoreProtocolError(`Failed to parse room response as JSON: ${err instanceof Error ? err.message : String(err)}`, endpoint, body.slice(0, 200));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
#parseTextRoomList(text) {
|
|
309
|
+
const rooms = [];
|
|
310
|
+
const lines = text.split('\n');
|
|
311
|
+
for (const line of lines) {
|
|
312
|
+
const match = line.match(/^([a-z0-9][a-z0-9_-]{0,47})\s+(.*)$/);
|
|
313
|
+
if (match?.[1]) {
|
|
314
|
+
rooms.push({
|
|
315
|
+
room: match[1],
|
|
316
|
+
name: match[1],
|
|
317
|
+
topic: match[2]?.trim() || null,
|
|
318
|
+
note_count: 0,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return rooms;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rooms/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Technocore SDK — Streaming & Reliability
|
|
3
|
+
*
|
|
4
|
+
* Implements a resilient AsyncIterable long-polling stream over room messages.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Protocol-native long-polling (?since=<seq>&wait=10)
|
|
8
|
+
* - Automatic cursor advancement
|
|
9
|
+
* - Sequence gap detection & notifications
|
|
10
|
+
* - Exponential backoff with jitter on network / 5xx / 429 rate limit errors
|
|
11
|
+
* - Respects server 429 retry-after headers/body
|
|
12
|
+
* - Clean termination via AbortSignal
|
|
13
|
+
* - Configurable error threshold to prevent infinite broken loops
|
|
14
|
+
*/
|
|
15
|
+
import { RoomsClient } from '../rooms/index.js';
|
|
16
|
+
import type { TechnocoreMessage } from '../types/index.js';
|
|
17
|
+
export interface StreamOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Sequence cursor to start reading from.
|
|
20
|
+
* If omitted, the stream begins by fetching the latest messages in the room.
|
|
21
|
+
*/
|
|
22
|
+
since?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Long-poll hold time in seconds per request (0..10, default: 10).
|
|
25
|
+
*/
|
|
26
|
+
wait?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Maximum consecutive errors before stream aborts (default: 10).
|
|
29
|
+
*/
|
|
30
|
+
maxConsecutiveErrors?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Initial backoff delay in ms on failure (default: 1000).
|
|
33
|
+
*/
|
|
34
|
+
backoffInitialMs?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Maximum backoff delay in ms (default: 30000).
|
|
37
|
+
*/
|
|
38
|
+
backoffMaxMs?: number;
|
|
39
|
+
/**
|
|
40
|
+
* AbortSignal for external stream cancellation.
|
|
41
|
+
*/
|
|
42
|
+
signal?: AbortSignal;
|
|
43
|
+
/**
|
|
44
|
+
* Callback fired when messages were dropped by the room ring retention boundary.
|
|
45
|
+
*/
|
|
46
|
+
onGap?: (gap: {
|
|
47
|
+
lastSeenSeq: number;
|
|
48
|
+
firstReceivedSeq: number;
|
|
49
|
+
missedEstimate: number;
|
|
50
|
+
}) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Callback fired on non-fatal retryable errors.
|
|
53
|
+
*/
|
|
54
|
+
onError?: (error: Error, consecutiveFailures: number, retryDelayMs: number) => void;
|
|
55
|
+
}
|
|
56
|
+
export interface StreamItem {
|
|
57
|
+
readonly message: TechnocoreMessage;
|
|
58
|
+
readonly room: string;
|
|
59
|
+
readonly gapDetected: boolean;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Creates an AsyncIterable stream that yields messages as they arrive in a room.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* for await (const item of streamRoom(roomsClient, 'lobby', { since: 1000 })) {
|
|
67
|
+
* console.log(`[${item.message.seq}] ${item.message.from}: ${item.message.text}`);
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare function streamRoom(roomsClient: RoomsClient, room: string, options?: StreamOptions): AsyncGenerator<StreamItem, void, unknown>;
|
|
72
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/streaming/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACjG;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;CACrF;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;;;;;GASG;AACH,wBAAuB,UAAU,CAC/B,WAAW,EAAE,WAAW,EACxB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,aAAkB,GAC1B,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC,CAkG3C"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Technocore SDK — Streaming & Reliability
|
|
3
|
+
*
|
|
4
|
+
* Implements a resilient AsyncIterable long-polling stream over room messages.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Protocol-native long-polling (?since=<seq>&wait=10)
|
|
8
|
+
* - Automatic cursor advancement
|
|
9
|
+
* - Sequence gap detection & notifications
|
|
10
|
+
* - Exponential backoff with jitter on network / 5xx / 429 rate limit errors
|
|
11
|
+
* - Respects server 429 retry-after headers/body
|
|
12
|
+
* - Clean termination via AbortSignal
|
|
13
|
+
* - Configurable error threshold to prevent infinite broken loops
|
|
14
|
+
*/
|
|
15
|
+
import { TechnocoreRateLimitError } from '../errors/index.js';
|
|
16
|
+
import { MAX_WAIT_SECONDS } from '../types/index.js';
|
|
17
|
+
/**
|
|
18
|
+
* Creates an AsyncIterable stream that yields messages as they arrive in a room.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* for await (const item of streamRoom(roomsClient, 'lobby', { since: 1000 })) {
|
|
23
|
+
* console.log(`[${item.message.seq}] ${item.message.from}: ${item.message.text}`);
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export async function* streamRoom(roomsClient, room, options = {}) {
|
|
28
|
+
const waitSec = Math.min(MAX_WAIT_SECONDS, Math.max(0, options.wait ?? MAX_WAIT_SECONDS));
|
|
29
|
+
const maxErrors = options.maxConsecutiveErrors ?? 10;
|
|
30
|
+
const initialBackoff = options.backoffInitialMs ?? 1000;
|
|
31
|
+
const maxBackoff = options.backoffMaxMs ?? 30_000;
|
|
32
|
+
const signal = options.signal;
|
|
33
|
+
let currentSince = options.since;
|
|
34
|
+
let consecutiveErrors = 0;
|
|
35
|
+
let currentBackoff = initialBackoff;
|
|
36
|
+
// If no initial since cursor is given, do a quick read to establish the baseline cursor
|
|
37
|
+
if (currentSince === undefined) {
|
|
38
|
+
if (signal?.aborted)
|
|
39
|
+
return;
|
|
40
|
+
try {
|
|
41
|
+
const initial = await roomsClient.read(room, { limit: 1 });
|
|
42
|
+
currentSince = initial.last_seq;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (signal?.aborted)
|
|
46
|
+
return;
|
|
47
|
+
// If room doesn't exist yet, start from cursor 0
|
|
48
|
+
currentSince = 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
while (!signal?.aborted) {
|
|
52
|
+
try {
|
|
53
|
+
const response = await roomsClient.read(room, {
|
|
54
|
+
since: currentSince,
|
|
55
|
+
wait: waitSec,
|
|
56
|
+
});
|
|
57
|
+
// Reset error state on successful request
|
|
58
|
+
consecutiveErrors = 0;
|
|
59
|
+
currentBackoff = initialBackoff;
|
|
60
|
+
if (response.gapDetected && response.first_seq !== null && currentSince !== undefined) {
|
|
61
|
+
const gap = {
|
|
62
|
+
lastSeenSeq: currentSince,
|
|
63
|
+
firstReceivedSeq: response.first_seq,
|
|
64
|
+
missedEstimate: Math.max(0, response.first_seq - (currentSince + 1)),
|
|
65
|
+
};
|
|
66
|
+
options.onGap?.(gap);
|
|
67
|
+
}
|
|
68
|
+
// Yield all new messages
|
|
69
|
+
for (const message of response.messages) {
|
|
70
|
+
if (signal?.aborted)
|
|
71
|
+
return;
|
|
72
|
+
yield {
|
|
73
|
+
message,
|
|
74
|
+
room,
|
|
75
|
+
gapDetected: response.gapDetected,
|
|
76
|
+
};
|
|
77
|
+
// Advance cursor
|
|
78
|
+
if (message.seq > (currentSince ?? 0)) {
|
|
79
|
+
currentSince = message.seq;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Update cursor even if no messages were returned
|
|
83
|
+
if (response.last_seq > (currentSince ?? 0)) {
|
|
84
|
+
currentSince = response.last_seq;
|
|
85
|
+
}
|
|
86
|
+
// If server returned wait_held: false (no long-poll slots), pause slightly before retrying
|
|
87
|
+
if (response.wait_held === false && response.messages.length === 0) {
|
|
88
|
+
await sleep(Math.min(waitSec * 1000, 3000), signal);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (signal?.aborted)
|
|
93
|
+
return;
|
|
94
|
+
consecutiveErrors++;
|
|
95
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
96
|
+
if (consecutiveErrors >= maxErrors) {
|
|
97
|
+
throw new Error(`Technocore streaming aborted after ${consecutiveErrors} consecutive errors on room '${room}': ${err.message}`, { cause: err });
|
|
98
|
+
}
|
|
99
|
+
// Calculate retry delay with jitter
|
|
100
|
+
let retryDelayMs = currentBackoff + Math.floor(Math.random() * 500);
|
|
101
|
+
// If rate-limited, respect server's Retry-After delay
|
|
102
|
+
if (error instanceof TechnocoreRateLimitError) {
|
|
103
|
+
retryDelayMs = Math.max(retryDelayMs, error.retryAfterSeconds * 1000);
|
|
104
|
+
}
|
|
105
|
+
options.onError?.(err, consecutiveErrors, retryDelayMs);
|
|
106
|
+
await sleep(retryDelayMs, signal);
|
|
107
|
+
// Increase exponential backoff
|
|
108
|
+
currentBackoff = Math.min(currentBackoff * 1.5, maxBackoff);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function sleep(ms, signal) {
|
|
113
|
+
return new Promise((resolve) => {
|
|
114
|
+
if (signal?.aborted) {
|
|
115
|
+
resolve();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const timeoutId = setTimeout(resolve, ms);
|
|
119
|
+
signal?.addEventListener('abort', () => {
|
|
120
|
+
clearTimeout(timeoutId);
|
|
121
|
+
resolve();
|
|
122
|
+
}, { once: true });
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/streaming/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Technocore SDK — HTTP Transport
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around fetch with:
|
|
5
|
+
* - configurable base URL
|
|
6
|
+
* - timeout / AbortSignal support
|
|
7
|
+
* - structured error mapping (429 → TechnocoreRateLimitError, etc.)
|
|
8
|
+
* - never retries writes automatically
|
|
9
|
+
*/
|
|
10
|
+
export interface TransportConfig {
|
|
11
|
+
/** Base URL for all requests. */
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
/** Custom fetch implementation. */
|
|
14
|
+
fetch?: typeof globalThis.fetch;
|
|
15
|
+
/** Default timeout in milliseconds. */
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface RequestOptions {
|
|
19
|
+
/** HTTP method. Default: GET. */
|
|
20
|
+
method?: 'GET' | 'POST';
|
|
21
|
+
/** Request body (for POST). */
|
|
22
|
+
body?: string;
|
|
23
|
+
/** Content-Type header (for POST). */
|
|
24
|
+
contentType?: string;
|
|
25
|
+
/** Request timeout in ms (overrides default). */
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
/** AbortSignal for external cancellation. */
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
/** Query parameters. */
|
|
30
|
+
params?: Record<string, string | number | undefined>;
|
|
31
|
+
}
|
|
32
|
+
export interface TransportResponse {
|
|
33
|
+
readonly status: number;
|
|
34
|
+
readonly body: string;
|
|
35
|
+
readonly contentType: string;
|
|
36
|
+
readonly headers: Headers;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* HTTP transport layer for Technocore protocol.
|
|
40
|
+
*/
|
|
41
|
+
export declare class HttpTransport {
|
|
42
|
+
#private;
|
|
43
|
+
readonly baseUrl: string;
|
|
44
|
+
constructor(config?: TransportConfig);
|
|
45
|
+
/**
|
|
46
|
+
* Make an HTTP request to the Technocore server.
|
|
47
|
+
* Throws typed errors for protocol-specific failure modes.
|
|
48
|
+
*/
|
|
49
|
+
request(path: string, options?: RequestOptions): Promise<TransportResponse>;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/transport/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,MAAM,WAAW,eAAe;IAC9B,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,wBAAwB;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,qBAAa,aAAa;;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAIb,MAAM,GAAE,eAAoB;IAMxC;;;OAGG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA4HtF"}
|