@thingd/sdk 0.72.0 → 0.74.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.
Files changed (77) hide show
  1. package/dist/__type-tests__/exports.d.ts +178 -0
  2. package/dist/__type-tests__/exports.d.ts.map +1 -0
  3. package/dist/__type-tests__/exports.js +87 -0
  4. package/dist/client/http-thing-store.d.ts +55 -0
  5. package/dist/client/http-thing-store.d.ts.map +1 -0
  6. package/dist/client/http-thing-store.js +233 -0
  7. package/dist/client/in-memory-thing-store.d.ts +41 -0
  8. package/dist/client/in-memory-thing-store.d.ts.map +1 -0
  9. package/dist/client/in-memory-thing-store.js +292 -0
  10. package/dist/client/index.d.ts +5 -0
  11. package/dist/client/index.d.ts.map +1 -0
  12. package/dist/client/index.js +3 -0
  13. package/dist/client/thingd.d.ts +24 -0
  14. package/dist/client/thingd.d.ts.map +1 -0
  15. package/dist/client/thingd.js +27 -0
  16. package/dist/constants.d.ts +4 -0
  17. package/dist/constants.d.ts.map +1 -0
  18. package/dist/constants.js +3 -0
  19. package/dist/index.d.ts +13 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +11 -0
  22. package/dist/mcp/audit.d.ts +27 -0
  23. package/dist/mcp/audit.d.ts.map +1 -0
  24. package/dist/mcp/audit.js +36 -0
  25. package/dist/mcp/config.d.ts +22 -0
  26. package/dist/mcp/config.d.ts.map +1 -0
  27. package/dist/mcp/config.js +52 -0
  28. package/dist/mcp/index.d.ts +6 -0
  29. package/dist/mcp/index.d.ts.map +1 -0
  30. package/dist/mcp/index.js +5 -0
  31. package/dist/mcp/result.d.ts +3 -0
  32. package/dist/mcp/result.d.ts.map +1 -0
  33. package/dist/mcp/result.js +10 -0
  34. package/dist/mcp/server.d.ts +19 -0
  35. package/dist/mcp/server.d.ts.map +1 -0
  36. package/dist/mcp/server.js +51 -0
  37. package/dist/mcp/tools.d.ts +10 -0
  38. package/dist/mcp/tools.d.ts.map +1 -0
  39. package/dist/mcp/tools.js +1033 -0
  40. package/dist/memory/index.d.ts +7 -0
  41. package/dist/memory/index.d.ts.map +1 -0
  42. package/dist/memory/index.js +7 -0
  43. package/dist/rest/helpers.d.ts +17 -0
  44. package/dist/rest/helpers.d.ts.map +1 -0
  45. package/dist/rest/helpers.js +60 -0
  46. package/dist/rest/index.d.ts +3 -0
  47. package/dist/rest/index.d.ts.map +1 -0
  48. package/dist/rest/index.js +2 -0
  49. package/dist/rest/server.d.ts +4 -0
  50. package/dist/rest/server.d.ts.map +1 -0
  51. package/dist/rest/server.js +472 -0
  52. package/dist/scheduler.d.ts +28 -0
  53. package/dist/scheduler.d.ts.map +1 -0
  54. package/dist/scheduler.js +451 -0
  55. package/dist/stores/cloud-thing-store.d.ts +60 -0
  56. package/dist/stores/cloud-thing-store.d.ts.map +1 -0
  57. package/dist/stores/cloud-thing-store.js +396 -0
  58. package/dist/stores/in-memory-thing-store.d.ts +59 -0
  59. package/dist/stores/in-memory-thing-store.d.ts.map +1 -0
  60. package/dist/stores/in-memory-thing-store.js +709 -0
  61. package/dist/stores/native-thing-store.d.ts +62 -0
  62. package/dist/stores/native-thing-store.d.ts.map +1 -0
  63. package/dist/stores/native-thing-store.js +374 -0
  64. package/dist/thingd.d.ts +84 -0
  65. package/dist/thingd.d.ts.map +1 -0
  66. package/dist/thingd.js +236 -0
  67. package/dist/types/index.d.ts +2 -0
  68. package/dist/types/index.d.ts.map +1 -0
  69. package/dist/types/index.js +1 -0
  70. package/dist/types.d.ts +448 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +1 -0
  73. package/dist/version.d.ts +6 -0
  74. package/dist/version.d.ts.map +1 -0
  75. package/dist/version.js +5 -0
  76. package/package.json +8 -7
  77. package/LICENSE +0 -201
@@ -0,0 +1,7 @@
1
+ export { InMemoryThingStore } from "../client/in-memory-thing-store.js";
2
+ /**
3
+ * Open a pure in-memory thingd connection.
4
+ * Browser-safe — no Node.js dependencies.
5
+ */
6
+ export declare const openMemoryThingD: () => Promise<import("../client/http-thing-store.js").HttpThingStore | import("../client/in-memory-thing-store.js").InMemoryThingStore>;
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/memory/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAExE;;;GAGG;AACH,eAAO,MAAM,gBAAgB,yIAAyC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { openThingD } from "../client/thingd.js";
2
+ export { InMemoryThingStore } from "../client/in-memory-thing-store.js";
3
+ /**
4
+ * Open a pure in-memory thingd connection.
5
+ * Browser-safe — no Node.js dependencies.
6
+ */
7
+ export const openMemoryThingD = () => openThingD({ driver: "memory" });
@@ -0,0 +1,17 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ type SortField = "id" | "collection" | "created_at" | "updated_at" | "version";
3
+ type SortDirection = "asc" | "desc";
4
+ type LocalSortBy = {
5
+ field: SortField;
6
+ direction?: SortDirection;
7
+ };
8
+ export declare function readBody(req: IncomingMessage, maxSize?: number): Promise<string>;
9
+ export declare function sendJson(res: ServerResponse, status: number, data: unknown): void;
10
+ export declare function sendData(res: ServerResponse, data: unknown): void;
11
+ export declare function sendDataList(res: ServerResponse, data: unknown[], total?: number): void;
12
+ export declare function sendError(res: ServerResponse, status: number, code: string, message: string): void;
13
+ export declare function parseSortBy(params: URLSearchParams): LocalSortBy | undefined;
14
+ export declare function parseFilter(params: URLSearchParams): Record<string, unknown> | undefined;
15
+ export declare function parseIntParam(value: string | null): number | undefined;
16
+ export {};
17
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/rest/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,KAAK,SAAS,GAAG,IAAI,GAAG,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;AAC/E,KAAK,aAAa,GAAG,KAAK,GAAG,MAAM,CAAC;AAEpC,KAAK,WAAW,GAAG;IACjB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B,CAAC;AAIF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,SAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAavF;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAGjF;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAEjE;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAEvF;AAED,wBAAgB,SAAS,CACvB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,IAAI,CAEN;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,eAAe,GAAG,WAAW,GAAG,SAAS,CAY5E;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAWxF;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAMtE"}
@@ -0,0 +1,60 @@
1
+ const MAX_BODY_SIZE = 524_288; // 512KB
2
+ export function readBody(req, maxSize = MAX_BODY_SIZE) {
3
+ return new Promise((resolve, reject) => {
4
+ let body = "";
5
+ req.on("data", (chunk) => {
6
+ body += chunk.toString();
7
+ if (body.length > maxSize) {
8
+ req.destroy(new Error(`Request body exceeds ${maxSize} bytes`));
9
+ reject(new Error(`Request body exceeds ${maxSize} bytes`));
10
+ }
11
+ });
12
+ req.on("end", () => resolve(body));
13
+ req.on("error", reject);
14
+ });
15
+ }
16
+ export function sendJson(res, status, data) {
17
+ res.writeHead(status, { "Content-Type": "application/json" });
18
+ res.end(JSON.stringify(data));
19
+ }
20
+ export function sendData(res, data) {
21
+ sendJson(res, 200, { data });
22
+ }
23
+ export function sendDataList(res, data, total) {
24
+ sendJson(res, 200, { data, ...(total !== undefined ? { total } : {}) });
25
+ }
26
+ export function sendError(res, status, code, message) {
27
+ sendJson(res, status, { error: { code, message } });
28
+ }
29
+ export function parseSortBy(params) {
30
+ const sort = params.get("sortBy");
31
+ if (!sort) {
32
+ return undefined;
33
+ }
34
+ const parts = sort.split(":");
35
+ const field = parts[0];
36
+ const dir = parts[1] ?? "asc";
37
+ if (!field) {
38
+ return undefined;
39
+ }
40
+ return { field: field, direction: dir };
41
+ }
42
+ export function parseFilter(params) {
43
+ const filter = {};
44
+ let hasFilter = false;
45
+ params.forEach((value, key) => {
46
+ if (key.startsWith("filter.")) {
47
+ const field = key.slice(7);
48
+ filter[field] = value;
49
+ hasFilter = true;
50
+ }
51
+ });
52
+ return hasFilter ? filter : undefined;
53
+ }
54
+ export function parseIntParam(value) {
55
+ if (!value) {
56
+ return undefined;
57
+ }
58
+ const n = Number(value);
59
+ return Number.isNaN(n) ? undefined : n;
60
+ }
@@ -0,0 +1,3 @@
1
+ export { parseFilter, parseIntParam, parseSortBy, readBody, sendData, sendDataList, sendError, sendJson, } from "./helpers.js";
2
+ export { handleRestRequest } from "./server.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rest/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,aAAa,EACb,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,QAAQ,GACT,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { parseFilter, parseIntParam, parseSortBy, readBody, sendData, sendDataList, sendError, sendJson, } from "./helpers.js";
2
+ export { handleRestRequest } from "./server.js";
@@ -0,0 +1,4 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { ThingD } from "../thingd.js";
3
+ export declare function handleRestRequest(db: ThingD, req: IncomingMessage, res: ServerResponse, pathname: string): Promise<void>;
4
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/rest/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AA0C3C,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAkff"}
@@ -0,0 +1,472 @@
1
+ import { SDK_VERSION } from "../version.js";
2
+ import { parseFilter, parseIntParam, parseSortBy, readBody, sendData, sendDataList, sendError, } from "./helpers.js";
3
+ function matchRoute(pathname, pattern) {
4
+ const patternParts = pattern.split("/");
5
+ const pathParts = pathname.split("/");
6
+ if (patternParts.length !== pathParts.length) {
7
+ return null;
8
+ }
9
+ const match = {};
10
+ for (let i = 0; i < patternParts.length; i++) {
11
+ const pp = patternParts[i] ?? "";
12
+ const xp = pathParts[i] ?? "";
13
+ if (pp.startsWith(":")) {
14
+ const key = pp.slice(1);
15
+ match[key] = xp;
16
+ }
17
+ else if (pp !== xp) {
18
+ return null;
19
+ }
20
+ }
21
+ return match;
22
+ }
23
+ export async function handleRestRequest(db, req, res, pathname) {
24
+ const method = req.method ?? "GET";
25
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
26
+ try {
27
+ // ─── Health ──────────────────────────────────────────────────
28
+ if (pathname === "/v1/health" && method === "GET") {
29
+ const [objects, events, links, queues, collections, streams] = await Promise.all([
30
+ db.countObjects(),
31
+ db.countEvents(),
32
+ db.countLinks(),
33
+ db.listQueues(),
34
+ db.listCollections(),
35
+ db.listStreams(),
36
+ ]);
37
+ sendData(res, {
38
+ status: "ok",
39
+ version: SDK_VERSION,
40
+ counts: {
41
+ objects,
42
+ events,
43
+ links,
44
+ queues: queues.length,
45
+ collections: collections.length,
46
+ streams: streams.length,
47
+ },
48
+ });
49
+ return;
50
+ }
51
+ // ─── Counts ──────────────────────────────────────────────────
52
+ if (pathname === "/v1/counts/objects" && method === "GET") {
53
+ sendData(res, { count: await db.countObjects() });
54
+ return;
55
+ }
56
+ const collectionCountMatch = pathname.match(/^\/v1\/counts\/objects\/([^/]+)$/);
57
+ if (collectionCountMatch && method === "GET" && collectionCountMatch[1]) {
58
+ const collection = decodeURIComponent(collectionCountMatch[1]);
59
+ sendData(res, { count: await db.countObjectsInCollection(collection) });
60
+ return;
61
+ }
62
+ if (pathname === "/v1/counts/events" && method === "GET") {
63
+ sendData(res, { count: await db.countEvents() });
64
+ return;
65
+ }
66
+ if (pathname === "/v1/counts/links" && method === "GET") {
67
+ sendData(res, { count: await db.countLinks() });
68
+ return;
69
+ }
70
+ // ─── Collections / Streams / Queues ──────────────────────────
71
+ if (pathname === "/v1/collections" && method === "GET") {
72
+ sendDataList(res, await db.listCollections());
73
+ return;
74
+ }
75
+ // GET /v1/collections/schema — all schemas
76
+ if (pathname === "/v1/collections/schema" && method === "GET") {
77
+ sendDataList(res, await db.schema());
78
+ return;
79
+ }
80
+ // GET /v1/collections/:name/schema — single schema
81
+ const schemaMatch = matchRoute(pathname, "/v1/collections/:name/schema");
82
+ if (schemaMatch?.name && method === "GET") {
83
+ const schemas = await db.schema(schemaMatch.name);
84
+ if (schemas.length === 0) {
85
+ sendError(res, 404, "not_found", `Collection '${schemaMatch.name}' not found or has no objects`);
86
+ return;
87
+ }
88
+ sendData(res, schemas[0]);
89
+ return;
90
+ }
91
+ if (pathname === "/v1/streams" && method === "GET") {
92
+ sendDataList(res, await db.listStreams());
93
+ return;
94
+ }
95
+ if (pathname === "/v1/queues" && method === "GET") {
96
+ sendDataList(res, await db.listQueues());
97
+ return;
98
+ }
99
+ // ─── Indexes ─────────────────────────────────────────────────
100
+ if (pathname === "/v1/indexes" && method === "GET") {
101
+ sendDataList(res, await db.listIndexes());
102
+ return;
103
+ }
104
+ if (pathname === "/v1/indexes" && method === "POST") {
105
+ const body = JSON.parse(await readBody(req));
106
+ await db.createIndex(body.collection, body.field);
107
+ sendData(res, { created: true });
108
+ return;
109
+ }
110
+ // ─── Objects ─────────────────────────────────────────────────
111
+ // GET /v1/objects?collection=...&filter.x=...&sortBy=...&limit=...&offset=...
112
+ if (pathname === "/v1/objects" && method === "GET") {
113
+ const collection = url.searchParams.get("collection");
114
+ if (!collection) {
115
+ sendError(res, 400, "bad_request", "Query parameter 'collection' is required");
116
+ return;
117
+ }
118
+ const filter = parseFilter(url.searchParams);
119
+ const sortBy = parseSortBy(url.searchParams);
120
+ const limit = parseIntParam(url.searchParams.get("limit"));
121
+ const offset = parseIntParam(url.searchParams.get("offset"));
122
+ const objects = await db.listObjects(collection, { filter, sortBy, limit, offset });
123
+ sendDataList(res, objects);
124
+ return;
125
+ }
126
+ // PUT /v1/objects/:collection/:id
127
+ const objMatch = matchRoute(pathname, "/v1/objects/:collection/:id");
128
+ if (objMatch?.collection && objMatch?.id && method === "PUT") {
129
+ const body = JSON.parse(await readBody(req));
130
+ body.id = objMatch.id;
131
+ const result = await db.put(objMatch.collection, body);
132
+ sendData(res, result);
133
+ return;
134
+ }
135
+ // GET /v1/objects/:collection/:id
136
+ if (objMatch?.collection && objMatch?.id && method === "GET") {
137
+ const result = await db.get(objMatch.collection, objMatch.id);
138
+ if (!result) {
139
+ sendError(res, 404, "not_found", `Object '${objMatch.id}' not found in collection '${objMatch.collection}'`);
140
+ return;
141
+ }
142
+ sendData(res, result);
143
+ return;
144
+ }
145
+ // DELETE /v1/objects/:collection/:id
146
+ if (objMatch?.collection && objMatch?.id && method === "DELETE") {
147
+ const result = await db.delete(objMatch.collection, objMatch.id);
148
+ sendData(res, result);
149
+ return;
150
+ }
151
+ // GET /v1/objects/batch?collection=...
152
+ if (pathname === "/v1/objects/batch" && method === "GET") {
153
+ const collection = url.searchParams.get("collection");
154
+ if (!collection) {
155
+ sendError(res, 400, "bad_request", "Query parameter 'collection' is required");
156
+ return;
157
+ }
158
+ const body = JSON.parse(await readBody(req));
159
+ const ids = Array.isArray(body) ? body : body.ids;
160
+ if (!Array.isArray(ids)) {
161
+ sendError(res, 400, "bad_request", "Body must be an array or { ids: [...] }");
162
+ return;
163
+ }
164
+ const objects = await db.getBatch(collection, ids);
165
+ sendDataList(res, objects);
166
+ return;
167
+ }
168
+ // PUT /v1/objects/batch?collection=...
169
+ if (pathname === "/v1/objects/batch" && method === "PUT") {
170
+ const collection = url.searchParams.get("collection");
171
+ if (!collection) {
172
+ sendError(res, 400, "bad_request", "Query parameter 'collection' is required");
173
+ return;
174
+ }
175
+ const body = JSON.parse(await readBody(req));
176
+ const objects = Array.isArray(body) ? body : body.objects;
177
+ if (!Array.isArray(objects)) {
178
+ sendError(res, 400, "bad_request", "Body must be an array or { objects: [...] }");
179
+ return;
180
+ }
181
+ const result = await db.putBatch(collection, objects);
182
+ sendData(res, result);
183
+ return;
184
+ }
185
+ // DELETE /v1/objects/batch?collection=...
186
+ if (pathname === "/v1/objects/batch" && method === "DELETE") {
187
+ const collection = url.searchParams.get("collection");
188
+ if (!collection) {
189
+ sendError(res, 400, "bad_request", "Query parameter 'collection' is required");
190
+ return;
191
+ }
192
+ const body = JSON.parse(await readBody(req));
193
+ const ids = Array.isArray(body) ? body : body.ids;
194
+ if (!Array.isArray(ids)) {
195
+ sendError(res, 400, "bad_request", "Body must be an array or { ids: [...] }");
196
+ return;
197
+ }
198
+ const count = await db.deleteBatch(collection, ids);
199
+ sendData(res, { deleted: count });
200
+ return;
201
+ }
202
+ // ─── Search ──────────────────────────────────────────────────
203
+ if (pathname === "/v1/search" && method === "POST") {
204
+ const body = JSON.parse(await readBody(req));
205
+ if (!body.query) {
206
+ sendError(res, 400, "bad_request", "Field 'query' is required");
207
+ return;
208
+ }
209
+ const results = await db.search(body.query, {
210
+ collections: body.collections,
211
+ limit: body.limit,
212
+ filter: body.filter,
213
+ });
214
+ sendData(res, results);
215
+ return;
216
+ }
217
+ // ─── Vector Search ──────────────────────────────────────────
218
+ if (pathname === "/v1/search/vector" && method === "POST") {
219
+ const body = JSON.parse(await readBody(req));
220
+ if (!body.collection) {
221
+ sendError(res, 400, "bad_request", "Field 'collection' is required");
222
+ return;
223
+ }
224
+ if (!body.vector || !Array.isArray(body.vector)) {
225
+ sendError(res, 400, "bad_request", "Field 'vector' must be an array of numbers");
226
+ return;
227
+ }
228
+ const results = await db.vectorSearch(body.collection, body.vector, {
229
+ topK: body.topK,
230
+ filter: body.filter,
231
+ });
232
+ sendData(res, results);
233
+ return;
234
+ }
235
+ // ─── Aggregate ──────────────────────────────────────────────
236
+ if (pathname === "/v1/aggregate" && method === "POST") {
237
+ const body = JSON.parse(await readBody(req));
238
+ if (!body.collection) {
239
+ sendError(res, 400, "bad_request", "Field 'collection' is required");
240
+ return;
241
+ }
242
+ if (!body.function) {
243
+ sendError(res, 400, "bad_request", "Field 'function' is required");
244
+ return;
245
+ }
246
+ let result;
247
+ switch (body.function) {
248
+ case "sum":
249
+ result = await db.aggregate.sum(body.collection, body.field ?? "", {
250
+ groupBy: body.groupBy,
251
+ filter: body.filter,
252
+ });
253
+ break;
254
+ case "avg":
255
+ result = await db.aggregate.avg(body.collection, body.field ?? "", {
256
+ groupBy: body.groupBy,
257
+ filter: body.filter,
258
+ });
259
+ break;
260
+ case "min":
261
+ result = await db.aggregate.min(body.collection, body.field ?? "", {
262
+ groupBy: body.groupBy,
263
+ filter: body.filter,
264
+ });
265
+ break;
266
+ case "max":
267
+ result = await db.aggregate.max(body.collection, body.field ?? "", {
268
+ groupBy: body.groupBy,
269
+ filter: body.filter,
270
+ });
271
+ break;
272
+ default:
273
+ result = await db.aggregate.count(body.collection, {
274
+ groupBy: body.groupBy,
275
+ filter: body.filter,
276
+ });
277
+ break;
278
+ }
279
+ sendData(res, result);
280
+ return;
281
+ }
282
+ if (pathname === "/v1/aggregate/timeseries" && method === "POST") {
283
+ const body = JSON.parse(await readBody(req));
284
+ if (!body.collection) {
285
+ sendError(res, 400, "bad_request", "Field 'collection' is required");
286
+ return;
287
+ }
288
+ if (!body.function) {
289
+ sendError(res, 400, "bad_request", "Field 'function' is required");
290
+ return;
291
+ }
292
+ if (!body.bucket) {
293
+ sendError(res, 400, "bad_request", "Field 'bucket' is required");
294
+ return;
295
+ }
296
+ const result = await db.timeseries(body.collection, {
297
+ function: body.function,
298
+ bucket: body.bucket,
299
+ field: body.field,
300
+ filter: body.filter,
301
+ from: body.from,
302
+ to: body.to,
303
+ });
304
+ sendData(res, result);
305
+ return;
306
+ }
307
+ // ─── NLQ ──────────────────────────────────────────────────
308
+ if (pathname === "/v1/nlq" && method === "POST") {
309
+ const body = JSON.parse(await readBody(req));
310
+ if (!body.question) {
311
+ sendError(res, 400, "bad_request", "Field 'question' is required");
312
+ return;
313
+ }
314
+ const result = await db.nlq.query(body.question, {
315
+ collection: body.collection,
316
+ });
317
+ sendData(res, result);
318
+ return;
319
+ }
320
+ // ─── Events ──────────────────────────────────────────────────
321
+ // POST /v1/events/:stream
322
+ const streamMatch = matchRoute(pathname, "/v1/events/:stream");
323
+ if (streamMatch?.stream && method === "POST") {
324
+ const body = JSON.parse(await readBody(req));
325
+ if (!body.type) {
326
+ sendError(res, 400, "bad_request", "Field 'type' is required");
327
+ return;
328
+ }
329
+ const event = await db.events.append(streamMatch.stream, body);
330
+ sendData(res, event);
331
+ return;
332
+ }
333
+ // GET /v1/events?stream=...&fromSequence=...&limit=...&since=...
334
+ if (pathname === "/v1/events" && method === "GET") {
335
+ const stream = url.searchParams.get("stream") ?? undefined;
336
+ const fromSequence = parseIntParam(url.searchParams.get("fromSequence"));
337
+ const limit = parseIntParam(url.searchParams.get("limit"));
338
+ const since = url.searchParams.get("since") ?? undefined;
339
+ const events = await db.events.list(stream, { fromSequence, limit, since });
340
+ sendDataList(res, events);
341
+ return;
342
+ }
343
+ // ─── Queues ──────────────────────────────────────────────────
344
+ // POST /v1/queues/:queue/push
345
+ const pushMatch = matchRoute(pathname, "/v1/queues/:queue/push");
346
+ if (pushMatch?.queue && method === "POST") {
347
+ const body = JSON.parse(await readBody(req));
348
+ const job = await db.queue(pushMatch.queue).push(body.payload ?? body, {
349
+ idempotencyKey: body.idempotencyKey,
350
+ maxAttempts: body.maxAttempts,
351
+ delayMs: body.delayMs,
352
+ });
353
+ sendData(res, job);
354
+ return;
355
+ }
356
+ // POST /v1/queues/:queue/claim
357
+ const claimMatch = matchRoute(pathname, "/v1/queues/:queue/claim");
358
+ if (claimMatch?.queue && method === "POST") {
359
+ const body = JSON.parse(await readBody(req));
360
+ const job = await db.queue(claimMatch.queue).claim({
361
+ leaseMs: body.leaseMs,
362
+ });
363
+ if (!job) {
364
+ sendData(res, null);
365
+ return;
366
+ }
367
+ sendData(res, job);
368
+ return;
369
+ }
370
+ // POST /v1/queues/:queue/ack
371
+ const ackMatch = matchRoute(pathname, "/v1/queues/:queue/ack");
372
+ if (ackMatch?.queue && method === "POST") {
373
+ const body = JSON.parse(await readBody(req));
374
+ const result = await db.queue(ackMatch.queue).ack(body.jobId);
375
+ if (!result.ok) {
376
+ sendError(res, 400, result.reason, `Ack failed: ${result.reason}`);
377
+ return;
378
+ }
379
+ sendData(res, result.job);
380
+ return;
381
+ }
382
+ // POST /v1/queues/:queue/nack
383
+ const nackMatch = matchRoute(pathname, "/v1/queues/:queue/nack");
384
+ if (nackMatch?.queue && method === "POST") {
385
+ const body = JSON.parse(await readBody(req));
386
+ const result = await db.queue(nackMatch.queue).nack(body.jobId, {
387
+ delayMs: body.delayMs,
388
+ error: body.error,
389
+ });
390
+ if (!result.ok) {
391
+ sendError(res, 400, result.reason, `Nack failed: ${result.reason}`);
392
+ return;
393
+ }
394
+ sendData(res, result.job);
395
+ return;
396
+ }
397
+ // GET /v1/queues/:queue/jobs
398
+ const jobsMatch = matchRoute(pathname, "/v1/queues/:queue/jobs");
399
+ if (jobsMatch?.queue && method === "GET") {
400
+ const jobs = await db.queue(jobsMatch.queue).list();
401
+ sendDataList(res, jobs);
402
+ return;
403
+ }
404
+ // GET /v1/queues/:queue/dead
405
+ const deadMatch = matchRoute(pathname, "/v1/queues/:queue/dead");
406
+ if (deadMatch?.queue && method === "GET") {
407
+ const jobs = await db.queue(deadMatch.queue).dead();
408
+ sendDataList(res, jobs);
409
+ return;
410
+ }
411
+ // ─── Links ───────────────────────────────────────────────────
412
+ // POST /v1/links
413
+ if (pathname === "/v1/links" && method === "POST") {
414
+ const body = JSON.parse(await readBody(req));
415
+ if (!body.fromRef || !body.linkType || !body.toRef) {
416
+ sendError(res, 400, "bad_request", "Fields 'fromRef', 'linkType', 'toRef' are required");
417
+ return;
418
+ }
419
+ const link = await db.links.create(body.fromRef, body.linkType, body.toRef, body.weight, body.metadataJson);
420
+ sendData(res, link);
421
+ return;
422
+ }
423
+ // GET /v1/links?id=...
424
+ if (pathname === "/v1/links" && method === "GET") {
425
+ const id = url.searchParams.get("id");
426
+ if (id) {
427
+ const link = await db.links.get(id);
428
+ if (!link) {
429
+ sendError(res, 404, "not_found", `Link '${id}' not found`);
430
+ return;
431
+ }
432
+ sendData(res, link);
433
+ return;
434
+ }
435
+ // Neighbors query
436
+ const reference = url.searchParams.get("reference");
437
+ if (reference) {
438
+ const direction = url.searchParams.get("direction") ?? "Both";
439
+ const linkType = url.searchParams.get("linkType") ?? undefined;
440
+ const limit = parseIntParam(url.searchParams.get("limit"));
441
+ const neighbors = await db.links.neighbors(reference, direction, { linkType, limit });
442
+ sendDataList(res, neighbors);
443
+ return;
444
+ }
445
+ sendError(res, 400, "bad_request", "Query parameter 'id' or 'reference' is required");
446
+ return;
447
+ }
448
+ // DELETE /v1/links/:id
449
+ const linkDeleteMatch = matchRoute(pathname, "/v1/links/:id");
450
+ if (linkDeleteMatch?.id && method === "DELETE") {
451
+ const deleted = await db.links.delete(linkDeleteMatch.id);
452
+ sendData(res, { deleted });
453
+ return;
454
+ }
455
+ // GET /v1/links/:id
456
+ if (linkDeleteMatch?.id && method === "GET") {
457
+ const link = await db.links.get(linkDeleteMatch.id);
458
+ if (!link) {
459
+ sendError(res, 404, "not_found", `Link '${linkDeleteMatch.id}' not found`);
460
+ return;
461
+ }
462
+ sendData(res, link);
463
+ return;
464
+ }
465
+ // ─── 404 ─────────────────────────────────────────────────────
466
+ sendError(res, 404, "not_found", `No route for ${method} ${pathname}`);
467
+ }
468
+ catch (err) {
469
+ const message = err instanceof Error ? err.message : String(err);
470
+ sendError(res, 500, "internal_error", message);
471
+ }
472
+ }
@@ -0,0 +1,28 @@
1
+ import type { Schedule, ScheduleIntervalOptions, ScheduleOnceOptions, ScheduleOptions, SchedulerEventType, SchedulerFacade, SchedulerListener, SchedulerStats, ThingStore } from "./types.js";
2
+ export declare class Scheduler implements SchedulerFacade {
3
+ private readonly store;
4
+ private handlers;
5
+ private listeners;
6
+ private heartbeatTimer;
7
+ private running;
8
+ private started;
9
+ constructor(store: ThingStore);
10
+ schedule(id: string, options: ScheduleOptions): Promise<Schedule>;
11
+ scheduleOnce(id: string, options: ScheduleOnceOptions): Promise<Schedule>;
12
+ scheduleInterval(id: string, options: ScheduleIntervalOptions): Promise<Schedule>;
13
+ get(id: string): Promise<Schedule | null>;
14
+ list(): Promise<Schedule[]>;
15
+ pause(id: string): Promise<Schedule>;
16
+ resume(id: string): Promise<Schedule>;
17
+ remove(id: string): Promise<boolean>;
18
+ run(id: string): Promise<void>;
19
+ stats(): Promise<SchedulerStats>;
20
+ start(): Promise<void>;
21
+ stop(): Promise<void>;
22
+ on(event: SchedulerEventType, listener: SchedulerListener): void;
23
+ off(event: SchedulerEventType, listener: SchedulerListener): void;
24
+ private emit;
25
+ private heartbeat;
26
+ private executeSchedule;
27
+ }
28
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EAIR,uBAAuB,EACvB,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,cAAc,EAEd,UAAU,EACX,MAAM,YAAY,CAAC;AAqJpB,qBAAa,SAAU,YAAW,eAAe;IAOnC,OAAO,CAAC,QAAQ,CAAC,KAAK;IANlC,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,SAAS,CAAyD;IAC1E,OAAO,CAAC,cAAc,CAA+C;IACrE,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,OAAO,CAAS;IAExB,YAA6B,KAAK,EAAE,UAAU,EAAI;IAE5C,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CA+BtE;IAEK,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoB9E;IAEK,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqBtF;IAEK,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAG9C;IAEK,IAAI,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAGhC;IAEK,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CASzC;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAU1C;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAIzC;IAEK,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUnC;IAEK,KAAK,IAAI,OAAO,CAAC,cAAc,CAAC,CAiBrC;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAsB3B;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAY1B;IAED,EAAE,CAAC,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAK/D;IAED,GAAG,CAAC,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAEhE;IAED,OAAO,CAAC,IAAI;YASE,SAAS;YAuBT,eAAe;CAyG9B"}