@objectstack/rest 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +22 -0
- package/LICENSE +202 -0
- package/dist/index.cjs +788 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +254 -0
- package/dist/index.d.ts +254 -0
- package/dist/index.js +757 -0
- package/dist/index.js.map +1 -0
- package/package.json +30 -0
- package/src/index.ts +14 -0
- package/src/rest-api-plugin.ts +80 -0
- package/src/rest-server.ts +669 -0
- package/src/route-manager.ts +306 -0
- package/tsconfig.json +9 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
// src/route-manager.ts
|
|
2
|
+
var RouteManager = class {
|
|
3
|
+
constructor(server) {
|
|
4
|
+
this.server = server;
|
|
5
|
+
this.routes = /* @__PURE__ */ new Map();
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Register a route
|
|
9
|
+
* @param entry - Route entry with method, path, handler, and metadata
|
|
10
|
+
*/
|
|
11
|
+
register(entry) {
|
|
12
|
+
if (typeof entry.handler === "string") {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`String-based route handlers are not supported yet. Received handler identifier "${entry.handler}". Please provide a RouteHandler function instead.`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
const handler = entry.handler;
|
|
18
|
+
const routeEntry = {
|
|
19
|
+
method: entry.method,
|
|
20
|
+
path: entry.path,
|
|
21
|
+
handler,
|
|
22
|
+
metadata: entry.metadata,
|
|
23
|
+
security: entry.security
|
|
24
|
+
};
|
|
25
|
+
const key = this.getRouteKey(entry.method, entry.path);
|
|
26
|
+
this.routes.set(key, routeEntry);
|
|
27
|
+
this.registerWithServer(routeEntry);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Register multiple routes
|
|
31
|
+
* @param entries - Array of route entries
|
|
32
|
+
*/
|
|
33
|
+
registerMany(entries) {
|
|
34
|
+
entries.forEach((entry) => this.register(entry));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Unregister a route
|
|
38
|
+
* @param method - HTTP method
|
|
39
|
+
* @param path - Route path
|
|
40
|
+
*/
|
|
41
|
+
unregister(method, path) {
|
|
42
|
+
const key = this.getRouteKey(method, path);
|
|
43
|
+
this.routes.delete(key);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get route by method and path
|
|
47
|
+
* @param method - HTTP method
|
|
48
|
+
* @param path - Route path
|
|
49
|
+
*/
|
|
50
|
+
get(method, path) {
|
|
51
|
+
const key = this.getRouteKey(method, path);
|
|
52
|
+
return this.routes.get(key);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get all routes
|
|
56
|
+
*/
|
|
57
|
+
getAll() {
|
|
58
|
+
return Array.from(this.routes.values());
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get routes by method
|
|
62
|
+
* @param method - HTTP method
|
|
63
|
+
*/
|
|
64
|
+
getByMethod(method) {
|
|
65
|
+
return this.getAll().filter((route) => route.method === method);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get routes by path prefix
|
|
69
|
+
* @param prefix - Path prefix
|
|
70
|
+
*/
|
|
71
|
+
getByPrefix(prefix) {
|
|
72
|
+
return this.getAll().filter((route) => route.path.startsWith(prefix));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get routes by tag
|
|
76
|
+
* @param tag - Tag name
|
|
77
|
+
*/
|
|
78
|
+
getByTag(tag) {
|
|
79
|
+
return this.getAll().filter(
|
|
80
|
+
(route) => route.metadata?.tags?.includes(tag)
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Create a route group with common prefix
|
|
85
|
+
* @param prefix - Common path prefix
|
|
86
|
+
* @param configure - Function to configure routes in the group
|
|
87
|
+
*/
|
|
88
|
+
group(prefix, configure) {
|
|
89
|
+
const builder = new RouteGroupBuilder(this, prefix);
|
|
90
|
+
configure(builder);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get route count
|
|
94
|
+
*/
|
|
95
|
+
count() {
|
|
96
|
+
return this.routes.size;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Clear all routes
|
|
100
|
+
*/
|
|
101
|
+
clear() {
|
|
102
|
+
this.routes.clear();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get route key for storage
|
|
106
|
+
*/
|
|
107
|
+
getRouteKey(method, path) {
|
|
108
|
+
return `${method}:${path}`;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Register route with underlying server
|
|
112
|
+
*/
|
|
113
|
+
registerWithServer(entry) {
|
|
114
|
+
const { method, path, handler } = entry;
|
|
115
|
+
switch (method) {
|
|
116
|
+
case "GET":
|
|
117
|
+
this.server.get(path, handler);
|
|
118
|
+
break;
|
|
119
|
+
case "POST":
|
|
120
|
+
this.server.post(path, handler);
|
|
121
|
+
break;
|
|
122
|
+
case "PUT":
|
|
123
|
+
this.server.put(path, handler);
|
|
124
|
+
break;
|
|
125
|
+
case "DELETE":
|
|
126
|
+
this.server.delete(path, handler);
|
|
127
|
+
break;
|
|
128
|
+
case "PATCH":
|
|
129
|
+
this.server.patch(path, handler);
|
|
130
|
+
break;
|
|
131
|
+
default:
|
|
132
|
+
throw new Error(`Unsupported HTTP method: ${method}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
var RouteGroupBuilder = class {
|
|
137
|
+
constructor(manager, prefix) {
|
|
138
|
+
this.manager = manager;
|
|
139
|
+
this.prefix = prefix;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Register GET route in group
|
|
143
|
+
*/
|
|
144
|
+
get(path, handler, metadata) {
|
|
145
|
+
this.manager.register({
|
|
146
|
+
method: "GET",
|
|
147
|
+
path: this.resolvePath(path),
|
|
148
|
+
handler,
|
|
149
|
+
metadata
|
|
150
|
+
});
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Register POST route in group
|
|
155
|
+
*/
|
|
156
|
+
post(path, handler, metadata) {
|
|
157
|
+
this.manager.register({
|
|
158
|
+
method: "POST",
|
|
159
|
+
path: this.resolvePath(path),
|
|
160
|
+
handler,
|
|
161
|
+
metadata
|
|
162
|
+
});
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Register PUT route in group
|
|
167
|
+
*/
|
|
168
|
+
put(path, handler, metadata) {
|
|
169
|
+
this.manager.register({
|
|
170
|
+
method: "PUT",
|
|
171
|
+
path: this.resolvePath(path),
|
|
172
|
+
handler,
|
|
173
|
+
metadata
|
|
174
|
+
});
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Register PATCH route in group
|
|
179
|
+
*/
|
|
180
|
+
patch(path, handler, metadata) {
|
|
181
|
+
this.manager.register({
|
|
182
|
+
method: "PATCH",
|
|
183
|
+
path: this.resolvePath(path),
|
|
184
|
+
handler,
|
|
185
|
+
metadata
|
|
186
|
+
});
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Register DELETE route in group
|
|
191
|
+
*/
|
|
192
|
+
delete(path, handler, metadata) {
|
|
193
|
+
this.manager.register({
|
|
194
|
+
method: "DELETE",
|
|
195
|
+
path: this.resolvePath(path),
|
|
196
|
+
handler,
|
|
197
|
+
metadata
|
|
198
|
+
});
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Resolve full path with prefix
|
|
203
|
+
*/
|
|
204
|
+
resolvePath(path) {
|
|
205
|
+
const normalizedPrefix = this.prefix.endsWith("/") ? this.prefix.slice(0, -1) : this.prefix;
|
|
206
|
+
const normalizedPath = path.startsWith("/") ? path : "/" + path;
|
|
207
|
+
return normalizedPrefix + normalizedPath;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// src/rest-server.ts
|
|
212
|
+
var RestServer = class {
|
|
213
|
+
constructor(server, protocol, config = {}) {
|
|
214
|
+
this.protocol = protocol;
|
|
215
|
+
this.config = this.normalizeConfig(config);
|
|
216
|
+
this.routeManager = new RouteManager(server);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Normalize configuration with defaults
|
|
220
|
+
*/
|
|
221
|
+
normalizeConfig(config) {
|
|
222
|
+
const api = config.api ?? {};
|
|
223
|
+
const crud = config.crud ?? {};
|
|
224
|
+
const metadata = config.metadata ?? {};
|
|
225
|
+
const batch = config.batch ?? {};
|
|
226
|
+
const routes = config.routes ?? {};
|
|
227
|
+
return {
|
|
228
|
+
api: {
|
|
229
|
+
version: api.version ?? "v1",
|
|
230
|
+
basePath: api.basePath ?? "/api",
|
|
231
|
+
apiPath: api.apiPath,
|
|
232
|
+
enableCrud: api.enableCrud ?? true,
|
|
233
|
+
enableMetadata: api.enableMetadata ?? true,
|
|
234
|
+
enableUi: api.enableUi ?? true,
|
|
235
|
+
enableBatch: api.enableBatch ?? true,
|
|
236
|
+
enableDiscovery: api.enableDiscovery ?? true,
|
|
237
|
+
documentation: api.documentation,
|
|
238
|
+
responseFormat: api.responseFormat
|
|
239
|
+
},
|
|
240
|
+
crud: {
|
|
241
|
+
operations: crud.operations ?? {
|
|
242
|
+
create: true,
|
|
243
|
+
read: true,
|
|
244
|
+
update: true,
|
|
245
|
+
delete: true,
|
|
246
|
+
list: true
|
|
247
|
+
},
|
|
248
|
+
patterns: crud.patterns,
|
|
249
|
+
dataPrefix: crud.dataPrefix ?? "/data",
|
|
250
|
+
objectParamStyle: crud.objectParamStyle ?? "path"
|
|
251
|
+
},
|
|
252
|
+
metadata: {
|
|
253
|
+
prefix: metadata.prefix ?? "/meta",
|
|
254
|
+
enableCache: metadata.enableCache ?? true,
|
|
255
|
+
cacheTtl: metadata.cacheTtl ?? 3600,
|
|
256
|
+
endpoints: metadata.endpoints ?? {
|
|
257
|
+
types: true,
|
|
258
|
+
items: true,
|
|
259
|
+
item: true,
|
|
260
|
+
schema: true
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
batch: {
|
|
264
|
+
maxBatchSize: batch.maxBatchSize ?? 200,
|
|
265
|
+
enableBatchEndpoint: batch.enableBatchEndpoint ?? true,
|
|
266
|
+
operations: batch.operations ?? {
|
|
267
|
+
createMany: true,
|
|
268
|
+
updateMany: true,
|
|
269
|
+
deleteMany: true,
|
|
270
|
+
upsertMany: true
|
|
271
|
+
},
|
|
272
|
+
defaultAtomic: batch.defaultAtomic ?? true
|
|
273
|
+
},
|
|
274
|
+
routes: {
|
|
275
|
+
includeObjects: routes.includeObjects,
|
|
276
|
+
excludeObjects: routes.excludeObjects,
|
|
277
|
+
nameTransform: routes.nameTransform ?? "none",
|
|
278
|
+
overrides: routes.overrides
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get the full API base path
|
|
284
|
+
*/
|
|
285
|
+
getApiBasePath() {
|
|
286
|
+
const { api } = this.config;
|
|
287
|
+
return api.apiPath ?? `${api.basePath}/${api.version}`;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Register all REST API routes
|
|
291
|
+
*/
|
|
292
|
+
registerRoutes() {
|
|
293
|
+
const basePath = this.getApiBasePath();
|
|
294
|
+
if (this.config.api.enableDiscovery) {
|
|
295
|
+
this.registerDiscoveryEndpoints(basePath);
|
|
296
|
+
}
|
|
297
|
+
if (this.config.api.enableMetadata) {
|
|
298
|
+
this.registerMetadataEndpoints(basePath);
|
|
299
|
+
}
|
|
300
|
+
if (this.config.api.enableUi) {
|
|
301
|
+
this.registerUiEndpoints(basePath);
|
|
302
|
+
}
|
|
303
|
+
if (this.config.api.enableCrud) {
|
|
304
|
+
this.registerCrudEndpoints(basePath);
|
|
305
|
+
}
|
|
306
|
+
if (this.config.api.enableBatch) {
|
|
307
|
+
this.registerBatchEndpoints(basePath);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Register discovery endpoints
|
|
312
|
+
*/
|
|
313
|
+
registerDiscoveryEndpoints(basePath) {
|
|
314
|
+
this.routeManager.register({
|
|
315
|
+
method: "GET",
|
|
316
|
+
path: basePath,
|
|
317
|
+
handler: async (_req, res) => {
|
|
318
|
+
try {
|
|
319
|
+
const discovery = await this.protocol.getDiscovery();
|
|
320
|
+
discovery.version = this.config.api.version;
|
|
321
|
+
if (discovery.endpoints) {
|
|
322
|
+
if (this.config.api.enableCrud) {
|
|
323
|
+
discovery.endpoints.data = `${basePath}${this.config.crud.dataPrefix}`;
|
|
324
|
+
}
|
|
325
|
+
if (this.config.api.enableMetadata) {
|
|
326
|
+
discovery.endpoints.metadata = `${basePath}${this.config.metadata.prefix}`;
|
|
327
|
+
}
|
|
328
|
+
if (this.config.api.enableUi) {
|
|
329
|
+
discovery.endpoints.ui = `${basePath}/ui`;
|
|
330
|
+
}
|
|
331
|
+
if (discovery.endpoints.auth) {
|
|
332
|
+
discovery.endpoints.auth = `${basePath}/auth`;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
res.json(discovery);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
res.status(500).json({ error: error.message });
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
metadata: {
|
|
341
|
+
summary: "Get API discovery information",
|
|
342
|
+
tags: ["discovery"]
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Register metadata endpoints
|
|
348
|
+
*/
|
|
349
|
+
registerMetadataEndpoints(basePath) {
|
|
350
|
+
const { metadata } = this.config;
|
|
351
|
+
const metaPath = `${basePath}${metadata.prefix}`;
|
|
352
|
+
if (metadata.endpoints.types !== false) {
|
|
353
|
+
this.routeManager.register({
|
|
354
|
+
method: "GET",
|
|
355
|
+
path: metaPath,
|
|
356
|
+
handler: async (_req, res) => {
|
|
357
|
+
try {
|
|
358
|
+
const types = await this.protocol.getMetaTypes();
|
|
359
|
+
res.json(types);
|
|
360
|
+
} catch (error) {
|
|
361
|
+
res.status(500).json({ error: error.message });
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
metadata: {
|
|
365
|
+
summary: "List all metadata types",
|
|
366
|
+
tags: ["metadata"]
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
if (metadata.endpoints.items !== false) {
|
|
371
|
+
this.routeManager.register({
|
|
372
|
+
method: "GET",
|
|
373
|
+
path: `${metaPath}/:type`,
|
|
374
|
+
handler: async (req, res) => {
|
|
375
|
+
try {
|
|
376
|
+
const items = await this.protocol.getMetaItems({ type: req.params.type });
|
|
377
|
+
res.json(items);
|
|
378
|
+
} catch (error) {
|
|
379
|
+
res.status(404).json({ error: error.message });
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
metadata: {
|
|
383
|
+
summary: "List metadata items of a type",
|
|
384
|
+
tags: ["metadata"]
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
if (metadata.endpoints.item !== false) {
|
|
389
|
+
this.routeManager.register({
|
|
390
|
+
method: "GET",
|
|
391
|
+
path: `${metaPath}/:type/:name`,
|
|
392
|
+
handler: async (req, res) => {
|
|
393
|
+
try {
|
|
394
|
+
if (metadata.enableCache && this.protocol.getMetaItemCached) {
|
|
395
|
+
const cacheRequest = {
|
|
396
|
+
ifNoneMatch: req.headers["if-none-match"],
|
|
397
|
+
ifModifiedSince: req.headers["if-modified-since"]
|
|
398
|
+
};
|
|
399
|
+
const result = await this.protocol.getMetaItemCached({
|
|
400
|
+
type: req.params.type,
|
|
401
|
+
name: req.params.name,
|
|
402
|
+
cacheRequest
|
|
403
|
+
});
|
|
404
|
+
if (result.notModified) {
|
|
405
|
+
res.status(304).send();
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (result.etag) {
|
|
409
|
+
const etagValue = result.etag.weak ? `W/"${result.etag.value}"` : `"${result.etag.value}"`;
|
|
410
|
+
res.header("ETag", etagValue);
|
|
411
|
+
}
|
|
412
|
+
if (result.lastModified) {
|
|
413
|
+
res.header("Last-Modified", new Date(result.lastModified).toUTCString());
|
|
414
|
+
}
|
|
415
|
+
if (result.cacheControl) {
|
|
416
|
+
const directives = result.cacheControl.directives.join(", ");
|
|
417
|
+
const maxAge = result.cacheControl.maxAge ? `, max-age=${result.cacheControl.maxAge}` : "";
|
|
418
|
+
res.header("Cache-Control", directives + maxAge);
|
|
419
|
+
}
|
|
420
|
+
res.json(result.data);
|
|
421
|
+
} else {
|
|
422
|
+
const item = await this.protocol.getMetaItem({ type: req.params.type, name: req.params.name });
|
|
423
|
+
res.json(item);
|
|
424
|
+
}
|
|
425
|
+
} catch (error) {
|
|
426
|
+
res.status(404).json({ error: error.message });
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
metadata: {
|
|
430
|
+
summary: "Get specific metadata item",
|
|
431
|
+
tags: ["metadata"]
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
this.routeManager.register({
|
|
436
|
+
method: "PUT",
|
|
437
|
+
path: `${metaPath}/:type/:name`,
|
|
438
|
+
handler: async (req, res) => {
|
|
439
|
+
try {
|
|
440
|
+
if (!this.protocol.saveMetaItem) {
|
|
441
|
+
res.status(501).json({ error: "Save operation not supported by protocol implementation" });
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const result = await this.protocol.saveMetaItem({
|
|
445
|
+
type: req.params.type,
|
|
446
|
+
name: req.params.name,
|
|
447
|
+
item: req.body
|
|
448
|
+
});
|
|
449
|
+
res.json(result);
|
|
450
|
+
} catch (error) {
|
|
451
|
+
res.status(400).json({ error: error.message });
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
metadata: {
|
|
455
|
+
summary: "Save specific metadata item",
|
|
456
|
+
tags: ["metadata"]
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Register UI endpoints
|
|
462
|
+
*/
|
|
463
|
+
registerUiEndpoints(basePath) {
|
|
464
|
+
const uiPath = `${basePath}/ui`;
|
|
465
|
+
this.routeManager.register({
|
|
466
|
+
method: "GET",
|
|
467
|
+
path: `${uiPath}/view/:object/:type`,
|
|
468
|
+
handler: async (req, res) => {
|
|
469
|
+
try {
|
|
470
|
+
if (this.protocol.getUiView) {
|
|
471
|
+
const view = await this.protocol.getUiView({
|
|
472
|
+
object: req.params.object,
|
|
473
|
+
type: req.params.type
|
|
474
|
+
});
|
|
475
|
+
res.json(view);
|
|
476
|
+
} else {
|
|
477
|
+
res.status(501).json({ error: "UI View resolution not supported by protocol implementation" });
|
|
478
|
+
}
|
|
479
|
+
} catch (error) {
|
|
480
|
+
res.status(404).json({ error: error.message });
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
metadata: {
|
|
484
|
+
summary: "Resolve UI View for object",
|
|
485
|
+
tags: ["ui"]
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Register CRUD endpoints for data operations
|
|
491
|
+
*/
|
|
492
|
+
registerCrudEndpoints(basePath) {
|
|
493
|
+
const { crud } = this.config;
|
|
494
|
+
const dataPath = `${basePath}${crud.dataPrefix}`;
|
|
495
|
+
const operations = crud.operations;
|
|
496
|
+
if (operations.list) {
|
|
497
|
+
this.routeManager.register({
|
|
498
|
+
method: "GET",
|
|
499
|
+
path: `${dataPath}/:object`,
|
|
500
|
+
handler: async (req, res) => {
|
|
501
|
+
try {
|
|
502
|
+
const result = await this.protocol.findData({
|
|
503
|
+
object: req.params.object,
|
|
504
|
+
query: req.query
|
|
505
|
+
});
|
|
506
|
+
res.json(result);
|
|
507
|
+
} catch (error) {
|
|
508
|
+
res.status(400).json({ error: error.message });
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
metadata: {
|
|
512
|
+
summary: "Query records",
|
|
513
|
+
tags: ["data", "crud"]
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
if (operations.read) {
|
|
518
|
+
this.routeManager.register({
|
|
519
|
+
method: "GET",
|
|
520
|
+
path: `${dataPath}/:object/:id`,
|
|
521
|
+
handler: async (req, res) => {
|
|
522
|
+
try {
|
|
523
|
+
const result = await this.protocol.getData({
|
|
524
|
+
object: req.params.object,
|
|
525
|
+
id: req.params.id
|
|
526
|
+
});
|
|
527
|
+
res.json(result);
|
|
528
|
+
} catch (error) {
|
|
529
|
+
res.status(404).json({ error: error.message });
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
metadata: {
|
|
533
|
+
summary: "Get record by ID",
|
|
534
|
+
tags: ["data", "crud"]
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
if (operations.create) {
|
|
539
|
+
this.routeManager.register({
|
|
540
|
+
method: "POST",
|
|
541
|
+
path: `${dataPath}/:object`,
|
|
542
|
+
handler: async (req, res) => {
|
|
543
|
+
try {
|
|
544
|
+
const result = await this.protocol.createData({
|
|
545
|
+
object: req.params.object,
|
|
546
|
+
data: req.body
|
|
547
|
+
});
|
|
548
|
+
res.status(201).json(result);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
res.status(400).json({ error: error.message });
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
metadata: {
|
|
554
|
+
summary: "Create record",
|
|
555
|
+
tags: ["data", "crud"]
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
if (operations.update) {
|
|
560
|
+
this.routeManager.register({
|
|
561
|
+
method: "PATCH",
|
|
562
|
+
path: `${dataPath}/:object/:id`,
|
|
563
|
+
handler: async (req, res) => {
|
|
564
|
+
try {
|
|
565
|
+
const result = await this.protocol.updateData({
|
|
566
|
+
object: req.params.object,
|
|
567
|
+
id: req.params.id,
|
|
568
|
+
data: req.body
|
|
569
|
+
});
|
|
570
|
+
res.json(result);
|
|
571
|
+
} catch (error) {
|
|
572
|
+
res.status(400).json({ error: error.message });
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
metadata: {
|
|
576
|
+
summary: "Update record",
|
|
577
|
+
tags: ["data", "crud"]
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
if (operations.delete) {
|
|
582
|
+
this.routeManager.register({
|
|
583
|
+
method: "DELETE",
|
|
584
|
+
path: `${dataPath}/:object/:id`,
|
|
585
|
+
handler: async (req, res) => {
|
|
586
|
+
try {
|
|
587
|
+
const result = await this.protocol.deleteData({
|
|
588
|
+
object: req.params.object,
|
|
589
|
+
id: req.params.id
|
|
590
|
+
});
|
|
591
|
+
res.json(result);
|
|
592
|
+
} catch (error) {
|
|
593
|
+
res.status(400).json({ error: error.message });
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
metadata: {
|
|
597
|
+
summary: "Delete record",
|
|
598
|
+
tags: ["data", "crud"]
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Register batch operation endpoints
|
|
605
|
+
*/
|
|
606
|
+
registerBatchEndpoints(basePath) {
|
|
607
|
+
const { crud, batch } = this.config;
|
|
608
|
+
const dataPath = `${basePath}${crud.dataPrefix}`;
|
|
609
|
+
const operations = batch.operations;
|
|
610
|
+
if (batch.enableBatchEndpoint && this.protocol.batchData) {
|
|
611
|
+
this.routeManager.register({
|
|
612
|
+
method: "POST",
|
|
613
|
+
path: `${dataPath}/:object/batch`,
|
|
614
|
+
handler: async (req, res) => {
|
|
615
|
+
try {
|
|
616
|
+
const result = await this.protocol.batchData({
|
|
617
|
+
object: req.params.object,
|
|
618
|
+
request: req.body
|
|
619
|
+
});
|
|
620
|
+
res.json(result);
|
|
621
|
+
} catch (error) {
|
|
622
|
+
res.status(400).json({ error: error.message });
|
|
623
|
+
}
|
|
624
|
+
},
|
|
625
|
+
metadata: {
|
|
626
|
+
summary: "Batch operations",
|
|
627
|
+
tags: ["data", "batch"]
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
if (operations.createMany && this.protocol.createManyData) {
|
|
632
|
+
this.routeManager.register({
|
|
633
|
+
method: "POST",
|
|
634
|
+
path: `${dataPath}/:object/createMany`,
|
|
635
|
+
handler: async (req, res) => {
|
|
636
|
+
try {
|
|
637
|
+
const result = await this.protocol.createManyData({
|
|
638
|
+
object: req.params.object,
|
|
639
|
+
records: req.body || []
|
|
640
|
+
});
|
|
641
|
+
res.status(201).json(result);
|
|
642
|
+
} catch (error) {
|
|
643
|
+
res.status(400).json({ error: error.message });
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
metadata: {
|
|
647
|
+
summary: "Create multiple records",
|
|
648
|
+
tags: ["data", "batch"]
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
if (operations.updateMany && this.protocol.updateManyData) {
|
|
653
|
+
this.routeManager.register({
|
|
654
|
+
method: "POST",
|
|
655
|
+
path: `${dataPath}/:object/updateMany`,
|
|
656
|
+
handler: async (req, res) => {
|
|
657
|
+
try {
|
|
658
|
+
const result = await this.protocol.updateManyData({
|
|
659
|
+
object: req.params.object,
|
|
660
|
+
...req.body
|
|
661
|
+
});
|
|
662
|
+
res.json(result);
|
|
663
|
+
} catch (error) {
|
|
664
|
+
res.status(400).json({ error: error.message });
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
metadata: {
|
|
668
|
+
summary: "Update multiple records",
|
|
669
|
+
tags: ["data", "batch"]
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
if (operations.deleteMany && this.protocol.deleteManyData) {
|
|
674
|
+
this.routeManager.register({
|
|
675
|
+
method: "POST",
|
|
676
|
+
path: `${dataPath}/:object/deleteMany`,
|
|
677
|
+
handler: async (req, res) => {
|
|
678
|
+
try {
|
|
679
|
+
const result = await this.protocol.deleteManyData({
|
|
680
|
+
object: req.params.object,
|
|
681
|
+
...req.body
|
|
682
|
+
});
|
|
683
|
+
res.json(result);
|
|
684
|
+
} catch (error) {
|
|
685
|
+
res.status(400).json({ error: error.message });
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
metadata: {
|
|
689
|
+
summary: "Delete multiple records",
|
|
690
|
+
tags: ["data", "batch"]
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Get the route manager
|
|
697
|
+
*/
|
|
698
|
+
getRouteManager() {
|
|
699
|
+
return this.routeManager;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Get all registered routes
|
|
703
|
+
*/
|
|
704
|
+
getRoutes() {
|
|
705
|
+
return this.routeManager.getAll();
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
// src/rest-api-plugin.ts
|
|
710
|
+
function createRestApiPlugin(config = {}) {
|
|
711
|
+
return {
|
|
712
|
+
name: "com.objectstack.rest.api",
|
|
713
|
+
version: "1.0.0",
|
|
714
|
+
init: async (_ctx) => {
|
|
715
|
+
},
|
|
716
|
+
start: async (ctx) => {
|
|
717
|
+
const serverService = config.serverServiceName || "http.server";
|
|
718
|
+
const protocolService = config.protocolServiceName || "protocol";
|
|
719
|
+
let server;
|
|
720
|
+
let protocol;
|
|
721
|
+
try {
|
|
722
|
+
server = ctx.getService(serverService);
|
|
723
|
+
} catch (e) {
|
|
724
|
+
}
|
|
725
|
+
try {
|
|
726
|
+
protocol = ctx.getService(protocolService);
|
|
727
|
+
} catch (e) {
|
|
728
|
+
}
|
|
729
|
+
if (!server) {
|
|
730
|
+
ctx.logger.warn(`RestApiPlugin: HTTP Server service '${serverService}' not found. REST routes skipped.`);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
if (!protocol) {
|
|
734
|
+
ctx.logger.warn(`RestApiPlugin: Protocol service '${protocolService}' not found. REST routes skipped.`);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
ctx.logger.info("Hydrating REST API from Protocol...");
|
|
738
|
+
try {
|
|
739
|
+
const restServer = new RestServer(server, protocol, config.api);
|
|
740
|
+
restServer.registerRoutes();
|
|
741
|
+
ctx.logger.info("REST API successfully registered");
|
|
742
|
+
} catch (err) {
|
|
743
|
+
ctx.logger.error("Failed to register REST API routes", { error: err.message });
|
|
744
|
+
throw err;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
var createApiRegistryPlugin = createRestApiPlugin;
|
|
750
|
+
export {
|
|
751
|
+
RestServer,
|
|
752
|
+
RouteGroupBuilder,
|
|
753
|
+
RouteManager,
|
|
754
|
+
createApiRegistryPlugin,
|
|
755
|
+
createRestApiPlugin
|
|
756
|
+
};
|
|
757
|
+
//# sourceMappingURL=index.js.map
|