@resourcexjs/server 2.5.1
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 +264 -0
- package/dist/index.d.ts +132 -0
- package/dist/index.js +280 -0
- package/dist/index.js.map +13 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# @resourcexjs/server
|
|
2
|
+
|
|
3
|
+
ResourceX Registry Server - HTTP API server for hosting and serving ResourceX resources.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @resourcexjs/server
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
This package provides three levels of abstraction for building a ResourceX registry server:
|
|
14
|
+
|
|
15
|
+
1. **Hono Server** - Ready-to-use server with all endpoints configured
|
|
16
|
+
2. **Handlers** - Framework-agnostic request handlers for custom integrations
|
|
17
|
+
3. **Protocol** - Type definitions and constants for building clients
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { createRegistryServer } from "@resourcexjs/server";
|
|
23
|
+
|
|
24
|
+
const server = createRegistryServer({
|
|
25
|
+
storagePath: "./data",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Bun
|
|
29
|
+
Bun.serve({ fetch: server.fetch, port: 3000 });
|
|
30
|
+
|
|
31
|
+
// Node.js (with @hono/node-server)
|
|
32
|
+
import { serve } from "@hono/node-server";
|
|
33
|
+
serve({ fetch: server.fetch, port: 3000 });
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
### `createRegistryServer(config?)`
|
|
39
|
+
|
|
40
|
+
Creates a Hono app with all registry endpoints configured.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
interface RegistryServerConfig {
|
|
44
|
+
storagePath?: string; // Default: "./data"
|
|
45
|
+
basePath?: string; // Default: ""
|
|
46
|
+
cors?: boolean; // Default: true
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### `createRegistry(config)`
|
|
51
|
+
|
|
52
|
+
Creates a registry instance for use with handlers.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { createRegistry } from "@resourcexjs/server";
|
|
56
|
+
|
|
57
|
+
const registry = createRegistry({ storagePath: "./data" });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Handlers
|
|
61
|
+
|
|
62
|
+
Framework-agnostic handlers for custom server integrations (Next.js, Express, etc.).
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import {
|
|
66
|
+
handlePublish,
|
|
67
|
+
handleGetResource,
|
|
68
|
+
handleHeadResource,
|
|
69
|
+
handleDeleteResource,
|
|
70
|
+
handleGetContent,
|
|
71
|
+
handleSearch,
|
|
72
|
+
createRegistry,
|
|
73
|
+
} from "@resourcexjs/server";
|
|
74
|
+
|
|
75
|
+
const registry = createRegistry({ storagePath: "./data" });
|
|
76
|
+
|
|
77
|
+
// Next.js Route Handler example
|
|
78
|
+
export async function POST(request: Request) {
|
|
79
|
+
return handlePublish(request, registry);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Handler Functions
|
|
84
|
+
|
|
85
|
+
| Handler | Description |
|
|
86
|
+
| ---------------------------------------------- | -------------------------------------- |
|
|
87
|
+
| `handlePublish(request, registry)` | Process multipart form publish request |
|
|
88
|
+
| `handleGetResource(locator, registry)` | Get resource manifest |
|
|
89
|
+
| `handleHeadResource(locator, registry)` | Check resource existence |
|
|
90
|
+
| `handleDeleteResource(locator, registry)` | Delete a resource |
|
|
91
|
+
| `handleGetContent(locator, registry)` | Download resource archive |
|
|
92
|
+
| `handleSearch(query, limit, offset, registry)` | Search resources |
|
|
93
|
+
|
|
94
|
+
## API Endpoints
|
|
95
|
+
|
|
96
|
+
All endpoints are prefixed with `/api/v1`.
|
|
97
|
+
|
|
98
|
+
### `GET /api/v1/health`
|
|
99
|
+
|
|
100
|
+
Health check endpoint.
|
|
101
|
+
|
|
102
|
+
**Response:**
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{ "status": "ok" }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `POST /api/v1/publish`
|
|
109
|
+
|
|
110
|
+
Publish a resource to the registry.
|
|
111
|
+
|
|
112
|
+
**Content-Type:** `multipart/form-data`
|
|
113
|
+
|
|
114
|
+
**Fields:**
|
|
115
|
+
|
|
116
|
+
- `locator` (string) - Resource locator (e.g., `hello.text@1.0.0`)
|
|
117
|
+
- `manifest` (file) - JSON manifest file
|
|
118
|
+
- `content` (file) - Archive file (tar.gz)
|
|
119
|
+
|
|
120
|
+
**Response (201):**
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{ "locator": "hello.text@1.0.0" }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### `GET /api/v1/resource/:locator`
|
|
127
|
+
|
|
128
|
+
Get resource manifest.
|
|
129
|
+
|
|
130
|
+
**Response (200):**
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"name": "hello",
|
|
135
|
+
"type": "text",
|
|
136
|
+
"tag": "1.0.0",
|
|
137
|
+
"registry": "example.com",
|
|
138
|
+
"path": "prompts"
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `HEAD /api/v1/resource/:locator`
|
|
143
|
+
|
|
144
|
+
Check if resource exists.
|
|
145
|
+
|
|
146
|
+
**Response:** `200` if exists, `404` if not.
|
|
147
|
+
|
|
148
|
+
### `DELETE /api/v1/resource/:locator`
|
|
149
|
+
|
|
150
|
+
Delete a resource.
|
|
151
|
+
|
|
152
|
+
**Response:** `204` on success.
|
|
153
|
+
|
|
154
|
+
### `GET /api/v1/content/:locator`
|
|
155
|
+
|
|
156
|
+
Download resource archive.
|
|
157
|
+
|
|
158
|
+
**Response:** Binary `application/gzip` content.
|
|
159
|
+
|
|
160
|
+
### `GET /api/v1/search`
|
|
161
|
+
|
|
162
|
+
Search for resources.
|
|
163
|
+
|
|
164
|
+
**Query Parameters:**
|
|
165
|
+
|
|
166
|
+
- `q` (string, optional) - Search query
|
|
167
|
+
- `limit` (number, default: 100) - Max results
|
|
168
|
+
- `offset` (number, default: 0) - Pagination offset
|
|
169
|
+
|
|
170
|
+
**Response (200):**
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"results": [
|
|
175
|
+
{
|
|
176
|
+
"locator": "hello.text@1.0.0",
|
|
177
|
+
"name": "hello",
|
|
178
|
+
"type": "text",
|
|
179
|
+
"tag": "1.0.0"
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
"total": 1
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Protocol
|
|
187
|
+
|
|
188
|
+
Type definitions and constants for building clients.
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import {
|
|
192
|
+
API_VERSION, // "v1"
|
|
193
|
+
API_PREFIX, // "/api/v1"
|
|
194
|
+
ENDPOINTS, // { publish, resource, content, search, health }
|
|
195
|
+
CONTENT_TYPES, // { json, binary, formData }
|
|
196
|
+
ERROR_CODES, // Error code constants
|
|
197
|
+
buildResourceUrl, // Build resource URL
|
|
198
|
+
buildContentUrl, // Build content URL
|
|
199
|
+
buildPublishUrl, // Build publish URL
|
|
200
|
+
buildSearchUrl, // Build search URL with params
|
|
201
|
+
} from "@resourcexjs/server";
|
|
202
|
+
|
|
203
|
+
// Types
|
|
204
|
+
import type {
|
|
205
|
+
ManifestData,
|
|
206
|
+
SearchQuery,
|
|
207
|
+
PublishResponse,
|
|
208
|
+
GetResourceResponse,
|
|
209
|
+
SearchResultItem,
|
|
210
|
+
SearchResponse,
|
|
211
|
+
ErrorResponse,
|
|
212
|
+
ErrorCode,
|
|
213
|
+
} from "@resourcexjs/server";
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### URL Builders
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { buildResourceUrl, buildSearchUrl } from "@resourcexjs/server";
|
|
220
|
+
|
|
221
|
+
const url = buildResourceUrl("https://registry.example.com", "hello.text@1.0.0");
|
|
222
|
+
// "https://registry.example.com/api/v1/resource/hello.text%401.0.0"
|
|
223
|
+
|
|
224
|
+
const searchUrl = buildSearchUrl("https://registry.example.com", {
|
|
225
|
+
q: "prompt",
|
|
226
|
+
limit: 10,
|
|
227
|
+
});
|
|
228
|
+
// "https://registry.example.com/api/v1/search?q=prompt&limit=10"
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Error Handling
|
|
232
|
+
|
|
233
|
+
All error responses follow a consistent format:
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"error": "Error message",
|
|
238
|
+
"code": "ERROR_CODE"
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Error Codes
|
|
243
|
+
|
|
244
|
+
| Code | Status | Description |
|
|
245
|
+
| -------------------- | ------ | ----------------------- |
|
|
246
|
+
| `LOCATOR_REQUIRED` | 400 | Missing locator field |
|
|
247
|
+
| `MANIFEST_REQUIRED` | 400 | Missing manifest file |
|
|
248
|
+
| `CONTENT_REQUIRED` | 400 | Missing content file |
|
|
249
|
+
| `INVALID_MANIFEST` | 400 | Invalid manifest format |
|
|
250
|
+
| `INVALID_LOCATOR` | 400 | Invalid locator format |
|
|
251
|
+
| `RESOURCE_NOT_FOUND` | 404 | Resource does not exist |
|
|
252
|
+
| `INTERNAL_ERROR` | 500 | Internal server error |
|
|
253
|
+
|
|
254
|
+
## Re-exports
|
|
255
|
+
|
|
256
|
+
For convenience, this package re-exports commonly used classes:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { LocalRegistry, FileSystemStorage, MemoryStorage } from "@resourcexjs/server";
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
Apache-2.0
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResourceX Registry HTTP API Protocol
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract between ResourceX client and registry server.
|
|
5
|
+
*/
|
|
6
|
+
declare const API_VERSION: string;
|
|
7
|
+
declare const API_PREFIX: string;
|
|
8
|
+
declare const ENDPOINTS: {
|
|
9
|
+
readonly publish: string
|
|
10
|
+
readonly resource: string
|
|
11
|
+
readonly content: string
|
|
12
|
+
readonly search: string
|
|
13
|
+
readonly health: string
|
|
14
|
+
};
|
|
15
|
+
declare const CONTENT_TYPES: {
|
|
16
|
+
readonly json: "application/json"
|
|
17
|
+
readonly binary: "application/gzip"
|
|
18
|
+
readonly formData: "multipart/form-data"
|
|
19
|
+
};
|
|
20
|
+
interface ManifestData {
|
|
21
|
+
registry?: string;
|
|
22
|
+
path?: string;
|
|
23
|
+
name: string;
|
|
24
|
+
type: string;
|
|
25
|
+
tag: string;
|
|
26
|
+
}
|
|
27
|
+
declare const PUBLISH_FIELDS: {
|
|
28
|
+
readonly locator: "locator"
|
|
29
|
+
readonly manifest: "manifest"
|
|
30
|
+
readonly content: "content"
|
|
31
|
+
};
|
|
32
|
+
interface SearchQuery {
|
|
33
|
+
q?: string;
|
|
34
|
+
limit?: number;
|
|
35
|
+
offset?: number;
|
|
36
|
+
}
|
|
37
|
+
interface PublishResponse {
|
|
38
|
+
locator: string;
|
|
39
|
+
}
|
|
40
|
+
type GetResourceResponse = ManifestData;
|
|
41
|
+
interface SearchResultItem {
|
|
42
|
+
locator: string;
|
|
43
|
+
registry?: string;
|
|
44
|
+
path?: string;
|
|
45
|
+
name: string;
|
|
46
|
+
type: string;
|
|
47
|
+
tag: string;
|
|
48
|
+
}
|
|
49
|
+
interface SearchResponse {
|
|
50
|
+
results: SearchResultItem[];
|
|
51
|
+
total?: number;
|
|
52
|
+
}
|
|
53
|
+
interface ErrorResponse {
|
|
54
|
+
error: string;
|
|
55
|
+
code?: string;
|
|
56
|
+
}
|
|
57
|
+
declare const ERROR_CODES: {
|
|
58
|
+
readonly LOCATOR_REQUIRED: "LOCATOR_REQUIRED"
|
|
59
|
+
readonly MANIFEST_REQUIRED: "MANIFEST_REQUIRED"
|
|
60
|
+
readonly CONTENT_REQUIRED: "CONTENT_REQUIRED"
|
|
61
|
+
readonly ARCHIVE_REQUIRED: "ARCHIVE_REQUIRED"
|
|
62
|
+
readonly INVALID_MANIFEST: "INVALID_MANIFEST"
|
|
63
|
+
readonly INVALID_LOCATOR: "INVALID_LOCATOR"
|
|
64
|
+
readonly MISSING_REQUIRED_FIELDS: "MISSING_REQUIRED_FIELDS"
|
|
65
|
+
readonly RESOURCE_NOT_FOUND: "RESOURCE_NOT_FOUND"
|
|
66
|
+
readonly INTERNAL_ERROR: "INTERNAL_ERROR"
|
|
67
|
+
};
|
|
68
|
+
type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
|
69
|
+
declare const ERROR_STATUS: Record<ErrorCode, number>;
|
|
70
|
+
declare function buildResourceUrl(baseUrl: string, locator: string): string;
|
|
71
|
+
declare function buildContentUrl(baseUrl: string, locator: string): string;
|
|
72
|
+
declare function buildPublishUrl(baseUrl: string): string;
|
|
73
|
+
declare function buildSearchUrl(baseUrl: string, params?: {
|
|
74
|
+
q?: string
|
|
75
|
+
limit?: number
|
|
76
|
+
offset?: number
|
|
77
|
+
}): string;
|
|
78
|
+
import { Registry } from "@resourcexjs/registry";
|
|
79
|
+
/**
|
|
80
|
+
* Handle POST /publish
|
|
81
|
+
*/
|
|
82
|
+
declare function handlePublish(request: Request, registry: Registry): Promise<Response>;
|
|
83
|
+
/**
|
|
84
|
+
* Handle GET /resource/:locator
|
|
85
|
+
*/
|
|
86
|
+
declare function handleGetResource(locator: string, registry: Registry): Promise<Response>;
|
|
87
|
+
/**
|
|
88
|
+
* Handle HEAD /resource/:locator
|
|
89
|
+
*/
|
|
90
|
+
declare function handleHeadResource(locator: string, registry: Registry): Promise<Response>;
|
|
91
|
+
/**
|
|
92
|
+
* Handle DELETE /resource/:locator
|
|
93
|
+
*/
|
|
94
|
+
declare function handleDeleteResource(locator: string, registry: Registry): Promise<Response>;
|
|
95
|
+
/**
|
|
96
|
+
* Handle GET /content/:locator
|
|
97
|
+
*/
|
|
98
|
+
declare function handleGetContent(locator: string, registry: Registry): Promise<Response>;
|
|
99
|
+
/**
|
|
100
|
+
* Handle GET /search
|
|
101
|
+
*/
|
|
102
|
+
declare function handleSearch(query: string | undefined, limit: number, offset: number, registry: Registry): Promise<Response>;
|
|
103
|
+
import { Hono } from "hono";
|
|
104
|
+
interface RegistryServerConfig {
|
|
105
|
+
/**
|
|
106
|
+
* Path for storing resources.
|
|
107
|
+
* @default "./data"
|
|
108
|
+
*/
|
|
109
|
+
storagePath?: string;
|
|
110
|
+
/**
|
|
111
|
+
* Base path for API routes.
|
|
112
|
+
* @default ""
|
|
113
|
+
*/
|
|
114
|
+
basePath?: string;
|
|
115
|
+
/**
|
|
116
|
+
* Enable CORS.
|
|
117
|
+
* @default true
|
|
118
|
+
*/
|
|
119
|
+
cors?: boolean;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Create a registry server Hono app.
|
|
123
|
+
*/
|
|
124
|
+
declare function createRegistryServer(config?: RegistryServerConfig): Hono;
|
|
125
|
+
import { LocalRegistry } from "@resourcexjs/registry";
|
|
126
|
+
import { FileSystemStorage, MemoryStorage } from "@resourcexjs/storage";
|
|
127
|
+
import { Registry as Registry2 } from "@resourcexjs/registry";
|
|
128
|
+
interface CreateRegistryConfig {
|
|
129
|
+
storagePath: string;
|
|
130
|
+
}
|
|
131
|
+
declare function createRegistry(config: CreateRegistryConfig): Registry2;
|
|
132
|
+
export { handleSearch, handlePublish, handleHeadResource, handleGetResource, handleGetContent, handleDeleteResource, createRegistryServer, createRegistry, buildSearchUrl, buildResourceUrl, buildPublishUrl, buildContentUrl, SearchResultItem, SearchResponse, SearchQuery, RegistryServerConfig, PublishResponse, PUBLISH_FIELDS, MemoryStorage, ManifestData, LocalRegistry, GetResourceResponse, FileSystemStorage, ErrorResponse, ErrorCode, ERROR_STATUS, ERROR_CODES, ENDPOINTS, CreateRegistryConfig, CONTENT_TYPES, API_VERSION, API_PREFIX };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// src/protocol.ts
|
|
2
|
+
var API_VERSION = "v1";
|
|
3
|
+
var API_PREFIX = `/api/${API_VERSION}`;
|
|
4
|
+
var ENDPOINTS = {
|
|
5
|
+
publish: `${API_PREFIX}/publish`,
|
|
6
|
+
resource: `${API_PREFIX}/resource`,
|
|
7
|
+
content: `${API_PREFIX}/content`,
|
|
8
|
+
search: `${API_PREFIX}/search`,
|
|
9
|
+
health: `${API_PREFIX}/health`
|
|
10
|
+
};
|
|
11
|
+
var CONTENT_TYPES = {
|
|
12
|
+
json: "application/json",
|
|
13
|
+
binary: "application/gzip",
|
|
14
|
+
formData: "multipart/form-data"
|
|
15
|
+
};
|
|
16
|
+
var PUBLISH_FIELDS = {
|
|
17
|
+
locator: "locator",
|
|
18
|
+
manifest: "manifest",
|
|
19
|
+
content: "content"
|
|
20
|
+
};
|
|
21
|
+
var ERROR_CODES = {
|
|
22
|
+
LOCATOR_REQUIRED: "LOCATOR_REQUIRED",
|
|
23
|
+
MANIFEST_REQUIRED: "MANIFEST_REQUIRED",
|
|
24
|
+
CONTENT_REQUIRED: "CONTENT_REQUIRED",
|
|
25
|
+
ARCHIVE_REQUIRED: "ARCHIVE_REQUIRED",
|
|
26
|
+
INVALID_MANIFEST: "INVALID_MANIFEST",
|
|
27
|
+
INVALID_LOCATOR: "INVALID_LOCATOR",
|
|
28
|
+
MISSING_REQUIRED_FIELDS: "MISSING_REQUIRED_FIELDS",
|
|
29
|
+
RESOURCE_NOT_FOUND: "RESOURCE_NOT_FOUND",
|
|
30
|
+
INTERNAL_ERROR: "INTERNAL_ERROR"
|
|
31
|
+
};
|
|
32
|
+
var ERROR_STATUS = {
|
|
33
|
+
[ERROR_CODES.LOCATOR_REQUIRED]: 400,
|
|
34
|
+
[ERROR_CODES.MANIFEST_REQUIRED]: 400,
|
|
35
|
+
[ERROR_CODES.CONTENT_REQUIRED]: 400,
|
|
36
|
+
[ERROR_CODES.ARCHIVE_REQUIRED]: 400,
|
|
37
|
+
[ERROR_CODES.INVALID_MANIFEST]: 400,
|
|
38
|
+
[ERROR_CODES.INVALID_LOCATOR]: 400,
|
|
39
|
+
[ERROR_CODES.MISSING_REQUIRED_FIELDS]: 400,
|
|
40
|
+
[ERROR_CODES.RESOURCE_NOT_FOUND]: 404,
|
|
41
|
+
[ERROR_CODES.INTERNAL_ERROR]: 500
|
|
42
|
+
};
|
|
43
|
+
function buildResourceUrl(baseUrl, locator) {
|
|
44
|
+
return `${baseUrl.replace(/\/$/, "")}${ENDPOINTS.resource}/${encodeURIComponent(locator)}`;
|
|
45
|
+
}
|
|
46
|
+
function buildContentUrl(baseUrl, locator) {
|
|
47
|
+
return `${baseUrl.replace(/\/$/, "")}${ENDPOINTS.content}/${encodeURIComponent(locator)}`;
|
|
48
|
+
}
|
|
49
|
+
function buildPublishUrl(baseUrl) {
|
|
50
|
+
return `${baseUrl.replace(/\/$/, "")}${ENDPOINTS.publish}`;
|
|
51
|
+
}
|
|
52
|
+
function buildSearchUrl(baseUrl, params) {
|
|
53
|
+
const url = new URL(`${baseUrl.replace(/\/$/, "")}${ENDPOINTS.search}`);
|
|
54
|
+
if (params?.q)
|
|
55
|
+
url.searchParams.set("q", params.q);
|
|
56
|
+
if (params?.limit)
|
|
57
|
+
url.searchParams.set("limit", String(params.limit));
|
|
58
|
+
if (params?.offset)
|
|
59
|
+
url.searchParams.set("offset", String(params.offset));
|
|
60
|
+
return url.toString();
|
|
61
|
+
}
|
|
62
|
+
// src/handlers.ts
|
|
63
|
+
import { parse, format, manifest, wrap, resource } from "@resourcexjs/core";
|
|
64
|
+
function jsonResponse(data, status = 200) {
|
|
65
|
+
return new Response(JSON.stringify(data), {
|
|
66
|
+
status,
|
|
67
|
+
headers: { "Content-Type": "application/json" }
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function errorResponse(error, code, status) {
|
|
71
|
+
const body = { error, code };
|
|
72
|
+
return new Response(JSON.stringify(body), {
|
|
73
|
+
status,
|
|
74
|
+
headers: { "Content-Type": "application/json" }
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async function handlePublish(request, registry) {
|
|
78
|
+
try {
|
|
79
|
+
const formData = await request.formData();
|
|
80
|
+
const locatorStr = formData.get(PUBLISH_FIELDS.locator);
|
|
81
|
+
if (!locatorStr || typeof locatorStr !== "string") {
|
|
82
|
+
return errorResponse("Missing locator field", ERROR_CODES.LOCATOR_REQUIRED, 400);
|
|
83
|
+
}
|
|
84
|
+
const manifestFile = formData.get(PUBLISH_FIELDS.manifest);
|
|
85
|
+
if (!manifestFile || typeof manifestFile === "string") {
|
|
86
|
+
return errorResponse("Missing manifest file", ERROR_CODES.MANIFEST_REQUIRED, 400);
|
|
87
|
+
}
|
|
88
|
+
const contentFile = formData.get(PUBLISH_FIELDS.content);
|
|
89
|
+
if (!contentFile || typeof contentFile === "string") {
|
|
90
|
+
return errorResponse("Missing content file", ERROR_CODES.CONTENT_REQUIRED, 400);
|
|
91
|
+
}
|
|
92
|
+
const rxl = parse(locatorStr);
|
|
93
|
+
const manifestText = await manifestFile.text();
|
|
94
|
+
const manifestData = JSON.parse(manifestText);
|
|
95
|
+
const rxm = manifest({
|
|
96
|
+
registry: manifestData.registry ?? rxl.registry,
|
|
97
|
+
path: manifestData.path ?? rxl.path,
|
|
98
|
+
name: manifestData.name ?? rxl.name,
|
|
99
|
+
type: manifestData.type,
|
|
100
|
+
tag: manifestData.tag ?? rxl.tag ?? "latest",
|
|
101
|
+
files: manifestData.files
|
|
102
|
+
});
|
|
103
|
+
const contentBuffer = Buffer.from(await contentFile.arrayBuffer());
|
|
104
|
+
const rxa = wrap(contentBuffer);
|
|
105
|
+
const rxr = resource(rxm, rxa);
|
|
106
|
+
await registry.put(rxr);
|
|
107
|
+
const response = { locator: format(rxr.locator) };
|
|
108
|
+
return jsonResponse(response, 201);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
111
|
+
return errorResponse(message, ERROR_CODES.INTERNAL_ERROR, 500);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function handleGetResource(locator, registry) {
|
|
115
|
+
try {
|
|
116
|
+
const rxl = parse(locator);
|
|
117
|
+
const rxr = await registry.get(rxl);
|
|
118
|
+
const response = {
|
|
119
|
+
registry: rxr.manifest.registry,
|
|
120
|
+
path: rxr.manifest.path,
|
|
121
|
+
name: rxr.manifest.name,
|
|
122
|
+
type: rxr.manifest.type,
|
|
123
|
+
tag: rxr.manifest.tag
|
|
124
|
+
};
|
|
125
|
+
return jsonResponse(response);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
128
|
+
if (message.includes("not found")) {
|
|
129
|
+
return errorResponse("Resource not found", ERROR_CODES.RESOURCE_NOT_FOUND, 404);
|
|
130
|
+
}
|
|
131
|
+
return errorResponse(message, ERROR_CODES.INTERNAL_ERROR, 500);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function handleHeadResource(locator, registry) {
|
|
135
|
+
try {
|
|
136
|
+
const rxl = parse(locator);
|
|
137
|
+
const exists = await registry.has(rxl);
|
|
138
|
+
return new Response(null, { status: exists ? 200 : 404 });
|
|
139
|
+
} catch {
|
|
140
|
+
return new Response(null, { status: 500 });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function handleDeleteResource(locator, registry) {
|
|
144
|
+
try {
|
|
145
|
+
const rxl = parse(locator);
|
|
146
|
+
const exists = await registry.has(rxl);
|
|
147
|
+
if (!exists) {
|
|
148
|
+
return errorResponse("Resource not found", ERROR_CODES.RESOURCE_NOT_FOUND, 404);
|
|
149
|
+
}
|
|
150
|
+
await registry.remove(rxl);
|
|
151
|
+
return new Response(null, { status: 204 });
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
154
|
+
return errorResponse(message, ERROR_CODES.INTERNAL_ERROR, 500);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function handleGetContent(locator, registry) {
|
|
158
|
+
try {
|
|
159
|
+
const rxl = parse(locator);
|
|
160
|
+
const rxr = await registry.get(rxl);
|
|
161
|
+
const buffer = await rxr.archive.buffer();
|
|
162
|
+
return new Response(buffer, {
|
|
163
|
+
status: 200,
|
|
164
|
+
headers: {
|
|
165
|
+
"Content-Type": "application/gzip",
|
|
166
|
+
"Content-Disposition": `attachment; filename="archive.tar.gz"`,
|
|
167
|
+
"Content-Length": buffer.byteLength.toString()
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
} catch (error) {
|
|
171
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
172
|
+
if (message.includes("not found")) {
|
|
173
|
+
return errorResponse("Resource not found", ERROR_CODES.RESOURCE_NOT_FOUND, 404);
|
|
174
|
+
}
|
|
175
|
+
return errorResponse(message, ERROR_CODES.INTERNAL_ERROR, 500);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function handleSearch(query, limit, offset, registry) {
|
|
179
|
+
try {
|
|
180
|
+
const results = await registry.list({
|
|
181
|
+
query: query || undefined,
|
|
182
|
+
limit,
|
|
183
|
+
offset
|
|
184
|
+
});
|
|
185
|
+
const response = {
|
|
186
|
+
results: results.map((rxl) => ({
|
|
187
|
+
locator: format(rxl),
|
|
188
|
+
registry: rxl.registry,
|
|
189
|
+
path: rxl.path,
|
|
190
|
+
name: rxl.name,
|
|
191
|
+
type: "",
|
|
192
|
+
tag: rxl.tag ?? "latest"
|
|
193
|
+
})),
|
|
194
|
+
total: results.length
|
|
195
|
+
};
|
|
196
|
+
return jsonResponse(response);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
199
|
+
return errorResponse(message, ERROR_CODES.INTERNAL_ERROR, 500);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// src/hono.ts
|
|
203
|
+
import { Hono } from "hono";
|
|
204
|
+
import { cors } from "hono/cors";
|
|
205
|
+
import { FileSystemStorage } from "@resourcexjs/storage";
|
|
206
|
+
import { LocalRegistry } from "@resourcexjs/registry";
|
|
207
|
+
function createRegistryServer(config) {
|
|
208
|
+
const storagePath = config?.storagePath ?? "./data";
|
|
209
|
+
const basePath = config?.basePath ?? "";
|
|
210
|
+
const enableCors = config?.cors ?? true;
|
|
211
|
+
const storage = new FileSystemStorage(storagePath);
|
|
212
|
+
const registry = new LocalRegistry(storage);
|
|
213
|
+
const app = new Hono().basePath(basePath);
|
|
214
|
+
if (enableCors) {
|
|
215
|
+
app.use("*", cors());
|
|
216
|
+
}
|
|
217
|
+
app.get(ENDPOINTS.health, (c) => c.json({ status: "ok" }));
|
|
218
|
+
app.post(ENDPOINTS.publish, async (c) => {
|
|
219
|
+
return handlePublish(c.req.raw, registry);
|
|
220
|
+
});
|
|
221
|
+
app.get(`${ENDPOINTS.resource}/:locator`, async (c) => {
|
|
222
|
+
const locator = decodeURIComponent(c.req.param("locator"));
|
|
223
|
+
return handleGetResource(locator, registry);
|
|
224
|
+
});
|
|
225
|
+
app.on("HEAD", `${ENDPOINTS.resource}/:locator`, async (c) => {
|
|
226
|
+
const locator = decodeURIComponent(c.req.param("locator"));
|
|
227
|
+
return handleHeadResource(locator, registry);
|
|
228
|
+
});
|
|
229
|
+
app.delete(`${ENDPOINTS.resource}/:locator`, async (c) => {
|
|
230
|
+
const locator = decodeURIComponent(c.req.param("locator"));
|
|
231
|
+
return handleDeleteResource(locator, registry);
|
|
232
|
+
});
|
|
233
|
+
app.get(`${ENDPOINTS.content}/:locator`, async (c) => {
|
|
234
|
+
const locator = decodeURIComponent(c.req.param("locator"));
|
|
235
|
+
return handleGetContent(locator, registry);
|
|
236
|
+
});
|
|
237
|
+
app.get(ENDPOINTS.search, async (c) => {
|
|
238
|
+
const query = c.req.query("q");
|
|
239
|
+
const limit = parseInt(c.req.query("limit") ?? "100", 10);
|
|
240
|
+
const offset = parseInt(c.req.query("offset") ?? "0", 10);
|
|
241
|
+
return handleSearch(query, limit, offset, registry);
|
|
242
|
+
});
|
|
243
|
+
return app;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/index.ts
|
|
247
|
+
import { LocalRegistry as LocalRegistry2 } from "@resourcexjs/registry";
|
|
248
|
+
import { FileSystemStorage as FileSystemStorage2, MemoryStorage } from "@resourcexjs/storage";
|
|
249
|
+
import { FileSystemStorage as FileSystemStorage3 } from "@resourcexjs/storage";
|
|
250
|
+
import { LocalRegistry as LocalRegistry3 } from "@resourcexjs/registry";
|
|
251
|
+
function createRegistry(config) {
|
|
252
|
+
const storage = new FileSystemStorage3(config.storagePath);
|
|
253
|
+
return new LocalRegistry3(storage);
|
|
254
|
+
}
|
|
255
|
+
export {
|
|
256
|
+
handleSearch,
|
|
257
|
+
handlePublish,
|
|
258
|
+
handleHeadResource,
|
|
259
|
+
handleGetResource,
|
|
260
|
+
handleGetContent,
|
|
261
|
+
handleDeleteResource,
|
|
262
|
+
createRegistryServer,
|
|
263
|
+
createRegistry,
|
|
264
|
+
buildSearchUrl,
|
|
265
|
+
buildResourceUrl,
|
|
266
|
+
buildPublishUrl,
|
|
267
|
+
buildContentUrl,
|
|
268
|
+
PUBLISH_FIELDS,
|
|
269
|
+
MemoryStorage,
|
|
270
|
+
LocalRegistry2 as LocalRegistry,
|
|
271
|
+
FileSystemStorage2 as FileSystemStorage,
|
|
272
|
+
ERROR_STATUS,
|
|
273
|
+
ERROR_CODES,
|
|
274
|
+
ENDPOINTS,
|
|
275
|
+
CONTENT_TYPES,
|
|
276
|
+
API_VERSION,
|
|
277
|
+
API_PREFIX
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
//# debugId=DA345CD276F2BE2D64756E2164756E21
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/protocol.ts", "../src/handlers.ts", "../src/hono.ts", "../src/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * ResourceX Registry HTTP API Protocol\n *\n * Defines the contract between ResourceX client and registry server.\n */\n\n// ============================================\n// API Endpoints\n// ============================================\n\nexport const API_VERSION: string = \"v1\";\nexport const API_PREFIX: string = `/api/${API_VERSION}`;\n\nexport const ENDPOINTS: {\n readonly publish: string;\n readonly resource: string;\n readonly content: string;\n readonly search: string;\n readonly health: string;\n} = {\n publish: `${API_PREFIX}/publish`,\n resource: `${API_PREFIX}/resource`,\n content: `${API_PREFIX}/content`,\n search: `${API_PREFIX}/search`,\n health: `${API_PREFIX}/health`,\n} as const;\n\nexport const CONTENT_TYPES = {\n json: \"application/json\",\n binary: \"application/gzip\",\n formData: \"multipart/form-data\",\n} as const;\n\n// ============================================\n// Request Types\n// ============================================\n\nexport interface ManifestData {\n registry?: string;\n path?: string;\n name: string;\n type: string;\n tag: string;\n}\n\nexport const PUBLISH_FIELDS = {\n locator: \"locator\",\n manifest: \"manifest\",\n content: \"content\",\n} as const;\n\nexport interface SearchQuery {\n q?: string;\n limit?: number;\n offset?: number;\n}\n\n// ============================================\n// Response Types\n// ============================================\n\nexport interface PublishResponse {\n locator: string;\n}\n\nexport type GetResourceResponse = ManifestData;\n\nexport interface SearchResultItem {\n locator: string;\n registry?: string;\n path?: string;\n name: string;\n type: string;\n tag: string;\n}\n\nexport interface SearchResponse {\n results: SearchResultItem[];\n total?: number;\n}\n\nexport interface ErrorResponse {\n error: string;\n code?: string;\n}\n\n// ============================================\n// Error Codes\n// ============================================\n\nexport const ERROR_CODES = {\n // 400 Bad Request\n LOCATOR_REQUIRED: \"LOCATOR_REQUIRED\",\n MANIFEST_REQUIRED: \"MANIFEST_REQUIRED\",\n CONTENT_REQUIRED: \"CONTENT_REQUIRED\",\n ARCHIVE_REQUIRED: \"ARCHIVE_REQUIRED\",\n INVALID_MANIFEST: \"INVALID_MANIFEST\",\n INVALID_LOCATOR: \"INVALID_LOCATOR\",\n MISSING_REQUIRED_FIELDS: \"MISSING_REQUIRED_FIELDS\",\n\n // 404 Not Found\n RESOURCE_NOT_FOUND: \"RESOURCE_NOT_FOUND\",\n\n // 500 Internal Server Error\n INTERNAL_ERROR: \"INTERNAL_ERROR\",\n} as const;\n\nexport type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];\n\nexport const ERROR_STATUS: Record<ErrorCode, number> = {\n [ERROR_CODES.LOCATOR_REQUIRED]: 400,\n [ERROR_CODES.MANIFEST_REQUIRED]: 400,\n [ERROR_CODES.CONTENT_REQUIRED]: 400,\n [ERROR_CODES.ARCHIVE_REQUIRED]: 400,\n [ERROR_CODES.INVALID_MANIFEST]: 400,\n [ERROR_CODES.INVALID_LOCATOR]: 400,\n [ERROR_CODES.MISSING_REQUIRED_FIELDS]: 400,\n [ERROR_CODES.RESOURCE_NOT_FOUND]: 404,\n [ERROR_CODES.INTERNAL_ERROR]: 500,\n};\n\n// ============================================\n// URL Builders\n// ============================================\n\nexport function buildResourceUrl(baseUrl: string, locator: string): string {\n return `${baseUrl.replace(/\\/$/, \"\")}${ENDPOINTS.resource}/${encodeURIComponent(locator)}`;\n}\n\nexport function buildContentUrl(baseUrl: string, locator: string): string {\n return `${baseUrl.replace(/\\/$/, \"\")}${ENDPOINTS.content}/${encodeURIComponent(locator)}`;\n}\n\nexport function buildPublishUrl(baseUrl: string): string {\n return `${baseUrl.replace(/\\/$/, \"\")}${ENDPOINTS.publish}`;\n}\n\nexport function buildSearchUrl(\n baseUrl: string,\n params?: { q?: string; limit?: number; offset?: number }\n): string {\n const url = new URL(`${baseUrl.replace(/\\/$/, \"\")}${ENDPOINTS.search}`);\n if (params?.q) url.searchParams.set(\"q\", params.q);\n if (params?.limit) url.searchParams.set(\"limit\", String(params.limit));\n if (params?.offset) url.searchParams.set(\"offset\", String(params.offset));\n return url.toString();\n}\n",
|
|
6
|
+
"/**\n * ResourceX Registry Handlers\n *\n * Handler functions for Next.js Route Handler or any other framework.\n */\n\nimport { parse, format, manifest, wrap, resource } from \"@resourcexjs/core\";\nimport type { Registry } from \"@resourcexjs/registry\";\nimport {\n PUBLISH_FIELDS,\n ERROR_CODES,\n type PublishResponse,\n type GetResourceResponse,\n type SearchResponse,\n type ErrorResponse,\n} from \"./protocol.js\";\n\n// ============================================\n// Helper Functions\n// ============================================\n\nfunction jsonResponse<T>(data: T, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nfunction errorResponse(error: string, code: string, status: number): Response {\n const body: ErrorResponse = { error, code };\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\n// ============================================\n// Handler Functions\n// ============================================\n\n/**\n * Handle POST /publish\n */\nexport async function handlePublish(request: Request, registry: Registry): Promise<Response> {\n try {\n const formData = await request.formData();\n\n const locatorStr = formData.get(PUBLISH_FIELDS.locator);\n if (!locatorStr || typeof locatorStr !== \"string\") {\n return errorResponse(\"Missing locator field\", ERROR_CODES.LOCATOR_REQUIRED, 400);\n }\n\n const manifestFile = formData.get(PUBLISH_FIELDS.manifest);\n if (!manifestFile || typeof manifestFile === \"string\") {\n return errorResponse(\"Missing manifest file\", ERROR_CODES.MANIFEST_REQUIRED, 400);\n }\n\n const contentFile = formData.get(PUBLISH_FIELDS.content);\n if (!contentFile || typeof contentFile === \"string\") {\n return errorResponse(\"Missing content file\", ERROR_CODES.CONTENT_REQUIRED, 400);\n }\n\n const rxl = parse(locatorStr);\n const manifestText = await manifestFile.text();\n const manifestData = JSON.parse(manifestText);\n\n const rxm = manifest({\n registry: manifestData.registry ?? rxl.registry,\n path: manifestData.path ?? rxl.path,\n name: manifestData.name ?? rxl.name,\n type: manifestData.type, // Type must come from manifest, not locator\n tag: manifestData.tag ?? rxl.tag ?? \"latest\",\n files: manifestData.files,\n });\n\n const contentBuffer = Buffer.from(await contentFile.arrayBuffer());\n const rxa = wrap(contentBuffer);\n const rxr = resource(rxm, rxa);\n\n await registry.put(rxr);\n\n const response: PublishResponse = { locator: format(rxr.locator) };\n return jsonResponse(response, 201);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n return errorResponse(message, ERROR_CODES.INTERNAL_ERROR, 500);\n }\n}\n\n/**\n * Handle GET /resource/:locator\n */\nexport async function handleGetResource(locator: string, registry: Registry): Promise<Response> {\n try {\n const rxl = parse(locator);\n const rxr = await registry.get(rxl);\n\n const response: GetResourceResponse = {\n registry: rxr.manifest.registry,\n path: rxr.manifest.path,\n name: rxr.manifest.name,\n type: rxr.manifest.type,\n tag: rxr.manifest.tag,\n };\n return jsonResponse(response);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n if (message.includes(\"not found\")) {\n return errorResponse(\"Resource not found\", ERROR_CODES.RESOURCE_NOT_FOUND, 404);\n }\n return errorResponse(message, ERROR_CODES.INTERNAL_ERROR, 500);\n }\n}\n\n/**\n * Handle HEAD /resource/:locator\n */\nexport async function handleHeadResource(locator: string, registry: Registry): Promise<Response> {\n try {\n const rxl = parse(locator);\n const exists = await registry.has(rxl);\n return new Response(null, { status: exists ? 200 : 404 });\n } catch {\n return new Response(null, { status: 500 });\n }\n}\n\n/**\n * Handle DELETE /resource/:locator\n */\nexport async function handleDeleteResource(locator: string, registry: Registry): Promise<Response> {\n try {\n const rxl = parse(locator);\n const exists = await registry.has(rxl);\n\n if (!exists) {\n return errorResponse(\"Resource not found\", ERROR_CODES.RESOURCE_NOT_FOUND, 404);\n }\n\n await registry.remove(rxl);\n return new Response(null, { status: 204 });\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n return errorResponse(message, ERROR_CODES.INTERNAL_ERROR, 500);\n }\n}\n\n/**\n * Handle GET /content/:locator\n */\nexport async function handleGetContent(locator: string, registry: Registry): Promise<Response> {\n try {\n const rxl = parse(locator);\n const rxr = await registry.get(rxl);\n const buffer = await rxr.archive.buffer();\n\n return new Response(buffer, {\n status: 200,\n headers: {\n \"Content-Type\": \"application/gzip\",\n \"Content-Disposition\": `attachment; filename=\"archive.tar.gz\"`,\n \"Content-Length\": buffer.byteLength.toString(),\n },\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n if (message.includes(\"not found\")) {\n return errorResponse(\"Resource not found\", ERROR_CODES.RESOURCE_NOT_FOUND, 404);\n }\n return errorResponse(message, ERROR_CODES.INTERNAL_ERROR, 500);\n }\n}\n\n/**\n * Handle GET /search\n */\nexport async function handleSearch(\n query: string | undefined,\n limit: number,\n offset: number,\n registry: Registry\n): Promise<Response> {\n try {\n const results = await registry.list({\n query: query || undefined,\n limit,\n offset,\n });\n\n const response: SearchResponse = {\n results: results.map((rxl) => ({\n locator: format(rxl),\n registry: rxl.registry,\n path: rxl.path,\n name: rxl.name,\n type: \"\", // Type not in RXL anymore, would need to read manifest\n tag: rxl.tag ?? \"latest\",\n })),\n total: results.length,\n };\n return jsonResponse(response);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n return errorResponse(message, ERROR_CODES.INTERNAL_ERROR, 500);\n }\n}\n",
|
|
7
|
+
"/**\n * ResourceX Registry Hono Server\n *\n * Ready-to-use Hono app implementing the ResourceX Registry Protocol.\n */\n\nimport { Hono } from \"hono\";\nimport { cors } from \"hono/cors\";\nimport { FileSystemStorage } from \"@resourcexjs/storage\";\nimport { LocalRegistry } from \"@resourcexjs/registry\";\nimport {\n handlePublish,\n handleGetResource,\n handleHeadResource,\n handleDeleteResource,\n handleGetContent,\n handleSearch,\n} from \"./handlers.js\";\nimport { ENDPOINTS } from \"./protocol.js\";\n\nexport interface RegistryServerConfig {\n /**\n * Path for storing resources.\n * @default \"./data\"\n */\n storagePath?: string;\n\n /**\n * Base path for API routes.\n * @default \"\"\n */\n basePath?: string;\n\n /**\n * Enable CORS.\n * @default true\n */\n cors?: boolean;\n}\n\n/**\n * Create a registry server Hono app.\n */\nexport function createRegistryServer(config?: RegistryServerConfig): Hono {\n const storagePath = config?.storagePath ?? \"./data\";\n const basePath = config?.basePath ?? \"\";\n const enableCors = config?.cors ?? true;\n\n const storage = new FileSystemStorage(storagePath);\n const registry = new LocalRegistry(storage);\n\n const app = new Hono().basePath(basePath);\n\n if (enableCors) {\n app.use(\"*\", cors());\n }\n\n // Health check\n app.get(ENDPOINTS.health, (c) => c.json({ status: \"ok\" }));\n\n // POST /publish\n app.post(ENDPOINTS.publish, async (c) => {\n return handlePublish(c.req.raw, registry);\n });\n\n // GET /resource/:locator\n app.get(`${ENDPOINTS.resource}/:locator`, async (c) => {\n const locator = decodeURIComponent(c.req.param(\"locator\"));\n return handleGetResource(locator, registry);\n });\n\n // HEAD /resource/:locator\n app.on(\"HEAD\", `${ENDPOINTS.resource}/:locator`, async (c) => {\n const locator = decodeURIComponent(c.req.param(\"locator\"));\n return handleHeadResource(locator, registry);\n });\n\n // DELETE /resource/:locator\n app.delete(`${ENDPOINTS.resource}/:locator`, async (c) => {\n const locator = decodeURIComponent(c.req.param(\"locator\"));\n return handleDeleteResource(locator, registry);\n });\n\n // GET /content/:locator\n app.get(`${ENDPOINTS.content}/:locator`, async (c) => {\n const locator = decodeURIComponent(c.req.param(\"locator\"));\n return handleGetContent(locator, registry);\n });\n\n // GET /search\n app.get(ENDPOINTS.search, async (c) => {\n const query = c.req.query(\"q\");\n const limit = parseInt(c.req.query(\"limit\") ?? \"100\", 10);\n const offset = parseInt(c.req.query(\"offset\") ?? \"0\", 10);\n return handleSearch(query, limit, offset, registry);\n });\n\n return app;\n}\n",
|
|
8
|
+
"/**\n * @resourcexjs/server\n *\n * ResourceX Registry Server - Protocol, Handlers, and Hono Server\n *\n * @example\n * ```typescript\n * // Option 1: Use Hono Server directly\n * import { createRegistryServer } from \"@resourcexjs/server\";\n * const server = createRegistryServer({ storagePath: \"./data\" });\n * Bun.serve({ fetch: server.fetch, port: 3000 });\n *\n * // Option 2: Use handlers with Next.js Route Handler\n * import { handlePublish, handleSearch, createRegistry } from \"@resourcexjs/server\";\n * const registry = createRegistry({ storagePath: \"./data\" });\n * export async function POST(req) { return handlePublish(req, registry); }\n *\n * // Option 3: Use protocol types only\n * import { ENDPOINTS, type PublishResponse } from \"@resourcexjs/server\";\n * ```\n *\n * @packageDocumentation\n */\n\n// Protocol\nexport {\n API_VERSION,\n API_PREFIX,\n ENDPOINTS,\n CONTENT_TYPES,\n PUBLISH_FIELDS,\n ERROR_CODES,\n ERROR_STATUS,\n buildResourceUrl,\n buildContentUrl,\n buildPublishUrl,\n buildSearchUrl,\n type ManifestData,\n type SearchQuery,\n type PublishResponse,\n type GetResourceResponse,\n type SearchResultItem,\n type SearchResponse,\n type ErrorResponse,\n type ErrorCode,\n} from \"./protocol.js\";\n\n// Handlers\nexport {\n handlePublish,\n handleGetResource,\n handleHeadResource,\n handleDeleteResource,\n handleGetContent,\n handleSearch,\n} from \"./handlers.js\";\n\n// Hono Server\nexport { createRegistryServer, type RegistryServerConfig } from \"./hono.js\";\n\n// Re-export Registry for convenience\nexport { LocalRegistry } from \"@resourcexjs/registry\";\nexport { FileSystemStorage, MemoryStorage } from \"@resourcexjs/storage\";\n\n/**\n * Convenience function to create a registry instance.\n */\nimport { FileSystemStorage } from \"@resourcexjs/storage\";\nimport { LocalRegistry } from \"@resourcexjs/registry\";\nimport type { Registry } from \"@resourcexjs/registry\";\n\nexport interface CreateRegistryConfig {\n storagePath: string;\n}\n\nexport function createRegistry(config: CreateRegistryConfig): Registry {\n const storage = new FileSystemStorage(config.storagePath);\n return new LocalRegistry(storage);\n}\n"
|
|
9
|
+
],
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "DA345CD276F2BE2D64756E2164756E21",
|
|
12
|
+
"names": []
|
|
13
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@resourcexjs/server",
|
|
3
|
+
"version": "2.5.1",
|
|
4
|
+
"description": "ResourceX Registry Server - Protocol, Handlers, and Hono Server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "bun run build.ts",
|
|
19
|
+
"lint": "eslint .",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"clean": "rm -rf dist"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@resourcexjs/core": "^2.5.1",
|
|
25
|
+
"@resourcexjs/registry": "^2.5.1",
|
|
26
|
+
"@resourcexjs/storage": "^2.5.1",
|
|
27
|
+
"hono": "^4.7.10"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"license": "Apache-2.0"
|
|
34
|
+
}
|