@resourcexjs/core 0.4.0 → 0.7.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 +194 -120
- package/dist/index.d.ts +139 -275
- package/dist/index.js +347 -462
- package/dist/index.js.map +11 -15
- package/package.json +7 -6
package/dist/index.js
CHANGED
|
@@ -6,521 +6,406 @@ class ResourceXError extends Error {
|
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
class
|
|
10
|
-
|
|
11
|
-
constructor(message,
|
|
9
|
+
class LocatorError extends ResourceXError {
|
|
10
|
+
locator;
|
|
11
|
+
constructor(message, locator) {
|
|
12
12
|
super(message);
|
|
13
|
-
this.
|
|
14
|
-
this.name = "
|
|
13
|
+
this.locator = locator;
|
|
14
|
+
this.name = "LocatorError";
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
class
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
this.transport = transport;
|
|
23
|
-
this.name = "TransportError";
|
|
18
|
+
class ManifestError extends ResourceXError {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "ManifestError";
|
|
24
22
|
}
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
class
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.semantic = semantic;
|
|
32
|
-
this.name = "SemanticError";
|
|
25
|
+
class ContentError extends ResourceXError {
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "ContentError";
|
|
33
29
|
}
|
|
34
30
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const separatorIndex = content.indexOf("://");
|
|
42
|
-
if (separatorIndex === -1) {
|
|
43
|
-
throw new ParseError(`Invalid ARP URL: missing "://"`, url);
|
|
44
|
-
}
|
|
45
|
-
const typePart = content.substring(0, separatorIndex);
|
|
46
|
-
const location = content.substring(separatorIndex + 3);
|
|
47
|
-
const colonIndex = typePart.indexOf(":");
|
|
48
|
-
if (colonIndex === -1) {
|
|
49
|
-
throw new ParseError(`Invalid ARP URL: must have exactly 2 types (semantic:transport)`, url);
|
|
50
|
-
}
|
|
51
|
-
const semantic = typePart.substring(0, colonIndex);
|
|
52
|
-
const transport = typePart.substring(colonIndex + 1);
|
|
53
|
-
if (!semantic) {
|
|
54
|
-
throw new ParseError(`Invalid ARP URL: semantic type cannot be empty`, url);
|
|
55
|
-
}
|
|
56
|
-
if (!transport) {
|
|
57
|
-
throw new ParseError(`Invalid ARP URL: transport type cannot be empty`, url);
|
|
58
|
-
}
|
|
59
|
-
if (!location) {
|
|
60
|
-
throw new ParseError(`Invalid ARP URL: location cannot be empty`, url);
|
|
61
|
-
}
|
|
62
|
-
return { semantic, transport, location };
|
|
31
|
+
|
|
32
|
+
class ResourceTypeError extends ResourceXError {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "ResourceTypeError";
|
|
36
|
+
}
|
|
63
37
|
}
|
|
64
|
-
// src/
|
|
65
|
-
class
|
|
38
|
+
// src/locator/parseRXL.ts
|
|
39
|
+
class RXLImpl {
|
|
40
|
+
domain;
|
|
41
|
+
path;
|
|
66
42
|
name;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const response = await fetch(url);
|
|
83
|
-
if (!response.ok) {
|
|
84
|
-
throw new TransportError(`HTTP ${response.status}: ${response.statusText} - ${url}`, this.name);
|
|
43
|
+
type;
|
|
44
|
+
version;
|
|
45
|
+
constructor(parts) {
|
|
46
|
+
this.domain = parts.domain;
|
|
47
|
+
this.path = parts.path;
|
|
48
|
+
this.name = parts.name;
|
|
49
|
+
this.type = parts.type;
|
|
50
|
+
this.version = parts.version;
|
|
51
|
+
}
|
|
52
|
+
toString() {
|
|
53
|
+
let result = "";
|
|
54
|
+
if (this.domain) {
|
|
55
|
+
result += this.domain + "/";
|
|
56
|
+
if (this.path) {
|
|
57
|
+
result += this.path + "/";
|
|
85
58
|
}
|
|
86
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
87
|
-
return Buffer.from(arrayBuffer);
|
|
88
|
-
} catch (error) {
|
|
89
|
-
if (error instanceof TransportError) {
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
|
-
throw new TransportError(`Network error: ${url}`, this.name, {
|
|
93
|
-
cause: error
|
|
94
|
-
});
|
|
95
59
|
}
|
|
60
|
+
result += this.name;
|
|
61
|
+
if (this.type) {
|
|
62
|
+
result += "." + this.type;
|
|
63
|
+
}
|
|
64
|
+
if (this.version) {
|
|
65
|
+
result += "@" + this.version;
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
96
68
|
}
|
|
97
69
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
throw new TransportError(`File read error: ${err.code} - ${filePath}`, this.name, {
|
|
122
|
-
cause: err
|
|
123
|
-
});
|
|
70
|
+
function isDomain(str) {
|
|
71
|
+
if (str === "localhost")
|
|
72
|
+
return true;
|
|
73
|
+
return str.includes(".");
|
|
74
|
+
}
|
|
75
|
+
function parseRXL(locator) {
|
|
76
|
+
let remaining = locator;
|
|
77
|
+
let version;
|
|
78
|
+
let type;
|
|
79
|
+
let domain;
|
|
80
|
+
let path;
|
|
81
|
+
let name;
|
|
82
|
+
const atIndex = remaining.indexOf("@");
|
|
83
|
+
if (atIndex !== -1) {
|
|
84
|
+
version = remaining.slice(atIndex + 1);
|
|
85
|
+
remaining = remaining.slice(0, atIndex);
|
|
86
|
+
}
|
|
87
|
+
const segments = remaining.split("/");
|
|
88
|
+
if (segments.length > 1 && isDomain(segments[0])) {
|
|
89
|
+
domain = segments[0];
|
|
90
|
+
const lastSegment = segments[segments.length - 1];
|
|
91
|
+
if (segments.length > 2) {
|
|
92
|
+
path = segments.slice(1, -1).join("/");
|
|
124
93
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
94
|
+
remaining = lastSegment;
|
|
95
|
+
} else {
|
|
96
|
+
remaining = segments.join("/");
|
|
97
|
+
}
|
|
98
|
+
const dotIndex = remaining.lastIndexOf(".");
|
|
99
|
+
if (dotIndex !== -1) {
|
|
100
|
+
type = remaining.slice(dotIndex + 1);
|
|
101
|
+
name = remaining.slice(0, dotIndex);
|
|
102
|
+
} else {
|
|
103
|
+
name = remaining;
|
|
104
|
+
}
|
|
105
|
+
return new RXLImpl({ domain, path, name, type, version });
|
|
106
|
+
}
|
|
107
|
+
// src/manifest/createRXM.ts
|
|
108
|
+
class RXMImpl {
|
|
109
|
+
domain;
|
|
110
|
+
path;
|
|
111
|
+
name;
|
|
112
|
+
type;
|
|
113
|
+
version;
|
|
114
|
+
constructor(data) {
|
|
115
|
+
this.domain = data.domain;
|
|
116
|
+
this.path = data.path;
|
|
117
|
+
this.name = data.name;
|
|
118
|
+
this.type = data.type;
|
|
119
|
+
this.version = data.version;
|
|
120
|
+
}
|
|
121
|
+
toLocator() {
|
|
122
|
+
let result = this.domain + "/";
|
|
123
|
+
if (this.path) {
|
|
124
|
+
result += this.path + "/";
|
|
136
125
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
126
|
+
result += this.name;
|
|
127
|
+
result += "." + this.type;
|
|
128
|
+
result += "@" + this.version;
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
toJSON() {
|
|
132
|
+
const json = {
|
|
133
|
+
domain: this.domain,
|
|
134
|
+
name: this.name,
|
|
135
|
+
type: this.type,
|
|
136
|
+
version: this.version
|
|
137
|
+
};
|
|
138
|
+
if (this.path !== undefined) {
|
|
139
|
+
json.path = this.path;
|
|
147
140
|
}
|
|
141
|
+
return json;
|
|
148
142
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
143
|
+
}
|
|
144
|
+
function createRXM(data) {
|
|
145
|
+
if (!data.domain) {
|
|
146
|
+
throw new ManifestError("domain is required");
|
|
147
|
+
}
|
|
148
|
+
if (!data.name) {
|
|
149
|
+
throw new ManifestError("name is required");
|
|
150
|
+
}
|
|
151
|
+
if (!data.type) {
|
|
152
|
+
throw new ManifestError("type is required");
|
|
153
|
+
}
|
|
154
|
+
if (!data.version) {
|
|
155
|
+
throw new ManifestError("version is required");
|
|
156
|
+
}
|
|
157
|
+
return new RXMImpl({
|
|
158
|
+
domain: data.domain,
|
|
159
|
+
path: data.path,
|
|
160
|
+
name: data.name,
|
|
161
|
+
type: data.type,
|
|
162
|
+
version: data.version
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// src/content/createRXC.ts
|
|
166
|
+
class RXCImpl {
|
|
167
|
+
_stream;
|
|
168
|
+
_consumed = false;
|
|
169
|
+
constructor(stream) {
|
|
170
|
+
this._stream = stream;
|
|
171
|
+
}
|
|
172
|
+
get stream() {
|
|
173
|
+
if (this._consumed) {
|
|
174
|
+
throw new ContentError("Content has already been consumed");
|
|
158
175
|
}
|
|
176
|
+
this._consumed = true;
|
|
177
|
+
return this._stream;
|
|
159
178
|
}
|
|
160
|
-
async
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
await access(filePath);
|
|
164
|
-
return true;
|
|
165
|
-
} catch {
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
179
|
+
async text() {
|
|
180
|
+
const buffer = await this.buffer();
|
|
181
|
+
return buffer.toString("utf-8");
|
|
168
182
|
}
|
|
169
|
-
async
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const stats = await fsStat(filePath);
|
|
173
|
-
return {
|
|
174
|
-
size: stats.size,
|
|
175
|
-
modifiedAt: stats.mtime,
|
|
176
|
-
isDirectory: stats.isDirectory()
|
|
177
|
-
};
|
|
178
|
-
} catch (error) {
|
|
179
|
-
const err = error;
|
|
180
|
-
throw new TransportError(`File stat error: ${err.code} - ${filePath}`, this.name, {
|
|
181
|
-
cause: err
|
|
182
|
-
});
|
|
183
|
+
async buffer() {
|
|
184
|
+
if (this._consumed) {
|
|
185
|
+
throw new ContentError("Content has already been consumed");
|
|
183
186
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
cause: err
|
|
193
|
-
});
|
|
187
|
+
this._consumed = true;
|
|
188
|
+
const reader = this._stream.getReader();
|
|
189
|
+
const chunks = [];
|
|
190
|
+
while (true) {
|
|
191
|
+
const { done, value } = await reader.read();
|
|
192
|
+
if (done)
|
|
193
|
+
break;
|
|
194
|
+
chunks.push(value);
|
|
194
195
|
}
|
|
196
|
+
return Buffer.concat(chunks);
|
|
197
|
+
}
|
|
198
|
+
async json() {
|
|
199
|
+
const text = await this.text();
|
|
200
|
+
return JSON.parse(text);
|
|
195
201
|
}
|
|
196
202
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
function resolvePath(location) {
|
|
206
|
-
return join(baseDir, location);
|
|
207
|
-
}
|
|
208
|
-
return {
|
|
209
|
-
name: "deepractice",
|
|
210
|
-
capabilities: {
|
|
211
|
-
canRead: true,
|
|
212
|
-
canWrite: true,
|
|
213
|
-
canList: true,
|
|
214
|
-
canDelete: true,
|
|
215
|
-
canStat: true
|
|
216
|
-
},
|
|
217
|
-
async read(location) {
|
|
218
|
-
const fullPath = resolvePath(location);
|
|
219
|
-
try {
|
|
220
|
-
return await readFile2(fullPath);
|
|
221
|
-
} catch (error) {
|
|
222
|
-
throw new TransportError(`Failed to read from deepractice: ${error.message}`, "deepractice", { cause: error });
|
|
223
|
-
}
|
|
224
|
-
},
|
|
225
|
-
async write(location, content) {
|
|
226
|
-
const fullPath = resolvePath(location);
|
|
227
|
-
try {
|
|
228
|
-
await mkdir2(join(fullPath, ".."), { recursive: true });
|
|
229
|
-
await writeFile2(fullPath, content);
|
|
230
|
-
} catch (error) {
|
|
231
|
-
throw new TransportError(`Failed to write to deepractice: ${error.message}`, "deepractice", { cause: error });
|
|
203
|
+
function createRXC(data) {
|
|
204
|
+
let stream;
|
|
205
|
+
if (typeof data === "string") {
|
|
206
|
+
const encoded = new TextEncoder().encode(data);
|
|
207
|
+
stream = new ReadableStream({
|
|
208
|
+
start(controller) {
|
|
209
|
+
controller.enqueue(encoded);
|
|
210
|
+
controller.close();
|
|
232
211
|
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
throw new TransportError(`Failed to list deepractice directory: ${error.message}`, "deepractice", { cause: error });
|
|
240
|
-
}
|
|
241
|
-
},
|
|
242
|
-
async exists(location) {
|
|
243
|
-
const fullPath = resolvePath(location);
|
|
244
|
-
try {
|
|
245
|
-
await access2(fullPath);
|
|
246
|
-
return true;
|
|
247
|
-
} catch {
|
|
248
|
-
return false;
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
async stat(location) {
|
|
252
|
-
const fullPath = resolvePath(location);
|
|
253
|
-
try {
|
|
254
|
-
const stats = await stat(fullPath);
|
|
255
|
-
return {
|
|
256
|
-
size: stats.size,
|
|
257
|
-
isDirectory: stats.isDirectory(),
|
|
258
|
-
modifiedAt: stats.mtime
|
|
259
|
-
};
|
|
260
|
-
} catch (error) {
|
|
261
|
-
throw new TransportError(`Failed to stat deepractice resource: ${error.message}`, "deepractice", { cause: error });
|
|
262
|
-
}
|
|
263
|
-
},
|
|
264
|
-
async delete(location) {
|
|
265
|
-
const fullPath = resolvePath(location);
|
|
266
|
-
try {
|
|
267
|
-
const stats = await stat(fullPath);
|
|
268
|
-
if (stats.isDirectory()) {
|
|
269
|
-
await rm2(fullPath, { recursive: true, force: true });
|
|
270
|
-
} else {
|
|
271
|
-
await unlink(fullPath);
|
|
272
|
-
}
|
|
273
|
-
} catch (error) {
|
|
274
|
-
throw new TransportError(`Failed to delete from deepractice: ${error.message}`, "deepractice", { cause: error });
|
|
212
|
+
});
|
|
213
|
+
} else if (Buffer.isBuffer(data)) {
|
|
214
|
+
stream = new ReadableStream({
|
|
215
|
+
start(controller) {
|
|
216
|
+
controller.enqueue(new Uint8Array(data));
|
|
217
|
+
controller.close();
|
|
275
218
|
}
|
|
219
|
+
});
|
|
220
|
+
} else {
|
|
221
|
+
stream = data;
|
|
222
|
+
}
|
|
223
|
+
return new RXCImpl(stream);
|
|
224
|
+
}
|
|
225
|
+
// src/content/loadRXC.ts
|
|
226
|
+
import { createReadStream } from "node:fs";
|
|
227
|
+
import { Readable } from "node:stream";
|
|
228
|
+
async function loadRXC(source) {
|
|
229
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
230
|
+
const response = await fetch(source);
|
|
231
|
+
if (!response.ok) {
|
|
232
|
+
throw new Error(`Failed to fetch ${source}: ${response.statusText}`);
|
|
233
|
+
}
|
|
234
|
+
if (!response.body) {
|
|
235
|
+
throw new Error(`No body in response from ${source}`);
|
|
276
236
|
}
|
|
277
|
-
|
|
237
|
+
return createRXC(response.body);
|
|
238
|
+
}
|
|
239
|
+
const nodeStream = createReadStream(source);
|
|
240
|
+
const webStream = Readable.toWeb(nodeStream);
|
|
241
|
+
return createRXC(webStream);
|
|
278
242
|
}
|
|
279
|
-
// src/
|
|
280
|
-
var
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (!handler) {
|
|
288
|
-
throw new TransportError(`Unsupported transport type: ${name}`, name);
|
|
289
|
-
}
|
|
290
|
-
return handler;
|
|
243
|
+
// src/resource/defineResourceType.ts
|
|
244
|
+
var resourceTypes = new Map;
|
|
245
|
+
function defineResourceType(config) {
|
|
246
|
+
if (resourceTypes.has(config.name)) {
|
|
247
|
+
throw new ResourceTypeError(`Resource type "${config.name}" is already registered`);
|
|
248
|
+
}
|
|
249
|
+
resourceTypes.set(config.name, config);
|
|
250
|
+
return config;
|
|
291
251
|
}
|
|
292
|
-
function
|
|
293
|
-
|
|
252
|
+
function getResourceType(name) {
|
|
253
|
+
return resourceTypes.get(name);
|
|
294
254
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
size: buffer.length,
|
|
307
|
-
encoding: "utf-8",
|
|
308
|
-
mimeType: "text/plain",
|
|
309
|
-
resolvedAt: context.timestamp.toISOString()
|
|
310
|
-
};
|
|
255
|
+
function clearResourceTypes() {
|
|
256
|
+
resourceTypes.clear();
|
|
257
|
+
}
|
|
258
|
+
// src/resource/builtinTypes.ts
|
|
259
|
+
var textSerializer = {
|
|
260
|
+
async serialize(rxr) {
|
|
261
|
+
const text = await rxr.content.text();
|
|
262
|
+
return Buffer.from(text, "utf-8");
|
|
263
|
+
},
|
|
264
|
+
async deserialize(data, manifest) {
|
|
265
|
+
const text = data.toString("utf-8");
|
|
311
266
|
return {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
267
|
+
locator: parseRXL(manifest.toLocator()),
|
|
268
|
+
manifest,
|
|
269
|
+
content: createRXC(text)
|
|
315
270
|
};
|
|
316
271
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const buffer = Buffer.from(data, "utf-8");
|
|
322
|
-
await transport.write(location, buffer);
|
|
272
|
+
};
|
|
273
|
+
var textResolver = {
|
|
274
|
+
async resolve(rxr) {
|
|
275
|
+
return rxr.content.text();
|
|
323
276
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
277
|
+
};
|
|
278
|
+
var textType = {
|
|
279
|
+
name: "text",
|
|
280
|
+
aliases: ["txt", "plaintext"],
|
|
281
|
+
description: "Plain text content",
|
|
282
|
+
serializer: textSerializer,
|
|
283
|
+
resolver: textResolver
|
|
284
|
+
};
|
|
285
|
+
var jsonSerializer = {
|
|
286
|
+
async serialize(rxr) {
|
|
287
|
+
const json = await rxr.content.json();
|
|
288
|
+
return Buffer.from(JSON.stringify(json, null, 2), "utf-8");
|
|
289
|
+
},
|
|
290
|
+
async deserialize(data, manifest) {
|
|
291
|
+
const text = data.toString("utf-8");
|
|
292
|
+
return {
|
|
293
|
+
locator: parseRXL(manifest.toLocator()),
|
|
294
|
+
manifest,
|
|
295
|
+
content: createRXC(text)
|
|
296
|
+
};
|
|
334
297
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
await transport.delete(location);
|
|
298
|
+
};
|
|
299
|
+
var jsonResolver = {
|
|
300
|
+
async resolve(rxr) {
|
|
301
|
+
return rxr.content.json();
|
|
340
302
|
}
|
|
341
|
-
}
|
|
342
|
-
var
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
303
|
+
};
|
|
304
|
+
var jsonType = {
|
|
305
|
+
name: "json",
|
|
306
|
+
aliases: ["config", "manifest"],
|
|
307
|
+
description: "JSON content",
|
|
308
|
+
serializer: jsonSerializer,
|
|
309
|
+
resolver: jsonResolver
|
|
310
|
+
};
|
|
311
|
+
var binarySerializer = {
|
|
312
|
+
async serialize(rxr) {
|
|
313
|
+
return rxr.content.buffer();
|
|
314
|
+
},
|
|
315
|
+
async deserialize(data, manifest) {
|
|
316
|
+
return {
|
|
317
|
+
locator: parseRXL(manifest.toLocator()),
|
|
318
|
+
manifest,
|
|
319
|
+
content: createRXC(data)
|
|
320
|
+
};
|
|
347
321
|
}
|
|
348
|
-
|
|
349
|
-
|
|
322
|
+
};
|
|
323
|
+
var binaryResolver = {
|
|
324
|
+
async resolve(rxr) {
|
|
325
|
+
return rxr.content.buffer();
|
|
350
326
|
}
|
|
351
|
-
|
|
352
|
-
|
|
327
|
+
};
|
|
328
|
+
var binaryType = {
|
|
329
|
+
name: "binary",
|
|
330
|
+
aliases: ["bin", "blob", "raw"],
|
|
331
|
+
description: "Binary content",
|
|
332
|
+
serializer: binarySerializer,
|
|
333
|
+
resolver: binaryResolver
|
|
334
|
+
};
|
|
335
|
+
var builtinTypes = [textType, jsonType, binaryType];
|
|
336
|
+
// src/resource/TypeHandlerChain.ts
|
|
337
|
+
class TypeHandlerChain {
|
|
338
|
+
handlers = new Map;
|
|
339
|
+
register(type) {
|
|
340
|
+
this.handlers.set(type.name, type);
|
|
341
|
+
if (type.aliases) {
|
|
342
|
+
for (const alias of type.aliases) {
|
|
343
|
+
this.handlers.set(alias, type);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
353
346
|
}
|
|
354
|
-
|
|
355
|
-
|
|
347
|
+
registerAll(types) {
|
|
348
|
+
for (const type of types) {
|
|
349
|
+
this.register(type);
|
|
350
|
+
}
|
|
356
351
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
class BinarySemanticHandler {
|
|
361
|
-
name = "binary";
|
|
362
|
-
async resolve(transport, location, context) {
|
|
363
|
-
const buffer = await transport.read(location);
|
|
364
|
-
const meta = {
|
|
365
|
-
url: context.url,
|
|
366
|
-
semantic: context.semantic,
|
|
367
|
-
transport: context.transport,
|
|
368
|
-
location: context.location,
|
|
369
|
-
size: buffer.length,
|
|
370
|
-
resolvedAt: context.timestamp.toISOString()
|
|
371
|
-
};
|
|
372
|
-
return {
|
|
373
|
-
type: "binary",
|
|
374
|
-
content: buffer,
|
|
375
|
-
meta
|
|
376
|
-
};
|
|
352
|
+
canHandle(typeName) {
|
|
353
|
+
return this.handlers.has(typeName);
|
|
377
354
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
throw new SemanticError(`Transport "${transport.name}" does not support write operation`, this.name);
|
|
381
|
-
}
|
|
382
|
-
const buffer = toBuffer(data);
|
|
383
|
-
await transport.write(location, buffer);
|
|
355
|
+
getHandler(typeName) {
|
|
356
|
+
return this.handlers.get(typeName);
|
|
384
357
|
}
|
|
385
|
-
async
|
|
386
|
-
|
|
387
|
-
|
|
358
|
+
async serialize(rxr) {
|
|
359
|
+
const typeName = rxr.manifest.type;
|
|
360
|
+
const handler = this.handlers.get(typeName);
|
|
361
|
+
if (!handler) {
|
|
362
|
+
throw new ResourceTypeError(`Unsupported resource type: ${typeName}`);
|
|
388
363
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
364
|
+
return handler.serializer.serialize(rxr);
|
|
365
|
+
}
|
|
366
|
+
async deserialize(data, manifest) {
|
|
367
|
+
const typeName = manifest.type;
|
|
368
|
+
const handler = this.handlers.get(typeName);
|
|
369
|
+
if (!handler) {
|
|
370
|
+
throw new ResourceTypeError(`Unsupported resource type: ${typeName}`);
|
|
394
371
|
}
|
|
372
|
+
return handler.serializer.deserialize(data, manifest);
|
|
395
373
|
}
|
|
396
|
-
async
|
|
397
|
-
|
|
398
|
-
|
|
374
|
+
async resolve(rxr) {
|
|
375
|
+
const typeName = rxr.manifest.type;
|
|
376
|
+
const handler = this.handlers.get(typeName);
|
|
377
|
+
if (!handler) {
|
|
378
|
+
throw new ResourceTypeError(`Unsupported resource type: ${typeName}`);
|
|
399
379
|
}
|
|
400
|
-
|
|
380
|
+
return handler.resolver.resolve(rxr);
|
|
401
381
|
}
|
|
402
382
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
["binary", binaryHandler]
|
|
408
|
-
]);
|
|
409
|
-
function getSemanticHandler(name) {
|
|
410
|
-
const handler = handlers2.get(name);
|
|
411
|
-
if (!handler) {
|
|
412
|
-
throw new SemanticError(`Unsupported semantic type: ${name}`, name);
|
|
413
|
-
}
|
|
414
|
-
return handler;
|
|
415
|
-
}
|
|
416
|
-
function registerSemanticHandler(handler) {
|
|
417
|
-
handlers2.set(handler.name, handler);
|
|
418
|
-
}
|
|
419
|
-
// src/resolve.ts
|
|
420
|
-
function createContext(url, semantic, transport, location) {
|
|
421
|
-
return {
|
|
422
|
-
url,
|
|
423
|
-
semantic,
|
|
424
|
-
transport,
|
|
425
|
-
location,
|
|
426
|
-
timestamp: new Date
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
async function resolve2(url) {
|
|
430
|
-
const parsed = parseARP(url);
|
|
431
|
-
const transport = getTransportHandler(parsed.transport);
|
|
432
|
-
const semantic = getSemanticHandler(parsed.semantic);
|
|
433
|
-
const context = createContext(url, parsed.semantic, parsed.transport, parsed.location);
|
|
434
|
-
return semantic.resolve(transport, parsed.location, context);
|
|
435
|
-
}
|
|
436
|
-
async function deposit(url, data) {
|
|
437
|
-
const parsed = parseARP(url);
|
|
438
|
-
const transport = getTransportHandler(parsed.transport);
|
|
439
|
-
const semantic = getSemanticHandler(parsed.semantic);
|
|
440
|
-
if (!semantic.deposit) {
|
|
441
|
-
throw new SemanticError(`Semantic "${semantic.name}" does not support deposit operation`, parsed.semantic);
|
|
442
|
-
}
|
|
443
|
-
const context = createContext(url, parsed.semantic, parsed.transport, parsed.location);
|
|
444
|
-
await semantic.deposit(transport, parsed.location, data, context);
|
|
445
|
-
}
|
|
446
|
-
async function resourceExists(url) {
|
|
447
|
-
const parsed = parseARP(url);
|
|
448
|
-
const transport = getTransportHandler(parsed.transport);
|
|
449
|
-
const semantic = getSemanticHandler(parsed.semantic);
|
|
450
|
-
const context = createContext(url, parsed.semantic, parsed.transport, parsed.location);
|
|
451
|
-
if (semantic.exists) {
|
|
452
|
-
return semantic.exists(transport, parsed.location, context);
|
|
453
|
-
}
|
|
454
|
-
if (transport.exists) {
|
|
455
|
-
return transport.exists(parsed.location);
|
|
456
|
-
}
|
|
457
|
-
try {
|
|
458
|
-
await transport.read(parsed.location);
|
|
459
|
-
return true;
|
|
460
|
-
} catch {
|
|
461
|
-
return false;
|
|
383
|
+
function createTypeHandlerChain(types) {
|
|
384
|
+
const chain = new TypeHandlerChain;
|
|
385
|
+
if (types) {
|
|
386
|
+
chain.registerAll(types);
|
|
462
387
|
}
|
|
388
|
+
return chain;
|
|
463
389
|
}
|
|
464
|
-
async function resourceDelete(url) {
|
|
465
|
-
const parsed = parseARP(url);
|
|
466
|
-
const transport = getTransportHandler(parsed.transport);
|
|
467
|
-
const semantic = getSemanticHandler(parsed.semantic);
|
|
468
|
-
const context = createContext(url, parsed.semantic, parsed.transport, parsed.location);
|
|
469
|
-
if (semantic.delete) {
|
|
470
|
-
return semantic.delete(transport, parsed.location, context);
|
|
471
|
-
}
|
|
472
|
-
if (!transport.delete) {
|
|
473
|
-
throw new SemanticError(`Neither semantic "${semantic.name}" nor transport "${transport.name}" supports delete operation`, parsed.semantic);
|
|
474
|
-
}
|
|
475
|
-
await transport.delete(parsed.location);
|
|
476
|
-
}
|
|
477
|
-
// src/resource/registry.ts
|
|
478
|
-
function createResourceRegistry() {
|
|
479
|
-
const registry = new Map;
|
|
480
|
-
return {
|
|
481
|
-
register(definition) {
|
|
482
|
-
if (!/^[a-z][a-z0-9-]*$/.test(definition.name)) {
|
|
483
|
-
throw new ParseError(`Invalid resource name: "${definition.name}". Must start with lowercase letter and contain only lowercase letters, numbers, and hyphens.`, definition.name);
|
|
484
|
-
}
|
|
485
|
-
getSemanticHandler(definition.semantic);
|
|
486
|
-
getTransportHandler(definition.transport);
|
|
487
|
-
registry.set(definition.name, definition);
|
|
488
|
-
},
|
|
489
|
-
get(name) {
|
|
490
|
-
return registry.get(name);
|
|
491
|
-
},
|
|
492
|
-
has(name) {
|
|
493
|
-
return registry.has(name);
|
|
494
|
-
},
|
|
495
|
-
clear() {
|
|
496
|
-
registry.clear();
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
// src/index.ts
|
|
501
|
-
var VERSION = "0.4.0";
|
|
502
390
|
export {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
deepracticeHandler,
|
|
517
|
-
createResourceRegistry,
|
|
518
|
-
binaryHandler,
|
|
519
|
-
VERSION,
|
|
520
|
-
TransportError,
|
|
521
|
-
SemanticError,
|
|
391
|
+
textType,
|
|
392
|
+
parseRXL,
|
|
393
|
+
loadRXC,
|
|
394
|
+
jsonType,
|
|
395
|
+
getResourceType,
|
|
396
|
+
defineResourceType,
|
|
397
|
+
createTypeHandlerChain,
|
|
398
|
+
createRXM,
|
|
399
|
+
createRXC,
|
|
400
|
+
clearResourceTypes,
|
|
401
|
+
builtinTypes,
|
|
402
|
+
binaryType,
|
|
403
|
+
TypeHandlerChain,
|
|
522
404
|
ResourceXError,
|
|
523
|
-
|
|
405
|
+
ResourceTypeError,
|
|
406
|
+
ManifestError,
|
|
407
|
+
LocatorError,
|
|
408
|
+
ContentError
|
|
524
409
|
};
|
|
525
410
|
|
|
526
|
-
//# debugId=
|
|
411
|
+
//# debugId=B9B3CCB31ADA300B64756E2164756E21
|