@rool-dev/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.
Files changed (54) hide show
  1. package/README.md +1070 -0
  2. package/dist/apps.d.ts +29 -0
  3. package/dist/apps.d.ts.map +1 -0
  4. package/dist/apps.js +88 -0
  5. package/dist/apps.js.map +1 -0
  6. package/dist/auth-browser.d.ts +80 -0
  7. package/dist/auth-browser.d.ts.map +1 -0
  8. package/dist/auth-browser.js +370 -0
  9. package/dist/auth-browser.js.map +1 -0
  10. package/dist/auth-node.d.ts +46 -0
  11. package/dist/auth-node.d.ts.map +1 -0
  12. package/dist/auth-node.js +316 -0
  13. package/dist/auth-node.js.map +1 -0
  14. package/dist/auth.d.ts +56 -0
  15. package/dist/auth.d.ts.map +1 -0
  16. package/dist/auth.js +96 -0
  17. package/dist/auth.js.map +1 -0
  18. package/dist/client.d.ts +202 -0
  19. package/dist/client.d.ts.map +1 -0
  20. package/dist/client.js +472 -0
  21. package/dist/client.js.map +1 -0
  22. package/dist/event-emitter.d.ts +38 -0
  23. package/dist/event-emitter.d.ts.map +1 -0
  24. package/dist/event-emitter.js +80 -0
  25. package/dist/event-emitter.js.map +1 -0
  26. package/dist/graphql.d.ts +71 -0
  27. package/dist/graphql.d.ts.map +1 -0
  28. package/dist/graphql.js +487 -0
  29. package/dist/graphql.js.map +1 -0
  30. package/dist/index.d.ts +6 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +11 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/jsonld.d.ts +47 -0
  35. package/dist/jsonld.d.ts.map +1 -0
  36. package/dist/jsonld.js +137 -0
  37. package/dist/jsonld.js.map +1 -0
  38. package/dist/media.d.ts +52 -0
  39. package/dist/media.d.ts.map +1 -0
  40. package/dist/media.js +173 -0
  41. package/dist/media.js.map +1 -0
  42. package/dist/space.d.ts +358 -0
  43. package/dist/space.d.ts.map +1 -0
  44. package/dist/space.js +1121 -0
  45. package/dist/space.js.map +1 -0
  46. package/dist/subscription.d.ts +57 -0
  47. package/dist/subscription.d.ts.map +1 -0
  48. package/dist/subscription.js +296 -0
  49. package/dist/subscription.js.map +1 -0
  50. package/dist/types.d.ts +409 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +6 -0
  53. package/dist/types.js.map +1 -0
  54. package/package.json +65 -0
package/README.md ADDED
@@ -0,0 +1,1070 @@
1
+ # Rool SDK
2
+
3
+ The TypeScript SDK for Rool Spaces, a persistent and collaborative environment for organizing objects and their relationships.
4
+
5
+ Rool Spaces enables you to build applications where AI operates on a structured world model rather than a text conversation. The context for all AI operations is the full object graph, allowing the system to reason about, update, and expand the state of your application directly.
6
+
7
+ Use Rool to programmatically instruct agents to generate content, research topics, or reorganize data. The client manages authentication, real-time synchronization, and media storage, supporting both single-user and multi-user workflows.
8
+
9
+ **Core primitives:**
10
+ - **Objects** — Key-value records with any fields you define
11
+ - **Relations** — Directional links between objects (e.g., `earth` → `orbits` → `sun`)
12
+ - **AI operations** — Create, update, or query objects using natural language and `{{placeholders}}`
13
+
14
+ See [Patterns & Examples](#patterns--examples) for what you can build.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @rool-dev/sdk
20
+ ```
21
+
22
+ ## Configuration
23
+
24
+ ```typescript
25
+ import { RoolClient } from '@rool-dev/sdk';
26
+
27
+ const client = new RoolClient({
28
+ baseUrl: 'https://api.dev.rool.dev',
29
+ });
30
+ ```
31
+
32
+ ### RoolClientConfig
33
+
34
+ ```typescript
35
+ interface RoolClientConfig {
36
+ baseUrl: string; // Base URL (e.g., 'https://api.dev.rool.dev')
37
+ graphqlUrl?: string; // Override GraphQL endpoint (default: {baseUrl}/graphql)
38
+ mediaUrl?: string; // Override media endpoint (default: {baseUrl}/media)
39
+ authUrl?: string; // Override auth endpoint (default: {baseUrl}/auth)
40
+ authProvider?: AuthProvider; // Optional, defaults to browser auth
41
+ }
42
+ ```
43
+
44
+ ### Base URLs
45
+
46
+ | Environment | URL |
47
+ |-------------|-----|
48
+ | Development | `https://api.dev.rool.dev` |
49
+ | Production | `https://api.rool.dev` |
50
+
51
+ ## Quick Start
52
+
53
+ ```typescript
54
+ import { RoolClient } from '@rool-dev/sdk';
55
+
56
+ // Create and initialize client
57
+ const client = new RoolClient({
58
+ baseUrl: 'https://api.dev.rool.dev',
59
+ });
60
+
61
+ // Process auth callbacks if we are returning from the login page
62
+ client.initialize();
63
+
64
+ if (!await client.isAuthenticated()) {
65
+ client.login('My App'); // Redirects to auth page, shows "Sign in to My App"
66
+ }
67
+
68
+ // Open an existing space
69
+ const space = await client.openSpace('abc1234');
70
+
71
+ // Create a new space
72
+ const newSpace = await client.createSpace('My New Space');
73
+
74
+ // Listen for changes (real-time updates are automatic)
75
+ space.on('objectCreated', ({ objectId, object, source }) => {
76
+ console.log('New object:', objectId, object, 'from', source);
77
+ });
78
+
79
+ space.on('linked', ({ sourceId, relation, targetId, source }) => {
80
+ console.log('New link:', sourceId, '->', targetId, `[${relation}]`, 'from', source);
81
+ });
82
+
83
+ // Create objects with AI-generated content.
84
+ // Field names are yours to define — only 'id' is reserved.
85
+ const { object: sun } = await space.createObject({
86
+ data: {
87
+ type: 'star',
88
+ name: 'Sun',
89
+ mass: '{{mass in solar masses}}',
90
+ radius: '{{radius in km}}',
91
+ temperature: '{{surface temperature}}'
92
+ }
93
+ });
94
+
95
+ const { object: earth } = await space.createObject({
96
+ data: {
97
+ type: 'planet',
98
+ name: 'Earth',
99
+ mass: '{{mass in Earth masses}}',
100
+ radius: '{{radius in km}}',
101
+ orbitalPeriod: '{{orbital period in days}}'
102
+ }
103
+ });
104
+
105
+ // Create an undo checkpoint
106
+ await space.checkpoint('before linking');
107
+
108
+ // Link them together
109
+ await space.link(earth.id, 'orbits', sun.id);
110
+
111
+ await space.undo(); // The link created above is now gone
112
+ await space.redo(); // The link is back ...
113
+
114
+ // Stop receiving events for this space and free resources
115
+ space.close();
116
+ ```
117
+
118
+ ## Core Concepts
119
+
120
+ ### Objects & Relations
121
+
122
+ **Objects** are plain key-value records. The `id` field is reserved; everything else is application-defined.
123
+
124
+ ```typescript
125
+ { id: 'abc123', type: 'article', title: 'Hello World', status: 'draft' }
126
+ ```
127
+
128
+ **Relations** connect objects directionally through named links. Each relation name represents a multi-valued reference set on the source object.
129
+
130
+ ```typescript
131
+ await space.link(earth.id, 'orbits', sun.id);
132
+ // Reads as: "earth.orbits includes sun"
133
+
134
+ const orbited = space.getChildren(earth.id, 'orbits'); // [sun] - what earth links TO
135
+ const orbiters = space.getParents(sun.id, 'orbits'); // [earth] - what links TO sun
136
+ ```
137
+
138
+ Relations are indexed and idempotent — creating the same link twice has no effect.
139
+
140
+ **Notes:**
141
+ - Relation names are application-defined strings
142
+ - Relations are not stored in object data fields
143
+ - Only relations created via `link()` participate in traversal (`getParents`, `getChildren`) and indexing
144
+ - Storing object IDs inside regular object fields is possible, but those references are opaque to the system
145
+
146
+ ### AI Placeholder Pattern
147
+
148
+ Use `{{description}}` in field values to have AI generate content:
149
+
150
+ ```typescript
151
+ // Create with AI-generated content
152
+ await space.createObject({
153
+ data: {
154
+ type: 'article',
155
+ headline: '{{catchy headline about coffee}}',
156
+ body: '{{informative paragraph}}'
157
+ },
158
+ prompt: 'Write about specialty coffee brewing'
159
+ });
160
+
161
+ // Update existing content with AI
162
+ await space.updateObject('abc123', {
163
+ prompt: 'Make the body shorter and more casual'
164
+ });
165
+
166
+ // Add new AI-generated field to existing object
167
+ await space.updateObject('abc123', {
168
+ data: { summary: '{{one-sentence summary}}' }
169
+ });
170
+ ```
171
+
172
+ When resolving placeholders, the agent has access to the full object data and the surrounding space context (except for `_`-prefixed fields). Placeholders are instructions, not templates, and do not need to repeat information already present in other fields.
173
+
174
+ Placeholders are resolved by the AI during the mutation and replaced with concrete values. The `{{...}}` syntax is never stored — it only guides the agent while creating or updating the object.
175
+
176
+ ### Checkpoints & Undo/Redo
177
+
178
+ Undo/redo works on **checkpoints**, not individual operations. Call `checkpoint()` before making changes to create a restore point.
179
+
180
+ ```typescript
181
+ // Create a checkpoint before user action
182
+ await space.checkpoint('Delete object');
183
+ await space.deleteObjects([objectId]);
184
+
185
+ // User can now undo back to the checkpoint
186
+ if (await space.canUndo()) {
187
+ await space.undo(); // Restores the deleted object
188
+ }
189
+
190
+ // Redo reapplies the undone action
191
+ if (await space.canRedo()) {
192
+ await space.redo(); // Deletes the object again
193
+ }
194
+ ```
195
+
196
+ Without a checkpoint, `undo()` has nothing to restore to. Undo always restores the space to the last checkpoint, regardless of how many changes were made since.
197
+
198
+ In collaborative scenarios, conflicting changes (modified by others since your checkpoint) are silently skipped.
199
+
200
+ ### Hidden Fields
201
+
202
+ Fields starting with `_` (e.g., `_ui`, `_cache`) are hidden from AI but otherwise behave like normal fields — they sync in real-time, persist to the server, support undo/redo, and are visible to all users of the Space. Use them for UI state, positions, or other data the AI shouldn't see or modify:
203
+
204
+ ```typescript
205
+ await space.createObject({
206
+ data: {
207
+ title: 'My Article',
208
+ author: "John Doe",
209
+ _ui: { x: 100, y: 200, collapsed: false }
210
+ }
211
+ });
212
+ ```
213
+
214
+ ### Real-time Sync
215
+
216
+ Events fire for both local and remote changes. The `source` field indicates origin:
217
+
218
+ - `local_user` — This client made the change
219
+ - `remote_user` — Another user/client made the change
220
+ - `remote_agent` — AI agent made the change
221
+ - `system` — Resync after error
222
+
223
+ ```typescript
224
+ // All UI updates happen in one place, regardless of change source
225
+ space.on('objectUpdated', ({ objectId, object, source }) => {
226
+ renderObject(objectId, object);
227
+ if (source === 'remote_agent') {
228
+ doLayout(); // AI might have added content
229
+ }
230
+ });
231
+
232
+ // Caller just makes the change - event handler does the UI work
233
+ space.updateObject(objectId, { prompt: 'expand this' });
234
+ ```
235
+
236
+ ### Custom Object IDs
237
+
238
+ By default, `createObject` generates a 6-character alphanumeric ID. Provide your own via `data.id`:
239
+
240
+ ```typescript
241
+ await space.createObject({ data: { id: 'article-42', title: 'The Meaning of Life' } });
242
+ ```
243
+
244
+ **Why use custom IDs?**
245
+ - **Fire-and-forget creation** — Know the ID immediately without awaiting the response. You can create an object and link to it in parallel; the sync happens in the background.
246
+ - **Meaningful IDs** — Use domain-specific IDs like `user-123` or `doc-abc` for easier debugging and external references.
247
+
248
+ ```typescript
249
+ // Fire-and-forget: create and link without waiting
250
+ const id = RoolClient.generateId();
251
+ space.createObject({ data: { id, type: 'note', text: '{{expand this idea}}' } });
252
+ space.link(parentId, 'hasNote', id); // Can link immediately
253
+ ```
254
+
255
+ **Constraints:**
256
+ - Must contain only alphanumeric characters, hyphens (`-`), and underscores (`_`)
257
+ - Must be unique within the space (throws if ID exists)
258
+ - Cannot be changed after creation (immutable)
259
+
260
+ Use `RoolClient.generateId()` when you need an ID before calling `createObject` but don't need it to be meaningful — it gives you a valid random ID without writing your own generator.
261
+
262
+ ## Authentication
263
+
264
+ ### Browser (Default)
265
+
266
+ No configuration needed. Uses localStorage for tokens, redirects to login page.
267
+
268
+ ```typescript
269
+ const client = new RoolClient({ baseUrl: 'https://api.rool.dev' });
270
+ client.initialize(); // Process auth callbacks if this is a callback from the auth page
271
+
272
+ if (!await client.isAuthenticated()) {
273
+ client.login('My App'); // Redirect to the auth page
274
+ }
275
+ ```
276
+
277
+ ### Node.js
278
+
279
+ For CLI tools and scripts. Stores credentials in `~/.config/rool/`, opens browser for login.
280
+
281
+ ```typescript
282
+ import { NodeAuthProvider } from '@rool-dev/sdk/node';
283
+
284
+ const client = new RoolClient({
285
+ baseUrl: 'https://api.rool.dev',
286
+ authProvider: new NodeAuthProvider()
287
+ });
288
+
289
+ if (!await client.isAuthenticated()) {
290
+ await client.login('My CLI Tool'); // Open auth page in system browser, await callback
291
+ }
292
+ ```
293
+
294
+ ### Auth Methods
295
+
296
+ | Method | Description |
297
+ |--------|-------------|
298
+ | `initialize(): boolean` | **Call on app startup if running in browser.** Processes auth callback from URL, sets up token refresh. |
299
+ | `login(appName): void` | Redirect to login page. The app name is displayed on the auth page ("Sign in to {appName}"). |
300
+ | `logout(): void` | Clear tokens and state |
301
+ | `isAuthenticated(): Promise<boolean>` | Check auth status (validates token) |
302
+ | `getToken(): Promise<string \| undefined>` | Get current access token |
303
+ | `getAuthUser(): AuthUser` | Get auth identity from JWT (`{ email, name }`) |
304
+
305
+ ## AI Agent
306
+
307
+ The `prompt()` method is the primary way to invoke the AI agent. The agent has editor-level capabilities — it can create, modify, delete, link, and research — but cannot see or modify `_`-prefixed fields.
308
+
309
+ ```typescript
310
+ const { message, objects } = await space.prompt(
311
+ "Create a topic node for the solar system, then child nodes for each planet."
312
+ );
313
+ console.log(`AI: ${message}`);
314
+ console.log(`Modified ${objects.length} objects:`, objects);
315
+ ```
316
+
317
+ Use `checkpoint()` before prompting to make operations undoable.
318
+
319
+ ### Method Signature
320
+
321
+ ```typescript
322
+ prompt(text: string, options?: PromptOptions): Promise<{ message: string; objects: RoolObject[] }>
323
+ ```
324
+
325
+ Returns a message (the AI's response) and the list of objects that were created or modified.
326
+
327
+ ### Options
328
+
329
+ | Option | Description |
330
+ |--------|-------------|
331
+ | `objectIds` | Limit context to specific objects |
332
+ | `responseSchema` | Request structured JSON instead of text summary |
333
+ | `effort` | Effort level: `'QUICK'`, `'STANDARD'` (default), `'REASONING'`, or `'RESEARCH'` |
334
+ | `ephemeral` | If true, don't record in conversation history (useful for tab completion) |
335
+ | `readOnly` | If true, disable mutation tools (create, update, link, unlink). Use for questions. |
336
+
337
+ ### Effort Levels
338
+
339
+ | Level | Description |
340
+ |-------|-------------|
341
+ | `QUICK` | Fast, lightweight model. Best for simple questions. |
342
+ | `STANDARD` | Default behavior with balanced capabilities. |
343
+ | `REASONING` | Extended reasoning for complex tasks. |
344
+ | `RESEARCH` | Pre-analysis and context gathering (reserved for future use). |
345
+
346
+ ### Examples
347
+
348
+ ```typescript
349
+ // Reorganize and link existing objects
350
+ const { objects } = await space.prompt(
351
+ "Group these notes by topic and create a parent node for each group."
352
+ );
353
+
354
+ // Work with specific objects
355
+ const result = await space.prompt(
356
+ "Summarize these articles",
357
+ { objectIds: ['article-1', 'article-2'] }
358
+ );
359
+
360
+ // Quick question without mutations (fast model + read-only)
361
+ const { message } = await space.prompt(
362
+ "What topics are covered?",
363
+ { effort: 'QUICK', readOnly: true }
364
+ );
365
+
366
+ // Complex analysis with extended reasoning
367
+ await space.prompt(
368
+ "Analyze relationships and reorganize",
369
+ { effort: 'REASONING' }
370
+ );
371
+ ```
372
+
373
+ ### Structured Responses
374
+
375
+ Use `responseSchema` to get structured JSON instead of a text message:
376
+
377
+ ```typescript
378
+ const { message } = await space.prompt("Categorize these items", {
379
+ objectIds: ['item-1', 'item-2', 'item-3'],
380
+ responseSchema: {
381
+ type: 'object',
382
+ properties: {
383
+ categories: {
384
+ type: 'array',
385
+ items: { type: 'string' }
386
+ },
387
+ summary: { type: 'string' }
388
+ }
389
+ }
390
+ });
391
+
392
+ const result = JSON.parse(message);
393
+ console.log(result.categories, result.summary);
394
+ ```
395
+
396
+ ### Context Flow
397
+
398
+ AI operations automatically receive context:
399
+ - **Interaction history** — Previous interactions and their results from this conversation
400
+ - **Recently modified objects** — Objects created or changed recently
401
+ - **Selected objects** — Objects passed via `objectIds` are given primary focus
402
+
403
+ This context flows automatically — no configuration needed. The AI sees enough history to maintain coherent interactions while respecting the `_`-prefixed field hiding rules.
404
+
405
+ ## Collaboration
406
+
407
+ ### Adding Users to a Space
408
+
409
+ To add a user to a space, you need their user ID. Use `searchUser()` to find them by email:
410
+
411
+ ```typescript
412
+ // Find the user by email
413
+ const user = await client.searchUser('colleague@example.com');
414
+ if (!user) {
415
+ throw new Error('User not found');
416
+ }
417
+
418
+ // Add them to the space
419
+ await space.addUser(user.id, 'editor');
420
+ ```
421
+
422
+ ### Roles
423
+
424
+ | Role | Capabilities |
425
+ |------|--------------|
426
+ | `owner` | Full control, can delete space and manage users |
427
+ | `editor` | Can create, modify, delete objects and links |
428
+ | `viewer` | Read-only access |
429
+
430
+ ### Space Collaboration Methods
431
+
432
+ | Method | Description |
433
+ |--------|-------------|
434
+ | `listUsers(): Promise<SpaceMember[]>` | List users with access |
435
+ | `addUser(userId, role): Promise<void>` | Add user to space |
436
+ | `removeUser(userId): Promise<void>` | Remove user from space |
437
+
438
+ ### Client User Methods
439
+
440
+ | Method | Description |
441
+ |--------|-------------|
442
+ | `getCurrentUser(): Promise<CurrentUser>` | Get current Rool user (id, email, name, plan, credits, createdAt, lastActivity, processedAt, storage) |
443
+ | `searchUser(email): Promise<UserResult \| null>` | Find user by exact email address (no partial matching) |
444
+
445
+ ### Real-time Collaboration
446
+
447
+ When multiple users have a space open, changes sync in real-time. The `source` field in events tells you who made the change:
448
+
449
+ ```typescript
450
+ space.on('objectUpdated', ({ objectId, object, source }) => {
451
+ if (source === 'remote_user') {
452
+ // Another user made this change
453
+ showCollaboratorActivity(object);
454
+ }
455
+ });
456
+ ```
457
+
458
+ See [Real-time Sync](#real-time-sync) for more on event sources.
459
+
460
+ ## RoolClient API
461
+
462
+ ### Space Lifecycle
463
+
464
+ | Method | Description |
465
+ |--------|-------------|
466
+ | `listSpaces(): Promise<RoolSpaceInfo[]>` | List available spaces |
467
+ | `openSpace(id, options?): Promise<RoolSpace>` | Open a space for editing. Options: `{ conversationId?: string }` |
468
+ | `createSpace(name?, options?): Promise<RoolSpace>` | Create a new space. Options: `{ conversationId?: string }` |
469
+ | `deleteSpace(id): Promise<void>` | Permanently delete a space (cannot be undone) |
470
+
471
+ ### User Storage
472
+
473
+ Server-side key-value storage for user preferences, UI state, and other persistent data. Replaces browser localStorage with cross-device, server-synced storage.
474
+
475
+ **Features:**
476
+ - Sync reads from local cache (available immediately, even before auth)
477
+ - Automatic sync to server and across tabs/devices via SSE
478
+ - `userStorageChanged` event fires on all changes (local or remote)
479
+ - Total storage limited to 10MB per user
480
+
481
+ | Method | Description |
482
+ |--------|-------------|
483
+ | `getUserStorage<T>(key): T \| undefined` | Get a value (sync, from local cache) |
484
+ | `setUserStorage(key, value): void` | Set a value (updates cache, syncs to server) |
485
+ | `getAllUserStorage(): Record<string, unknown>` | Get all stored data (sync, from local cache) |
486
+
487
+ ```typescript
488
+ // Sync read at startup (before auth completes)
489
+ const theme = client.getUserStorage<string>('theme');
490
+ applyTheme(theme ?? 'light');
491
+
492
+ // Write - updates immediately, syncs to server in background
493
+ client.setUserStorage('theme', 'dark');
494
+ client.setUserStorage('sidebar', { collapsed: true, width: 280 });
495
+
496
+ // Delete a key
497
+ client.setUserStorage('theme', null);
498
+
499
+ // The cache may be stale from a previous session — listen for updates
500
+ // to apply fresh values once sync completes (or when other tabs/devices change values)
501
+ client.on('userStorageChanged', ({ key, value, source }) => {
502
+ // source: 'local' (this client) or 'remote' (server/other client)
503
+ if (key === 'theme') applyTheme(value as string);
504
+ });
505
+ ```
506
+
507
+ ### Utilities
508
+
509
+ | Method | Description |
510
+ |--------|-------------|
511
+ | `RoolClient.generateId(): string` | Generate 6-char alphanumeric ID (static) |
512
+ | `graphql<T>(query, variables?): Promise<T>` | Execute raw GraphQL |
513
+ | `destroy(): void` | Clean up resources |
514
+
515
+ ### Client Events
516
+
517
+ ```typescript
518
+ client.on('authStateChanged', (authenticated: boolean) => void)
519
+ client.on('spaceCreated', (space: RoolSpaceInfo) => void)
520
+ client.on('spaceDeleted', (spaceId: string) => void)
521
+ client.on('spaceRenamed', (spaceId: string, newName: string) => void)
522
+ client.on('userStorageChanged', ({ key, value, source }: UserStorageChangedEvent) => void)
523
+ client.on('connectionStateChanged', (state: 'connected' | 'disconnected' | 'reconnecting') => void)
524
+ client.on('error', (error: Error, context?: string) => void)
525
+ ```
526
+
527
+ ## RoolSpace API
528
+
529
+ Spaces are first-class objects with built-in undo/redo, event emission, and real-time sync.
530
+
531
+ ### Properties
532
+
533
+ | Property | Description |
534
+ |----------|-------------|
535
+ | `id: string` | Space ID |
536
+ | `name: string` | Space name |
537
+ | `role: RoolUserRole` | User's role (`'owner' \| 'editor' \| 'viewer'`) |
538
+ | `userId: string` | Current user's ID |
539
+ | `conversationId: string` | ID for interaction history (tracks AI context). Writable — set to switch conversations. |
540
+ | `isReadOnly(): boolean` | True if viewer role |
541
+
542
+ ### Lifecycle
543
+
544
+ | Method | Description |
545
+ |--------|-------------|
546
+ | `close(): void` | Clean up resources and stop receiving updates |
547
+ | `rename(newName): Promise<void>` | Rename the space |
548
+
549
+ ### Object Operations
550
+
551
+ Objects are plain key/value records. `id` is the only reserved field; everything else is application-defined.
552
+
553
+ | Method | Description |
554
+ |--------|-------------|
555
+ | `getObject(objectId): Promise<RoolObject \| undefined>` | Get object data, or undefined if not found. |
556
+ | `stat(objectId): Promise<RoolObjectStat \| undefined>` | Get object stat (audit info: modifiedAt, modifiedBy, modifiedByName), or undefined if not found. |
557
+ | `findObjects(options): Promise<{ objects, message }>` | Find objects using structured filters and natural language. Results sorted by modifiedAt (desc by default). |
558
+ | `getObjectIds(options?): string[]` | Get all object IDs. Sorted by modifiedAt (desc by default). Options: `{ limit?, order? }`. |
559
+ | `createObject(options): Promise<{ object, message }>` | Create a new object. Returns the object (with AI-filled content) and message. |
560
+ | `updateObject(objectId, options): Promise<{ object, message }>` | Update an existing object. Returns the updated object and message. |
561
+ | `deleteObjects(objectIds): Promise<void>` | Delete objects. Outbound links are removed automatically. |
562
+
563
+ #### createObject / updateObject Options
564
+
565
+ Both methods accept an options object:
566
+
567
+ | Option | Description |
568
+ |--------|-------------|
569
+ | `data` | Object data fields (any key-value pairs). Include `id` to use a custom ID (createObject only). Use `{{placeholder}}` for AI-generated content. Using `null`/`undefined` deletes a field. Fields prefixed with `_` are hidden from AI. Required for `createObject`, optional for `updateObject`. |
570
+ | `prompt` | Natural language instruction for AI to generate or modify content. |
571
+ | `ephemeral` | If true, the operation won't be recorded in conversation history. Useful for transient operations. |
572
+
573
+ #### findObjects Options
574
+
575
+ Find objects using structured filters, semantic matching, and natural language. All queries are executed server-side.
576
+
577
+ | Option | Description |
578
+ |--------|-------------|
579
+ | `where` | Structured field requirements. Static values = exact match. `{{placeholder}}` values = semantic match by AI. |
580
+ | `prompt` | Natural language query for additional filtering. |
581
+ | `limit` | Maximum number of results to return. |
582
+ | `objectIds` | Scope search to specific objects (like `prompt()`). |
583
+ | `order` | Sort order by modifiedAt: `'asc'` or `'desc'` (default: `'desc'`). |
584
+ | `ephemeral` | If true, the query won't be recorded in conversation history. Useful for responsive search. |
585
+
586
+ **Examples:**
587
+
588
+ ```typescript
589
+ // Exact field matching (no AI needed)
590
+ const { objects } = await space.findObjects({
591
+ where: { type: 'article', status: 'published' }
592
+ });
593
+
594
+ // Pure natural language query (AI interprets)
595
+ const { objects, message } = await space.findObjects({
596
+ prompt: 'articles about space exploration published this year'
597
+ });
598
+
599
+ // Semantic field matching with {{...}} placeholders
600
+ const { objects } = await space.findObjects({
601
+ where: {
602
+ type: 'product',
603
+ category: '{{something edible}}' // AI interprets this
604
+ }
605
+ });
606
+
607
+ // Combined: structured + semantic + natural language
608
+ const { objects } = await space.findObjects({
609
+ where: {
610
+ type: 'article',
611
+ topic: '{{related to climate}}'
612
+ },
613
+ prompt: 'that discuss solutions positively',
614
+ limit: 10
615
+ });
616
+ ```
617
+
618
+ The AI has access to the full object graph context (except `_`-prefixed fields) when evaluating queries. The returned `message` explains why objects matched the criteria.
619
+
620
+ ### Relations
621
+
622
+ | Method | Description |
623
+ |--------|-------------|
624
+ | `link(sourceId, relation, targetId): Promise<void>` | Add target to the named relation on source. Reads as "source.relation includes target". |
625
+ | `unlink(sourceId, relation?, targetId?): Promise<boolean>` | Remove relations. Three forms: `(source, relation, target)` removes one link, `(source, relation)` clears all targets for that relation, `(source)` clears all relations on source. |
626
+ | `getParents(objectId, relation?, options?): Promise<RoolObject[]>` | Get objects that link TO this object. Sorted by modifiedAt (desc by default). Options: `{ limit?, order? }`. |
627
+ | `getChildren(objectId, relation?, options?): Promise<RoolObject[]>` | Get objects this object links TO. Sorted by modifiedAt (desc by default). Options: `{ limit?, order? }`. |
628
+
629
+ **Examples:**
630
+
631
+ ```typescript
632
+ // Create relations
633
+ await space.link(earth.id, 'orbits', sun.id);
634
+ await space.link(sun.id, 'hasPlanet', earth.id);
635
+
636
+ // This reads as:
637
+ // "earth.orbits includes sun"
638
+ // "sun.hasPlanet includes earth"
639
+
640
+ // Remove one specific link
641
+ await space.unlink(earth.id, 'orbits', sun.id);
642
+
643
+ // Clear all targets for a relation
644
+ await space.unlink(earth.id, 'orbits');
645
+
646
+ // Clear ALL relations on an object
647
+ await space.unlink(earth.id);
648
+
649
+ // Query relations
650
+ const planets = space.getChildren(sun.id, 'hasPlanet');
651
+ const stars = space.getParents(earth.id, 'orbits');
652
+ ```
653
+
654
+ ### Undo/Redo
655
+
656
+ | Method | Description |
657
+ |--------|-------------|
658
+ | `checkpoint(label?): Promise<string>` | Call before mutations. Saves current state for undo. |
659
+ | `canUndo(): Promise<boolean>` | Check if undo available |
660
+ | `canRedo(): Promise<boolean>` | Check if redo available |
661
+ | `undo(): Promise<boolean>` | Undo to previous checkpoint |
662
+ | `redo(): Promise<boolean>` | Redo undone action |
663
+ | `clearHistory(): Promise<void>` | Clear undo/redo stack |
664
+
665
+ See [Checkpoints & Undo/Redo](#checkpoints--undoredo) for semantics.
666
+
667
+ ### Space Metadata
668
+
669
+ Store arbitrary data alongside the Space without it being part of the graph content (e.g., viewport state, user preferences).
670
+
671
+ | Method | Description |
672
+ |--------|-------------|
673
+ | `setMetadata(key, value): void` | Set space-level metadata |
674
+ | `getMetadata(key): unknown` | Get metadata value, or undefined if key not set |
675
+ | `getAllMetadata(): Record<string, unknown>` | Get all metadata |
676
+
677
+ ### Media
678
+
679
+ Media URLs in object fields are visible to AI. Both uploaded and AI-generated media work the same way — use `fetchMedia` to retrieve them for display.
680
+
681
+ | Method | Description |
682
+ |--------|-------------|
683
+ | `uploadMedia(file): Promise<string>` | Upload file, returns URL |
684
+ | `fetchMedia(url): Promise<MediaResponse>` | Fetch any URL, returns headers and blob() method (adds auth for backend URLs, works for external URLs too) |
685
+ | `deleteMedia(url): Promise<void>` | Delete media file by URL |
686
+ | `listMedia(): Promise<MediaInfo[]>` | List all media with metadata |
687
+
688
+ ```typescript
689
+ // Upload an image
690
+ const url = await space.uploadMedia(file);
691
+ await space.createObject({ data: { title: 'Photo', image: url } });
692
+
693
+ // Or let AI generate one using a placeholder
694
+ await space.createObject({
695
+ data: { title: 'Mascot', image: '{{generate an image of a flying tortoise}}' }
696
+ });
697
+
698
+ // Display media (handles auth automatically)
699
+ const response = await space.fetchMedia(object.image);
700
+ if (response.contentType.startsWith('image/')) {
701
+ const blob = await response.blob();
702
+ img.src = URL.createObjectURL(blob);
703
+ }
704
+ ```
705
+
706
+ ### Import/Export
707
+
708
+ Export space data as JSON-LD for backup, portability, or migration:
709
+
710
+ | Method | Description |
711
+ |--------|-------------|
712
+ | `export(): JsonLdDocument` | Export all objects and relations as JSON-LD |
713
+ | `import(data): Promise<void>` | Import JSON-LD into empty space |
714
+ | `exportArchive(): Promise<Blob>` | Export objects, relations, and media as a zip archive |
715
+ | `importArchive(archive): Promise<void>` | Import from a zip archive into empty space |
716
+
717
+ **Export (data only):**
718
+ ```typescript
719
+ const jsonld = space.export();
720
+ const json = JSON.stringify(jsonld, null, 2);
721
+ ```
722
+
723
+ **Export (with media):**
724
+ ```typescript
725
+ const archive = await space.exportArchive();
726
+ // Save as .zip file
727
+ const url = URL.createObjectURL(archive);
728
+ ```
729
+
730
+ **Import (data only):**
731
+ ```typescript
732
+ // Space must be empty
733
+ const newSpace = await client.createSpace('Imported Data');
734
+ await newSpace.import(jsonld);
735
+ ```
736
+
737
+ **Import (with media):**
738
+ ```typescript
739
+ // Space must be empty
740
+ const newSpace = await client.createSpace('Imported Data');
741
+ await newSpace.importArchive(archiveBlob);
742
+ ```
743
+
744
+ The JSON-LD format follows W3C standards. The archive format bundles `data.json` (JSON-LD with media URLs rewritten to relative paths) and a `media/` folder containing the actual files. Space metadata and interaction history are not included in either format.
745
+
746
+ ### Space Events
747
+
748
+ Semantic events describe what changed. Events fire for both local changes and remote changes.
749
+
750
+ ```typescript
751
+ // source indicates origin:
752
+ // - 'local_user': This client made the change
753
+ // - 'remote_user': Another user/client made the change
754
+ // - 'remote_agent': AI agent made the change
755
+ // - 'system': Resync after error
756
+
757
+ // Object events
758
+ space.on('objectCreated', ({ objectId, object, source }) => void)
759
+ space.on('objectUpdated', ({ objectId, object, source }) => void)
760
+ space.on('objectDeleted', ({ objectId, source }) => void)
761
+
762
+ // Link events
763
+ space.on('linked', ({ sourceId, relation, targetId, source }) => void)
764
+ space.on('unlinked', ({ sourceId, relation, targetId, source }) => void)
765
+
766
+ // Space metadata
767
+ space.on('metadataUpdated', ({ metadata, source }) => void)
768
+
769
+ // Conversation updated (fetch with getInteractions())
770
+ space.on('conversationUpdated', ({ conversationId, source }) => void)
771
+
772
+ // Full state replacement (undo/redo, resync after error)
773
+ space.on('reset', ({ source }) => void)
774
+
775
+ // ConversationId was changed on the space
776
+ space.on('conversationIdChanged', ({ previousConversationId, newConversationId }) => void)
777
+
778
+ // Sync error occurred, space resynced from server
779
+ space.on('syncError', (error: Error) => void)
780
+ ```
781
+
782
+ ### Error Handling
783
+
784
+ AI operations may fail due to rate limiting or other transient errors. Check `error.message` for user-friendly error text:
785
+
786
+ ```typescript
787
+ try {
788
+ await space.updateObject(objectId, { prompt: 'expand this' });
789
+ } catch (error) {
790
+ if (error.message.includes('temporarily unavailable')) {
791
+ showToast('Service busy, please try again in a moment');
792
+ } else {
793
+ showToast(error.message);
794
+ }
795
+ }
796
+ ```
797
+
798
+ ### Internal / Advanced
799
+
800
+ | Method | Description |
801
+ |--------|-------------|
802
+ | `getData(): RoolSpaceData` | Get full space data (internal format) |
803
+
804
+ ## Interaction History
805
+
806
+ Each `RoolSpace` instance has a `conversationId` that tracks interaction history for that space. The history records all meaningful interactions (prompts, object changes, links) as self-contained entries, each capturing the request and its result. History is stored in the space data itself and syncs in real-time to all clients.
807
+
808
+ ### What the AI Receives
809
+
810
+ AI operations (`prompt`, `createObject`, `updateObject`, `findObjects`) automatically receive:
811
+
812
+ - **Interaction history** — Previous interactions and their results from this conversation
813
+ - **Recently modified objects** — Objects in the space recently created or changed
814
+ - **Selected objects** — Objects passed via `objectIds` are given primary focus
815
+
816
+ This context flows automatically — no configuration needed. The AI sees enough history to maintain coherent interactions while respecting the `_`-prefixed field hiding rules.
817
+
818
+ ### Accessing History
819
+
820
+ ```typescript
821
+ // Get interactions for the current conversationId
822
+ const interactions = space.getInteractions();
823
+ // Returns: Interaction[]
824
+
825
+ // Get interactions for a specific conversation ID
826
+ const interactions = space.getInteractionsById('other-conversation-id');
827
+ // Returns: Interaction[]
828
+
829
+ // List all conversation IDs that have interactions
830
+ const conversationIds = space.getConversationIds();
831
+ // Returns: string[]
832
+ ```
833
+
834
+ ### Conversation Access Methods
835
+
836
+ | Method | Description |
837
+ |--------|-------------|
838
+ | `getInteractions(): Interaction[]` | Get interactions for the current conversationId |
839
+ | `getInteractionsById(id): Interaction[]` | Get interactions for a specific conversation ID |
840
+ | `getConversationIds(): string[]` | List all conversation IDs that have conversations |
841
+ | `deleteConversation(conversationId?): Promise<void>` | Delete a conversation and its history. Defaults to current conversation. |
842
+ | `renameConversation(id, name): Promise<void>` | Rename a conversation. Creates it if it doesn't exist. |
843
+ | `listConversations(): Promise<ConversationInfo[]>` | List all conversations with summary info. |
844
+
845
+ ### Listening for Updates
846
+
847
+ ```typescript
848
+ space.on('conversationUpdated', ({ conversationId, source }) => {
849
+ // Conversation changed - refresh if needed
850
+ const interactions = space.getInteractions();
851
+ renderInteractions(interactions);
852
+ });
853
+ ```
854
+
855
+ ### Conversation Isolation
856
+
857
+ By default, each call to `openSpace()` or `createSpace()` generates a new `conversationId`. This means:
858
+ - Opening a space twice gives you two independent AI conversation histories
859
+ - Closing and reopening a space starts fresh
860
+
861
+ ### Switching Conversations
862
+
863
+ Set `conversationId` to switch conversations without reopening the space:
864
+
865
+ ```typescript
866
+ const space = await client.openSpace('abc123');
867
+
868
+ // User clicks "Research" thread in sidebar
869
+ space.conversationId = 'research-thread';
870
+ await space.prompt("Analyze this data");
871
+
872
+ // User clicks "Main" thread
873
+ space.conversationId = 'main-thread';
874
+ await space.prompt("Summarize findings");
875
+
876
+ // Listen for conversation switches
877
+ space.on('conversationIdChanged', ({ previousConversationId, newConversationId }) => {
878
+ // Re-render chat UI with new conversation's history
879
+ renderChat(space.getInteractions());
880
+ });
881
+ ```
882
+
883
+ ### Resuming Conversations
884
+
885
+ Pass a `conversationId` at open time to start with a specific conversation:
886
+
887
+ ```typescript
888
+ // Resume a known conversation when opening
889
+ const space = await client.openSpace('abc123', { conversationId: 'research-thread' });
890
+ ```
891
+
892
+ **Use cases:**
893
+ - **Page refresh** — Store `conversationId` in localStorage to maintain context across reloads
894
+ - **Multiple conversations** — Switch between different conversation contexts using the setter
895
+ - **Collaborative conversations** — Share a `conversationId` between users to enable shared AI conversation history
896
+
897
+ **Tip:** Use the user's id as `conversationId` to share context across tabs/devices, or a fixed string like `'shared'` to share context across all users.
898
+
899
+ Note: Interaction history is truncated to the most recent 50 entries to manage space size.
900
+
901
+ ### The ai Field
902
+
903
+ The `ai` field in interactions distinguishes AI-generated responses from synthetic confirmations:
904
+ - `ai: true` — AI processed this operation (prompt, or createObject/updateObject with placeholders)
905
+ - `ai: false` — System confirmation only (e.g., "Linked X to Y", "Created object abc123")
906
+
907
+ ### Tool Calls
908
+
909
+ The `toolCalls` array captures what the AI agent did during execution. Use it to build responsive UIs that show progress while the agent works — the `conversationUpdated` event fires as each tool completes, letting you display status updates or hints in real-time.
910
+
911
+ ## Data Types
912
+
913
+ ### Space Data
914
+
915
+ ```typescript
916
+ // RoolObject represents the object data you work with
917
+ // Always contains `id`, plus any additional fields
918
+ // Fields prefixed with _ are hidden from AI
919
+ interface RoolObject {
920
+ id: string;
921
+ [key: string]: unknown;
922
+ }
923
+
924
+ // Object stat - audit information returned by space.stat()
925
+ interface RoolObjectStat {
926
+ modifiedAt: number;
927
+ modifiedBy: string;
928
+ modifiedByName: string | null;
929
+ }
930
+
931
+ // Conversation container with metadata
932
+ interface Conversation {
933
+ name?: string; // Conversation name (optional)
934
+ createdAt?: number; // Timestamp when conversation was created
935
+ interactions: Interaction[]; // Interaction history
936
+ }
937
+
938
+ // Conversation summary info (returned by listConversations)
939
+ interface ConversationInfo {
940
+ id: string;
941
+ name: string | null;
942
+ createdAt: number | null;
943
+ interactionCount: number;
944
+ }
945
+
946
+ // Internal space data structure
947
+ interface RoolSpaceData {
948
+ version: number; // Monotonically increasing version for sync consistency
949
+ objects: Record<string, RoolObjectEntry>;
950
+ meta: Record<string, unknown>; // Space-level metadata
951
+ conversations?: Record<string, Conversation>; // Conversations keyed by conversationId
952
+ }
953
+
954
+ // Full stored object structure (for advanced use with getData())
955
+ interface RoolObjectEntry {
956
+ links: Record<string, string[]>; // relation -> [targetId1, targetId2, ...]
957
+ data: RoolObject; // The actual object data
958
+ modifiedAt: number; // Timestamp of last modification
959
+ modifiedBy: string; // User ID who last modified
960
+ modifiedByName: string | null; // Display name at time of modification
961
+ }
962
+ ```
963
+
964
+ ### Interaction Types
965
+
966
+ ```typescript
967
+ interface ToolCall {
968
+ name: string; // Tool name (e.g., "create_object", "link", "search_web")
969
+ input: unknown; // Arguments passed to the tool
970
+ result: string; // Truncated result (max 500 chars)
971
+ }
972
+
973
+ interface Interaction {
974
+ id: string; // Unique ID for this interaction
975
+ timestamp: number;
976
+ userId: string; // Who performed this interaction
977
+ userName?: string | null; // Display name at time of interaction
978
+ operation: 'prompt' | 'createObject' | 'updateObject' | 'link' | 'unlink' | 'deleteObjects';
979
+ input: string; // What the user did: prompt text or action description
980
+ output: string | null; // Result: AI response or confirmation message (null while in-progress)
981
+ ai: boolean; // Whether AI was invoked (vs synthetic confirmation)
982
+ modifiedObjectIds: string[]; // Objects affected by this interaction
983
+ toolCalls: ToolCall[]; // Tools called during this interaction (for AI prompts)
984
+ }
985
+ ```
986
+
987
+ ### Info Types
988
+
989
+ ```typescript
990
+ type RoolUserRole = 'owner' | 'editor' | 'viewer';
991
+
992
+ interface RoolSpaceInfo { id: string; name: string; role: RoolUserRole; ownerId: string; size: number; createdAt: string; updatedAt: string; }
993
+ interface SpaceMember { id: string; email: string; role: RoolUserRole; }
994
+ interface UserResult { id: string; email: string; name: string | null; }
995
+ interface CurrentUser { id: string; email: string; name: string | null; plan: string; creditsBalance: number; createdAt: string; lastActivity: string; processedAt: string; storage: Record<string, unknown>; }
996
+ interface MediaInfo { uuid: string; url: string; contentType: string; size: number; createdAt: string; }
997
+ interface MediaResponse { contentType: string; size: number | null; blob(): Promise<Blob>; }
998
+ type ChangeSource = 'local_user' | 'remote_user' | 'remote_agent' | 'system';
999
+ ```
1000
+
1001
+ ### Prompt Options
1002
+
1003
+ ```typescript
1004
+ type PromptEffort = 'QUICK' | 'STANDARD' | 'REASONING' | 'RESEARCH';
1005
+
1006
+ interface PromptOptions {
1007
+ objectIds?: string[]; // Scope to specific objects
1008
+ responseSchema?: Record<string, unknown>;
1009
+ effort?: PromptEffort; // Effort level (default: 'STANDARD')
1010
+ ephemeral?: boolean; // Don't record in conversation history
1011
+ readOnly?: boolean; // Disable mutation tools (default: false)
1012
+ }
1013
+ ```
1014
+
1015
+ ## Patterns & Examples
1016
+
1017
+ A Rool Space is a persistent, shared world model. Applications project different interaction patterns onto the same core primitives:
1018
+
1019
+ - **Objects and relations** store durable state
1020
+ - **Interaction history** tracks what happened (requests, results, modified objects)
1021
+ - **Events** describe what changed in real-time
1022
+
1023
+ Below are a few representative patterns.
1024
+
1025
+ ### Chat With Generated Artifacts
1026
+
1027
+ - **Space**: documents, notes, images, tasks as objects
1028
+ - **Interaction history**: prompts and AI responses stored in space, synced across clients
1029
+ - **UI**: renders interactions from `getInteractions()` as chat; derives artifact lists from object events
1030
+
1031
+ **Pattern**
1032
+ - Interaction history syncs in real-time; UI renders entries as chat bubbles
1033
+ - Artifacts are persistent objects
1034
+ - Listen to `conversationUpdated` to update chat UI
1035
+ - Selecting objects defines the AI working set via `objectIds`
1036
+
1037
+ ### Multi-User World / Text Adventure
1038
+
1039
+ - **Space**: rooms, items, NPCs, players as objects
1040
+ - **Relations**: navigation, containment, location
1041
+ - **Conversation**: player commands and narrative continuity
1042
+
1043
+ **Pattern**
1044
+ - The space is the shared world state
1045
+ - Objects can be created dynamically as the world expands
1046
+ - AI generates descriptions and events using `{{placeholders}}`
1047
+
1048
+ ### Collaborative Knowledge Graph
1049
+
1050
+ - **Space**: concepts, sources, hypotheses as objects
1051
+ - **Relations**: semantic links between them
1052
+ - **Conversation**: exploratory analysis and questioning
1053
+
1054
+ **Pattern**
1055
+ - Graph structure lives in relations
1056
+ - AI operates on selected subgraphs via `objectIds`
1057
+ - Analysis results are stored; reasoning steps are transient
1058
+
1059
+ ### Common Design Invariants
1060
+
1061
+ - Durable content lives in space objects and relations
1062
+ - Interaction history lives in space conversations (persistent, synced, truncated to 50 entries)
1063
+ - UI state lives in the client, space metadata, or `_`-prefixed fields
1064
+ - AI focus is controlled by object selection, not by replaying history
1065
+
1066
+ ## License
1067
+
1068
+ This client is intended for use with the Rool platform and requires an account. Access is currently by invitation.
1069
+
1070
+ Proprietary — © Lightpost One. All rights reserved.