@rool-dev/sdk 0.11.3-dev.5dfa2e5 → 0.11.3-dev.7c57c94

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.
Files changed (49) hide show
  1. package/README.md +156 -105
  2. package/dist/auth-base.d.ts +100 -0
  3. package/dist/auth-base.d.ts.map +1 -0
  4. package/dist/auth-base.js +377 -0
  5. package/dist/auth-base.js.map +1 -0
  6. package/dist/auth-browser.d.ts +6 -77
  7. package/dist/auth-browser.d.ts.map +1 -1
  8. package/dist/auth-browser.js +32 -328
  9. package/dist/auth-browser.js.map +1 -1
  10. package/dist/auth-native.d.ts +72 -0
  11. package/dist/auth-native.d.ts.map +1 -0
  12. package/dist/auth-native.js +260 -0
  13. package/dist/auth-native.js.map +1 -0
  14. package/dist/auth.d.ts +17 -1
  15. package/dist/auth.d.ts.map +1 -1
  16. package/dist/auth.js +36 -5
  17. package/dist/auth.js.map +1 -1
  18. package/dist/client.d.ts +27 -8
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +52 -26
  21. package/dist/client.js.map +1 -1
  22. package/dist/graphql.d.ts +13 -19
  23. package/dist/graphql.d.ts.map +1 -1
  24. package/dist/graphql.js +35 -70
  25. package/dist/graphql.js.map +1 -1
  26. package/dist/index.d.ts +4 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +4 -2
  29. package/dist/index.js.map +1 -1
  30. package/dist/reroute.d.ts.map +1 -1
  31. package/dist/reroute.js +9 -0
  32. package/dist/reroute.js.map +1 -1
  33. package/dist/{channel.d.ts → space-session.d.ts} +44 -139
  34. package/dist/space-session.d.ts.map +1 -0
  35. package/dist/{channel.js → space-session.js} +111 -325
  36. package/dist/space-session.js.map +1 -0
  37. package/dist/space.d.ts +18 -48
  38. package/dist/space.d.ts.map +1 -1
  39. package/dist/space.js +84 -223
  40. package/dist/space.js.map +1 -1
  41. package/dist/subscription.d.ts +2 -2
  42. package/dist/subscription.d.ts.map +1 -1
  43. package/dist/subscription.js +6 -30
  44. package/dist/subscription.js.map +1 -1
  45. package/dist/types.d.ts +58 -83
  46. package/dist/types.d.ts.map +1 -1
  47. package/package.json +1 -1
  48. package/dist/channel.d.ts.map +0 -1
  49. package/dist/channel.js.map +0 -1
package/README.md CHANGED
@@ -4,9 +4,8 @@ TypeScript SDK for Rool, a persistent collaborative workspace for objects, AI-as
4
4
 
5
5
  Core primitives:
6
6
 
7
- - **Spaces** — containers for objects, schema, metadata, channels, collaborators, and files.
8
- - **Channels** — named contexts within a space for object operations and AI conversations.
9
- - **Conversations** — independent interaction histories within a channel.
7
+ - **Spaces** — containers for objects, schema, metadata, conversations, collaborators, and files.
8
+ - **Conversations** — independent interaction histories in a space.
10
9
  - **Objects** — JSON records addressed by object paths such as `/space/article/welcome.json`.
11
10
  - **Files** — user-visible files stored under `/rool-drive/...` through WebDAV.
12
11
 
@@ -31,36 +30,36 @@ async function main() {
31
30
  }
32
31
 
33
32
  const space = await client.createSpace('Solar System');
34
- const channel = await space.openChannel('main');
33
+ const conversation = space.conversation('main');
35
34
 
36
- await channel.createCollection('body', [
35
+ await conversation.createCollection('body', [
37
36
  { name: 'name', type: { kind: 'string' } },
38
37
  { name: 'mass', type: { kind: 'string' } },
39
38
  { name: 'radius', type: { kind: 'string' } },
40
39
  { name: 'orbits', type: { kind: 'maybe', inner: { kind: 'ref' } } },
41
40
  ]);
42
41
 
43
- const { object: sun } = await channel.putObject('/space/body/sun.json', {
42
+ const { object: sun } = await conversation.putObject('/space/body/sun.json', {
44
43
  name: 'Sun',
45
44
  mass: '1 solar mass',
46
45
  radius: '696,340 km',
47
46
  });
48
47
 
49
- const { object: earth } = await channel.putObject('/space/body/earth.json', {
48
+ const { object: earth } = await conversation.putObject('/space/body/earth.json', {
50
49
  name: 'Earth',
51
50
  mass: '1 Earth mass',
52
51
  radius: '6,371 km',
53
52
  orbits: sun.path,
54
53
  });
55
54
 
56
- const { message, objects } = await channel.prompt(
55
+ const { message, objects } = await conversation.prompt(
57
56
  'Add the other planets in our solar system, each referencing the Sun.'
58
57
  );
59
58
 
60
59
  console.log(message);
61
60
  console.log(`Modified ${objects.length} objects`);
62
61
 
63
- const loadedEarth = await channel.getObject(earth.path);
62
+ const loadedEarth = await space.getObject(earth.path);
64
63
  console.log(loadedEarth?.body.name);
65
64
 
66
65
  space.close();
@@ -142,6 +141,74 @@ if (!authenticated) {
142
141
  if (!authenticated) throw new Error('Login required');
143
142
  ```
144
143
 
144
+ ### Native (Capacitor, Cordova, Tauri, ...)
145
+
146
+ Use the native PKCE provider for JS app shells that sign in through an external
147
+ system browser. `login()`/`signup()` open the auth server's `/authorize` page
148
+ via the `openExternal` callback you supply; when the deep link returns, feed it
149
+ to `client.handleAuthRedirect(url)` from your platform's deep-link handler. The
150
+ code is exchanged for a session at `/token` and tokens are stored like the
151
+ browser provider.
152
+
153
+ ```typescript
154
+ import { RoolClient, NativePkceAuthProvider } from '@rool-dev/sdk';
155
+ import { Browser } from '@capacitor/browser';
156
+ import { App } from '@capacitor/app';
157
+
158
+ const client = new RoolClient({
159
+ authProvider: new NativePkceAuthProvider({
160
+ redirectUri: 'roolandroidauth://auth/callback', // must match the server allowlist
161
+ defaultProvider: 'google', // 'google' | 'apple'
162
+ openExternal: (url) => Browser.open({ url }),
163
+ }),
164
+ });
165
+
166
+ // Complete sign-in when the OS hands the app its deep link.
167
+ App.addListener('appUrlOpen', async ({ url }) => {
168
+ if (await client.handleAuthRedirect(url)) {
169
+ await Browser.close();
170
+ // Now authenticated — refresh your UI.
171
+ }
172
+ });
173
+
174
+ if (!(await client.initialize())) {
175
+ // Opens the system browser; completion arrives via the listener above.
176
+ await client.login('My App'); // pass { provider: 'apple' } to override the default
177
+ }
178
+ ```
179
+
180
+ #### Email + password and magic links (native)
181
+
182
+ The native provider also supports email/password sign-in and magic links —
183
+ no system browser, the server returns the token set as JSON directly.
184
+
185
+ ```typescript
186
+ // Password sign-in
187
+ const result = await client.signInWithPassword(email, password);
188
+ if (result.status === 'signed_in') {
189
+ // authenticated — refresh your UI
190
+ } else {
191
+ // status === 'verify_required': the email isn't verified yet and the server
192
+ // has emailed a magic link. Tell the user to check their inbox.
193
+ }
194
+
195
+ // Or request a magic link explicitly
196
+ await client.requestMagicLink(email);
197
+ ```
198
+
199
+ The magic link carries a `?verify=<token>` param; complete sign-in by passing
200
+ that token to `client.verify(token)` once the link lands back in the app.
201
+
202
+ > **⚠️ Magic links open the website, not the app, until Universal Links / App
203
+ > Links are configured.** The emailed link is an `https://` URL for the Rool
204
+ > web app. On native, an `https` link only re-opens your app if you've set up
205
+ > [iOS Universal Links](https://developer.apple.com/ios/universal-links/) /
206
+ > [Android App Links](https://developer.android.com/training/app-links) for that
207
+ > domain (custom-scheme deep links don't apply to email links). **Without that
208
+ > setup the magic link completes sign-in in the browser/website, not in the
209
+ > native app** — so for now treat magic links on native as a website hand-off,
210
+ > and prefer password or social sign-in for an in-app experience.
211
+
145
212
  ### Auth API
146
213
 
147
214
  | Method | Description |
@@ -150,6 +217,9 @@ if (!authenticated) throw new Error('Login required');
150
217
  | `login(appName, params?): Promise<void>` | Start login flow. |
151
218
  | `signup(appName, params?): Promise<void>` | Start signup flow. |
152
219
  | `verify(token): Promise<boolean>` | Complete email verification token flow; returns `false` when the active auth provider does not implement verification. |
220
+ | `handleAuthRedirect(url): Promise<boolean>` | Complete a native PKCE sign-in from a deep-link callback URL. Returns `false` when the active auth provider does not implement it. |
221
+ | `signInWithPassword(email, password): Promise<PasswordSignInResult>` | Email + password sign-in (native provider). Resolves `{ status: 'signed_in' }`, or `{ status: 'verify_required' }` when the email is unverified (a magic link was emailed). Rejects on bad credentials. |
222
+ | `requestMagicLink(email): Promise<void>` | Email the user a magic sign-in link (native provider). See the caveat below — on native the link opens the **website**, not the app, until Universal Links / App Links are configured. |
153
223
  | `logout(): void` | Clear auth state and close open spaces. |
154
224
  | `isAuthenticated(): Promise<boolean>` | Whether credentials are held locally. No network call — a server outage does not read as logged out. |
155
225
  | `getAuthUser(): AuthUser` | Return auth identity decoded from the token. |
@@ -159,58 +229,57 @@ if (!authenticated) throw new Error('Login required');
159
229
 
160
230
  A temporarily unreachable server never reads as "logged out". `initialize()` reports authentication from stored credentials, so on an offline start it can return `true` while `currentUser` is still `null` and user storage is empty — the SDK keeps reconnecting in the background and hydrates both automatically once the server is reachable, emitting `currentUserChanged`. Only an invalid or expired refresh token ends the session, via `authStateChanged(false)`.
161
231
 
162
- ## Spaces and Channels
232
+ ## Spaces and Conversations
163
233
 
164
- Open a space to receive live events and manage collaborators, file storage, and channels. Open a channel to work with objects, schema, metadata, and AI.
234
+ Open a space to receive live events and manage collaborators, file storage, and conversations. Open a conversation to work with objects, schema, metadata, and AI.
165
235
 
166
236
  ```typescript
167
237
  const space = await client.openSpace('space-id');
168
238
 
169
- space.on('channelCreated', (channel) => console.log(channel.id));
239
+ const conversation = space.conversation('main');
170
240
  space.on('filesChanged', () => console.log('files changed'));
171
241
 
172
- const channel = await space.openChannel('main');
173
- await channel.prompt('Summarize this space');
242
+ await conversation.prompt('Summarize this space');
174
243
  ```
175
244
 
176
- Channel IDs must be 1–32 characters and contain only letters, numbers, `_`, and `-`.
245
+ Conversation IDs must be 1–32 characters and contain only letters, numbers, `_`, and `-`.
177
246
 
178
247
  ## Object Operations
179
248
 
180
249
  Objects are JSON files under `/space`. Create the collection before writing objects in it.
181
250
 
182
251
  ```typescript
183
- await channel.createCollection('article', [
252
+ await conversation.createCollection('article', [
184
253
  { name: 'title', type: { kind: 'string' } },
185
254
  { name: 'status', type: { kind: 'string' } },
186
255
  ]);
187
256
 
188
257
  // Create or replace an exact object path
189
- const { object } = await channel.putObject('/space/article/welcome.json', {
258
+ const { object } = await conversation.putObject('/space/article/welcome.json', {
190
259
  title: 'Welcome',
191
260
  status: 'draft',
192
261
  });
193
262
 
194
263
  // Patch fields; null or undefined deletes a field
195
- await channel.patchObject(object.path, {
264
+ await conversation.patchObject(object.path, {
196
265
  data: { status: 'published', obsoleteField: null },
197
266
  });
198
267
 
199
268
  // Read one or many objects
200
- await channel.getObject('/space/article/welcome.json');
201
- await channel.getObjects([
269
+ await space.getObject('/space/article/welcome.json');
270
+ await space.getObjects([
202
271
  '/space/article/welcome.json',
203
272
  '/space/article/intro.json',
204
273
  ]);
205
274
 
206
275
  // Rename or move an object
207
- await channel.moveObject(
276
+ await conversation.moveObject(
208
277
  '/space/article/welcome.json',
209
278
  '/space/article/hello-world.json'
210
279
  );
211
280
 
212
281
  // Delete objects
213
- await channel.deleteObjects(['/space/article/hello-world.json']);
282
+ await conversation.deleteObjects(['/space/article/hello-world.json']);
214
283
  ```
215
284
 
216
285
  | Method | Description |
@@ -228,7 +297,7 @@ await channel.deleteObjects(['/space/article/hello-world.json']);
228
297
  `prompt()` invokes the AI agent. The agent can inspect space context and, unless `readOnly` or a read-only effort is used, create/modify/move/delete objects.
229
298
 
230
299
  ```typescript
231
- const { message, objects } = await channel.prompt(
300
+ const { message, objects } = await conversation.prompt(
232
301
  'Create a topic node for the solar system, then child nodes for each planet.'
233
302
  );
234
303
 
@@ -251,12 +320,12 @@ console.log(objects.map((object) => object.path));
251
320
 
252
321
  ```typescript
253
322
  // Read-only quick question
254
- await channel.prompt('What topics are covered?', {
323
+ await conversation.prompt('What topics are covered?', {
255
324
  effort: 'QUICK', // fast/read-only
256
325
  });
257
326
 
258
327
  // Focus on existing objects and files
259
- await channel.prompt('Compare these resources', {
328
+ await conversation.prompt('Compare these resources', {
260
329
  attachments: [
261
330
  '/space/article/intro.json',
262
331
  'rool-machine:/rool-drive/docs/report.pdf',
@@ -264,12 +333,12 @@ await channel.prompt('Compare these resources', {
264
333
  });
265
334
 
266
335
  // Upload a local file as an attachment
267
- await channel.prompt('Describe this image', {
336
+ await conversation.prompt('Describe this image', {
268
337
  attachments: [fileInput.files![0]],
269
338
  });
270
339
 
271
340
  // Structured response
272
- const { message } = await channel.prompt('Categorize these items', {
341
+ const { message } = await conversation.prompt('Categorize these items', {
273
342
  responseSchema: {
274
343
  type: 'object',
275
344
  properties: {
@@ -282,7 +351,7 @@ const result = JSON.parse(message);
282
351
 
283
352
  // Stop a long prompt with a signal (when the caller holds the controller)
284
353
  const ac = new AbortController();
285
- const promptPromise = channel.prompt('Do a deep analysis', {
354
+ const promptPromise = conversation.prompt('Do a deep analysis', {
286
355
  effort: 'RESEARCH',
287
356
  signal: ac.signal,
288
357
  });
@@ -294,39 +363,33 @@ await promptPromise;
294
363
 
295
364
  Use `signal` when the same call site cancels the prompt. When the Stop button
296
365
  lives elsewhere — a different component, after a reload, or a prompt another
297
- client started — stop by ID or stop the conversation's active interaction
298
- instead. Both are best-effort: the server halts the agent loop and closes the
299
- stream, but an LLM turn already in flight keeps generating server-side and is
300
- billed.
366
+ client started — stop by interaction ID or through a conversation handle. Both
367
+ are best-effort: the server halts the agent loop and closes the stream, but an
368
+ LLM turn already in flight keeps generating server-side and is billed.
301
369
 
302
370
  ```typescript
303
- // Stop whatever is in flight on this channel's (default) conversation.
304
- // No-op returning false when nothing is running.
305
- await channel.stop();
306
-
307
- // Stop a specific interaction by ID (e.g. from channel.activeLeafId or
308
- // the interactions list). Returns whether the server stopped it.
309
- await channel.stopInteraction(channel.activeLeafId!);
371
+ // Stop a specific interaction by ID (for example, from conversation.activeLeafId
372
+ // or the interactions list). Returns whether the server stopped it.
373
+ await space.stopInteraction(conversation.activeLeafId!);
310
374
 
311
375
  // Conversation handles stop their own in-flight interaction.
312
- const thread = channel.conversation('thread-42');
376
+ const thread = space.conversation('thread-42');
313
377
  await thread.stop();
314
378
  ```
315
379
 
316
380
  | Method | Description |
317
381
  | --- | --- |
318
- | `stop(): Promise<boolean>` | Stop the in-flight interaction on the default conversation; `false` if none. |
319
382
  | `stopInteraction(id): Promise<boolean>` | Ask the server to stop a specific interaction by ID. |
320
383
  | `conversation.stop(): Promise<boolean>` | Stop a specific conversation's in-flight interaction. |
321
384
 
322
385
  ## Conversations
323
386
 
324
- Every channel has a default conversation. Use `channel.conversation(id)` for independent histories (for example, multiple chat threads). Conversations are represented as trees: interactions point at a `parentId`, and the SDK tracks an active leaf for each conversation.
387
+ Use `space.conversation(id)` for independent histories (for example, multiple chat threads). Conversations are represented as trees: interactions point at a `parentId`, and the SDK tracks an active leaf for each conversation.
325
388
 
326
389
  ```typescript
327
- await channel.prompt('Hello'); // default conversation
390
+ await conversation.prompt('Hello');
328
391
 
329
- const thread = channel.conversation('thread-42');
392
+ const thread = space.conversation('thread-42');
330
393
  await thread.prompt('Hello from another thread');
331
394
  await thread.setSystemInstruction('Answer in haiku');
332
395
 
@@ -340,15 +403,14 @@ if (thread.activeLeafId) {
340
403
 
341
404
  | Method/property | Description |
342
405
  | --- | --- |
343
- | `channel.conversation(id): ConversationHandle` | Get a conversation-scoped handle. |
406
+ | `space.conversation(id): ConversationHandle` | Get a conversation-scoped handle. |
344
407
  | `getInteractions(): Interaction[]` | Active branch as a flat list. |
345
408
  | `getTree(): Record<string, Interaction>` | Full interaction tree. |
346
409
  | `activeLeafId` | Current branch tip. |
347
410
  | `setActiveLeaf(id): void` | Switch branches. |
348
411
  | `getSystemInstruction()` / `setSystemInstruction(value)` | Manage conversation system instruction. Pass `null` to clear. |
349
- | `getConversations(): ConversationInfo[]` | List channel conversations (on `RoolChannel`). |
412
+ | `getConversations(): ConversationInfo[]` | List conversations in the space. |
350
413
  | `deleteConversation(id): Promise<void>` | Delete a non-active conversation. |
351
- | `renameConversation(name): Promise<void>` | Rename the current/default conversation (on `RoolChannel`). |
352
414
  | `conversation.rename(name): Promise<void>` | Rename a specific conversation handle. |
353
415
 
354
416
  `ConversationHandle` also supports conversation-scoped `putObject`, `patchObject`, `moveObject`, `deleteObjects`, `prompt`, `stop`, collection-schema methods, and `setMetadata`.
@@ -358,7 +420,7 @@ if (thread.activeLeafId) {
358
420
  Collections define the schema visible to the AI agent. Hidden body fields whose names start with `_` are useful for app/UI state that should not be considered by AI.
359
421
 
360
422
  ```typescript
361
- await channel.createCollection('article', {
423
+ await conversation.createCollection('article', {
362
424
  schemaOrgType: 'Article',
363
425
  fields: [
364
426
  { name: 'title', type: { kind: 'string' } },
@@ -368,15 +430,15 @@ await channel.createCollection('article', {
368
430
  ],
369
431
  });
370
432
 
371
- const schema = channel.getSchema();
433
+ const schema = space.getSchema();
372
434
 
373
- await channel.alterCollection('article', [
435
+ await conversation.alterCollection('article', [
374
436
  { name: 'title', type: { kind: 'string' } },
375
437
  { name: 'status', type: { kind: 'string' } },
376
438
  ]);
377
439
 
378
- channel.setMetadata('viewport', { x: 0, y: 0, zoom: 1 });
379
- const viewport = channel.getMetadata('viewport');
440
+ conversation.setMetadata('viewport', { x: 0, y: 0, zoom: 1 });
441
+ const viewport = space.getMetadata('viewport');
380
442
  ```
381
443
 
382
444
  | Method | Description |
@@ -393,14 +455,14 @@ Field kinds: `string`, `number`, `boolean`, `ref`, `enum`, `literal`, `array`, a
393
455
 
394
456
  ## Undo/Redo
395
457
 
396
- Undo/redo uses checkpoints for the current channel ID. A checkpoint captures space state; call `checkpoint()` before a user action you want to make undoable.
458
+ Undo/redo uses checkpoints over the whole space. A checkpoint captures space state; call `checkpoint()` before a user action you want to make undoable.
397
459
 
398
460
  ```typescript
399
- await channel.checkpoint('Delete article');
400
- await channel.deleteObjects(['/space/article/welcome.json']);
461
+ await space.checkpoint('Delete article');
462
+ await conversation.deleteObjects(['/space/article/welcome.json']);
401
463
 
402
- if (await channel.canUndo()) {
403
- await channel.undo();
464
+ if (await space.canUndo()) {
465
+ await space.undo();
404
466
  }
405
467
  ```
406
468
 
@@ -413,7 +475,7 @@ if (await channel.canUndo()) {
413
475
  | `redo(): Promise<boolean>` | Reapply undone work. |
414
476
  | `clearHistory(): Promise<void>` | Clear checkpoint history. |
415
477
 
416
- Undo/redo availability and history are scoped to the channel handle (`channel.channelId`).
478
+ Undo/redo availability and history are scoped to the space.
417
479
 
418
480
  ## File Storage and WebDAV
419
481
 
@@ -579,7 +641,7 @@ const client = new RoolClient({
579
641
  | `getAllUserStorage(): Record<string, unknown>` | Copy all cached user storage. |
580
642
  | `reportEvent(event, url?): void` | Fire-and-forget telemetry event. |
581
643
  | `destroy(): void` | Close subscriptions, spaces, auth resources, and listeners. |
582
- | `generateId(): string` | Generate a 6-character alphanumeric ID. |
644
+ | `generateId(): string` | Generate a unique ID suitable for conversation IDs. |
583
645
 
584
646
  ### Client events
585
647
 
@@ -589,9 +651,6 @@ client.on('currentUserChanged', (user) => void 0); // CurrentUser | null; null o
589
651
  client.on('spaceAdded', (space) => void 0);
590
652
  client.on('spaceRemoved', (spaceId) => void 0);
591
653
  client.on('spaceRenamed', (spaceId, newName) => void 0);
592
- client.on('channelCreated', (spaceId, channel) => void 0);
593
- client.on('channelUpdated', (spaceId, channel) => void 0);
594
- client.on('channelDeleted', (spaceId, channelId) => void 0);
595
654
  client.on('userStorageChanged', ({ key, value, source }) => void 0);
596
655
  client.on('connectionStateChanged', (state) => void 0);
597
656
  client.on('error', (error, context) => void 0);
@@ -599,12 +658,18 @@ client.on('error', (error, context) => void 0);
599
658
 
600
659
  ## RoolSpace API
601
660
 
602
- Properties: `id`, `name`, `role`, `memberCount`, `channels`, `route`, `webdav`.
661
+ Properties: `id`, `name`, `role`, `memberCount`, `conversations`, `route`, `webdav`.
603
662
 
604
663
  | Method | Description |
605
664
  | --- | --- |
606
- | `openChannel(channelId): Promise<RoolChannel>` | Open/create a channel. |
607
- | `close(): void` | Stop subscription and close open channels. |
665
+ | `conversation(conversationId): ConversationHandle` | Get a conversation-scoped handle. |
666
+ | `getObject`, `getObjects`, `stat` | Read object data and stats. |
667
+ | `getConversations`, `deleteConversation` | List and delete conversations. |
668
+ | `getMetadata`, `getAllMetadata`, `getSchema` | Read metadata and schema. |
669
+ | `checkpoint`, `canUndo`, `canRedo`, `undo`, `redo`, `clearHistory` | Space history controls. |
670
+ | `stopInteraction(interactionId): Promise<boolean>` | Stop an in-flight interaction by ID. |
671
+ | `fetch(url, init?): Promise<Response>` | Proxy an external HTTP request through the server to bypass browser CORS. |
672
+ | `close(): void` | Stop the space subscription. |
608
673
  | `rename(newName): Promise<void>` | Rename the space. |
609
674
  | `delete(): Promise<void>` | Permanently delete the space. |
610
675
  | `listUsers(): Promise<SpaceMember[]>` | List collaborators. |
@@ -613,8 +678,6 @@ Properties: `id`, `name`, `role`, `memberCount`, `channels`, `route`, `webdav`.
613
678
  | `createInvite(role, options?): Promise<SpaceInviteCreated>` | Mint an invite link; `options` takes `email`, `expiresInDays`, `maxUses`. |
614
679
  | `listInvites(): Promise<SpaceInvite[]>` | List currently redeemable invites. |
615
680
  | `revokeInvite(inviteId): Promise<boolean>` | Revoke an invite so its link stops working. |
616
- | `renameChannel(channelId, name): Promise<void>` | Rename a channel. |
617
- | `deleteChannel(channelId): Promise<void>` | Delete a channel and history. |
618
681
  | `exportArchive(): Promise<Blob>` | Export a space archive. |
619
682
  | `refresh(): Promise<void>` | Refresh cached space data. |
620
683
  | `fetchPath(path, options?): Promise<Response>` | Fetch a `/rool-drive/...` file. |
@@ -623,42 +686,15 @@ Properties: `id`, `name`, `role`, `memberCount`, `channels`, `route`, `webdav`.
623
686
  Events:
624
687
 
625
688
  ```typescript
626
- space.on('channelCreated', (channel) => void 0);
627
- space.on('channelUpdated', (channel) => void 0);
628
- space.on('channelDeleted', (channelId) => void 0);
689
+ space.on('metadataUpdated', ({ metadata, source }) => void 0);
690
+ space.on('schemaUpdated', ({ schema, source }) => void 0);
691
+ space.on('conversationUpdated', ({ conversationId, source }) => void 0);
692
+ space.on('reset', ({ source }) => void 0);
693
+ space.on('syncError', (error) => void 0);
629
694
  space.on('filesChanged', ({ spaceId, source, timestamp }) => void 0);
630
695
  space.on('filesReset', ({ spaceId, source, timestamp }) => void 0);
631
696
  space.on('connectionStateChanged', (state) => void 0);
632
697
  ```
633
-
634
- ## RoolChannel API
635
-
636
- Properties: `id` (space ID), `name` (space name), `role`, `userId`, `channelId`, `channelName`, `conversationId`, `isReadOnly`, `activeLeafId`.
637
-
638
- | Area | Methods |
639
- | --- | --- |
640
- | Lifecycle | `close()`, `rename(name)`, `conversation(id)` |
641
- | Objects | `getObject`, `getObjects`, `stat`, `putObject`, `patchObject`, `moveObject`, `deleteObjects` |
642
- | Schema | `getSchema`, `createCollection`, `alterCollection`, `dropCollection` |
643
- | Metadata | `setMetadata`, `getMetadata`, `getAllMetadata` |
644
- | Conversations | `getInteractions`, `getTree`, `setActiveLeaf`, `getConversations`, `deleteConversation`, `getSystemInstruction`, `setSystemInstruction`, `renameConversation` |
645
- | AI | `prompt`, `stop`, `stopInteraction` |
646
- | Undo/redo | `checkpoint`, `canUndo`, `canRedo`, `undo`, `redo`, `clearHistory` |
647
- | Utilities | `fetch(url, init?)` server-side proxied fetch |
648
-
649
- Channel events:
650
-
651
- ```typescript
652
- channel.on('metadataUpdated', ({ metadata, source }) => void 0);
653
- channel.on('schemaUpdated', ({ schema, source }) => void 0);
654
- channel.on('channelUpdated', ({ channelId, source }) => void 0);
655
- channel.on('conversationUpdated', ({ conversationId, channelId, source }) => void 0);
656
- channel.on('reset', ({ source }) => void 0);
657
- channel.on('syncError', (error) => void 0);
658
- ```
659
-
660
- `channel.fetch(url, init?)` proxies external HTTP requests through the server to bypass browser CORS.
661
-
662
698
  ## Import/Export
663
699
 
664
700
  ```typescript
@@ -666,11 +702,15 @@ const archive = await space.exportArchive();
666
702
  const imported = await client.importArchive('Imported Data', archive);
667
703
  ```
668
704
 
669
- Archives include objects, metadata, channels/conversations, and file storage.
705
+ Archives include objects, metadata, conversations, and file storage.
670
706
 
671
707
  ## Data Types
672
708
 
673
709
  ```typescript
710
+ // Outcome of signInWithPassword. 'verify_required' means the account's email
711
+ // isn't verified yet and the server has emailed a magic link.
712
+ type PasswordSignInResult = { status: 'signed_in' | 'verify_required' };
713
+
674
714
  type FieldType =
675
715
  | { kind: 'string' }
676
716
  | { kind: 'number' }
@@ -717,10 +757,22 @@ interface SpaceInvite {
717
757
  useCount: number;
718
758
  }
719
759
 
720
- // Outcome of the invite email send; null when no email was involved (open link).
721
- // 'not_configured' means the server has no mail provider (local dev). Future
722
- // codes may appear; treat unknown values as not sent.
723
- type InviteEmailStatus = 'sent' | 'not_configured' | 'failed' | (string & {});
760
+ // Outcome of the invite email send. Null when no email was involved (open link).
761
+ // The invite is always minted and its `url` is usable regardless of this value;
762
+ // only the email delivery is reflected here.
763
+ // - 'sent': email dispatched
764
+ // - 'not_configured': server has no mail provider (local dev)
765
+ // - 'failed': provider rejected the send
766
+ // - 'cooldown': a recent invite to this same address was already emailed
767
+ // - 'rate_limited': the inviter hit their daily email-invite cap
768
+ // Treat unknown values as not sent.
769
+ type InviteEmailStatus =
770
+ | 'sent'
771
+ | 'not_configured'
772
+ | 'failed'
773
+ | 'cooldown'
774
+ | 'rate_limited'
775
+ | (string & {});
724
776
 
725
777
  interface SpaceInviteCreated {
726
778
  inviteId: string;
@@ -752,7 +804,6 @@ interface RoolObjectStat {
752
804
  modifiedAt: number;
753
805
  modifiedBy: string;
754
806
  modifiedByName: string | null;
755
- modifiedInChannel: string;
756
807
  modifiedInConversation: string | null;
757
808
  modifiedInInteraction: string | null;
758
809
  }
@@ -0,0 +1,100 @@
1
+ import type { AuthProvider, AuthUser } from './types.js';
2
+ import { type Logger } from './logger.js';
3
+ export interface BaseTokenAuthConfig {
4
+ /** Auth URL (e.g. https://rool.dev/auth). Injected by RoolClient if omitted. */
5
+ authUrl?: string;
6
+ /** Injected by RoolClient if omitted. */
7
+ logger?: Logger;
8
+ /** Injected by RoolClient if omitted, so auth-state reaches client events. */
9
+ onAuthStateChanged?: (authenticated: boolean) => void;
10
+ }
11
+ export declare abstract class BaseTokenAuthProvider implements AuthProvider {
12
+ protected logger: Logger;
13
+ private _authUrl;
14
+ private _onAuthStateChanged;
15
+ private refreshPromise;
16
+ private refreshTimeoutId;
17
+ private boundVisibilityHandler;
18
+ constructor(config?: BaseTokenAuthConfig);
19
+ setAuthUrl(url: string): void;
20
+ setLogger(logger: Logger): void;
21
+ setAuthStateChangedHandler(handler: (authenticated: boolean) => void): void;
22
+ abstract initialize(): boolean;
23
+ abstract login(appName: string, params?: Record<string, string>): Promise<void> | void;
24
+ abstract signup(appName: string, params?: Record<string, string>): Promise<void> | void;
25
+ /**
26
+ * Check if user is currently authenticated.
27
+ *
28
+ * This reports identity (do we hold credentials), NOT server reachability.
29
+ * It deliberately does not perform a network refresh: a backend outage must
30
+ * not read as "logged out". A genuinely invalid/expired refresh token
31
+ * surfaces later as a 401 on first real use, which clears tokens and fires
32
+ * onAuthStateChanged(false) — the only path that ends the session.
33
+ */
34
+ isAuthenticated(): Promise<boolean>;
35
+ /**
36
+ * Get current access token and rool token, refreshing if expired.
37
+ * Returns undefined if not authenticated.
38
+ */
39
+ getTokens(): Promise<{
40
+ accessToken: string;
41
+ roolToken: string;
42
+ } | undefined>;
43
+ /**
44
+ * Get auth identity decoded from JWT token.
45
+ */
46
+ getAuthUser(): AuthUser;
47
+ /**
48
+ * Complete an email verification flow. Exchanges a verify JWT (from the
49
+ * verification email link) for a fresh token set and signs the user in.
50
+ */
51
+ verify(token: string): Promise<boolean>;
52
+ /**
53
+ * Logout - clear all tokens and state.
54
+ */
55
+ logout(): void;
56
+ /**
57
+ * Destroy auth manager - clear refresh timers and listeners.
58
+ */
59
+ destroy(): void;
60
+ /** Auth URL without trailing slash */
61
+ protected get authBaseUrl(): string;
62
+ protected notifyAuthState(authenticated: boolean): void;
63
+ /** Persist a fresh token set and (re)arm the background refresh timer. */
64
+ protected acceptTokens(accessToken: string, refreshToken: string | null, roolToken: string | null, expiresAt: number): void;
65
+ /** Arm auto-refresh + visibility re-scheduling. Call from initialize(). */
66
+ protected initBase(): void;
67
+ /**
68
+ * Overridable hook: clear subclass-owned transient state on logout/cancel
69
+ * (e.g. an in-flight OAuth `state` or PKCE verifier). No-op by default.
70
+ */
71
+ protected clearTransientState(): void;
72
+ protected get storagePrefix(): string;
73
+ /** Build a storage key scoped to this auth endpoint. */
74
+ protected keyFor(name: string): string;
75
+ protected get storageKeys(): {
76
+ readonly access: `${string}access_token`;
77
+ readonly refresh: `${string}refresh_token`;
78
+ readonly rool: `${string}rool_token`;
79
+ readonly expiresAt: `${string}token_expires_at`;
80
+ };
81
+ protected readString(key: string): string | null;
82
+ protected writeString(key: string, value: string): void;
83
+ protected removeString(key: string): void;
84
+ protected scheduleTokenRefresh(): void;
85
+ protected writeTokens(accessToken: string | null, refreshToken: string | null, expiresAt: number | null): void;
86
+ protected writeRoolToken(token: string | null): void;
87
+ protected clearTokens(): void;
88
+ protected readAccessToken(): string | null;
89
+ protected readRoolToken(): string;
90
+ protected readExpiresAt(): number | null;
91
+ /**
92
+ * Get a short hash of the auth URL for scoping storage by endpoint.
93
+ */
94
+ private get endpointHash();
95
+ private tryRefreshToken;
96
+ private listenForVisibility;
97
+ private cancelScheduledRefresh;
98
+ private decodeAuthUser;
99
+ }
100
+ //# sourceMappingURL=auth-base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-base.d.ts","sourceRoot":"","sources":["../src/auth-base.ts"],"names":[],"mappings":"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"}