@thingd/cli 0.32.0 → 0.32.2

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.
@@ -1,317 +0,0 @@
1
- import { parseFilter, parseIntParam, parseSortBy, readBody, sendData, sendDataList, sendError, } from "./helpers.js";
2
- function matchRoute(pathname, pattern) {
3
- const patternParts = pattern.split("/");
4
- const pathParts = pathname.split("/");
5
- if (patternParts.length !== pathParts.length) {
6
- return null;
7
- }
8
- const match = {};
9
- for (let i = 0; i < patternParts.length; i++) {
10
- const pp = patternParts[i] ?? "";
11
- const xp = pathParts[i] ?? "";
12
- if (pp.startsWith(":")) {
13
- const key = pp.slice(1);
14
- match[key] = xp;
15
- }
16
- else if (pp !== xp) {
17
- return null;
18
- }
19
- }
20
- return match;
21
- }
22
- export async function handleRestRequest(db, req, res, pathname) {
23
- const method = req.method ?? "GET";
24
- const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
25
- try {
26
- // ─── Health ──────────────────────────────────────────────────
27
- if (pathname === "/v1/health" && method === "GET") {
28
- const [objects, events, links, queues, collections, streams] = await Promise.all([
29
- db.countObjects(),
30
- db.countEvents(),
31
- db.countLinks(),
32
- db.listQueues(),
33
- db.listCollections(),
34
- db.listStreams(),
35
- ]);
36
- sendData(res, {
37
- status: "ok",
38
- version: "0.31.0",
39
- counts: {
40
- objects,
41
- events,
42
- links,
43
- queues: queues.length,
44
- collections: collections.length,
45
- streams: streams.length,
46
- },
47
- });
48
- return;
49
- }
50
- // ─── Counts ──────────────────────────────────────────────────
51
- if (pathname === "/v1/counts/objects" && method === "GET") {
52
- sendData(res, { count: await db.countObjects() });
53
- return;
54
- }
55
- if (pathname === "/v1/counts/events" && method === "GET") {
56
- sendData(res, { count: await db.countEvents() });
57
- return;
58
- }
59
- if (pathname === "/v1/counts/links" && method === "GET") {
60
- sendData(res, { count: await db.countLinks() });
61
- return;
62
- }
63
- // ─── Collections / Streams / Queues ──────────────────────────
64
- if (pathname === "/v1/collections" && method === "GET") {
65
- sendDataList(res, await db.listCollections());
66
- return;
67
- }
68
- if (pathname === "/v1/streams" && method === "GET") {
69
- sendDataList(res, await db.listStreams());
70
- return;
71
- }
72
- if (pathname === "/v1/queues" && method === "GET") {
73
- sendDataList(res, await db.listQueues());
74
- return;
75
- }
76
- // ─── Objects ─────────────────────────────────────────────────
77
- // GET /v1/objects?collection=...&filter.x=...&sortBy=...&limit=...&offset=...
78
- if (pathname === "/v1/objects" && method === "GET") {
79
- const collection = url.searchParams.get("collection");
80
- if (!collection) {
81
- sendError(res, 400, "bad_request", "Query parameter 'collection' is required");
82
- return;
83
- }
84
- const filter = parseFilter(url.searchParams);
85
- const sortBy = parseSortBy(url.searchParams);
86
- const limit = parseIntParam(url.searchParams.get("limit"));
87
- const offset = parseIntParam(url.searchParams.get("offset"));
88
- const objects = await db.listObjects(collection, { filter, sortBy, limit, offset });
89
- sendDataList(res, objects);
90
- return;
91
- }
92
- // PUT /v1/objects/:collection/:id
93
- const objMatch = matchRoute(pathname, "/v1/objects/:collection/:id");
94
- if (objMatch?.collection && objMatch?.id && method === "PUT") {
95
- const body = JSON.parse(await readBody(req));
96
- body.id = objMatch.id;
97
- const result = await db.put(objMatch.collection, body);
98
- sendData(res, result);
99
- return;
100
- }
101
- // GET /v1/objects/:collection/:id
102
- if (objMatch?.collection && objMatch?.id && method === "GET") {
103
- const result = await db.get(objMatch.collection, objMatch.id);
104
- if (!result) {
105
- sendError(res, 404, "not_found", `Object '${objMatch.id}' not found in collection '${objMatch.collection}'`);
106
- return;
107
- }
108
- sendData(res, result);
109
- return;
110
- }
111
- // DELETE /v1/objects/:collection/:id
112
- if (objMatch?.collection && objMatch?.id && method === "DELETE") {
113
- const result = await db.delete(objMatch.collection, objMatch.id);
114
- sendData(res, result);
115
- return;
116
- }
117
- // PUT /v1/objects/batch?collection=...
118
- if (pathname === "/v1/objects/batch" && method === "PUT") {
119
- const collection = url.searchParams.get("collection");
120
- if (!collection) {
121
- sendError(res, 400, "bad_request", "Query parameter 'collection' is required");
122
- return;
123
- }
124
- const body = JSON.parse(await readBody(req));
125
- const objects = Array.isArray(body) ? body : body.objects;
126
- if (!Array.isArray(objects)) {
127
- sendError(res, 400, "bad_request", "Body must be an array or { objects: [...] }");
128
- return;
129
- }
130
- const result = await db.putBatch(collection, objects);
131
- sendData(res, result);
132
- return;
133
- }
134
- // DELETE /v1/objects/batch?collection=...
135
- if (pathname === "/v1/objects/batch" && method === "DELETE") {
136
- const collection = url.searchParams.get("collection");
137
- if (!collection) {
138
- sendError(res, 400, "bad_request", "Query parameter 'collection' is required");
139
- return;
140
- }
141
- const body = JSON.parse(await readBody(req));
142
- const ids = Array.isArray(body) ? body : body.ids;
143
- if (!Array.isArray(ids)) {
144
- sendError(res, 400, "bad_request", "Body must be an array or { ids: [...] }");
145
- return;
146
- }
147
- const count = await db.deleteBatch(collection, ids);
148
- sendData(res, { deleted: count });
149
- return;
150
- }
151
- // ─── Search ──────────────────────────────────────────────────
152
- if (pathname === "/v1/search" && method === "POST") {
153
- const body = JSON.parse(await readBody(req));
154
- if (!body.query) {
155
- sendError(res, 400, "bad_request", "Field 'query' is required");
156
- return;
157
- }
158
- const results = await db.search(body.query, {
159
- collections: body.collections,
160
- limit: body.limit,
161
- filter: body.filter,
162
- });
163
- sendData(res, results);
164
- return;
165
- }
166
- // ─── Events ──────────────────────────────────────────────────
167
- // POST /v1/events/:stream
168
- const streamMatch = matchRoute(pathname, "/v1/events/:stream");
169
- if (streamMatch?.stream && method === "POST") {
170
- const body = JSON.parse(await readBody(req));
171
- if (!body.type) {
172
- sendError(res, 400, "bad_request", "Field 'type' is required");
173
- return;
174
- }
175
- const event = await db.events.append(streamMatch.stream, body);
176
- sendData(res, event);
177
- return;
178
- }
179
- // GET /v1/events?stream=...&fromSequence=...&limit=...
180
- if (pathname === "/v1/events" && method === "GET") {
181
- const stream = url.searchParams.get("stream") ?? undefined;
182
- const fromSequence = parseIntParam(url.searchParams.get("fromSequence"));
183
- const limit = parseIntParam(url.searchParams.get("limit"));
184
- const events = await db.events.list(stream, { fromSequence, limit });
185
- sendDataList(res, events);
186
- return;
187
- }
188
- // ─── Queues ──────────────────────────────────────────────────
189
- // POST /v1/queues/:queue/push
190
- const pushMatch = matchRoute(pathname, "/v1/queues/:queue/push");
191
- if (pushMatch?.queue && method === "POST") {
192
- const body = JSON.parse(await readBody(req));
193
- const job = await db.queue(pushMatch.queue).push(body.payload ?? body, {
194
- idempotencyKey: body.idempotencyKey,
195
- maxAttempts: body.maxAttempts,
196
- delayMs: body.delayMs,
197
- });
198
- sendData(res, job);
199
- return;
200
- }
201
- // POST /v1/queues/:queue/claim
202
- const claimMatch = matchRoute(pathname, "/v1/queues/:queue/claim");
203
- if (claimMatch?.queue && method === "POST") {
204
- const body = JSON.parse(await readBody(req));
205
- const job = await db.queue(claimMatch.queue).claim({
206
- leaseMs: body.leaseMs,
207
- });
208
- if (!job) {
209
- sendData(res, null);
210
- return;
211
- }
212
- sendData(res, job);
213
- return;
214
- }
215
- // POST /v1/queues/:queue/ack
216
- const ackMatch = matchRoute(pathname, "/v1/queues/:queue/ack");
217
- if (ackMatch?.queue && method === "POST") {
218
- const body = JSON.parse(await readBody(req));
219
- const result = await db.queue(ackMatch.queue).ack(body.jobId);
220
- if (!result.ok) {
221
- sendError(res, 400, result.reason, `Ack failed: ${result.reason}`);
222
- return;
223
- }
224
- sendData(res, result.job);
225
- return;
226
- }
227
- // POST /v1/queues/:queue/nack
228
- const nackMatch = matchRoute(pathname, "/v1/queues/:queue/nack");
229
- if (nackMatch?.queue && method === "POST") {
230
- const body = JSON.parse(await readBody(req));
231
- const result = await db.queue(nackMatch.queue).nack(body.jobId, {
232
- delayMs: body.delayMs,
233
- error: body.error,
234
- });
235
- if (!result.ok) {
236
- sendError(res, 400, result.reason, `Nack failed: ${result.reason}`);
237
- return;
238
- }
239
- sendData(res, result.job);
240
- return;
241
- }
242
- // GET /v1/queues/:queue/jobs
243
- const jobsMatch = matchRoute(pathname, "/v1/queues/:queue/jobs");
244
- if (jobsMatch?.queue && method === "GET") {
245
- const jobs = await db.queue(jobsMatch.queue).list();
246
- sendDataList(res, jobs);
247
- return;
248
- }
249
- // GET /v1/queues/:queue/dead
250
- const deadMatch = matchRoute(pathname, "/v1/queues/:queue/dead");
251
- if (deadMatch?.queue && method === "GET") {
252
- const jobs = await db.queue(deadMatch.queue).dead();
253
- sendDataList(res, jobs);
254
- return;
255
- }
256
- // ─── Links ───────────────────────────────────────────────────
257
- // POST /v1/links
258
- if (pathname === "/v1/links" && method === "POST") {
259
- const body = JSON.parse(await readBody(req));
260
- if (!body.fromRef || !body.linkType || !body.toRef) {
261
- sendError(res, 400, "bad_request", "Fields 'fromRef', 'linkType', 'toRef' are required");
262
- return;
263
- }
264
- const link = await db.links.create(body.fromRef, body.linkType, body.toRef, body.weight, body.metadataJson);
265
- sendData(res, link);
266
- return;
267
- }
268
- // GET /v1/links?id=...
269
- if (pathname === "/v1/links" && method === "GET") {
270
- const id = url.searchParams.get("id");
271
- if (id) {
272
- const link = await db.links.get(id);
273
- if (!link) {
274
- sendError(res, 404, "not_found", `Link '${id}' not found`);
275
- return;
276
- }
277
- sendData(res, link);
278
- return;
279
- }
280
- // Neighbors query
281
- const reference = url.searchParams.get("reference");
282
- if (reference) {
283
- const direction = url.searchParams.get("direction") ?? "Both";
284
- const linkType = url.searchParams.get("linkType") ?? undefined;
285
- const limit = parseIntParam(url.searchParams.get("limit"));
286
- const neighbors = await db.links.neighbors(reference, direction, { linkType, limit });
287
- sendDataList(res, neighbors);
288
- return;
289
- }
290
- sendError(res, 400, "bad_request", "Query parameter 'id' or 'reference' is required");
291
- return;
292
- }
293
- // DELETE /v1/links/:id
294
- const linkDeleteMatch = matchRoute(pathname, "/v1/links/:id");
295
- if (linkDeleteMatch?.id && method === "DELETE") {
296
- const deleted = await db.links.delete(linkDeleteMatch.id);
297
- sendData(res, { deleted });
298
- return;
299
- }
300
- // GET /v1/links/:id
301
- if (linkDeleteMatch?.id && method === "GET") {
302
- const link = await db.links.get(linkDeleteMatch.id);
303
- if (!link) {
304
- sendError(res, 404, "not_found", `Link '${linkDeleteMatch.id}' not found`);
305
- return;
306
- }
307
- sendData(res, link);
308
- return;
309
- }
310
- // ─── 404 ─────────────────────────────────────────────────────
311
- sendError(res, 404, "not_found", `No route for ${method} ${pathname}`);
312
- }
313
- catch (err) {
314
- const message = err instanceof Error ? err.message : String(err);
315
- sendError(res, 500, "internal_error", message);
316
- }
317
- }