@asaidimu/utils-workspace 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Saidimu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,443 @@
1
+ # @asaidimu/utils-workspace
2
+
3
+ Content-addressed workspace and conversation management for AI applications. Handles roles, preferences, context, transcripts, and binary blobs with a unified storage model.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@asaidimu/utils-workspace.svg)](https://www.npmjs.com/package/@asaidimu/utils-workspace)
6
+ [![license](https://img.shields.io/npm/l/@asaidimu/utils-workspace.svg)](LICENSE)
7
+ [![build status](https://img.shields.io/github/actions/workflow/status/asaidimu/utils-workspace/ci.yml)](https://github.com/asaidimu/utils-workspace/actions)
8
+ [![typescript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
9
+
10
+ ## Table of Contents
11
+
12
+ - [Overview & Features](#overview--features)
13
+ - [Installation & Setup](#installation--setup)
14
+ - [Usage Documentation](#usage-documentation)
15
+ - [Project Architecture](#project-architecture)
16
+ - [Development & Contributing](#development--contributing)
17
+ - [Additional Information](#additional-information)
18
+
19
+ ---
20
+
21
+ ## Overview & Features
22
+
23
+ **@asaidimu/utils-workspace** provides a complete solution for managing conversational state in AI applications. Built on a content-addressed storage model, it handles everything from role definitions and user preferences to session transcripts and binary blobs. The library separates concerns between index state (fast, always in memory) and content storage (asynchronous, backend-agnostic), enabling efficient operation in both browser and Node.js environments.
24
+
25
+ The system implements a command-reducer pattern where state mutations are expressed as commands that produce patches. This design enables optimistic UI updates, offline-first operation, and easy persistence snapshots. Blob management uses SHA-256 content addressing for automatic deduplication, ref counting, and remote ID tracking across providers like Anthropic or OpenAI.
26
+
27
+ ### Key Features
28
+
29
+ - **Content-Addressed Blob Storage** — SHA-256 based deduplication, reference counting, and atomic storage with IndexedDB or memory backends
30
+ - **Turn-Based Transcripts** — Versioned conversation trees with branching, editing, and subtree deletion
31
+ - **Rich Content Blocks** — Support for text, images, documents, tool use, and thinking blocks with token estimation
32
+ - **Preference & Context Management** — Conflict resolution with timestamps, topic-based filtering, and relevance scoring with freshness decay
33
+ - **Prompt Building** — Token budget allocation, transcript summarization, and context ranking that produces provider-agnostic prompts
34
+ - **Multiple Storage Backends** — IndexedDB for browsers, MemoryStorage for testing and server-side use
35
+ - **TypeScript First** — Full type definitions with strict mode compatibility
36
+ - **Offline-Ready** — Dirty buffer management with configurable flush strategies and optimistic updates
37
+
38
+ ---
39
+
40
+ ## Installation & Setup
41
+
42
+ ### Prerequisites
43
+
44
+ - Node.js 18+ (for server-side usage) or modern browser with Web Crypto API support
45
+ - TypeScript 5.0+ (optional, for type safety)
46
+
47
+ ### Installation
48
+
49
+ ```bash
50
+ npm install @asaidimu/utils-workspace
51
+ ```
52
+
53
+ or with yarn:
54
+
55
+ ```bash
56
+ yarn add @asaidimu/utils-workspace
57
+ ```
58
+
59
+ ### Basic Setup
60
+
61
+ ```typescript
62
+ import {
63
+ IndexedDBStorage,
64
+ ContentStore,
65
+ WorkspaceManager,
66
+ PromptBuilder,
67
+ MemoryBlobStorage,
68
+ BlobStore
69
+ merge,
70
+ } from '@asaidimu/utils-workspace';
71
+
72
+ // Initialize content storage (IndexedDB for persistence)
73
+ const contentBackend = new IndexedDBStorage({ dbName: 'my-workspace' });
74
+ await contentBackend.open();
75
+
76
+ // Initialize blob storage (memory for this example, IndexedDB also available)
77
+ const blobBackend = new MemoryBlobStorage();
78
+ const blobStore = new BlobStore(blobBackend);
79
+ await blobStore.init();
80
+
81
+ // Create content store with default configuration
82
+ const contentStore = new ContentStore(contentBackend);
83
+
84
+ // Create workspace manager
85
+ const manager = new WorkspaceManager(contentStore);
86
+
87
+ // Initialize empty index state
88
+ const emptyState = {
89
+ format: 'aiworkspace/4.0',
90
+ workspace: {
91
+ id: 'ws-1',
92
+ settings: { language: 'en', defaultRole: 'assistant' },
93
+ project: { name: 'My Project', owner: 'user' },
94
+ indexes: { sessions: {}, roles: {}, preferences: {}, context: {}, topics: {} }
95
+ },
96
+ roles: {},
97
+ blobs: {},
98
+ preferences: {},
99
+ context: {},
100
+ sessions: {}
101
+ };
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Usage Documentation
107
+
108
+ ### Basic Usage
109
+
110
+ Create a role, session, and add a turn:
111
+
112
+ ```typescript
113
+ let indexState = emptyState;
114
+
115
+ // 1. Add a role
116
+ const addRoleCmd = {
117
+ type: 'role:add' as const,
118
+ timestamp: new Date().toISOString(),
119
+ payload: {
120
+ id: 'role-1',
121
+ name: 'assistant',
122
+ label: 'Assistant',
123
+ persona: 'You are a helpful assistant.',
124
+ preferences: []
125
+ }
126
+ };
127
+
128
+ const roleResult = await manager.dispatch(indexState, addRoleCmd);
129
+ if (roleResult.ok) {
130
+ // Merge the patch into your state
131
+ indexState = merge(indexState, roleResult.value) ;
132
+ }
133
+
134
+ // 2. Create a session
135
+ const createSessionCmd = {
136
+ type: 'session:create' as const,
137
+ timestamp: new Date().toISOString(),
138
+ payload: {
139
+ id: 'session-1',
140
+ label: 'My First Conversation',
141
+ role: 'assistant',
142
+ topics: ['general'],
143
+ preferences: []
144
+ }
145
+ };
146
+
147
+ const sessionResult = await manager.dispatch(indexState, createSessionCmd);
148
+ if (sessionResult.ok) {
149
+ indexState = merge(indexState, sessionResult.value) ;
150
+ }
151
+
152
+ // 3. Activate the session (enables dirty buffer for performance)
153
+ await manager.activateSession(indexState, 'session-1');
154
+
155
+ // 4. Add a turn
156
+ const addTurnCmd = {
157
+ type: 'turn:add' as const,
158
+ timestamp: new Date().toISOString(),
159
+ payload: {
160
+ sessionId: 'session-1',
161
+ turn: {
162
+ id: 'turn-1',
163
+ version: 0,
164
+ role: 'user',
165
+ blocks: [{ type: 'text', text: 'Hello, how are you?' }],
166
+ timestamp: new Date().toISOString(),
167
+ roleSnapshot: 'assistant',
168
+ parent: null
169
+ }
170
+ }
171
+ };
172
+
173
+ await manager.dispatch(indexState, addTurnCmd);
174
+ await manager.flush(); // Persist dirty buffer to storage
175
+ ```
176
+
177
+ ### Registering and Using Blobs
178
+
179
+ ```typescript
180
+ // Register a blob (e.g., an image or document)
181
+ const imageData = new TextEncoder().encode('fake-image-data');
182
+ const blobResult = await blobStore.register(imageData, 'image/jpeg', 'photo.jpg');
183
+
184
+ if (blobResult.ok) {
185
+ const blobRef = blobResult.value;
186
+
187
+ // Use the blob in a turn
188
+ const turnWithBlob = {
189
+ id: 'turn-2',
190
+ version: 0,
191
+ role: 'user',
192
+ blocks: [
193
+ { type: 'text', text: 'What is in this image?' },
194
+ { type: 'image', blob: blobRef }
195
+ ],
196
+ timestamp: new Date().toISOString(),
197
+ roleSnapshot: 'assistant',
198
+ parent: null
199
+ };
200
+
201
+ await manager.dispatch(indexState, {
202
+ type: 'turn:add',
203
+ timestamp: new Date().toISOString(),
204
+ payload: { sessionId: 'session-1', turn: turnWithBlob }
205
+ });
206
+ }
207
+ ```
208
+
209
+ ### Building Prompts
210
+
211
+ ```typescript
212
+ // Resolve the session to get effective preferences, context, and transcript
213
+ const resolved = await manager.resolveSession(indexState, 'session-1');
214
+
215
+ if (resolved.ok) {
216
+ // Resolve all blobs in the session
217
+ const refs = resolved.value.transcript
218
+ .flatMap(t => t.blocks.filter(b => b.type === 'image' || b.type === 'document'))
219
+ .map(b => b.blob);
220
+
221
+ const { resolved: resolvedBlobs, errors } = await blobStore.resolveRefs(refs, null);
222
+
223
+ // Build the prompt
224
+ const builder = new PromptBuilder();
225
+ const prompt = await builder.build(resolved.value, {
226
+ resolvedBlobs,
227
+ tokenBudget: { total: 4000 }
228
+ });
229
+
230
+ console.log(prompt.system); // System prompt with persona, preferences, context
231
+ console.log(prompt.turns); // Conversation turns with resolved blobs
232
+ }
233
+ ```
234
+
235
+ ### Configuration
236
+
237
+ **ContentStore Configuration**
238
+
239
+ ```typescript
240
+ const contentStore = new ContentStore(backend, {
241
+ cache: {
242
+ roles: 10, // Max roles in LRU cache
243
+ preferences: 50, // Max preferences in LRU cache
244
+ contextEntries: 20, // Max context entries in LRU cache
245
+ transcriptWindows: 3 // Max transcript windows in LRU cache
246
+ },
247
+ flush: {
248
+ maxBufferSize: 10, // Auto-flush after 10 turns
249
+ flushIntervalMs: 30000 // Auto-flush every 30 seconds
250
+ },
251
+ transcriptWindowSize: 20 // Default turns per window
252
+ });
253
+ ```
254
+
255
+ **BlobStore Configuration**
256
+
257
+ ```typescript
258
+ const blobStore = new BlobStore(backend, {
259
+ eagerEviction: false // When false, blobs with refCount 0 persist until gc()
260
+ });
261
+ ```
262
+
263
+ **IndexedDB Configuration**
264
+
265
+ ```typescript
266
+ const contentBackend = new IndexedDBStorage({ dbName: 'custom-db-name' });
267
+ const blobBackend = new IndexedDBBlobStorage({ dbName: 'custom-blob-db' });
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Project Architecture
273
+
274
+ ### Core Components
275
+
276
+ **IndexState** — The in-memory representation of the workspace. Contains summaries of all entities (roles, preferences, contexts, sessions, blobs) and is designed for fast reads. State mutations produce patches that can be merged optimistically.
277
+
278
+ **ContentStore** — Manages asynchronous access to persisted content. Implements LRU caching with pinning for active sessions, dirty buffer management for turns, and maintains the distinction between index state and full content.
279
+
280
+ **WorkspaceManager** — The public facade that coordinates between commands, reducer, and content persistence. Handles side effects (saving to storage) while keeping the reducer pure.
281
+
282
+ **BlobStore** — Content-addressed blob management with SHA-256 deduplication, reference counting, remote ID tracking, and resolution between inline and remote blob references.
283
+
284
+ **PromptBuilder** — Transforms an EffectiveSession into a provider-agnostic prompt structure with token budgeting, preference conflict resolution, context ranking, and optional summarization.
285
+
286
+ **Storage Backends** — Pluggable storage layers:
287
+ - **MemoryStorage** — In-memory implementation for testing
288
+ - **IndexedDBStorage** — Persistent browser storage for content
289
+ - **MemoryBlobStorage** — In-memory blob storage
290
+ - **IndexedDBBlobStorage** — Persistent browser storage for blobs
291
+
292
+ ### Data Flow
293
+
294
+ ```
295
+ Command → WorkspaceManager.dispatch()
296
+
297
+ WorkspaceReducer (pure patch generation)
298
+
299
+ ContentStore (side effects: save roles, preferences, turns, etc.)
300
+
301
+ Storage Backend (IndexedDB / Memory)
302
+ ```
303
+
304
+ For resolution and prompt building:
305
+
306
+ ```
307
+ IndexState + SessionId → ContentStore.resolveSession()
308
+ ↓ (async content loading)
309
+ EffectiveSession → BlobStore.resolveRefs()
310
+ ↓ (blob resolution)
311
+ PromptBuilder.build()
312
+ ↓ (token budgeting, summarization)
313
+ Prompt (system, turns, warnings)
314
+ ```
315
+
316
+ ### Extension Points
317
+
318
+ **Custom Storage Backends** — Implement the `ContentStorage` or `BlobStorage` interfaces to support alternative persistence (SQLite, cloud storage, etc.)
319
+
320
+ **Summarizer** — Inject a custom summarizer into `PromptBuilder` for transcript compression:
321
+
322
+ ```typescript
323
+ interface Summarizer {
324
+ summarize(transcript: Turn[], tokenBudget: number): Promise<{
325
+ summary: string;
326
+ remaining: Turn[];
327
+ }>;
328
+ }
329
+
330
+ const builder = new PromptBuilder(myCustomSummarizer);
331
+ ```
332
+
333
+ **Token Estimators** — Override the default text estimator for provider-specific token counting:
334
+
335
+ ```typescript
336
+ const prompt = await builder.build(session, {
337
+ tokenBudget: {
338
+ total: 8000,
339
+ estimator: (text) => myProviderTokenCounter.count(text)
340
+ }
341
+ });
342
+ ```
343
+
344
+ ---
345
+
346
+ ## Development & Contributing
347
+
348
+ ### Development Setup
349
+
350
+ 1. Clone the repository:
351
+ ```bash
352
+ git clone https://github.com/asaidimu/utils-workspace.git
353
+ cd utils-workspace
354
+ ```
355
+
356
+ 2. Install dependencies:
357
+ ```bash
358
+ npm install
359
+ ```
360
+
361
+ 3. Build the project:
362
+ ```bash
363
+ npm run build
364
+ ```
365
+
366
+ ### Available Scripts
367
+
368
+ | Script | Description |
369
+ |--------|-------------|
370
+ | `npm run build` | Compile TypeScript to JavaScript |
371
+ | `npm run test` | Run all tests with Vitest |
372
+ | `npm run test:watch` | Run tests in watch mode |
373
+ | `npm run lint` | Run ESLint |
374
+ | `npm run format` | Format code with Prettier |
375
+ | `npm run typecheck` | Run TypeScript type checking |
376
+
377
+ ### Testing
378
+
379
+ The test suite uses Vitest with fake-indexeddb for browser environment simulation. Run tests with:
380
+
381
+ ```bash
382
+ npm run test
383
+ ```
384
+
385
+ Coverage reports can be generated with:
386
+
387
+ ```bash
388
+ npm run test:coverage
389
+ ```
390
+
391
+ ### Contributing Guidelines
392
+
393
+ 1. **Fork and Branch** — Create a feature branch from `main`
394
+ 2. **Write Tests** — Add tests for new functionality or bug fixes
395
+ 3. **Update Documentation** — Keep README and JSDoc comments current
396
+ 4. **Commit Convention** — Use conventional commits (feat:, fix:, docs:, etc.)
397
+ 5. **Submit PR** — Open a pull request with a clear description of changes
398
+
399
+ ### Issue Reporting
400
+
401
+ - **Bug Reports** — Include minimal reproduction, expected vs actual behavior, and environment details
402
+ - **Feature Requests** — Describe use case and expected API
403
+ - **Security Issues** — Email maintainers directly (see package.json for contact)
404
+
405
+ ---
406
+
407
+ ## Additional Information
408
+
409
+ ### Troubleshooting
410
+
411
+ **Q: Blob bytes not found locally error**
412
+
413
+ Blobs with refCount 0 are eligible for garbage collection. If you need them later, ensure:
414
+ - `eagerEviction: false` in BlobStore config
415
+ - `gc()` is not called before resolution
416
+ - Register the blob again if it was evicted
417
+
418
+ **Q: IndexedDB open fails with "blocked" error**
419
+
420
+ Another browser tab has an open connection to the same database. Close the other tab or wait for it to release the connection.
421
+
422
+ **Q: Turn edits not persisting**
423
+
424
+ Ensure `flush()` is called after batch operations, or configure `flush.maxBufferSize` to auto-flush.
425
+
426
+ **Q: TypeScript errors with custom storage backends**
427
+
428
+ Import the interface types and implement all required methods:
429
+
430
+ ```typescript
431
+ import type { ContentStorage, BlobStorage } from '@asaidimu/utils-workspace';
432
+ ```
433
+
434
+ ### License
435
+
436
+ MIT © [Saidimu](https://github.com/asaidimu)
437
+
438
+ See the [LICENSE](LICENSE) file for details.
439
+
440
+ ---
441
+
442
+ **Related Projects**
443
+ - [@asaidimu/utils-store](https://github.com/asaidimu/utils-store) — Deep merge utilities and store patterns