@resourcexjs/registry 2.3.0 → 2.4.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/README.md CHANGED
@@ -10,52 +10,61 @@ bun add @resourcexjs/registry
10
10
 
11
11
  ## Overview
12
12
 
13
- The `@resourcexjs/registry` package provides a Maven-style registry for storing and resolving resources (local or remote).
13
+ The `@resourcexjs/registry` package provides a Maven-style registry for storing and resolving resources.
14
14
 
15
15
  ### Key Concepts
16
16
 
17
17
  - **Registry**: Interface for resource storage and retrieval
18
- - **LocalRegistry**: Filesystem-based implementation for local storage
19
- - **RemoteRegistry**: HTTP API-based implementation for remote access
20
- - **Well-known discovery**: Auto-discover registry endpoint via `/.well-known/resourcex`
21
- - **Maven-style**: Organized by `domain/path/name.type/version`
18
+ - **DefaultRegistry**: Main implementation combining Storage with type handling
19
+ - **LocalStorage**: Filesystem-based storage for local resources
20
+ - **Storage**: Abstract interface for different storage backends
21
+ - **Well-known discovery**: Auto-discover registry endpoints via `/.well-known/resourcex`
22
+ - **Isolator**: Sandbox execution for resolver code
22
23
 
23
24
  ## Usage
24
25
 
25
26
  ### Create Registry
26
27
 
27
28
  ```typescript
28
- import { createRegistry, discoverRegistry } from "@resourcexjs/registry";
29
+ import { createRegistry } from "@resourcexjs/registry";
29
30
 
30
- // Local registry (default)
31
+ // Default registry (uses LocalStorage at ~/.resourcex)
31
32
  const registry = createRegistry();
32
33
 
33
- // Local registry with custom path
34
+ // Custom local path
34
35
  const registry2 = createRegistry({
35
36
  path: "./my-registry",
36
37
  });
37
38
 
38
- // Local registry with extension types
39
- import { promptType } from "@my-org/types";
39
+ // With custom resource types
40
+ import { bundleResourceType } from "@resourcexjs/type";
41
+ const promptType = await bundleResourceType("./prompt.type.ts");
40
42
 
41
43
  const registry3 = createRegistry({
42
- path: "~/.resourcex",
43
44
  types: [promptType],
44
45
  });
45
46
 
46
- // Remote registry
47
+ // With sandbox isolation
47
48
  const registry4 = createRegistry({
48
- endpoint: "https://registry.deepractice.ai/v1",
49
+ isolator: "srt", // "none" | "srt" | "cloudflare" | "e2b"
50
+ });
51
+
52
+ // With remote mirror
53
+ const registry5 = createRegistry({
54
+ mirror: "https://registry.deepractice.ai/v1",
49
55
  });
50
56
 
51
- // Remote registry with well-known discovery
52
- const endpoint = await discoverRegistry("deepractice.ai");
53
- const registry5 = createRegistry({ endpoint });
57
+ // Server mode with custom storage
58
+ import { LocalStorage } from "@resourcexjs/registry";
59
+
60
+ const registry6 = createRegistry({
61
+ storage: new LocalStorage({ path: "./data" }),
62
+ });
54
63
  ```
55
64
 
56
- ### Link Resource
65
+ ### Add Resource
57
66
 
58
- Link a resource to local registry for development or caching:
67
+ Add a resource to the registry:
59
68
 
60
69
  ```typescript
61
70
  import { loadResource } from "@resourcexjs/loader";
@@ -64,32 +73,59 @@ import { createRegistry } from "@resourcexjs/registry";
64
73
  // Load resource from folder
65
74
  const rxr = await loadResource("./my-prompt");
66
75
 
67
- // Link to registry
76
+ // Add to registry
68
77
  const registry = createRegistry();
69
78
  await registry.add(rxr);
70
79
 
71
- // Now available at: ~/.resourcex/localhost/my-prompt.text@1.0.0/
80
+ // Or add directly from path
81
+ await registry.add("./my-prompt");
82
+
83
+ // Now stored at: ~/.resourcex/local/my-prompt.text/1.0.0/
84
+ ```
85
+
86
+ ### Link Resource (Development)
87
+
88
+ Link creates a symlink for live development changes:
89
+
90
+ ```typescript
91
+ const registry = createRegistry();
92
+
93
+ // Link directory - changes reflect immediately
94
+ await registry.link("./my-prompts/assistant");
72
95
  ```
73
96
 
74
97
  ### Resolve Resource
75
98
 
76
- Retrieve a resource by its locator:
99
+ Retrieve and execute a resource:
77
100
 
78
101
  ```typescript
79
102
  const registry = createRegistry();
80
103
 
81
- // Resolve by full locator
82
- const rxr = await registry.resolve("localhost/my-prompt.text@1.0.0");
104
+ // Resolve returns ResolvedResource with execute()
105
+ const resolved = await registry.resolve("localhost/my-prompt.text@1.0.0");
106
+
107
+ // Execute to get content
108
+ const text = await resolved.execute();
109
+ console.log(text);
110
+
111
+ // Check schema (for types with arguments)
112
+ console.log(resolved.schema); // undefined for text type
113
+ ```
114
+
115
+ ### Get Raw Resource
116
+
117
+ Get the RXR without resolving:
118
+
119
+ ```typescript
120
+ const rxr = await registry.get("localhost/my-prompt.text@1.0.0");
83
121
 
84
122
  console.log(rxr.manifest.name); // "my-prompt"
85
- console.log(await rxr.content.text()); // Content
123
+ console.log(rxr.manifest.type); // "text"
86
124
  ```
87
125
 
88
126
  ### Check Existence
89
127
 
90
128
  ```typescript
91
- const registry = createRegistry();
92
-
93
129
  if (await registry.exists("localhost/my-prompt.text@1.0.0")) {
94
130
  console.log("Resource exists");
95
131
  }
@@ -98,9 +134,39 @@ if (await registry.exists("localhost/my-prompt.text@1.0.0")) {
98
134
  ### Delete Resource
99
135
 
100
136
  ```typescript
137
+ await registry.delete("localhost/my-prompt.text@1.0.0");
138
+ ```
139
+
140
+ ### Search Resources
141
+
142
+ ```typescript
143
+ // List all resources
144
+ const all = await registry.search();
145
+
146
+ // Search by name
147
+ const results = await registry.search({ query: "assistant" });
148
+
149
+ // With pagination
150
+ const page = await registry.search({
151
+ query: "prompt",
152
+ limit: 10,
153
+ offset: 20,
154
+ });
155
+ ```
156
+
157
+ ### Support Custom Types
158
+
159
+ ```typescript
160
+ import { bundleResourceType } from "@resourcexjs/type";
161
+
101
162
  const registry = createRegistry();
102
163
 
103
- await registry.delete("localhost/my-prompt.text@1.0.0");
164
+ // Bundle and add type at runtime
165
+ const promptType = await bundleResourceType("./prompt.type.ts");
166
+ registry.supportType(promptType);
167
+
168
+ // Now can resolve prompt resources
169
+ const resolved = await registry.resolve("localhost/assistant.prompt@1.0.0");
104
170
  ```
105
171
 
106
172
  ## API Reference
@@ -111,207 +177,208 @@ Create a new registry instance.
111
177
 
112
178
  **Parameters:**
113
179
 
114
- - `config?: RegistryConfig`
115
- - `path?: string` - Storage path (default: `~/.resourcex`)
116
- - `types?: ResourceType[]` - Extension types to register globally
180
+ - `config?: ClientRegistryConfig | ServerRegistryConfig`
181
+
182
+ **Client mode (default):**
183
+
184
+ - `path?: string` - Local cache path (default: `~/.resourcex`)
185
+ - `mirror?: string` - Mirror URL for remote fetch
186
+ - `types?: BundledType[]` - Custom resource types
187
+ - `isolator?: IsolatorType` - Sandbox isolation level
188
+
189
+ **Server mode:**
190
+
191
+ - `storage: Storage` - Custom storage implementation
192
+ - `types?: BundledType[]` - Custom resource types
193
+ - `isolator?: IsolatorType` - Sandbox isolation level
117
194
 
118
195
  **Returns**: `Registry`
119
196
 
120
197
  ```typescript
198
+ // Client mode
121
199
  const registry = createRegistry({
122
- path: "./custom-registry",
123
- types: [promptType, toolType],
200
+ path: "~/.resourcex",
201
+ mirror: "https://registry.deepractice.ai/v1",
202
+ types: [promptType],
203
+ isolator: "srt",
124
204
  });
125
- ```
126
205
 
127
- ### Registry Interface
206
+ // Server mode
207
+ const registry = createRegistry({
208
+ storage: new LocalStorage({ path: "./data" }),
209
+ types: [promptType],
210
+ });
211
+ ```
128
212
 
129
- #### `link(resource: RXR): Promise<void>`
213
+ ### `discoverRegistry(domain)`
130
214
 
131
- Link a resource to local registry.
215
+ Discover registry endpoints for a domain via well-known.
132
216
 
133
217
  **Parameters:**
134
218
 
135
- - `resource: RXR` - Complete resource object
219
+ - `domain: string` - Domain to discover (e.g., "deepractice.ai")
220
+
221
+ **Returns**: `Promise<DiscoveryResult>`
136
222
 
137
223
  ```typescript
138
- await registry.add(rxr);
224
+ const result = await discoverRegistry("deepractice.ai");
225
+ // { domain: "deepractice.ai", registries: ["https://..."] }
139
226
  ```
140
227
 
141
- #### `resolve(locator: string): Promise<RXR>`
228
+ ### Registry Interface
142
229
 
143
- Resolve a resource by locator.
230
+ #### `supportType(type: BundledType): void`
144
231
 
145
- **Parameters:**
232
+ Add support for a custom resource type at runtime.
146
233
 
147
- - `locator: string` - Full resource locator
234
+ #### `link(path: string): Promise<void>`
148
235
 
149
- **Returns**: `Promise<RXR>`
236
+ Create a symlink for development. Changes in the source directory are immediately reflected.
150
237
 
151
- **Throws**: `RegistryError` if resource not found
238
+ #### `add(source: string | RXR): Promise<void>`
152
239
 
153
- ```typescript
154
- const rxr = await registry.resolve("localhost/my-prompt.text@1.0.0");
155
- ```
240
+ Add resource to storage. Accepts a folder path or RXR object.
156
241
 
157
- #### `exists(locator: string): Promise<boolean>`
242
+ #### `get(locator: string): Promise<RXR>`
158
243
 
159
- Check if resource exists in registry.
244
+ Get raw RXR by locator without resolving.
160
245
 
161
- **Parameters:**
246
+ #### `resolve<TArgs, TResult>(locator: string): Promise<ResolvedResource<TArgs, TResult>>`
162
247
 
163
- - `locator: string` - Full resource locator
248
+ Resolve resource and return structured result with execute function.
164
249
 
165
- **Returns**: `Promise<boolean>`
250
+ #### `exists(locator: string): Promise<boolean>`
166
251
 
167
- ```typescript
168
- if (await registry.exists("localhost/my-prompt.text@1.0.0")) {
169
- // Resource exists
170
- }
171
- ```
252
+ Check if resource exists.
172
253
 
173
254
  #### `delete(locator: string): Promise<void>`
174
255
 
175
- Delete resource from local registry.
256
+ Delete resource from storage.
176
257
 
177
- **Parameters:**
258
+ #### `search(options?: SearchOptions): Promise<RXL[]>`
178
259
 
179
- - `locator: string` - Full resource locator
260
+ Search for resources.
180
261
 
181
- ```typescript
182
- await registry.delete("localhost/my-prompt.text@1.0.0");
183
- ```
262
+ - `query?: string` - Filter by locator substring
263
+ - `limit?: number` - Max results
264
+ - `offset?: number` - Skip first N results
184
265
 
185
- #### `publish(resource: RXR): Promise<void>`
266
+ ### Storage Interface
186
267
 
187
- Publish resource to remote registry (TODO: not yet implemented).
188
-
189
- #### `search(options?): Promise<RXL[]>`
190
-
191
- Search for resources in local registry.
192
-
193
- **Parameters:**
268
+ ```typescript
269
+ interface Storage {
270
+ readonly type: string;
271
+ get(locator: string): Promise<RXR>;
272
+ put(rxr: RXR): Promise<void>;
273
+ exists(locator: string): Promise<boolean>;
274
+ delete(locator: string): Promise<void>;
275
+ search(options?: SearchOptions): Promise<RXL[]>;
276
+ }
277
+ ```
194
278
 
195
- - `options?: SearchOptions`
196
- - `query?: string` - Filter by locator substring
197
- - `limit?: number` - Max results to return
198
- - `offset?: number` - Skip first N results
279
+ ### LocalStorage
199
280
 
200
- **Returns**: `Promise<RXL[]>` - Array of matching locators
281
+ Filesystem-based storage implementation.
201
282
 
202
283
  ```typescript
203
- // Search by name
204
- const results = await registry.search({ query: "assistant" });
205
-
206
- // With pagination
207
- const page = await registry.search({ query: "prompt", limit: 10, offset: 20 });
284
+ import { LocalStorage } from "@resourcexjs/registry";
208
285
 
209
- // List all resources
210
- const all = await registry.search();
286
+ const storage = new LocalStorage({
287
+ path: "~/.resourcex", // optional, defaults to ~/.resourcex
288
+ });
211
289
  ```
212
290
 
213
291
  ## Storage Structure
214
292
 
215
- Resources are stored in two separate areas:
216
-
217
- - **local/** - Development resources (organized by name.type/version)
218
- - **cache/** - Remote cached resources (organized by domain/path/name.type/version)
293
+ Resources are stored in two areas:
219
294
 
220
295
  ```
221
296
  ~/.resourcex/
222
- ├── local/ # Development area
297
+ ├── local/ # Development resources
223
298
  │ └── {name}.{type}/
224
299
  │ └── {version}/
225
300
  │ ├── manifest.json
226
- │ └── content.tar.gz
301
+ │ └── archive.tar.gz
227
302
 
228
- └── cache/ # Remote cache area
303
+ └── cache/ # Remote cached resources
229
304
  └── {domain}/
230
305
  └── {path}/
231
306
  └── {name}.{type}/
232
307
  └── {version}/
233
308
  ├── manifest.json
234
- └── content.tar.gz
309
+ └── archive.tar.gz
235
310
  ```
236
311
 
237
- ### Example
312
+ ### Resolution Order
238
313
 
239
- For a local development resource `my-prompt.text@1.0.0`:
314
+ 1. **local/** is checked first (development resources)
315
+ 2. **cache/** is checked second (remote cached resources)
316
+ 3. If not found and domain is not localhost, fetches from remote
240
317
 
241
- ```
242
- ~/.resourcex/
243
- └── local/
244
- └── my-prompt.text/
245
- └── 1.0.0/
246
- ├── manifest.json # domain can be "deepractice.ai" or "localhost"
247
- └── content.tar.gz
248
- ```
318
+ ## Remote Fetch Flow
249
319
 
250
- For a cached remote resource `deepractice.ai/prompts/assistant.text@1.0.0`:
320
+ For non-localhost domains:
251
321
 
252
- ```
253
- ~/.resourcex/
254
- └── cache/
255
- └── deepractice.ai/
256
- └── prompts/
257
- └── assistant.text/
258
- └── 1.0.0/
259
- ├── manifest.json
260
- └── content.tar.gz
322
+ 1. Check local storage (cache)
323
+ 2. If mirror configured, try mirror first
324
+ 3. Discover source via `https://{domain}/.well-known/resourcex`
325
+ 4. Fetch from discovered endpoint
326
+ 5. Cache to local storage
327
+
328
+ **Well-known format:**
329
+
330
+ ```json
331
+ {
332
+ "version": "1.0",
333
+ "registries": ["https://registry.example.com/v1"]
334
+ }
261
335
  ```
262
336
 
263
- ### Resolution Order
337
+ ## Middleware
264
338
 
265
- 1. **local/** is checked first (development resources)
266
- 2. **cache/** is checked second (remote cached resources)
339
+ ### RegistryMiddleware
267
340
 
268
- **manifest.json:**
341
+ Base class for creating custom middleware:
269
342
 
270
- ```json
271
- {
272
- "domain": "deepractice.ai",
273
- "path": "prompts",
274
- "name": "assistant",
275
- "type": "prompt",
276
- "version": "1.0.0"
343
+ ```typescript
344
+ import { RegistryMiddleware } from "@resourcexjs/registry";
345
+
346
+ class LoggingMiddleware extends RegistryMiddleware {
347
+ async get(locator: string) {
348
+ console.log("Getting:", locator);
349
+ return this.inner.get(locator);
350
+ }
277
351
  }
278
352
  ```
279
353
 
280
- **content.tar.gz:** Archive containing resource files (managed by TypeHandlerChain)
354
+ ### DomainValidation
281
355
 
282
- ## Extension Types
356
+ Built-in middleware for validating resource domains:
357
+
358
+ ```typescript
359
+ import { withDomainValidation } from "@resourcexjs/registry";
360
+
361
+ const validatedRegistry = withDomainValidation(registry, "deepractice.ai");
362
+
363
+ // Throws if resource.manifest.domain !== "deepractice.ai"
364
+ await validatedRegistry.get("deepractice.ai/assistant.text@1.0.0");
365
+ ```
283
366
 
284
- Register extension types globally when creating registry:
367
+ ## Isolator Types
368
+
369
+ Sandbox isolation for resolver execution:
370
+
371
+ | Type | Description | Latency |
372
+ | -------------- | -------------------------- | ------- |
373
+ | `"none"` | No isolation (development) | ~10ms |
374
+ | `"srt"` | OS-level isolation | ~50ms |
375
+ | `"cloudflare"` | Container isolation | ~100ms |
376
+ | `"e2b"` | MicroVM isolation | ~150ms |
285
377
 
286
378
  ```typescript
287
- import { createRegistry } from "@resourcexjs/registry";
288
- import type { ResourceType } from "@resourcexjs/type";
289
-
290
- const promptType: ResourceType<string> = {
291
- name: "prompt",
292
- description: "AI Prompt template",
293
- serializer: {
294
- async serialize(rxr) {
295
- const text = await rxr.content.text();
296
- return Buffer.from(text, "utf-8");
297
- },
298
- async deserialize(data, manifest) {
299
- // ... implementation
300
- },
301
- },
302
- resolver: {
303
- async resolve(rxr) {
304
- return rxr.content.text();
305
- },
306
- },
307
- };
308
-
309
- // Register when creating registry
310
379
  const registry = createRegistry({
311
- types: [promptType],
380
+ isolator: "srt",
312
381
  });
313
-
314
- // Now can link/resolve prompt resources
315
382
  ```
316
383
 
317
384
  ## Error Handling
@@ -320,28 +387,20 @@ const registry = createRegistry({
320
387
  import { RegistryError } from "@resourcexjs/registry";
321
388
 
322
389
  try {
323
- const rxr = await registry.resolve("localhost/not-exist.text@1.0.0");
390
+ const rxr = await registry.get("localhost/not-exist.text@1.0.0");
324
391
  } catch (error) {
325
392
  if (error instanceof RegistryError) {
326
393
  console.error("Registry error:", error.message);
327
- // "Resource not found: localhost/not-exist.text@1.0.0"
328
394
  }
329
395
  }
330
396
  ```
331
397
 
332
398
  ### Common Errors
333
399
 
334
- **Resource not found:**
335
-
336
- ```
337
- RegistryError: Resource not found: localhost/my-prompt.text@1.0.0
338
- ```
339
-
340
- **Unsupported type:**
341
-
342
- ```
343
- RegistryError: Unsupported resource type 'unknown'
344
- ```
400
+ - `Resource not found: {locator}`
401
+ - `Unsupported resource type: {type}`
402
+ - `Well-known discovery failed for {domain}: {status}`
403
+ - `{storage} is read-only: {operation} not supported`
345
404
 
346
405
  ## Examples
347
406
 
@@ -351,120 +410,37 @@ RegistryError: Unsupported resource type 'unknown'
351
410
  import { loadResource } from "@resourcexjs/loader";
352
411
  import { createRegistry } from "@resourcexjs/registry";
353
412
 
354
- // 1. Load resource from folder
355
- const rxr = await loadResource("./my-prompts/assistant");
356
-
357
- // 2. Create registry
413
+ // 1. Create registry
358
414
  const registry = createRegistry();
359
415
 
360
- // 3. Link to local registry
416
+ // 2. Load and add resource
417
+ const rxr = await loadResource("./my-prompts/assistant");
361
418
  await registry.add(rxr);
362
419
 
363
- // 4. Resolve later
364
- const resolved = await registry.resolve("localhost/assistant.prompt@1.0.0");
365
-
366
- // 5. Use content
367
- const text = await resolved.content.text();
420
+ // 3. Resolve and execute
421
+ const resolved = await registry.resolve("localhost/assistant.text@1.0.0");
422
+ const text = await resolved.execute();
368
423
  console.log(text);
369
424
  ```
370
425
 
371
- ### Versioning
372
-
373
- ```typescript
374
- const registry = createRegistry();
375
-
376
- // Link multiple versions
377
- await registry.add(promptV1); // v1.0.0
378
- await registry.add(promptV2); // v2.0.0
379
- await registry.add(promptV3); // v3.0.0
380
-
381
- // Resolve specific version
382
- const v1 = await registry.resolve("localhost/prompt.text@1.0.0");
383
- const v2 = await registry.resolve("localhost/prompt.text@2.0.0");
384
- const latest = await registry.resolve("localhost/prompt.text@3.0.0");
385
- ```
386
-
387
- ### Custom Storage Path
388
-
389
- ```typescript
390
- // Project-local registry
391
- const registry = createRegistry({
392
- path: "./project-registry",
393
- });
394
-
395
- await registry.add(rxr);
396
- // Stored at: ./project-registry/localhost/...
397
- ```
398
-
399
426
  ### With Custom Types
400
427
 
401
428
  ```typescript
402
- import { promptType, toolType, agentType } from "@my-org/ai-types";
429
+ import { createRegistry } from "@resourcexjs/registry";
430
+ import { bundleResourceType } from "@resourcexjs/type";
431
+
432
+ // Bundle custom type
433
+ const promptType = await bundleResourceType("./prompt.type.ts");
403
434
 
435
+ // Create registry with type
404
436
  const registry = createRegistry({
405
- types: [promptType, toolType, agentType],
437
+ types: [promptType],
406
438
  });
407
439
 
408
- // Now can handle these custom types
409
- await registry.add(promptResource);
410
- await registry.add(toolResource);
411
- await registry.add(agentResource);
412
- ```
413
-
414
- ## Resolution Strategy
415
-
416
- ### LocalRegistry
417
-
418
- 1. **Check local filesystem** (`~/.resourcex` or custom path)
419
- 2. **If not found**: Throw RegistryError
420
- 3. **Return** RXR
421
-
422
- ### RemoteRegistry
423
-
424
- 1. **Fetch from HTTP API** (`GET /resource` + `GET /content`)
425
- 2. **Return** RXR (no local caching)
426
-
427
- ### Future: Hybrid Strategy (TODO)
428
-
429
- 1. Check local cache first
430
- 2. If not found, fetch from remote based on domain
431
- 3. Cache locally
432
- 4. Return RXR
433
-
434
- ## Architecture
435
-
436
- ```
437
- ┌─────────────────────────────────────────┐
438
- │ Registry Interface │
439
- └────────────┬────────────────────────────┘
440
-
441
- ┌────────┴────────┐
442
- │ │
443
- ┌───▼──────────┐ ┌──▼──────────────┐
444
- │LocalRegistry │ │RemoteRegistry │
445
- │(filesystem) │ │(HTTP API) │
446
- └───┬──────────┘ └──┬──────────────┘
447
- │ │
448
- ┌───▼───────┐ ┌──▼──────┐
449
- │ Node.js │ │ fetch │
450
- │ fs module │ │ │
451
- └───────────┘ └─────────┘
452
- │ │
453
- ┌────▼────────────────▼─────┐
454
- │ TypeHandlerChain │
455
- │ (serialization logic) │
456
- └───────────────────────────┘
457
- ```
458
-
459
- ## Type Safety
460
-
461
- All operations are fully typed:
462
-
463
- ```typescript
464
- import type { RXR, Registry } from "@resourcexjs/registry";
465
-
466
- const registry: Registry = createRegistry();
467
- const rxr: RXR = await registry.resolve("localhost/test.text@1.0.0");
440
+ // Add and resolve
441
+ await registry.add("./my-prompt");
442
+ const resolved = await registry.resolve<void, string>("localhost/my-prompt.prompt@1.0.0");
443
+ const text = await resolved.execute();
468
444
  ```
469
445
 
470
446
  ## License