miniledger 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -35
- package/dashboard/app.js +744 -156
- package/dashboard/index.html +33 -84
- package/dashboard/style.css +500 -75
- package/dist/bin/miniledger.cjs +135 -23
- package/dist/bin/miniledger.cjs.map +1 -1
- package/package.json +33 -9
package/dist/bin/miniledger.cjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
#!/usr/bin/env node
|
|
3
2
|
"use strict";
|
|
4
3
|
var __create = Object.create;
|
|
5
4
|
var __defProp = Object.defineProperty;
|
|
@@ -2631,7 +2630,7 @@ var MiniLedgerNode = class _MiniLedgerNode extends import_node_events3.EventEmit
|
|
|
2631
2630
|
// src/api/server.ts
|
|
2632
2631
|
var fs4 = __toESM(require("fs"), 1);
|
|
2633
2632
|
var path4 = __toESM(require("path"), 1);
|
|
2634
|
-
var
|
|
2633
|
+
var import_hono9 = require("hono");
|
|
2635
2634
|
var import_cors = require("hono/cors");
|
|
2636
2635
|
|
|
2637
2636
|
// src/api/middleware/logging.ts
|
|
@@ -2681,9 +2680,16 @@ function blockRoutes(node) {
|
|
|
2681
2680
|
app.get("/blocks", async (c) => {
|
|
2682
2681
|
const stores = node.getStores();
|
|
2683
2682
|
const currentHeight = stores.blocks.getHeight();
|
|
2684
|
-
const
|
|
2685
|
-
const
|
|
2686
|
-
|
|
2683
|
+
const page = Math.max(1, Number.parseInt(c.req.query("page") ?? "1", 10) || 1);
|
|
2684
|
+
const limit = Math.min(100, Math.max(1, Number.parseInt(c.req.query("limit") ?? "20", 10) || 20));
|
|
2685
|
+
const toHeight = currentHeight - (page - 1) * limit;
|
|
2686
|
+
const fromHeight = Math.max(0, toHeight - limit + 1);
|
|
2687
|
+
if (toHeight < 0) {
|
|
2688
|
+
return c.json({ blocks: [], height: currentHeight, page, limit, totalPages: Math.ceil((currentHeight + 1) / limit) });
|
|
2689
|
+
}
|
|
2690
|
+
const blocks = stores.blocks.getRange(fromHeight, toHeight).reverse();
|
|
2691
|
+
const totalPages = Math.ceil((currentHeight + 1) / limit);
|
|
2692
|
+
return c.json({ blocks, height: currentHeight, page, limit, totalPages });
|
|
2687
2693
|
});
|
|
2688
2694
|
return app;
|
|
2689
2695
|
}
|
|
@@ -2717,6 +2723,49 @@ function transactionRoutes(node) {
|
|
|
2717
2723
|
return c.json({ error: message }, 400);
|
|
2718
2724
|
}
|
|
2719
2725
|
});
|
|
2726
|
+
app.get("/tx/recent", async (c) => {
|
|
2727
|
+
const page = Math.max(1, Number.parseInt(c.req.query("page") ?? "1", 10) || 1);
|
|
2728
|
+
const limit = Math.min(100, Math.max(1, Number.parseInt(c.req.query("limit") ?? "20", 10) || 20));
|
|
2729
|
+
const typeFilter = c.req.query("type") ?? "";
|
|
2730
|
+
const offset = (page - 1) * limit;
|
|
2731
|
+
try {
|
|
2732
|
+
let countSql = "SELECT COUNT(*) as total FROM transactions WHERE status = 'confirmed'";
|
|
2733
|
+
let dataSql = "SELECT hash, type, sender, nonce, timestamp, payload, signature, block_height, position FROM transactions WHERE status = 'confirmed'";
|
|
2734
|
+
const params = [];
|
|
2735
|
+
const countParams = [];
|
|
2736
|
+
if (typeFilter) {
|
|
2737
|
+
countSql += " AND type = ?";
|
|
2738
|
+
dataSql += " AND type = ?";
|
|
2739
|
+
params.push(typeFilter);
|
|
2740
|
+
countParams.push(typeFilter);
|
|
2741
|
+
}
|
|
2742
|
+
dataSql += " ORDER BY block_height DESC, position DESC LIMIT ? OFFSET ?";
|
|
2743
|
+
params.push(limit, offset);
|
|
2744
|
+
const db = node.getDatabase().raw();
|
|
2745
|
+
const countRow = db.prepare(countSql).get(...countParams);
|
|
2746
|
+
const rows = db.prepare(dataSql).all(...params);
|
|
2747
|
+
const transactions = rows.map((r) => ({
|
|
2748
|
+
hash: r.hash,
|
|
2749
|
+
type: r.type,
|
|
2750
|
+
sender: r.sender,
|
|
2751
|
+
nonce: r.nonce,
|
|
2752
|
+
timestamp: r.timestamp,
|
|
2753
|
+
payload: JSON.parse(r.payload),
|
|
2754
|
+
signature: r.signature,
|
|
2755
|
+
blockHeight: r.block_height
|
|
2756
|
+
}));
|
|
2757
|
+
const totalPages = Math.ceil(countRow.total / limit);
|
|
2758
|
+
return c.json({ transactions, total: countRow.total, page, limit, totalPages });
|
|
2759
|
+
} catch (err) {
|
|
2760
|
+
const message = err instanceof Error ? err.message : "Query failed";
|
|
2761
|
+
return c.json({ error: message }, 500);
|
|
2762
|
+
}
|
|
2763
|
+
});
|
|
2764
|
+
app.get("/tx/sender/:pubkey", async (c) => {
|
|
2765
|
+
const pubkey = c.req.param("pubkey");
|
|
2766
|
+
const transactions = node.getStores().txs.getBySender(pubkey, 200);
|
|
2767
|
+
return c.json({ transactions, count: transactions.length });
|
|
2768
|
+
});
|
|
2720
2769
|
app.get("/tx/:hash", async (c) => {
|
|
2721
2770
|
const hash = c.req.param("hash");
|
|
2722
2771
|
const tx = await node.getTransaction(hash);
|
|
@@ -2735,6 +2784,31 @@ function transactionRoutes(node) {
|
|
|
2735
2784
|
var import_hono4 = require("hono");
|
|
2736
2785
|
function stateRoutes(node) {
|
|
2737
2786
|
const app = new import_hono4.Hono();
|
|
2787
|
+
app.get("/state", async (c) => {
|
|
2788
|
+
const page = Math.max(1, Number.parseInt(c.req.query("page") ?? "1", 10) || 1);
|
|
2789
|
+
const limit = Math.min(100, Math.max(1, Number.parseInt(c.req.query("limit") ?? "20", 10) || 20));
|
|
2790
|
+
const offset = (page - 1) * limit;
|
|
2791
|
+
try {
|
|
2792
|
+
const db = node.getDatabase().raw();
|
|
2793
|
+
const countRow = db.prepare("SELECT COUNT(*) as total FROM world_state WHERE key NOT LIKE '\\_%' ESCAPE '\\'").get();
|
|
2794
|
+
const rows = db.prepare(
|
|
2795
|
+
"SELECT key, value, version, updated_at, updated_by, block_height FROM world_state WHERE key NOT LIKE '\\_%' ESCAPE '\\' ORDER BY updated_at DESC LIMIT ? OFFSET ?"
|
|
2796
|
+
).all(limit, offset);
|
|
2797
|
+
const entries = rows.map((r) => ({
|
|
2798
|
+
key: r.key,
|
|
2799
|
+
value: JSON.parse(r.value),
|
|
2800
|
+
version: r.version,
|
|
2801
|
+
updatedAt: r.updated_at,
|
|
2802
|
+
updatedBy: r.updated_by,
|
|
2803
|
+
blockHeight: r.block_height
|
|
2804
|
+
}));
|
|
2805
|
+
const totalPages = Math.ceil(countRow.total / limit);
|
|
2806
|
+
return c.json({ entries, total: countRow.total, page, limit, totalPages });
|
|
2807
|
+
} catch (err) {
|
|
2808
|
+
const message = err instanceof Error ? err.message : "Query failed";
|
|
2809
|
+
return c.json({ error: message }, 500);
|
|
2810
|
+
}
|
|
2811
|
+
});
|
|
2738
2812
|
app.get("/state/:key", async (c) => {
|
|
2739
2813
|
const key = c.req.param("key");
|
|
2740
2814
|
const entry = await node.getState(key);
|
|
@@ -2824,6 +2898,48 @@ function governanceRoutes(node) {
|
|
|
2824
2898
|
return app;
|
|
2825
2899
|
}
|
|
2826
2900
|
|
|
2901
|
+
// src/api/routes/search.ts
|
|
2902
|
+
var import_hono8 = require("hono");
|
|
2903
|
+
function searchRoutes(node) {
|
|
2904
|
+
const app = new import_hono8.Hono();
|
|
2905
|
+
app.get("/search", async (c) => {
|
|
2906
|
+
const q = (c.req.query("q") ?? "").trim();
|
|
2907
|
+
if (!q) return c.json({ error: "Missing query parameter q" }, 400);
|
|
2908
|
+
const stores = node.getStores();
|
|
2909
|
+
if (/^\d+$/.test(q)) {
|
|
2910
|
+
const height = Number.parseInt(q, 10);
|
|
2911
|
+
const block = stores.blocks.getByHeight(height);
|
|
2912
|
+
if (block) {
|
|
2913
|
+
return c.json({ type: "block", height: block.height, hash: block.hash });
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
if (/^[0-9a-fA-F]{16,}$/.test(q)) {
|
|
2917
|
+
const tx = stores.txs.getByHash(q);
|
|
2918
|
+
if (tx) {
|
|
2919
|
+
return c.json({ type: "transaction", hash: tx.hash });
|
|
2920
|
+
}
|
|
2921
|
+
const block = stores.blocks.getByHash(q);
|
|
2922
|
+
if (block) {
|
|
2923
|
+
return c.json({ type: "block", height: block.height, hash: block.hash });
|
|
2924
|
+
}
|
|
2925
|
+
const senderTxs = stores.txs.getBySender(q, 1);
|
|
2926
|
+
if (senderTxs.length > 0) {
|
|
2927
|
+
return c.json({ type: "address", pubkey: q });
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
const stateEntry = stores.state.get(q);
|
|
2931
|
+
if (stateEntry) {
|
|
2932
|
+
return c.json({ type: "state", key: stateEntry.key });
|
|
2933
|
+
}
|
|
2934
|
+
const contract = node.getContractRegistry().getInstance(q);
|
|
2935
|
+
if (contract) {
|
|
2936
|
+
return c.json({ type: "contract", name: contract.name });
|
|
2937
|
+
}
|
|
2938
|
+
return c.json({ type: "not_found", query: q }, 404);
|
|
2939
|
+
});
|
|
2940
|
+
return app;
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2827
2943
|
// src/api/server.ts
|
|
2828
2944
|
var import_meta = {};
|
|
2829
2945
|
function findDashboardDir() {
|
|
@@ -2837,16 +2953,8 @@ function findDashboardDir() {
|
|
|
2837
2953
|
}
|
|
2838
2954
|
return null;
|
|
2839
2955
|
}
|
|
2840
|
-
var MIME_TYPES = {
|
|
2841
|
-
".html": "text/html",
|
|
2842
|
-
".css": "text/css",
|
|
2843
|
-
".js": "application/javascript",
|
|
2844
|
-
".json": "application/json",
|
|
2845
|
-
".png": "image/png",
|
|
2846
|
-
".svg": "image/svg+xml"
|
|
2847
|
-
};
|
|
2848
2956
|
function createApp(node) {
|
|
2849
|
-
const app = new
|
|
2957
|
+
const app = new import_hono9.Hono();
|
|
2850
2958
|
if (node.config.api.cors) {
|
|
2851
2959
|
app.use("*", (0, import_cors.cors)());
|
|
2852
2960
|
}
|
|
@@ -2858,20 +2966,24 @@ function createApp(node) {
|
|
|
2858
2966
|
app.route("/", identityRoutes(node));
|
|
2859
2967
|
app.route("/", networkRoutes(node));
|
|
2860
2968
|
app.route("/", governanceRoutes(node));
|
|
2969
|
+
app.route("/", searchRoutes(node));
|
|
2861
2970
|
const dashboardDir = findDashboardDir();
|
|
2862
2971
|
if (dashboardDir) {
|
|
2863
|
-
app.get("/dashboard", (c) => {
|
|
2972
|
+
app.get("/dashboard/app.js", (c) => {
|
|
2973
|
+
const content = fs4.readFileSync(path4.join(dashboardDir, "app.js"), "utf-8");
|
|
2974
|
+
return c.text(content, 200, { "Content-Type": "application/javascript" });
|
|
2975
|
+
});
|
|
2976
|
+
app.get("/dashboard/style.css", (c) => {
|
|
2977
|
+
const content = fs4.readFileSync(path4.join(dashboardDir, "style.css"), "utf-8");
|
|
2978
|
+
return c.text(content, 200, { "Content-Type": "text/css" });
|
|
2979
|
+
});
|
|
2980
|
+
app.get("/dashboard/*", (c) => {
|
|
2864
2981
|
const html = fs4.readFileSync(path4.join(dashboardDir, "index.html"), "utf-8");
|
|
2865
2982
|
return c.html(html);
|
|
2866
2983
|
});
|
|
2867
|
-
app.get("/dashboard
|
|
2868
|
-
const
|
|
2869
|
-
|
|
2870
|
-
if (!fs4.existsSync(filePath)) return c.notFound();
|
|
2871
|
-
const ext = path4.extname(file);
|
|
2872
|
-
const mime = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
2873
|
-
const content = fs4.readFileSync(filePath, "utf-8");
|
|
2874
|
-
return c.text(content, 200, { "Content-Type": mime });
|
|
2984
|
+
app.get("/dashboard", (c) => {
|
|
2985
|
+
const html = fs4.readFileSync(path4.join(dashboardDir, "index.html"), "utf-8");
|
|
2986
|
+
return c.html(html);
|
|
2875
2987
|
});
|
|
2876
2988
|
}
|
|
2877
2989
|
app.get("/", (c) => {
|