@raisindb/client 0.2.4 → 0.2.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 +281 -70
- package/dist/chunk-3KMOXRTK.mjs +1377 -0
- package/dist/chunk-3KMOXRTK.mjs.map +1 -0
- package/dist/chunk-B6EFDYFS.mjs +170 -0
- package/dist/chunk-B6EFDYFS.mjs.map +1 -0
- package/dist/chunk-P44XWTPA.mjs +190 -0
- package/dist/chunk-P44XWTPA.mjs.map +1 -0
- package/dist/chunk-Q4IBPW2W.mjs +866 -0
- package/dist/chunk-Q4IBPW2W.mjs.map +1 -0
- package/dist/chunk-QJKYNLT4.mjs +532 -0
- package/dist/chunk-QJKYNLT4.mjs.map +1 -0
- package/dist/chunk-T2ZSNYFG.mjs +248 -0
- package/dist/chunk-T2ZSNYFG.mjs.map +1 -0
- package/dist/conversation-list-store-S5MPSqoB.d.mts +6823 -0
- package/dist/conversation-list-store-S5MPSqoB.d.ts +6823 -0
- package/dist/index-3-fS4i8b.d.mts +301 -0
- package/dist/index-Bs7UkpRs.d.ts +301 -0
- package/dist/index.d.mts +66 -6108
- package/dist/index.d.ts +66 -6108
- package/dist/index.js +1601 -201
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1199 -3046
- package/dist/index.mjs.map +1 -0
- package/dist/react.d.mts +4 -0
- package/dist/react.d.ts +4 -0
- package/dist/react.js +2519 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +9 -0
- package/dist/react.mjs.map +1 -0
- package/dist/svelte-sql-BCuXrg8L.d.mts +52 -0
- package/dist/svelte-sql-DO1X-WO3.d.ts +52 -0
- package/dist/svelte.d.mts +288 -0
- package/dist/svelte.d.ts +288 -0
- package/dist/svelte.js +2432 -0
- package/dist/svelte.js.map +1 -0
- package/dist/svelte.mjs +25 -0
- package/dist/svelte.mjs.map +1 -0
- package/dist/vue.d.mts +197 -0
- package/dist/vue.d.ts +197 -0
- package/dist/vue.js +1460 -0
- package/dist/vue.js.map +1 -0
- package/dist/vue.mjs +9 -0
- package/dist/vue.mjs.map +1 -0
- package/package.json +18 -3
package/README.md
CHANGED
|
@@ -317,6 +317,99 @@ const subscription4 = await events.subscribeToNodeType('Page', (event) => {
|
|
|
317
317
|
await subscription1.unsubscribe();
|
|
318
318
|
```
|
|
319
319
|
|
|
320
|
+
### Build a chatbox
|
|
321
|
+
|
|
322
|
+
Everything chat lives behind `db.conversations` (low-level) and
|
|
323
|
+
`ConversationStore` (framework-agnostic state container). The store creates
|
|
324
|
+
the conversation lazily on the first message, streams the agent's reply over
|
|
325
|
+
SSE, tracks in-flight tool calls, and projects plans for approval UIs.
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import { ConversationStore } from '@raisindb/client';
|
|
329
|
+
|
|
330
|
+
const db = client.database('myrepo');
|
|
331
|
+
|
|
332
|
+
const store = new ConversationStore({
|
|
333
|
+
database: db,
|
|
334
|
+
// Conversation is created on the first sendMessage(). The participant is
|
|
335
|
+
// an agent path, so this becomes an `ai_chat` conversation.
|
|
336
|
+
createOptions: { participant: '/agents/support' },
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
store.subscribe((s) => {
|
|
340
|
+
renderMessages(s.messages); // ChatMessage[]
|
|
341
|
+
if (s.isStreaming) renderPartial(s.streamingText); // live tokens
|
|
342
|
+
|
|
343
|
+
// Tool-call badges: "running" entries are in-flight
|
|
344
|
+
for (const tc of s.activeToolCalls) {
|
|
345
|
+
renderToolBadge(tc.functionName, tc.status); // running | completed | failed
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Plans awaiting approval
|
|
349
|
+
for (const plan of s.plans) {
|
|
350
|
+
if (plan.status === 'pending_approval') {
|
|
351
|
+
renderPlanCard(plan, {
|
|
352
|
+
approve: () => store.approvePlan(plan.planPath),
|
|
353
|
+
reject: (why) => store.rejectPlan(plan.planPath, why),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (s.error) showError(s.error);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
await store.sendMessage('Plan next week\'s shifts');
|
|
362
|
+
// ...
|
|
363
|
+
store.stop(); // abort the current turn in the UI
|
|
364
|
+
store.destroy(); // on teardown
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
To resume an existing conversation, pass `conversationPath` instead of (or in
|
|
368
|
+
addition to) `createOptions` and call `store.loadMessages()` for history. To
|
|
369
|
+
build an inbox-style list of conversations, use `ConversationListStore`
|
|
370
|
+
(`load()`, `createConversation()`, `markAsRead()`, `realtime: true`).
|
|
371
|
+
|
|
372
|
+
### Listen to the inbox (notifications without an extra API)
|
|
373
|
+
|
|
374
|
+
Server-side messaging delivers items into the logged-in user's home inbox in
|
|
375
|
+
the `raisin:access_control` workspace. A plain node subscription on
|
|
376
|
+
`${home}/inbox/**` is all an inbox bell needs — no polling, no extra API.
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import { normalizeHomePath } from '@raisindb/client';
|
|
380
|
+
|
|
381
|
+
const user = await client.initSession('myrepo');
|
|
382
|
+
|
|
383
|
+
// user.home may be workspace-prefixed (/raisin:access_control/users/...).
|
|
384
|
+
// Subscription paths must be workspace-relative — always normalize.
|
|
385
|
+
const home = normalizeHomePath(user!.home)!;
|
|
386
|
+
|
|
387
|
+
await db.workspace('raisin:access_control').events().subscribe(
|
|
388
|
+
{
|
|
389
|
+
path: `${home}/inbox/**`,
|
|
390
|
+
event_types: ['node:created'],
|
|
391
|
+
include_node: true, // deliver the full node so we can render a title
|
|
392
|
+
},
|
|
393
|
+
(event) => {
|
|
394
|
+
const props = event.payload.node?.properties ?? {};
|
|
395
|
+
if (props.role === 'user') return; // skip the user's own chat messages
|
|
396
|
+
bumpBadge();
|
|
397
|
+
showToast((props.title as string) ?? (props.subject as string) ?? 'New inbox item');
|
|
398
|
+
},
|
|
399
|
+
);
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**Path filter semantics** (important — there is no implicit prefix matching):
|
|
403
|
+
|
|
404
|
+
| Pattern | Matches |
|
|
405
|
+
|---------|---------|
|
|
406
|
+
| `/users/u1/inbox` | exactly that node, nothing below it |
|
|
407
|
+
| `/users/u1/inbox/*` | direct children only (`*` = one path segment) |
|
|
408
|
+
| `/users/u1/inbox/**` | the whole subtree, any depth (`**` = recursive) |
|
|
409
|
+
|
|
410
|
+
Inbox items nest (e.g. `inbox/chats/<conversation>/<message>`), so you almost
|
|
411
|
+
always want `/**`.
|
|
412
|
+
|
|
320
413
|
### Workspace Management
|
|
321
414
|
|
|
322
415
|
```typescript
|
|
@@ -446,104 +539,117 @@ const client = new RaisinClient('raisin://localhost:8080/sys/default', {
|
|
|
446
539
|
|
|
447
540
|
## Framework Integration
|
|
448
541
|
|
|
542
|
+
The SDK ships dedicated subpath exports — `@raisindb/client/react`,
|
|
543
|
+
`@raisindb/client/svelte`, and `@raisindb/client/vue`. None of them make the
|
|
544
|
+
framework a dependency: React and Vue use a "bring your own framework"
|
|
545
|
+
factory (you pass the module in once), Svelte gets framework-free adapters
|
|
546
|
+
that bind naturally to runes.
|
|
547
|
+
|
|
449
548
|
### React
|
|
450
549
|
|
|
451
550
|
```tsx
|
|
452
|
-
|
|
453
|
-
import
|
|
551
|
+
// lib/raisin-react.ts
|
|
552
|
+
import React from 'react';
|
|
553
|
+
import { RaisinClient, LocalStorageTokenStorage } from '@raisindb/client';
|
|
554
|
+
import { createRaisinReact } from '@raisindb/client/react';
|
|
454
555
|
|
|
455
|
-
const client = new RaisinClient('ws://localhost:8081/sys/default/myrepo', {
|
|
556
|
+
export const client = new RaisinClient('ws://localhost:8081/sys/default/myrepo', {
|
|
456
557
|
tokenStorage: new LocalStorageTokenStorage('myapp'),
|
|
457
558
|
});
|
|
458
559
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
useEffect(() => {
|
|
464
|
-
// Initialize session on mount
|
|
465
|
-
client.initSession('myrepo').then(setUser).finally(() => setLoading(false));
|
|
560
|
+
export const {
|
|
561
|
+
RaisinProvider, useAuth, useConnection, useSql, useSubscription,
|
|
562
|
+
useConversation, useConversationList,
|
|
563
|
+
} = createRaisinReact(React);
|
|
466
564
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
565
|
+
// App.tsx
|
|
566
|
+
function Chat() {
|
|
567
|
+
const chat = useConversation({
|
|
568
|
+
database: client.database('myrepo'),
|
|
569
|
+
createOptions: { participant: '/agents/support' },
|
|
570
|
+
});
|
|
471
571
|
|
|
472
|
-
|
|
473
|
-
|
|
572
|
+
return (
|
|
573
|
+
<div>
|
|
574
|
+
{chat.messages.map((m, i) => <p key={i}>{m.content}</p>)}
|
|
575
|
+
{chat.isStreaming && <p className="streaming">{chat.streamingText}</p>}
|
|
576
|
+
<button onClick={() => chat.sendMessage('Hello!')}>Send</button>
|
|
577
|
+
</div>
|
|
578
|
+
);
|
|
579
|
+
}
|
|
474
580
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
581
|
+
export default function App() {
|
|
582
|
+
return (
|
|
583
|
+
<RaisinProvider client={client} repository="myrepo">
|
|
584
|
+
<Chat />
|
|
585
|
+
</RaisinProvider>
|
|
586
|
+
);
|
|
478
587
|
}
|
|
479
588
|
```
|
|
480
589
|
|
|
481
|
-
###
|
|
590
|
+
### Svelte 5
|
|
482
591
|
|
|
483
592
|
```typescript
|
|
484
|
-
// lib/
|
|
485
|
-
import {
|
|
486
|
-
import {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
export const user = writable<IdentityUser | null>(null);
|
|
493
|
-
|
|
494
|
-
// Subscribe to auth changes
|
|
495
|
-
client.onAuthStateChange(({ session }) => {
|
|
496
|
-
user.set(session.user);
|
|
593
|
+
// lib/chat.svelte.ts
|
|
594
|
+
import { ConversationStore, type ConversationStoreSnapshot } from '@raisindb/client';
|
|
595
|
+
import { createConversationAdapter } from '@raisindb/client/svelte';
|
|
596
|
+
import { db } from '$lib/raisin';
|
|
597
|
+
|
|
598
|
+
const adapter = createConversationAdapter({
|
|
599
|
+
database: db,
|
|
600
|
+
createOptions: { participant: '/agents/support' },
|
|
497
601
|
});
|
|
498
602
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
return
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
603
|
+
let snapshot = $state<ConversationStoreSnapshot>(adapter.getSnapshot());
|
|
604
|
+
adapter.subscribe((s) => { snapshot = s; });
|
|
605
|
+
|
|
606
|
+
export const chat = {
|
|
607
|
+
get messages() { return snapshot.messages; },
|
|
608
|
+
get isStreaming() { return snapshot.isStreaming; },
|
|
609
|
+
get streamingText() { return snapshot.streamingText; },
|
|
610
|
+
get plans() { return snapshot.plans; },
|
|
611
|
+
send: adapter.sendMessage,
|
|
612
|
+
approvePlan: adapter.approvePlan,
|
|
613
|
+
destroy: adapter.destroy,
|
|
614
|
+
};
|
|
511
615
|
```
|
|
512
616
|
|
|
513
617
|
### Vue 3
|
|
514
618
|
|
|
515
619
|
```typescript
|
|
516
|
-
//
|
|
517
|
-
import
|
|
518
|
-
import { RaisinClient, LocalStorageTokenStorage
|
|
620
|
+
// lib/raisin-vue.ts
|
|
621
|
+
import * as vue from 'vue';
|
|
622
|
+
import { RaisinClient, LocalStorageTokenStorage } from '@raisindb/client';
|
|
623
|
+
import { createRaisinVue } from '@raisindb/client/vue';
|
|
519
624
|
|
|
520
|
-
const client = new RaisinClient('ws://localhost:8081/sys/default/myrepo', {
|
|
625
|
+
export const client = new RaisinClient('ws://localhost:8081/sys/default/myrepo', {
|
|
521
626
|
tokenStorage: new LocalStorageTokenStorage('myapp'),
|
|
522
627
|
});
|
|
628
|
+
export const db = client.database('myrepo');
|
|
523
629
|
|
|
524
|
-
export
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
onMounted(async () => {
|
|
530
|
-
user.value = await client.initSession('myrepo');
|
|
531
|
-
loading.value = false;
|
|
532
|
-
|
|
533
|
-
unsubscribe = client.onAuthStateChange(({ session }) => {
|
|
534
|
-
user.value = session.user;
|
|
535
|
-
});
|
|
536
|
-
});
|
|
630
|
+
export const {
|
|
631
|
+
useAuth, useConnection, useSql, useSubscription,
|
|
632
|
+
useConversation, useConversationList,
|
|
633
|
+
} = createRaisinVue(vue);
|
|
634
|
+
```
|
|
537
635
|
|
|
538
|
-
|
|
636
|
+
```vue
|
|
637
|
+
<!-- Chat.vue -->
|
|
638
|
+
<script setup lang="ts">
|
|
639
|
+
import { db, useConversation } from '@/lib/raisin-vue';
|
|
539
640
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
641
|
+
const chat = useConversation({
|
|
642
|
+
database: db,
|
|
643
|
+
createOptions: { participant: '/agents/support' },
|
|
644
|
+
});
|
|
645
|
+
// Cleanup is automatic: the composable destroys its store onUnmounted.
|
|
646
|
+
</script>
|
|
647
|
+
|
|
648
|
+
<template>
|
|
649
|
+
<p v-for="(m, i) in chat.messages.value" :key="i">{{ m.content }}</p>
|
|
650
|
+
<p v-if="chat.isStreaming.value" class="streaming">{{ chat.streamingText.value }}</p>
|
|
651
|
+
<button @click="chat.sendMessage('Hello!')">Send</button>
|
|
652
|
+
</template>
|
|
547
653
|
```
|
|
548
654
|
|
|
549
655
|
### Server-Side Rendering (SSR)
|
|
@@ -723,6 +829,111 @@ npm run dev
|
|
|
723
829
|
npm run typecheck
|
|
724
830
|
```
|
|
725
831
|
|
|
832
|
+
## Stability options
|
|
833
|
+
|
|
834
|
+
Options and behaviors that keep chat, inbox, and workflow UIs responsive when
|
|
835
|
+
the network or server misbehaves.
|
|
836
|
+
|
|
837
|
+
### `requestTimeout` (RaisinClient / RaisinHttpClient option)
|
|
838
|
+
|
|
839
|
+
Default: `30000` ms. Applies to:
|
|
840
|
+
|
|
841
|
+
- Every WebSocket request (resolved/rejected via the internal request tracker).
|
|
842
|
+
- All HTTP auth calls (`loginWithEmail`, `registerWithEmail`, `refreshToken`,
|
|
843
|
+
`initSession` token refresh) and `signAssetUrl` — these abort the underlying
|
|
844
|
+
`fetch` after `requestTimeout` and reject with a `RaisinTimeoutError`
|
|
845
|
+
(`code: 'REQUEST_TIMEOUT'`) instead of hanging forever.
|
|
846
|
+
|
|
847
|
+
Auth failures from the identity endpoints are thrown as `RaisinAuthError`
|
|
848
|
+
instances (real `Error` subclasses) that still carry the `{ code, message }`
|
|
849
|
+
fields, so existing destructuring keeps working:
|
|
850
|
+
|
|
851
|
+
```typescript
|
|
852
|
+
try {
|
|
853
|
+
await client.loginWithEmail(email, password, 'my-repo');
|
|
854
|
+
} catch (err) {
|
|
855
|
+
if (err instanceof RaisinAuthError) {
|
|
856
|
+
console.log(err.code, err.message, err.status);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
### `sendMessage` inactivity timeout (per-turn SSE)
|
|
862
|
+
|
|
863
|
+
`db.conversations.sendMessage(path, text, options)` streams the assistant turn
|
|
864
|
+
over a single-shot SSE connection. If that stream dies mid-turn (proxy reset,
|
|
865
|
+
server restart, dead TCP), the turn now ends with a synthetic `waiting` event
|
|
866
|
+
after `inactivityTimeoutMs` of silence (default: `120000` ms, reset on every
|
|
867
|
+
byte received) instead of stalling the chat UI forever:
|
|
868
|
+
|
|
869
|
+
```typescript
|
|
870
|
+
for await (const event of db.conversations.sendMessage(path, 'Hi', {
|
|
871
|
+
inactivityTimeoutMs: 60_000, // override; 0 disables
|
|
872
|
+
})) {
|
|
873
|
+
// ... a final { type: 'waiting' } event is guaranteed even if the stream dies
|
|
874
|
+
}
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
The same option exists on the lower-level `SSEClient` (`inactivityTimeoutMs`).
|
|
878
|
+
With reconnection enabled the SSE client silently reconnects on inactivity;
|
|
879
|
+
with reconnection disabled the stream ends with a `RaisinTimeoutError`
|
|
880
|
+
(`code: 'SSE_INACTIVITY_TIMEOUT'`).
|
|
881
|
+
|
|
882
|
+
### `streamingTimeoutMs` + `watchdogIntervalMs` (ConversationStore)
|
|
883
|
+
|
|
884
|
+
`ConversationStore` adds two recovery layers on top of the SSE stream:
|
|
885
|
+
|
|
886
|
+
- `streamingTimeoutMs` (default: `120000`) — if no SSE event arrives for this
|
|
887
|
+
long while a turn is streaming, the store auto-recovers (reloads messages
|
|
888
|
+
and clears the streaming state).
|
|
889
|
+
- `watchdogIntervalMs` (default: `30000`) — while streaming, the store
|
|
890
|
+
periodically asks the backend whether the turn is actually still active
|
|
891
|
+
(`checkTurnHealth`) and recovers if the backend says it finished.
|
|
892
|
+
|
|
893
|
+
```typescript
|
|
894
|
+
const store = new ConversationStore({
|
|
895
|
+
database: db,
|
|
896
|
+
conversationPath,
|
|
897
|
+
streamingTimeoutMs: 90_000,
|
|
898
|
+
watchdogIntervalMs: 15_000,
|
|
899
|
+
});
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
### Request queueing during reconnects
|
|
903
|
+
|
|
904
|
+
Requests issued while the WebSocket is temporarily down are no longer thrown
|
|
905
|
+
away. If the connection state is `Connecting`/`Reconnecting` (or
|
|
906
|
+
`Disconnected` with auto-reconnect enabled after a previous successful
|
|
907
|
+
connection), `RaisinClient` holds the request and flushes it once the
|
|
908
|
+
connection is re-established **and** re-authenticated:
|
|
909
|
+
|
|
910
|
+
- Each queued request still honors its own `requestTimeout` — it rejects if
|
|
911
|
+
the connection doesn't come back in time.
|
|
912
|
+
- The queue is bounded (100 requests); overflowing requests reject
|
|
913
|
+
immediately with a "Request queue is full" error.
|
|
914
|
+
- If re-authentication fails after reconnect, queued requests reject with a
|
|
915
|
+
`RaisinAuthError`.
|
|
916
|
+
- A permanent disconnect (manual `disconnect()`, max reconnect attempts
|
|
917
|
+
reached) cancels everything in the queue.
|
|
918
|
+
|
|
919
|
+
### `subscription_restore_failed` event
|
|
920
|
+
|
|
921
|
+
After a reconnect, the client restores event subscriptions automatically and
|
|
922
|
+
retries on failure (3 retries with 1s/3s/9s backoff, without duplicating the
|
|
923
|
+
subscriptions that already succeeded). If restoration still fails, the client
|
|
924
|
+
emits `subscription_restore_failed` so the app can resync its state:
|
|
925
|
+
|
|
926
|
+
```typescript
|
|
927
|
+
client.on('subscription_restore_failed', (error) => {
|
|
928
|
+
// Realtime events may be stale: reload lists and/or re-subscribe manually
|
|
929
|
+
console.warn('Subscriptions not restored after reconnect:', error);
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
client.onReconnected(() => {
|
|
933
|
+
// Fired only after connection + auth + successful subscription restore
|
|
934
|
+
});
|
|
935
|
+
```
|
|
936
|
+
|
|
726
937
|
## License
|
|
727
938
|
|
|
728
939
|
BSL-1.1 - See LICENSE file for details.
|