@plures/pluresdb 1.5.3 → 2.9.6

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 (103) hide show
  1. package/README.md +106 -414
  2. package/crates/README.md +99 -0
  3. package/crates/pluresdb-node/README.md +181 -0
  4. package/crates/pluresdb-node/index.d.ts +0 -0
  5. package/crates/pluresdb-node/index.js +265 -0
  6. package/crates/pluresdb-node/package.json +35 -0
  7. package/dist/napi/index.js +60 -0
  8. package/embedded.d.ts +1 -0
  9. package/embedded.js +46 -0
  10. package/package.json +27 -10
  11. package/dist/.tsbuildinfo +0 -1
  12. package/dist/better-sqlite3-shared.d.ts +0 -12
  13. package/dist/better-sqlite3-shared.d.ts.map +0 -1
  14. package/dist/better-sqlite3-shared.js +0 -143
  15. package/dist/better-sqlite3-shared.js.map +0 -1
  16. package/dist/better-sqlite3.d.ts +0 -4
  17. package/dist/better-sqlite3.d.ts.map +0 -1
  18. package/dist/better-sqlite3.js +0 -8
  19. package/dist/better-sqlite3.js.map +0 -1
  20. package/dist/cli.d.ts +0 -7
  21. package/dist/cli.d.ts.map +0 -1
  22. package/dist/cli.js.map +0 -1
  23. package/dist/node-index.d.ts +0 -148
  24. package/dist/node-index.d.ts.map +0 -1
  25. package/dist/node-index.js +0 -665
  26. package/dist/node-index.js.map +0 -1
  27. package/dist/node-wrapper.d.ts +0 -44
  28. package/dist/node-wrapper.d.ts.map +0 -1
  29. package/dist/node-wrapper.js +0 -296
  30. package/dist/node-wrapper.js.map +0 -1
  31. package/dist/types/index.d.ts +0 -28
  32. package/dist/types/index.d.ts.map +0 -1
  33. package/dist/types/index.js +0 -3
  34. package/dist/types/index.js.map +0 -1
  35. package/dist/types/node-types.d.ts +0 -71
  36. package/dist/types/node-types.d.ts.map +0 -1
  37. package/dist/types/node-types.js +0 -6
  38. package/dist/types/node-types.js.map +0 -1
  39. package/dist/vscode/extension.d.ts +0 -81
  40. package/dist/vscode/extension.d.ts.map +0 -1
  41. package/dist/vscode/extension.js +0 -309
  42. package/dist/vscode/extension.js.map +0 -1
  43. package/examples/basic-usage.d.ts +0 -2
  44. package/examples/basic-usage.d.ts.map +0 -1
  45. package/examples/basic-usage.js +0 -26
  46. package/examples/basic-usage.js.map +0 -1
  47. package/examples/basic-usage.ts +0 -29
  48. package/examples/vscode-extension-example/README.md +0 -95
  49. package/examples/vscode-extension-example/package.json +0 -49
  50. package/examples/vscode-extension-example/src/extension.ts +0 -172
  51. package/examples/vscode-extension-example/tsconfig.json +0 -12
  52. package/examples/vscode-extension-integration.d.ts +0 -31
  53. package/examples/vscode-extension-integration.d.ts.map +0 -1
  54. package/examples/vscode-extension-integration.js +0 -319
  55. package/examples/vscode-extension-integration.js.map +0 -1
  56. package/examples/vscode-extension-integration.ts +0 -41
  57. package/legacy/benchmarks/memory-benchmarks.ts +0 -350
  58. package/legacy/benchmarks/run-benchmarks.ts +0 -315
  59. package/legacy/better-sqlite3-shared.ts +0 -157
  60. package/legacy/better-sqlite3.ts +0 -4
  61. package/legacy/cli.ts +0 -241
  62. package/legacy/config.ts +0 -50
  63. package/legacy/core/crdt.ts +0 -107
  64. package/legacy/core/database.ts +0 -529
  65. package/legacy/healthcheck.ts +0 -162
  66. package/legacy/http/api-server.ts +0 -438
  67. package/legacy/index.ts +0 -28
  68. package/legacy/logic/rules.ts +0 -46
  69. package/legacy/main.rs +0 -3
  70. package/legacy/main.ts +0 -197
  71. package/legacy/network/websocket-server.ts +0 -115
  72. package/legacy/node-index.ts +0 -823
  73. package/legacy/node-wrapper.ts +0 -329
  74. package/legacy/sqlite-compat.ts +0 -633
  75. package/legacy/sqlite3-compat.ts +0 -55
  76. package/legacy/storage/kv-storage.ts +0 -73
  77. package/legacy/tests/core.test.ts +0 -305
  78. package/legacy/tests/fixtures/performance-data.json +0 -71
  79. package/legacy/tests/fixtures/test-data.json +0 -129
  80. package/legacy/tests/integration/api-server.test.ts +0 -334
  81. package/legacy/tests/integration/mesh-network.test.ts +0 -303
  82. package/legacy/tests/logic.test.ts +0 -34
  83. package/legacy/tests/performance/load.test.ts +0 -290
  84. package/legacy/tests/security/input-validation.test.ts +0 -286
  85. package/legacy/tests/unit/core.test.ts +0 -226
  86. package/legacy/tests/unit/subscriptions.test.ts +0 -135
  87. package/legacy/tests/unit/vector-search.test.ts +0 -173
  88. package/legacy/tests/vscode_extension_test.ts +0 -281
  89. package/legacy/types/index.ts +0 -32
  90. package/legacy/types/node-types.ts +0 -80
  91. package/legacy/util/debug.ts +0 -14
  92. package/legacy/vector/index.ts +0 -59
  93. package/legacy/vscode/extension.ts +0 -387
  94. package/scripts/compiled-crud-verify.ts +0 -30
  95. package/scripts/dogfood.ts +0 -297
  96. package/scripts/postinstall.js +0 -156
  97. package/scripts/publish-crates.sh +0 -95
  98. package/scripts/release-check.js +0 -224
  99. package/scripts/run-tests.ts +0 -178
  100. package/scripts/setup-libclang.ps1 +0 -209
  101. package/scripts/update-changelog.js +0 -214
  102. package/web/README.md +0 -27
  103. package/web/svelte/package.json +0 -31
@@ -1,438 +0,0 @@
1
- import { GunDB } from "../core/database.ts";
2
- import { loadConfig, saveConfig } from "../config.ts";
3
-
4
- export interface ApiServerHandle {
5
- url: string;
6
- close: () => void;
7
- }
8
-
9
- const STATIC_ROOT = new URL("../../web/svelte/dist/", import.meta.url);
10
-
11
- function corsHeaders(extra?: Record<string, string>): Headers {
12
- const headers = new Headers(extra);
13
- headers.set("Access-Control-Allow-Origin", "*");
14
- headers.set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
15
- headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
16
- headers.set("Vary", "Origin");
17
- return headers;
18
- }
19
-
20
- export function startApiServer(
21
- opts: { port: number; db: GunDB },
22
- ): ApiServerHandle {
23
- const { port, db } = opts;
24
-
25
- const handler = async (req: Request): Promise<Response> => {
26
- try {
27
- const url = new URL(req.url);
28
- const path = url.pathname;
29
-
30
- if (req.method === "OPTIONS") {
31
- return new Response(null, { status: 200, headers: corsHeaders() });
32
- }
33
-
34
- if (path === "/api/events") {
35
- const stream = new ReadableStream({
36
- start(controller) {
37
- const enc = new TextEncoder();
38
- const send = (evt: { id: string; node: unknown | null }) => {
39
- const line = `data: ${JSON.stringify(evt)}\n\n`;
40
- controller.enqueue(enc.encode(line));
41
- };
42
- const cb = (e: { id: string; node: unknown | null }) => send(e);
43
- db.onAny(cb as any);
44
- (async () => {
45
- for await (const n of db.list()) {
46
- send({ id: n.id, node: { id: n.id, data: n.data } });
47
- }
48
- })();
49
- return () => db.offAny(cb as any);
50
- },
51
- });
52
- return new Response(stream, {
53
- headers: corsHeaders({ "content-type": "text/event-stream" }),
54
- });
55
- }
56
-
57
- if (path.startsWith("/api/nodes/")) {
58
- const id = decodeURIComponent(path.slice("/api/nodes/".length));
59
- if (req.method === "GET") {
60
- const val = await db.get<Record<string, unknown>>(id);
61
- if (!val) return json({ error: "not found" }, 404);
62
- return json(val);
63
- }
64
- if (req.method === "PUT") {
65
- const body = await req.json().catch(() => null);
66
- if (!body || typeof body !== "object" || Array.isArray(body)) {
67
- return json({ error: "invalid json" }, 400);
68
- }
69
- await db.put(id, body as Record<string, unknown>);
70
- return json({ ok: true });
71
- }
72
- if (req.method === "DELETE") {
73
- await db.delete(id);
74
- return json({ ok: true });
75
- }
76
- return json({ error: "method" }, 405);
77
- }
78
-
79
- if (path.startsWith("/api/")) {
80
- switch (path) {
81
- case "/api/config": {
82
- if (req.method === "GET") {
83
- const cfg = await loadConfig();
84
- return json(cfg);
85
- }
86
- if (req.method === "POST") {
87
- const body = (await req.json().catch(() => null)) as
88
- | Record<string, unknown>
89
- | null;
90
- if (!body) return json({ error: "missing body" }, 400);
91
- const current = await loadConfig();
92
- const next = { ...current, ...body } as Record<string, unknown>;
93
- await saveConfig(next);
94
- return json({ ok: true });
95
- }
96
- return json({ error: "method" }, 405);
97
- }
98
- case "/api/get": {
99
- const id = url.searchParams.get("id");
100
- if (!id) return json({ error: "missing id" }, 400);
101
- const val = await db.get<Record<string, unknown>>(id);
102
- return json(val);
103
- }
104
- case "/api/put": {
105
- if (req.method !== "POST") return json({ error: "method" }, 405);
106
- const body = (await req.json().catch(() => null)) as
107
- | { id?: string; data?: Record<string, unknown> }
108
- | null;
109
- if (!body?.id || !body?.data) {
110
- return json({ error: "missing body {id,data}" }, 400);
111
- }
112
- await db.put(body.id, body.data);
113
- return json({ ok: true });
114
- }
115
- case "/api/delete": {
116
- const id = url.searchParams.get("id");
117
- if (!id) return json({ error: "missing id" }, 400);
118
- await db.delete(id);
119
- return json({ ok: true });
120
- }
121
- case "/api/search": {
122
- if (req.method === "POST") {
123
- const payload = (await req.json().catch(() => null)) as
124
- | { query?: string | number[]; limit?: number }
125
- | null;
126
- if (!payload || (!payload.query && payload.query !== "")) {
127
- return json({ error: "missing query" }, 400);
128
- }
129
- const limit = Number.isFinite(payload.limit) ? payload.limit! : 5;
130
- const nodes = await db.vectorSearch(payload.query ?? "", limit);
131
- return json(nodes.map((n) => ({ id: n.id, data: n.data })));
132
- }
133
- const q = url.searchParams.get("q") ?? "";
134
- const k = Number(url.searchParams.get("k") ?? "5");
135
- const nodes = await db.vectorSearch(q, Number.isFinite(k) ? k : 5);
136
- return json(nodes.map((n) => ({ id: n.id, data: n.data })));
137
- }
138
- case "/api/list": {
139
- const out: Array<{ id: string; data: Record<string, unknown> }> =
140
- [];
141
- for await (const n of db.list()) {
142
- out.push({ id: n.id, data: n.data as Record<string, unknown> });
143
- }
144
- return json(out);
145
- }
146
- case "/api/instances": {
147
- const typeName = url.searchParams.get("type");
148
- if (!typeName) return json({ error: "missing type" }, 400);
149
- const nodes = await db.instancesOf(typeName);
150
- return json(nodes.map((n) => ({ id: n.id, data: n.data })));
151
- }
152
- case "/api/history": {
153
- const id = url.searchParams.get("id");
154
- if (!id) return json({ error: "missing id" }, 400);
155
- const history = await db.getNodeHistory(id);
156
- return json(
157
- history.map((h) => ({
158
- id: h.id,
159
- data: h.data,
160
- timestamp: h.timestamp,
161
- vectorClock: h.vectorClock,
162
- state: h.state,
163
- })),
164
- );
165
- }
166
- case "/api/restore": {
167
- const id = url.searchParams.get("id");
168
- const timestamp = url.searchParams.get("timestamp");
169
- if (!id || !timestamp) {
170
- return json({ error: "missing id or timestamp" }, 400);
171
- }
172
- await db.restoreNodeVersion(id, parseInt(timestamp));
173
- return json({ success: true });
174
- }
175
- case "/api/identity": {
176
- if (req.method !== "POST") return json({ error: "method" }, 405);
177
- const body = (await req.json().catch(() => null)) as
178
- | { name?: string; email?: string }
179
- | null;
180
- if (!body?.name || !body?.email) {
181
- return json({ error: "missing name or email" }, 400);
182
- }
183
- // Generate a simple identity (stub implementation)
184
- const id = `peer_${Date.now()}_${Math.random().toString(36).substring(7)}`;
185
- const publicKey = `pk_${Math.random().toString(36).substring(2)}`;
186
- return json({ id, publicKey, name: body.name, email: body.email });
187
- }
188
- case "/api/peers/search": {
189
- const q = url.searchParams.get("q") ?? "";
190
- // Stub implementation - return empty array for now
191
- // In a real implementation, this would search for peers in the network
192
- return json([]);
193
- }
194
- case "/api/share": {
195
- if (req.method !== "POST") return json({ error: "method" }, 405);
196
- const body = (await req.json().catch(() => null)) as
197
- | {
198
- nodeId?: string;
199
- targetPeerId?: string;
200
- accessLevel?: string;
201
- }
202
- | null;
203
- if (!body?.nodeId || !body?.targetPeerId) {
204
- return json(
205
- { error: "missing nodeId or targetPeerId" },
206
- 400,
207
- );
208
- }
209
- // Stub implementation - generate a shared node ID
210
- const sharedNodeId = `shared_${Date.now()}_${Math.random().toString(36).substring(7)}`;
211
- return json({
212
- sharedNodeId,
213
- nodeId: body.nodeId,
214
- targetPeerId: body.targetPeerId,
215
- accessLevel: body.accessLevel || "read-only",
216
- });
217
- }
218
- case "/api/share/accept": {
219
- if (req.method !== "POST") return json({ error: "method" }, 405);
220
- const body = (await req.json().catch(() => null)) as
221
- | { sharedNodeId?: string }
222
- | null;
223
- if (!body?.sharedNodeId) {
224
- return json({ error: "missing sharedNodeId" }, 400);
225
- }
226
- // Stub implementation - accept shared node
227
- return json({ success: true, sharedNodeId: body.sharedNodeId });
228
- }
229
- case "/api/devices": {
230
- if (req.method === "POST") {
231
- const body = (await req.json().catch(() => null)) as
232
- | { name?: string; type?: string }
233
- | null;
234
- if (!body?.name || !body?.type) {
235
- return json({ error: "missing name or type" }, 400);
236
- }
237
- // Stub implementation - generate device ID
238
- const id = `device_${Date.now()}_${Math.random().toString(36).substring(7)}`;
239
- return json({
240
- id,
241
- name: body.name,
242
- type: body.type,
243
- status: "online",
244
- });
245
- }
246
- // GET devices list
247
- return json([]);
248
- }
249
- case "/api/devices/sync": {
250
- if (req.method !== "POST") return json({ error: "method" }, 405);
251
- const body = (await req.json().catch(() => null)) as
252
- | { deviceId?: string }
253
- | null;
254
- if (!body?.deviceId) {
255
- return json({ error: "missing deviceId" }, 400);
256
- }
257
- // Stub implementation - sync with device
258
- return json({ success: true, deviceId: body.deviceId });
259
- }
260
- default:
261
- return json({ error: "not found" }, 404);
262
- }
263
- }
264
-
265
- if (req.method === "GET") {
266
- const mapPath = path === "/" ? "/index.html" : path;
267
- const fileUrl = new URL(
268
- mapPath.startsWith("/") ? `.${mapPath}` : mapPath,
269
- STATIC_ROOT,
270
- );
271
- try {
272
- const data = await Deno.readFile(fileUrl);
273
- const contentType = mapPath.endsWith(".html")
274
- ? "text/html; charset=utf-8"
275
- : mapPath.endsWith(".js")
276
- ? "application/javascript"
277
- : mapPath.endsWith(".css")
278
- ? "text/css"
279
- : mapPath.endsWith(".json")
280
- ? "application/json"
281
- : mapPath.endsWith(".svg")
282
- ? "image/svg+xml"
283
- : mapPath.endsWith(".png")
284
- ? "image/png"
285
- : "application/octet-stream";
286
- return new Response(data, {
287
- headers: corsHeaders({ "content-type": contentType }),
288
- });
289
- } catch {
290
- if (path === "/" || path === "/index.html") {
291
- return new Response(INDEX_HTML, {
292
- headers: corsHeaders({
293
- "content-type": "text/html; charset=utf-8",
294
- }),
295
- });
296
- }
297
- }
298
- }
299
-
300
- return new Response("Not Found", { status: 404, headers: corsHeaders() });
301
- } catch (e) {
302
- const msg = e && typeof e === "object" && "message" in e
303
- ? String((e as any).message)
304
- : String(e);
305
- return json({ error: msg }, 500);
306
- }
307
- };
308
-
309
- const server = Deno.serve({ port, onListen: () => {} }, handler);
310
- return {
311
- url: `http://localhost:${port}`,
312
- close: () => {
313
- try {
314
- server.shutdown();
315
- } catch {
316
- /* ignore */
317
- }
318
- },
319
- };
320
- }
321
-
322
- function json(data: unknown, status = 200): Response {
323
- return new Response(JSON.stringify(data), {
324
- status,
325
- headers: corsHeaders({ "content-type": "application/json" }),
326
- });
327
- }
328
-
329
- const INDEX_HTML = `<!doctype html>
330
- <html>
331
- <head>
332
- <meta charset="utf-8" />
333
- <meta name="viewport" content="width=device-width, initial-scale=1" />
334
- <title>PluresDB</title>
335
- <style>
336
- body { font-family: system-ui, sans-serif; margin: 24px; }
337
- h1 { margin-top: 0; }
338
- section { border: 1px solid #ddd; padding: 12px; margin-bottom: 16px; border-radius: 8px; }
339
- input, textarea, button, select { font: inherit; }
340
- code { background:#f5f5f5; padding:2px 4px; border-radius:4px; }
341
- pre { background:#f9f9f9; padding:8px; border-radius:6px; overflow:auto; }
342
- label { display:block; margin:6px 0 4px; }
343
- </style>
344
- </head>
345
- <body>
346
- <h1>PluresDB</h1>
347
- <p>Reactive UI. Data updates automatically.</p>
348
- <div style="display:flex; gap:16px; align-items:flex-start;">
349
- <section style="flex:1; min-width:300px;">
350
- <h3>Nodes</h3>
351
- <input id="filter" placeholder="filter by id..." oninput="render()" />
352
- <ul id="list" style="list-style:none; padding-left:0; max-height:60vh; overflow:auto;"></ul>
353
- <button onclick="createNode()">Create node</button>
354
- </section>
355
- <section style="flex:2;">
356
- <h3>Details</h3>
357
- <div id="detail-empty">Select a node on the left</div>
358
- <div id="detail" style="display:none;">
359
- <label>Id</label>
360
- <input id="d-id" disabled />
361
- <label>JSON data</label>
362
- <textarea id="d-json" rows="12" oninput="debouncedSave()"></textarea>
363
- <div style="margin-top:8px; display:flex; gap:8px;">
364
- <button onclick="delSelected()">Delete</button>
365
- </div>
366
- </div>
367
- <h3>Vector search</h3>
368
- <input id="q" placeholder="search text" oninput="debouncedSearch()" />
369
- <pre id="search-out"></pre>
370
- </section>
371
- </div>
372
- <script>
373
- const state = { items: new Map(), selected: null, timer: null, stimer: null };
374
- const ev = new EventSource('/api/events');
375
- ev.onmessage = (e) => {
376
- const evt = JSON.parse(e.data);
377
- if(evt.node){ state.items.set(evt.id, evt.node.data); } else { state.items.delete(evt.id); }
378
- if(state.selected === evt.id && evt.node) {
379
- // Avoid clobber when user is actively editing: basic heuristic
380
- const el = document.getElementById('d-json');
381
- if(document.activeElement !== el) el.value = JSON.stringify(evt.node.data,null,2);
382
- }
383
- render();
384
- };
385
- function render(){
386
- const list = document.getElementById('list');
387
- const f = (document.getElementById('filter').value||'').toLowerCase();
388
- list.innerHTML = '';
389
- const ids = Array.from(state.items.keys()).filter(id=>id.toLowerCase().includes(f)).sort();
390
- for(const id of ids){
391
- const li = document.createElement('li');
392
- li.textContent = id;
393
- li.style.cursor='pointer';
394
- li.style.padding='4px 6px';
395
- if(id===state.selected) { li.style.background='#eef'; }
396
- li.onclick=()=>select(id);
397
- list.appendChild(li);
398
- }
399
- const hasSel = !!state.selected;
400
- document.getElementById('detail-empty').style.display = hasSel? 'none':'block';
401
- document.getElementById('detail').style.display = hasSel? 'block':'none';
402
- }
403
- async function select(id){
404
- state.selected = id;
405
- document.getElementById('d-id').value = id;
406
- const data = state.items.get(id) ?? {};
407
- document.getElementById('d-json').value = JSON.stringify(data,null,2);
408
- render();
409
- }
410
- function debouncedSave(){
411
- clearTimeout(state.timer);
412
- state.timer = setTimeout(saveNow, 350);
413
- }
414
- async function saveNow(){
415
- const id = state.selected; if(!id) return;
416
- let data; try{ data = JSON.parse(document.getElementById('d-json').value); }catch{ return }
417
- await fetch('/api/put', { method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify({id, data}) });
418
- }
419
- async function delSelected(){
420
- const id = state.selected; if(!id) return;
421
- await fetch('/api/delete?id='+encodeURIComponent(id));
422
- state.selected = null; render();
423
- }
424
- async function createNode(){
425
- const id = prompt('New node id:'); if(!id) return;
426
- await fetch('/api/put', { method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify({id, data:{}}) });
427
- select(id);
428
- }
429
- function debouncedSearch(){ clearTimeout(state.stimer); state.stimer = setTimeout(searchNow,300); }
430
- async function searchNow(){
431
- const q = document.getElementById('q').value || '';
432
- const res = await fetch('/api/search?q='+encodeURIComponent(q)+'&k=5');
433
- const j = await res.json();
434
- document.getElementById('search-out').textContent = JSON.stringify(j,null,2);
435
- }
436
- </script>
437
- </body>
438
- </html>`;
package/legacy/index.ts DELETED
@@ -1,28 +0,0 @@
1
- /**
2
- * Deno-first entry point for PluresDB.
3
- *
4
- * This module intentionally re-exports only browser/Deno compatible code.
5
- * Node.js consumers should use the compiled entry at `pluresdb/node` instead.
6
- */
7
-
8
- export { GunDB } from "./core/database.ts";
9
- export type { DatabaseOptions, ServeOptions } from "./core/database.ts";
10
-
11
- export { mergeNodes } from "./core/crdt.ts";
12
- export type { MeshMessage, NodeRecord, VectorClock } from "./types/index.ts";
13
-
14
- export { startApiServer } from "./http/api-server.ts";
15
- export type { ApiServerHandle } from "./http/api-server.ts";
16
-
17
- export { loadConfig, saveConfig } from "./config.ts";
18
-
19
- export { connectToPeer, startMeshServer } from "./network/websocket-server.ts";
20
- export type { MeshServer } from "./network/websocket-server.ts";
21
-
22
- export { RuleEngine } from "./logic/rules.ts";
23
- export type { Rule, RuleContext } from "./logic/rules.ts";
24
-
25
- export { BruteForceVectorIndex } from "./vector/index.ts";
26
- export type { VectorIndex, VectorIndexResult } from "./vector/index.ts";
27
-
28
- export { debugLog } from "./util/debug.ts";
@@ -1,46 +0,0 @@
1
- import type { NodeRecord } from "../types/index.ts";
2
-
3
- // Minimal DB interface to avoid circular imports
4
- export interface DatabaseLike {
5
- put(id: string, data: Record<string, unknown>): Promise<void>;
6
- get<T = Record<string, unknown>>(
7
- id: string,
8
- ): Promise<(T & { id: string }) | null>;
9
- }
10
-
11
- export interface RuleContext {
12
- db: DatabaseLike;
13
- }
14
-
15
- export type RulePredicate = (node: NodeRecord) => boolean | Promise<boolean>;
16
- export type RuleAction = (ctx: RuleContext, node: NodeRecord) => Promise<void>;
17
-
18
- export interface Rule {
19
- name: string;
20
- whenType?: string;
21
- predicate?: RulePredicate;
22
- action: RuleAction;
23
- }
24
-
25
- export class RuleEngine {
26
- private readonly rules: Map<string, Rule> = new Map();
27
-
28
- addRule(rule: Rule): void {
29
- this.rules.set(rule.name, rule);
30
- }
31
-
32
- removeRule(name: string): void {
33
- this.rules.delete(name);
34
- }
35
-
36
- async evaluateNode(node: NodeRecord, ctx: RuleContext): Promise<void> {
37
- for (const rule of this.rules.values()) {
38
- if (rule.whenType && node.type !== rule.whenType) continue;
39
- if (rule.predicate) {
40
- const ok = await rule.predicate(node);
41
- if (!ok) continue;
42
- }
43
- await rule.action(ctx, node);
44
- }
45
- }
46
- }
package/legacy/main.rs DELETED
@@ -1,3 +0,0 @@
1
- fn main() {
2
- println!("Hello, world!");
3
- }