@thingd/sdk 0.31.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.
Files changed (72) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +39 -0
  3. package/dist/client/http-thing-store.d.ts +41 -0
  4. package/dist/client/http-thing-store.d.ts.map +1 -0
  5. package/dist/client/http-thing-store.js +178 -0
  6. package/dist/client/in-memory-thing-store.d.ts +38 -0
  7. package/dist/client/in-memory-thing-store.d.ts.map +1 -0
  8. package/dist/client/in-memory-thing-store.js +270 -0
  9. package/dist/client/index.d.ts +5 -0
  10. package/dist/client/index.d.ts.map +1 -0
  11. package/dist/client/index.js +3 -0
  12. package/dist/client/thingd.d.ts +46 -0
  13. package/dist/client/thingd.d.ts.map +1 -0
  14. package/dist/client/thingd.js +115 -0
  15. package/dist/index.d.ts +11 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +9 -0
  18. package/dist/mcp/audit.d.ts +27 -0
  19. package/dist/mcp/audit.d.ts.map +1 -0
  20. package/dist/mcp/audit.js +36 -0
  21. package/dist/mcp/config.d.ts +22 -0
  22. package/dist/mcp/config.d.ts.map +1 -0
  23. package/dist/mcp/config.js +52 -0
  24. package/dist/mcp/index.d.ts +6 -0
  25. package/dist/mcp/index.d.ts.map +1 -0
  26. package/dist/mcp/index.js +5 -0
  27. package/dist/mcp/result.d.ts +3 -0
  28. package/dist/mcp/result.d.ts.map +1 -0
  29. package/dist/mcp/result.js +10 -0
  30. package/dist/mcp/server.d.ts +19 -0
  31. package/dist/mcp/server.d.ts.map +1 -0
  32. package/dist/mcp/server.js +51 -0
  33. package/dist/mcp/tools.d.ts +10 -0
  34. package/dist/mcp/tools.d.ts.map +1 -0
  35. package/dist/mcp/tools.js +568 -0
  36. package/dist/memory/index.d.ts +36 -0
  37. package/dist/memory/index.d.ts.map +1 -0
  38. package/dist/memory/index.js +86 -0
  39. package/dist/rest/helpers.d.ts +17 -0
  40. package/dist/rest/helpers.d.ts.map +1 -0
  41. package/dist/rest/helpers.js +55 -0
  42. package/dist/rest/index.d.ts +3 -0
  43. package/dist/rest/index.d.ts.map +1 -0
  44. package/dist/rest/index.js +2 -0
  45. package/dist/rest/server.d.ts +4 -0
  46. package/dist/rest/server.d.ts.map +1 -0
  47. package/dist/rest/server.js +317 -0
  48. package/dist/stores/cloud-thing-store.d.ts +49 -0
  49. package/dist/stores/cloud-thing-store.d.ts.map +1 -0
  50. package/dist/stores/cloud-thing-store.js +243 -0
  51. package/dist/stores/in-memory-thing-store.d.ts +44 -0
  52. package/dist/stores/in-memory-thing-store.d.ts.map +1 -0
  53. package/dist/stores/in-memory-thing-store.js +411 -0
  54. package/dist/stores/native-thing-store.d.ts +53 -0
  55. package/dist/stores/native-thing-store.d.ts.map +1 -0
  56. package/dist/stores/native-thing-store.js +312 -0
  57. package/dist/stores/remote-thing-store.d.ts +27 -0
  58. package/dist/stores/remote-thing-store.d.ts.map +1 -0
  59. package/dist/stores/remote-thing-store.js +131 -0
  60. package/dist/thingd.d.ts +48 -0
  61. package/dist/thingd.d.ts.map +1 -0
  62. package/dist/thingd.js +147 -0
  63. package/dist/types/index.d.ts +2 -0
  64. package/dist/types/index.d.ts.map +1 -0
  65. package/dist/types/index.js +1 -0
  66. package/dist/types.d.ts +185 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/types.js +1 -0
  69. package/dist/version.d.ts +6 -0
  70. package/dist/version.d.ts.map +1 -0
  71. package/dist/version.js +5 -0
  72. package/package.json +79 -0
@@ -0,0 +1,270 @@
1
+ const DEFAULT_LEASE_MS = 30_000;
2
+ function uid() {
3
+ return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
4
+ ? crypto.randomUUID()
5
+ : `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
6
+ }
7
+ function now() {
8
+ return new Date().toISOString();
9
+ }
10
+ export class InMemoryThingStore {
11
+ objects = new Map();
12
+ events = new Map();
13
+ jobs = new Map();
14
+ links = new Map();
15
+ linkCounter = 0;
16
+ async put(collection, object) {
17
+ const key = `${collection}:${object.id}`;
18
+ const existing = this.objects.get(key);
19
+ const timestamp = now();
20
+ const stored = {
21
+ ...object,
22
+ id: object.id,
23
+ collection,
24
+ createdAt: existing?.data.createdAt ?? timestamp,
25
+ updatedAt: timestamp,
26
+ version: (existing?.data.version ?? 0) + 1,
27
+ };
28
+ this.objects.set(key, { collection, data: stored });
29
+ return stored;
30
+ }
31
+ async get(collection, id) {
32
+ const row = this.objects.get(`${collection}:${id}`);
33
+ return row ? row.data : null;
34
+ }
35
+ async delete(_collection, _id) {
36
+ const deleted = this.objects.delete(`${_collection}:${_id}`);
37
+ return { deleted };
38
+ }
39
+ async listObjects(collection, options) {
40
+ let items = Array.from(this.objects.values())
41
+ .filter((r) => r.collection === collection)
42
+ .map((r) => r.data);
43
+ if (options?.filter) {
44
+ const filter = options.filter;
45
+ items = items.filter((obj) => Object.entries(filter).every(([k, v]) => obj[k] === v));
46
+ }
47
+ if (options?.sortBy) {
48
+ const field = options.sortBy.field === "created_at"
49
+ ? "createdAt"
50
+ : options.sortBy.field === "updated_at"
51
+ ? "updatedAt"
52
+ : options.sortBy.field;
53
+ const dir = options.sortBy.direction === "desc" ? -1 : 1;
54
+ items.sort((a, b) => {
55
+ const va = a[field];
56
+ const vb = b[field];
57
+ if (va == null) {
58
+ return 1;
59
+ }
60
+ if (vb == null) {
61
+ return -1;
62
+ }
63
+ return va < vb ? -dir : va > vb ? dir : 0;
64
+ });
65
+ }
66
+ if (options?.offset) {
67
+ items = items.slice(options.offset);
68
+ }
69
+ if (options?.limit) {
70
+ items = items.slice(0, options.limit);
71
+ }
72
+ return items;
73
+ }
74
+ async appendEvent(stream, event) {
75
+ const streamEvents = this.events.get(stream) ?? [];
76
+ const sequence = streamEvents.length + 1;
77
+ const stored = {
78
+ ...event,
79
+ id: String(sequence),
80
+ stream,
81
+ sequence,
82
+ createdAt: now(),
83
+ };
84
+ streamEvents.push(stored);
85
+ this.events.set(stream, streamEvents);
86
+ return stored;
87
+ }
88
+ async listEvents(stream, options) {
89
+ let items;
90
+ if (stream) {
91
+ items = this.events.get(stream) ?? [];
92
+ }
93
+ else {
94
+ items = Array.from(this.events.values()).flat();
95
+ }
96
+ if (options?.fromSequence) {
97
+ items = items.filter((e) => e.sequence > (options.fromSequence ?? 0));
98
+ }
99
+ if (options?.limit) {
100
+ items = items.slice(0, options.limit);
101
+ }
102
+ return items;
103
+ }
104
+ async pushJob(queue, payload, options = {}) {
105
+ const timestamp = now();
106
+ const job = {
107
+ id: options.idempotencyKey ?? uid(),
108
+ queue,
109
+ payload,
110
+ status: "ready",
111
+ attempts: 0,
112
+ maxAttempts: options.maxAttempts ?? 3,
113
+ createdAt: timestamp,
114
+ availableAt: new Date(Date.now() + (options.delayMs ?? 0)).toISOString(),
115
+ };
116
+ const queueJobs = this.jobs.get(queue) ?? [];
117
+ queueJobs.push(job);
118
+ this.jobs.set(queue, queueJobs);
119
+ return job;
120
+ }
121
+ async claimJob(queue, options = {}) {
122
+ const queueJobs = this.jobs.get(queue);
123
+ if (!queueJobs) {
124
+ return null;
125
+ }
126
+ const now_ = new Date();
127
+ const idx = queueJobs.findIndex((c) => c.status === "ready" && c.availableAt <= now_.toISOString());
128
+ if (idx === -1) {
129
+ return null;
130
+ }
131
+ const job = queueJobs[idx];
132
+ job.status = "leased";
133
+ job.attempts += 1;
134
+ job.leasedAt = now_.toISOString();
135
+ job.leaseExpiresAt = new Date(now_.getTime() + (options.leaseMs ?? DEFAULT_LEASE_MS)).toISOString();
136
+ return job;
137
+ }
138
+ async ackJob(queue, jobId) {
139
+ const job = this.findJob(queue, jobId);
140
+ if (!job) {
141
+ return { ok: false, reason: "not_found" };
142
+ }
143
+ if (job.status !== "leased") {
144
+ return { ok: false, reason: "not_leased" };
145
+ }
146
+ job.status = "completed";
147
+ job.completedAt = now();
148
+ return { ok: true, job };
149
+ }
150
+ async nackJob(queue, jobId, options = {}) {
151
+ const job = this.findJob(queue, jobId);
152
+ if (!job) {
153
+ return { ok: false, reason: "not_found" };
154
+ }
155
+ if (job.status !== "leased") {
156
+ return { ok: false, reason: "not_leased" };
157
+ }
158
+ job.lastError = options.error;
159
+ if (job.attempts >= job.maxAttempts) {
160
+ job.status = "dead";
161
+ job.deadAt = now();
162
+ }
163
+ else {
164
+ job.status = "ready";
165
+ job.availableAt = new Date(Date.now() + (options.delayMs ?? 0)).toISOString();
166
+ }
167
+ return { ok: true, job };
168
+ }
169
+ async listJobs(queue) {
170
+ return this.jobs.get(queue) ?? [];
171
+ }
172
+ async listDeadJobs(queue) {
173
+ return (this.jobs.get(queue) ?? []).filter((j) => j.status === "dead");
174
+ }
175
+ async search(_query, _options = {}) {
176
+ return [];
177
+ }
178
+ async putBatch(collection, objects) {
179
+ return Promise.all(objects.map((obj) => this.put(collection, obj)));
180
+ }
181
+ async deleteBatch(collection, ids) {
182
+ let count = 0;
183
+ for (const id of ids) {
184
+ const result = await this.delete(collection, id);
185
+ if (result.deleted) {
186
+ count++;
187
+ }
188
+ }
189
+ return count;
190
+ }
191
+ async createLink(fromRef, linkType, toRef, weight, metadataJson) {
192
+ this.linkCounter++;
193
+ const link = {
194
+ id: uid(),
195
+ fromRef,
196
+ linkType,
197
+ toRef,
198
+ weight,
199
+ metadataJson: metadataJson ?? "{}",
200
+ createdAt: now(),
201
+ };
202
+ this.links.set(link.id, link);
203
+ return link;
204
+ }
205
+ async deleteLink(id) {
206
+ return this.links.delete(id);
207
+ }
208
+ async getLink(id) {
209
+ return this.links.get(id) ?? null;
210
+ }
211
+ async getNeighbors(reference, direction = "Both", options = {}) {
212
+ let items = Array.from(this.links.values()).filter((l) => {
213
+ if (direction === "Outgoing" || direction === "Both") {
214
+ if (l.fromRef === reference) {
215
+ return true;
216
+ }
217
+ }
218
+ if (direction === "Incoming" || direction === "Both") {
219
+ if (l.toRef === reference) {
220
+ return true;
221
+ }
222
+ }
223
+ return false;
224
+ });
225
+ if (options.linkType) {
226
+ items = items.filter((l) => l.linkType === options.linkType);
227
+ }
228
+ if (options.limit) {
229
+ items = items.slice(0, options.limit);
230
+ }
231
+ return items;
232
+ }
233
+ async countObjects() {
234
+ return this.objects.size;
235
+ }
236
+ async countEvents() {
237
+ return Array.from(this.events.values()).reduce((acc, e) => acc + e.length, 0);
238
+ }
239
+ async countActiveJobs() {
240
+ let active = 0;
241
+ for (const jobs of this.jobs.values()) {
242
+ active += jobs.filter((j) => j.status === "ready" || j.status === "leased").length;
243
+ }
244
+ return active;
245
+ }
246
+ async countDeadJobs() {
247
+ let dead = 0;
248
+ for (const jobs of this.jobs.values()) {
249
+ dead += jobs.filter((j) => j.status === "dead").length;
250
+ }
251
+ return dead;
252
+ }
253
+ async countLinks() {
254
+ return this.links.size;
255
+ }
256
+ async listCollections() {
257
+ const collections = new Set(Array.from(this.objects.values()).map((r) => r.collection));
258
+ return Array.from(collections);
259
+ }
260
+ async listStreams() {
261
+ return Array.from(this.events.keys());
262
+ }
263
+ async listQueues() {
264
+ return Array.from(this.jobs.keys());
265
+ }
266
+ async close() { }
267
+ findJob(queue, jobId) {
268
+ return (this.jobs.get(queue) ?? []).find((j) => j.id === jobId);
269
+ }
270
+ }
@@ -0,0 +1,5 @@
1
+ export type { Link, LinkDirection, LinkQueryOptions, ListEventsOptions, ListObjectsOptions, MemoryEvent, MemoryObject, MemoryQueue, MemorySearchOptions, MemorySearchResult, QueueClaimOptions, QueueJob, QueueJobOptions, QueueJobPayload, QueueJobResult, QueueJobStatus, QueueNackOptions, SortBy, SortDirection, StoredMemoryEvent, StoredMemoryObject, ThingDConnection, ThingDeleteResult, ThingStore, } from "../types.js";
2
+ export { HttpThingStore, type HttpThingStoreOptions } from "./http-thing-store.js";
3
+ export { InMemoryThingStore } from "./in-memory-thing-store.js";
4
+ export { ThingD, type ThingDDriver, type ThingDOpenConfig, type ThingDOpenOptions, } from "./thingd.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,IAAI,EACJ,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,eAAe,EACf,eAAe,EACf,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,MAAM,EACN,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EACL,MAAM,EACN,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,GACvB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { HttpThingStore } from "./http-thing-store.js";
2
+ export { InMemoryThingStore } from "./in-memory-thing-store.js";
3
+ export { ThingD, } from "./thingd.js";
@@ -0,0 +1,46 @@
1
+ import type { ListEventsOptions, ListObjectsOptions, MemoryEvent, MemoryObject, MemoryQueue, MemorySearchOptions, MemorySearchResult, StoredMemoryEvent, StoredMemoryObject, ThingDConnection, ThingDeleteResult, ThingStore } from "../types.js";
2
+ export type ThingDDriver = "memory" | "http";
3
+ export type ThingDOpenOptions = {
4
+ driver?: ThingDDriver;
5
+ store?: ThingStore;
6
+ authToken?: string;
7
+ };
8
+ export type ThingDOpenConfig = ThingDOpenOptions & {
9
+ path?: string;
10
+ url?: string;
11
+ };
12
+ export declare class ThingD implements ThingDConnection {
13
+ readonly path: string;
14
+ private readonly store;
15
+ static open(pathOrConfig?: string | ThingDOpenConfig, options?: ThingDOpenOptions): Promise<ThingD>;
16
+ private constructor();
17
+ put(collection: string, object: MemoryObject): Promise<StoredMemoryObject>;
18
+ get<T = StoredMemoryObject>(collection: string, id: string): Promise<T | null>;
19
+ delete(collection: string, id: string): Promise<ThingDeleteResult>;
20
+ listObjects<T = StoredMemoryObject>(collection: string, options?: ListObjectsOptions): Promise<T[]>;
21
+ search(query: string, options?: MemorySearchOptions): Promise<MemorySearchResult[]>;
22
+ searchObjects<T = StoredMemoryObject>(query: string, options?: MemorySearchOptions): Promise<T[]>;
23
+ putBatch(collection: string, objects: MemoryObject[]): Promise<StoredMemoryObject[]>;
24
+ deleteBatch(collection: string, ids: string[]): Promise<number>;
25
+ readonly events: {
26
+ append: (stream: string, event: MemoryEvent) => Promise<StoredMemoryEvent>;
27
+ list: <T = StoredMemoryEvent>(stream?: string, options?: ListEventsOptions) => Promise<T[]>;
28
+ };
29
+ queue(name: string): MemoryQueue;
30
+ readonly links: {
31
+ create: (fromRef: string, linkType: string, toRef: string, weight?: number, metadataJson?: string) => Promise<import("../types.js").Link>;
32
+ delete: (id: string) => Promise<boolean>;
33
+ get: (id: string) => Promise<import("../types.js").Link | null>;
34
+ neighbors: (reference: string, direction?: import("../types.js").LinkDirection, options?: import("../types.js").LinkQueryOptions) => Promise<import("../types.js").Link[]>;
35
+ };
36
+ close(): Promise<void>;
37
+ countObjects(): Promise<number>;
38
+ countEvents(): Promise<number>;
39
+ countActiveJobs(): Promise<number>;
40
+ countDeadJobs(): Promise<number>;
41
+ countLinks(): Promise<number>;
42
+ listCollections(): Promise<string[]>;
43
+ listStreams(): Promise<string[]>;
44
+ listQueues(): Promise<string[]>;
45
+ }
46
+ //# sourceMappingURL=thingd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thingd.d.ts","sourceRoot":"","sources":["../../src/client/thingd.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ import { HttpThingStore } from "../client/http-thing-store.js";
2
+ import { InMemoryThingStore } from "../client/in-memory-thing-store.js";
3
+ export class ThingD {
4
+ path;
5
+ store;
6
+ static async open(pathOrConfig, options = {}) {
7
+ const resolved = resolveOpenOptions(pathOrConfig, options);
8
+ return new ThingD(resolved.path, await openStore(resolved.path, resolved));
9
+ }
10
+ constructor(path, store) {
11
+ this.path = path;
12
+ this.store = store;
13
+ }
14
+ put(collection, object) {
15
+ return this.store.put(collection, object);
16
+ }
17
+ get(collection, id) {
18
+ return this.store.get(collection, id);
19
+ }
20
+ delete(collection, id) {
21
+ return this.store.delete(collection, id);
22
+ }
23
+ listObjects(collection, options) {
24
+ return this.store.listObjects?.(collection, options) ?? Promise.resolve([]);
25
+ }
26
+ search(query, options = {}) {
27
+ return this.store.search(query, options);
28
+ }
29
+ async searchObjects(query, options = {}) {
30
+ const results = await this.search(query, options);
31
+ return results
32
+ .filter((r) => r.kind === "object")
33
+ .map((r) => r.value);
34
+ }
35
+ async putBatch(collection, objects) {
36
+ return (this.store.putBatch?.(collection, objects) ??
37
+ Promise.reject(new Error("Batch put not supported by this driver")));
38
+ }
39
+ async deleteBatch(collection, ids) {
40
+ return (this.store.deleteBatch?.(collection, ids) ??
41
+ Promise.reject(new Error("Batch delete not supported by this driver")));
42
+ }
43
+ events = {
44
+ append: (stream, event) => this.store.appendEvent(stream, event),
45
+ list: (stream, options) => this.store.listEvents(stream, options),
46
+ };
47
+ queue(name) {
48
+ return {
49
+ push: (payload, options) => this.store.pushJob(name, payload, options),
50
+ claim: (options) => this.store.claimJob(name, options),
51
+ ack: (jobId) => this.store.ackJob(name, jobId),
52
+ nack: (jobId, options) => this.store.nackJob(name, jobId, options),
53
+ list: () => this.store.listJobs(name),
54
+ dead: () => this.store.listDeadJobs(name),
55
+ };
56
+ }
57
+ links = {
58
+ create: (fromRef, linkType, toRef, weight, metadataJson) => this.store.createLink?.(fromRef, linkType, toRef, weight, metadataJson) ??
59
+ Promise.reject(new Error("Graph links not supported by this driver")),
60
+ delete: (id) => this.store.deleteLink?.(id) ?? Promise.resolve(false),
61
+ get: (id) => this.store.getLink?.(id) ?? Promise.resolve(null),
62
+ neighbors: (reference, direction = "Both", options = {}) => this.store.getNeighbors?.(reference, direction, options) ?? Promise.resolve([]),
63
+ };
64
+ async close() {
65
+ await this.store.close?.();
66
+ }
67
+ async countObjects() {
68
+ return this.store.countObjects?.() ?? 0;
69
+ }
70
+ async countEvents() {
71
+ return this.store.countEvents?.() ?? 0;
72
+ }
73
+ async countActiveJobs() {
74
+ return this.store.countActiveJobs?.() ?? 0;
75
+ }
76
+ async countDeadJobs() {
77
+ return this.store.countDeadJobs?.() ?? 0;
78
+ }
79
+ async countLinks() {
80
+ return this.store.countLinks?.() ?? 0;
81
+ }
82
+ async listCollections() {
83
+ return this.store.listCollections?.() ?? [];
84
+ }
85
+ async listStreams() {
86
+ return this.store.listStreams?.() ?? [];
87
+ }
88
+ async listQueues() {
89
+ return this.store.listQueues?.() ?? [];
90
+ }
91
+ }
92
+ function resolveOpenOptions(pathOrConfig, options) {
93
+ const config = typeof pathOrConfig === "string" ? { path: pathOrConfig } : (pathOrConfig ?? {});
94
+ const path = config.url ?? config.path ?? ":memory:";
95
+ const driver = options.driver ?? config.driver ?? (isHttpPath(path) ? "http" : "memory");
96
+ return {
97
+ ...config,
98
+ ...options,
99
+ path,
100
+ driver,
101
+ authToken: options.authToken ?? config.authToken,
102
+ };
103
+ }
104
+ function isHttpPath(path) {
105
+ return path.startsWith("http://") || path.startsWith("https://") || path.startsWith("thingd://");
106
+ }
107
+ async function openStore(path, options) {
108
+ if (options.store) {
109
+ return options.store;
110
+ }
111
+ if (options.driver === "http") {
112
+ return HttpThingStore.open({ url: path, authToken: options.authToken });
113
+ }
114
+ return new InMemoryThingStore();
115
+ }
@@ -0,0 +1,11 @@
1
+ export { appendMcpAuditEvent, createThingdMcpServer, jsonResult, parseCollectionAllowlist, parsePayloadSizeLimit, type RegisterThingdToolsOptions, readMcpHardeningOptionsFromEnv, registerThingdTools, resolveThingdMcpAuditOptions, type ThingdMcpAuditMetadata, type ThingdMcpAuditOptions, type ThingdMcpHardeningOptions, type ThingdMcpServerOptions, } from "./mcp/index.js";
2
+ export { handleRestRequest, parseFilter, parseIntParam, parseSortBy, readBody, sendData, sendDataList, sendError, sendJson, } from "./rest/index.js";
3
+ export type { CloudThingStoreOptions } from "./stores/cloud-thing-store.js";
4
+ export { CloudThingStore } from "./stores/cloud-thing-store.js";
5
+ export { InMemoryThingStore } from "./stores/in-memory-thing-store.js";
6
+ export { NativeThingStore } from "./stores/native-thing-store.js";
7
+ export type { ThingDDriver, ThingDOpenConfig, ThingDOpenOptions } from "./thingd.js";
8
+ export { ThingD } from "./thingd.js";
9
+ export type { Link, LinkDirection, LinkQueryOptions, ListEventsOptions, ListObjectsOptions, MemoryEvent, MemoryObject, MemoryQueue, MemorySearchOptions, MemorySearchResult, QueueClaimOptions, QueueJob, QueueJobOptions, QueueJobPayload, QueueJobResult, QueueJobStatus, QueueNackOptions, SortBy, SortDirection, StoredMemoryEvent, StoredMemoryObject, ThingDConnection, ThingDeleteResult, ThingStore, } from "./types.js";
10
+ export { SDK_VERSION } from "./version.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,UAAU,EACV,wBAAwB,EACxB,qBAAqB,EACrB,KAAK,0BAA0B,EAC/B,8BAA8B,EAC9B,mBAAmB,EACnB,4BAA4B,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,GAC5B,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,QAAQ,GACT,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EACV,IAAI,EACJ,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,eAAe,EACf,eAAe,EACf,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,MAAM,EACN,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ // MCP server (tool handlers + factory)
2
+ export { appendMcpAuditEvent, createThingdMcpServer, jsonResult, parseCollectionAllowlist, parsePayloadSizeLimit, readMcpHardeningOptionsFromEnv, registerThingdTools, resolveThingdMcpAuditOptions, } from "./mcp/index.js";
3
+ // REST API (route handlers + helpers)
4
+ export { handleRestRequest, parseFilter, parseIntParam, parseSortBy, readBody, sendData, sendDataList, sendError, sendJson, } from "./rest/index.js";
5
+ export { CloudThingStore } from "./stores/cloud-thing-store.js";
6
+ export { InMemoryThingStore } from "./stores/in-memory-thing-store.js";
7
+ export { NativeThingStore } from "./stores/native-thing-store.js";
8
+ export { ThingD } from "./thingd.js";
9
+ export { SDK_VERSION } from "./version.js";
@@ -0,0 +1,27 @@
1
+ import type { ThingD } from "../thingd.js";
2
+ export type ThingdMcpAuditOptions = {
3
+ enabled?: boolean;
4
+ actor?: string;
5
+ source?: string;
6
+ stream?: string;
7
+ };
8
+ export type ThingdMcpAuditMetadata = {
9
+ actor?: string;
10
+ source?: string;
11
+ };
12
+ type ResolvedThingdMcpAuditOptions = {
13
+ enabled: boolean;
14
+ actor: string;
15
+ source: string;
16
+ stream: string;
17
+ };
18
+ type ThingdMcpAuditEventOptions = {
19
+ action: string;
20
+ target: Record<string, unknown>;
21
+ metadata?: ThingdMcpAuditMetadata;
22
+ result?: Record<string, unknown>;
23
+ };
24
+ export declare function resolveThingdMcpAuditOptions(options: ThingdMcpAuditOptions | false | undefined): ResolvedThingdMcpAuditOptions;
25
+ export declare function appendMcpAuditEvent(db: ThingD, options: ResolvedThingdMcpAuditOptions, event: ThingdMcpAuditEventOptions): Promise<void>;
26
+ export {};
27
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/mcp/audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,KAAK,0BAA0B,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC,CAAC;AAMF,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,qBAAqB,GAAG,KAAK,GAAG,SAAS,GACjD,6BAA6B,CAgB/B;AAED,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,6BAA6B,EACtC,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,IAAI,CAAC,CAkBf"}
@@ -0,0 +1,36 @@
1
+ const DEFAULT_AUDIT_STREAM = "__thingd:mcp:audit";
2
+ const DEFAULT_AUDIT_ACTOR = "mcp-client";
3
+ const DEFAULT_AUDIT_SOURCE = "thingd-mcp";
4
+ export function resolveThingdMcpAuditOptions(options) {
5
+ if (options === false || options?.enabled === false) {
6
+ return {
7
+ enabled: false,
8
+ actor: DEFAULT_AUDIT_ACTOR,
9
+ source: DEFAULT_AUDIT_SOURCE,
10
+ stream: DEFAULT_AUDIT_STREAM,
11
+ };
12
+ }
13
+ return {
14
+ enabled: true,
15
+ actor: options?.actor ?? DEFAULT_AUDIT_ACTOR,
16
+ source: options?.source ?? DEFAULT_AUDIT_SOURCE,
17
+ stream: options?.stream ?? DEFAULT_AUDIT_STREAM,
18
+ };
19
+ }
20
+ export async function appendMcpAuditEvent(db, options, event) {
21
+ if (!options.enabled) {
22
+ return;
23
+ }
24
+ const actor = event.metadata?.actor ?? options.actor;
25
+ const source = event.metadata?.source ?? options.source;
26
+ const auditEvent = {
27
+ type: `mcp.${event.action}`,
28
+ text: `MCP ${event.action} by ${actor}`,
29
+ actor,
30
+ source,
31
+ target: event.target,
32
+ result: event.result,
33
+ at: new Date().toISOString(),
34
+ };
35
+ await db.events.append(options.stream, auditEvent);
36
+ }
@@ -0,0 +1,22 @@
1
+ export type ThingdMcpHardeningOptions = {
2
+ /** Comma-separated collection allowlist from THINGD_MCP_COLLECTIONS. Empty = all allowed. */
3
+ collectionAllowlist?: Set<string>;
4
+ /** When true, all write tools are rejected. Set via THINGD_MCP_READ_ONLY=true. */
5
+ readOnly?: boolean;
6
+ /** Maximum HTTP request body in bytes. Set via THINGD_MCP_MAX_PAYLOAD_BYTES. Default 512 KB. */
7
+ maxPayloadBytes?: number;
8
+ };
9
+ /**
10
+ * Parse THINGD_MCP_COLLECTIONS into a Set.
11
+ * An empty string or missing env var means all collections are allowed.
12
+ */
13
+ export declare function parseCollectionAllowlist(value: string | undefined): Set<string> | undefined;
14
+ /**
15
+ * Parse THINGD_MCP_MAX_PAYLOAD_BYTES. Defaults to 512 KB if unset or zero.
16
+ */
17
+ export declare function parsePayloadSizeLimit(value: string | undefined, defaultBytes?: number): number;
18
+ /**
19
+ * Read all Phase-6 MCP hardening options from the environment.
20
+ */
21
+ export declare function readMcpHardeningOptionsFromEnv(env: Record<string, string | undefined>): ThingdMcpHardeningOptions;
22
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/mcp/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,yBAAyB,GAAG;IACtC,6FAA6F;IAC7F,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClC,kFAAkF;IAClF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gGAAgG;IAChG,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,CAW3F;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,SAAU,GAAG,MAAM,CAW/F;AAmBD;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtC,yBAAyB,CAQ3B"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Parse THINGD_MCP_COLLECTIONS into a Set.
3
+ * An empty string or missing env var means all collections are allowed.
4
+ */
5
+ export function parseCollectionAllowlist(value) {
6
+ if (!value?.trim()) {
7
+ return undefined;
8
+ }
9
+ const names = value
10
+ .split(",")
11
+ .map((s) => s.trim())
12
+ .filter(Boolean);
13
+ return names.length > 0 ? new Set(names) : undefined;
14
+ }
15
+ /**
16
+ * Parse THINGD_MCP_MAX_PAYLOAD_BYTES. Defaults to 512 KB if unset or zero.
17
+ */
18
+ export function parsePayloadSizeLimit(value, defaultBytes = 524_288) {
19
+ if (!value) {
20
+ return defaultBytes;
21
+ }
22
+ const n = Number.parseInt(value, 10);
23
+ if (!Number.isInteger(n) || n <= 0) {
24
+ throw new Error(`Invalid THINGD_MCP_MAX_PAYLOAD_BYTES: ${value}`);
25
+ }
26
+ return n;
27
+ }
28
+ function parseBooleanFlag(value, name) {
29
+ if (!value) {
30
+ return false;
31
+ }
32
+ const normalized = value.toLowerCase();
33
+ if (["1", "true", "yes", "on"].includes(normalized)) {
34
+ return true;
35
+ }
36
+ if (["0", "false", "no", "off"].includes(normalized)) {
37
+ return false;
38
+ }
39
+ throw new Error(`Invalid ${name}: expected true or false`);
40
+ }
41
+ /**
42
+ * Read all Phase-6 MCP hardening options from the environment.
43
+ */
44
+ export function readMcpHardeningOptionsFromEnv(env) {
45
+ return {
46
+ collectionAllowlist: parseCollectionAllowlist(env.THINGD_MCP_COLLECTIONS),
47
+ readOnly: env.THINGD_MCP_READ_ONLY
48
+ ? parseBooleanFlag(env.THINGD_MCP_READ_ONLY, "THINGD_MCP_READ_ONLY")
49
+ : undefined,
50
+ maxPayloadBytes: parsePayloadSizeLimit(env.THINGD_MCP_MAX_PAYLOAD_BYTES),
51
+ };
52
+ }
@@ -0,0 +1,6 @@
1
+ export { appendMcpAuditEvent, resolveThingdMcpAuditOptions, type ThingdMcpAuditMetadata, type ThingdMcpAuditOptions, } from "./audit.js";
2
+ export { parseCollectionAllowlist, parsePayloadSizeLimit, readMcpHardeningOptionsFromEnv, type ThingdMcpHardeningOptions, } from "./config.js";
3
+ export { jsonResult } from "./result.js";
4
+ export { createThingdMcpServer, type ThingdMcpServerOptions } from "./server.js";
5
+ export { type RegisterThingdToolsOptions, registerThingdTools } from "./tools.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,GAC3B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,8BAA8B,EAC9B,KAAK,yBAAyB,GAC/B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,qBAAqB,EAAE,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,KAAK,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { appendMcpAuditEvent, resolveThingdMcpAuditOptions, } from "./audit.js";
2
+ export { parseCollectionAllowlist, parsePayloadSizeLimit, readMcpHardeningOptionsFromEnv, } from "./config.js";
3
+ export { jsonResult } from "./result.js";
4
+ export { createThingdMcpServer } from "./server.js";
5
+ export { registerThingdTools } from "./tools.js";
@@ -0,0 +1,3 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare function jsonResult(value: unknown): CallToolResult;
3
+ //# sourceMappingURL=result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../src/mcp/result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CASzD"}