@economicagents/graph 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @economicagents/graph
2
2
 
3
- Economic graph for AEP: payments, credit events, credit scoring, and provider recommendations. Synced to SQLite (`graph.db`).
3
+ Economic graph for AEP: sync on-chain payments and credit events into SQLite (`graph.db`), compute credit scores, and provider recommendations.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,15 +8,15 @@ Economic graph for AEP: payments, credit events, credit scoring, and provider re
8
8
  pnpm add @economicagents/graph
9
9
  ```
10
10
 
11
- From monorepo: `cd packages/graph && pnpm run build`.
11
+ **From a local clone** of [economicagents/AEP](https://github.com/economicagents/AEP): `cd packages/graph && pnpm run build`.
12
12
 
13
13
  ## Usage
14
14
 
15
15
  ```bash
16
- # Via CLI (when installed)
16
+ # Standalone binary when installed
17
17
  aep-graph sync
18
18
 
19
- # Via @economicagents/cli (includes graph)
19
+ # Via CLI meta-package
20
20
  aep graph sync
21
21
  aep analytics <address>
22
22
  aep credit-score <address>
@@ -30,26 +30,25 @@ const score = computeCreditScore(graphPath, accountAddress);
30
30
  const recs = getRecommendations(graphPath, providers, accountAddress, capability, limit);
31
31
  ```
32
32
 
33
- Used by the resolver for recommendation boost (`accountAddress` + `graphPath`).
33
+ The resolver uses `graphPath` + `accountAddress` for recommendation boost.
34
34
 
35
35
  ## Configuration
36
36
 
37
- - **Graph path:** `~/.aep/graph.db` by default; override via `graphPath` in config
38
- - **RPC:** Required for sync; `RPC_URL` or config `rpcUrl`
39
- - **Config:** `~/.aep/config.json`
37
+ - **Graph path:** default `~/.aep/graph.db`; override `graphPath` in `~/.aep/config.json`
38
+ - **RPC:** `RPC_URL` or config `rpcUrl` for `graph sync`
40
39
 
41
40
  ## Dependencies
42
41
 
43
- Optional: `better-sqlite3` (v12+). Tests fall back to `sql.js` when native bindings unavailable.
42
+ Optional native `better-sqlite3` (v12+); tests fall back to `sql.js` when bindings are unavailable.
44
43
 
45
- ## Build & Test
44
+ ## Build & test
46
45
 
47
46
  ```bash
48
47
  pnpm run build
49
48
  pnpm run test
50
49
  ```
51
50
 
52
- ## Docs
51
+ ## Documentation
53
52
 
54
- - [Cookbook](../../docs/COOKBOOK.md) — Fleet, analytics
55
- - [Architecture](../../docs/ARCHITECTURE.md) — Graph schema
53
+ - [Cookbook](https://github.com/economicagents/AEP/blob/main/docs/COOKBOOK.md) — Fleet, analytics
54
+ - [Architecture](https://github.com/economicagents/AEP/blob/main/docs/ARCHITECTURE.md) — Graph role in the stack
package/dist/cli.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * CLI for AEP economic graph sync.
4
- * Usage: npx aep-graph sync [options]
5
4
  */
6
5
  export {};
7
6
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;GAGG"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;GAEG"}
package/dist/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * CLI for AEP economic graph sync.
4
- * Usage: npx aep-graph sync [options]
5
4
  */
6
5
  import { join } from "path";
7
6
  import { homedir } from "os";
8
7
  import { existsSync, readFileSync } from "fs";
8
+ import { Command } from "commander";
9
9
  import { syncGraph } from "./index.js";
10
10
  function getConfigPath() {
11
11
  const env = process.env.AEP_CONFIG_PATH;
@@ -29,29 +29,36 @@ function loadConfig() {
29
29
  }
30
30
  return {};
31
31
  }
32
- async function main() {
33
- const args = process.argv.slice(2);
34
- const command = args[0] ?? "sync";
32
+ const program = new Command();
33
+ program
34
+ .name("aep-graph")
35
+ .description("AEP economic graph: sync on-chain events into local SQLite graph store")
36
+ .version("0.2.0")
37
+ .addHelpText("after", `\nExamples:\n $ aep-graph sync\n $ aep-graph sync -r https://sepolia.base.org --graph-path ~/.aep/graph\n`);
38
+ program
39
+ .command("sync")
40
+ .description("Incremental graph sync (accounts, payments, credit, escrow, splitter, SLA)")
41
+ .option("-r, --rpc <url>", "JSON-RPC URL", DEFAULT_RPC)
42
+ .option("--graph-path <path>", "Graph database path", DEFAULT_GRAPH_PATH)
43
+ .option("--chain-id <id>", "Chain id (overrides config when set)")
44
+ .action(async (opts) => {
35
45
  const config = loadConfig();
36
- if (command !== "sync") {
37
- console.error("Usage: aep-graph <sync> [options]");
38
- console.error(" sync [--rpc <url>] [--graph-path <path>] [--chain-id <id>]");
39
- process.exit(1);
40
- }
41
- const rpcIndex = args.indexOf("--rpc");
42
- const rpcUrl = rpcIndex >= 0 ? args[rpcIndex + 1] : config.rpcUrl ?? DEFAULT_RPC;
43
- const pathIndex = args.indexOf("--graph-path");
44
- const graphPath = pathIndex >= 0 ? args[pathIndex + 1] : config.graphPath ?? DEFAULT_GRAPH_PATH;
46
+ const rpcUrl = opts.rpc ?? config.rpcUrl ?? DEFAULT_RPC;
47
+ const graphPath = opts.graphPath ?? config.graphPath ?? DEFAULT_GRAPH_PATH;
45
48
  const factoryAddress = (config.aepAccountFactoryAddress ?? config.factoryAddress);
46
49
  if (!factoryAddress || factoryAddress === "0x0000000000000000000000000000000000000000") {
47
50
  console.error("Error: factoryAddress or aepAccountFactoryAddress required in config (deploy factory first)");
48
51
  process.exit(1);
49
52
  }
50
- const chainIdArg = args.indexOf("--chain-id");
51
- let chainId = chainIdArg >= 0 && args[chainIdArg + 1]
52
- ? parseInt(args[chainIdArg + 1], 10)
53
- : config.chainId ??
54
- parseInt(process.env.AEP_CHAIN_ID ?? process.env.BASE_SEPOLIA_CHAIN_ID ?? "84532", 10);
53
+ let chainId;
54
+ if (opts.chainId != null && opts.chainId !== "") {
55
+ chainId = parseInt(String(opts.chainId), 10);
56
+ }
57
+ else {
58
+ chainId =
59
+ config.chainId ??
60
+ parseInt(process.env.AEP_CHAIN_ID ?? process.env.BASE_SEPOLIA_CHAIN_ID ?? "84532", 10);
61
+ }
55
62
  if (Number.isNaN(chainId) || chainId <= 0) {
56
63
  console.error("Error: invalid chain-id (use e.g. 84532 for Base Sepolia, 8453 for Base mainnet)");
57
64
  process.exit(1);
@@ -77,5 +84,22 @@ async function main() {
77
84
  console.error("Error:", err instanceof Error ? err.message : String(err));
78
85
  process.exit(1);
79
86
  }
87
+ });
88
+ async function main() {
89
+ let argv = process.argv.slice(2);
90
+ if (argv.length === 0) {
91
+ argv = ["sync"];
92
+ }
93
+ const first = argv[0];
94
+ if (first &&
95
+ !first.startsWith("-") &&
96
+ first !== "sync" &&
97
+ first !== "-h" &&
98
+ first !== "--help" &&
99
+ first !== "-V" &&
100
+ first !== "--version") {
101
+ argv = ["sync", ...argv];
102
+ }
103
+ await program.parseAsync(argv, { from: "user" });
80
104
  }
81
105
  main();
@@ -1 +1 @@
1
- {"version":3,"file":"fleet-alerts.d.ts","sourceRoot":"","sources":["../src/fleet-alerts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAOpC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EAAE,EAC1B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,UAAU,EAAE,CAAC,CAwOvB"}
1
+ {"version":3,"file":"fleet-alerts.d.ts","sourceRoot":"","sources":["../src/fleet-alerts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAOpC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EAAE,EAC1B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,UAAU,EAAE,CAAC,CAwOvB"}
@@ -2,7 +2,8 @@
2
2
  * Fleet alerts — one-shot query for security-relevant events for fleet accounts.
3
3
  * Queries graph DB for linked facilities/SLAs, then RPC for on-chain events.
4
4
  */
5
- import { createPublicClient, http, parseAbiItem, isAddress } from "viem";
5
+ import { createPublicClient, parseAbiItem, isAddress } from "viem";
6
+ import { transportFromRpcUrl } from "@economicagents/viem-rpc";
6
7
  import { base, baseSepolia } from "viem/chains";
7
8
  import { getDatabase } from "./store.js";
8
9
  const CHUNK_SIZE = 9999n;
@@ -17,7 +18,7 @@ export async function getFleetAlerts(graphPath, rpcUrl, accountAddresses, option
17
18
  if (!rpcUrl || typeof rpcUrl !== "string" || !rpcUrl.trim()) {
18
19
  throw new Error("getFleetAlerts requires a non-empty rpcUrl");
19
20
  }
20
- const db = getDatabase(graphPath);
21
+ const db = getDatabase(graphPath, { readonly: true });
21
22
  // 1. Facilities where lender or borrower in fleet accounts
22
23
  const facilityAddresses = [];
23
24
  if (normalized.length > 0) {
@@ -40,7 +41,7 @@ export async function getFleetAlerts(graphPath, rpcUrl, accountAddresses, option
40
41
  const chain = chainId === base.id ? base : baseSepolia;
41
42
  const client = createPublicClient({
42
43
  chain,
43
- transport: http(rpcUrl),
44
+ transport: transportFromRpcUrl(rpcUrl),
44
45
  });
45
46
  const toBlock = options?.toBlock ?? Number(await client.getBlockNumber());
46
47
  const fromBlock = options?.fromBlock ?? Math.max(0, toBlock - DEFAULT_BLOCK_RANGE);
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export { getPaymentsFrom, getPaymentsTo, getAccountAnalytics, getAccountAnalytic
8
8
  export { getFleetAlerts } from "./fleet-alerts.js";
9
9
  export { getRecommendations } from "./recommendations.js";
10
10
  export type { GraphConfig, SyncResult, PaymentSource, } from "./types.js";
11
+ export type { GraphDatabaseOptions } from "./store.js";
11
12
  export type { PaymentRow, AccountAnalytics, CreditScoreResult, PaymentTrend, FleetSummary, } from "./queries.js";
12
13
  export type { FleetAlert, GetFleetAlertsOptions } from "./fleet-alerts.js";
13
14
  export type { ProviderRecommendation, ProviderInfo } from "./recommendations.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAuBH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAK1D,wBAAsB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAokBxE;AAED,OAAO,EACL,WAAW,EACX,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,0BAA0B,EAC1B,kBAAkB,EAClB,yBAAyB,EACzB,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,YAAY,EACV,WAAW,EACX,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,YAAY,GACb,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,YAAY,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAwBH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAK1D,wBAAsB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAokBxE;AAED,OAAO,EACL,WAAW,EACX,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,0BAA0B,EAC1B,kBAAkB,EAClB,yBAAyB,EACzB,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,YAAY,EACV,WAAW,EACX,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACvD,YAAY,EACV,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,YAAY,GACb,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,YAAY,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Economic graph sync - indexes on-chain events to build transaction graph.
3
3
  */
4
- import { createPublicClient, http, parseAbiItem } from "viem";
4
+ import { createPublicClient, parseAbiItem } from "viem";
5
+ import { transportFromRpcUrl } from "@economicagents/viem-rpc";
5
6
  import { base, baseSepolia } from "viem/chains";
6
7
  import { getDatabase, insertAccount, insertPayment, insertUserOp, insertFacility, insertEscrow, insertSplitter, insertSLA, insertSLAEvent, getSyncState, setSyncState, getAccountAddresses, getFacilityAddresses, getEscrowAddresses, getSplitterAddresses, getSLAAddresses, } from "./store.js";
7
8
  const CHUNK_SIZE = 9999n;
@@ -10,7 +11,7 @@ export async function syncGraph(config) {
10
11
  const chain = config.chainId === base.id ? base : baseSepolia;
11
12
  const client = createPublicClient({
12
13
  chain,
13
- transport: http(config.rpcUrl),
14
+ transport: transportFromRpcUrl(config.rpcUrl),
14
15
  });
15
16
  const db = getDatabase(config.graphPath);
16
17
  const result = {
package/dist/queries.js CHANGED
@@ -16,7 +16,7 @@ export function getBlockRangeForPeriod(graphPath, period) {
16
16
  const blocks = BLOCKS_PER_PERIOD[period];
17
17
  if (!blocks)
18
18
  return null;
19
- const db = getDatabase(graphPath);
19
+ const db = getDatabase(graphPath, { readonly: true });
20
20
  const maxBlock = getMaxBlock(db);
21
21
  return { fromBlock: Math.max(0, maxBlock - blocks), toBlock: maxBlock };
22
22
  }
@@ -33,7 +33,7 @@ export function getPaymentsTo(db, address) {
33
33
  return rows;
34
34
  }
35
35
  export function getAccountAnalytics(graphPath, address) {
36
- const db = getDatabase(graphPath);
36
+ const db = getDatabase(graphPath, { readonly: true });
37
37
  const addr = address.toLowerCase();
38
38
  const outflows = db
39
39
  .prepare("SELECT amount, source FROM payments WHERE fromAddr = ?")
@@ -81,7 +81,7 @@ export function getAccountAnalytics(graphPath, address) {
81
81
  };
82
82
  }
83
83
  export function computeCreditScore(graphPath, address) {
84
- const db = getDatabase(graphPath);
84
+ const db = getDatabase(graphPath, { readonly: true });
85
85
  const addr = address.toLowerCase();
86
86
  const outflows = getPaymentsFrom(db, addr);
87
87
  const inflows = getPaymentsTo(db, addr);
@@ -119,7 +119,7 @@ export function computeCreditScore(graphPath, address) {
119
119
  };
120
120
  }
121
121
  export function computeCreditScoreInRange(graphPath, address, fromBlock, toBlock) {
122
- const db = getDatabase(graphPath);
122
+ const db = getDatabase(graphPath, { readonly: true });
123
123
  const addr = address.toLowerCase();
124
124
  const outflows = db
125
125
  .prepare("SELECT fromAddr, toAddr, amount, token, blockNumber, source FROM payments WHERE fromAddr = ? AND blockNumber >= ? AND blockNumber <= ? ORDER BY blockNumber")
@@ -162,7 +162,7 @@ export function computeCreditScoreInRange(graphPath, address, fromBlock, toBlock
162
162
  };
163
163
  }
164
164
  export function getAccountAnalyticsInRange(graphPath, address, fromBlock, toBlock) {
165
- const db = getDatabase(graphPath);
165
+ const db = getDatabase(graphPath, { readonly: true });
166
166
  const addr = address.toLowerCase();
167
167
  const outflows = db
168
168
  .prepare("SELECT amount, source FROM payments WHERE fromAddr = ? AND blockNumber >= ? AND blockNumber <= ?")
@@ -211,7 +211,7 @@ export function getAccountAnalyticsInRange(graphPath, address, fromBlock, toBloc
211
211
  }
212
212
  /** Rolling spend/revenue trends. Groups by block bucket (~1 day = 43200 blocks on Base). */
213
213
  export function getPaymentTrends(graphPath, address, period) {
214
- const db = getDatabase(graphPath);
214
+ const db = getDatabase(graphPath, { readonly: true });
215
215
  const addr = address.toLowerCase();
216
216
  const maxBlock = getMaxBlock(db);
217
217
  const blocks = BLOCKS_PER_PERIOD[period] ?? BLOCKS_PER_PERIOD["30d"];
@@ -252,7 +252,7 @@ export function getPaymentTrends(graphPath, address, period) {
252
252
  });
253
253
  }
254
254
  export function exportPaymentsCsv(graphPath, address, fromBlock, toBlock) {
255
- const db = getDatabase(graphPath);
255
+ const db = getDatabase(graphPath, { readonly: true });
256
256
  const addr = address.toLowerCase();
257
257
  let sql = "SELECT fromAddr, toAddr, amount, token, blockNumber, txHash, source FROM payments WHERE (fromAddr = ? OR toAddr = ?)";
258
258
  const args = [addr, addr];
@@ -278,7 +278,7 @@ export function exportPaymentsCsv(graphPath, address, fromBlock, toBlock) {
278
278
  return [header, ...lines].join("\n");
279
279
  }
280
280
  export function getFleetSummary(graphPath, accountAddresses) {
281
- const db = getDatabase(graphPath);
281
+ const db = getDatabase(graphPath, { readonly: true });
282
282
  const normalized = accountAddresses.map((a) => a.toLowerCase());
283
283
  let totalOutflow = 0n;
284
284
  let totalInflow = 0n;
@@ -10,7 +10,7 @@ import { getDatabase } from "./store.js";
10
10
  * @param providers - Provider list (e.g. from loadProviders(indexPath))
11
11
  */
12
12
  export function getRecommendations(graphPath, providers, accountAddress, capability, limit = 5) {
13
- const db = getDatabase(graphPath);
13
+ const db = getDatabase(graphPath, { readonly: true });
14
14
  const addr = accountAddress.toLowerCase();
15
15
  const paymentWalletToProvider = new Map();
16
16
  for (const p of providers) {
package/dist/store.d.ts CHANGED
@@ -33,8 +33,12 @@ export declare function setSqlJsFallback(SQL: {
33
33
  export declare function isUsingBetterSqlite3(): boolean;
34
34
  /** Exported for tests: whether SQLite (better-sqlite3 or sql.js fallback) is available. */
35
35
  export declare function isSqliteAvailable(): boolean;
36
+ export type GraphDatabaseOptions = {
37
+ /** Use read-only mode (API / analytics). Lets graph sync hold the write lock without SQLITE_BUSY. */
38
+ readonly?: boolean;
39
+ };
36
40
  export declare function ensureGraphDir(graphPath: string): void;
37
- export declare function getDatabase(graphPath: string): SqliteDb;
41
+ export declare function getDatabase(graphPath: string, options?: GraphDatabaseOptions): SqliteDb;
38
42
  export declare function closeDatabase(): void;
39
43
  export declare function insertAccount(database: SqliteDb, address: string, owner: string, firstSeenBlock: number): void;
40
44
  export declare function insertPayment(database: SqliteDb, fromAddr: string, toAddr: string, amount: string, token: string, blockNumber: number, txHash: string, logIndex: number | null, source: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
package/dist/store.js CHANGED
@@ -8,8 +8,12 @@ import { join } from "path";
8
8
  const require = createRequire(import.meta.url);
9
9
  let db = null;
10
10
  let dbPath = null;
11
+ /** Read-only handle (API analytics) — avoids write locks vs graph sync in a second process */
12
+ let roDb = null;
13
+ let roDbPath = null;
11
14
  let sqliteAvailable = null;
12
15
  let sqlJsFallback = null;
16
+ const GRAPH_SQLITE_BUSY_TIMEOUT_MS = 30_000;
13
17
  /** For tests: set sql.js as fallback when better-sqlite3 native bindings are unavailable. */
14
18
  export function setSqlJsFallback(SQL) {
15
19
  sqlJsFallback = SQL;
@@ -74,14 +78,29 @@ export function isSqliteAvailable() {
74
78
  return true;
75
79
  return sqlJsFallback !== null;
76
80
  }
77
- function getDb(graphPath) {
81
+ function getDb(graphPath, options) {
82
+ const wantsReadonly = options?.readonly === true;
83
+ /** sql.js tests use one in-memory DB; read-only file semantics do not apply */
84
+ isSqliteAvailable();
85
+ const readonly = wantsReadonly && sqliteAvailable === true && sqlJsFallback === null;
78
86
  const resolvedPath = join(graphPath, "graph.db");
79
- if (db && dbPath === resolvedPath)
80
- return db;
81
- if (db) {
82
- db.close();
83
- db = null;
84
- dbPath = null;
87
+ if (readonly) {
88
+ if (roDb && roDbPath === resolvedPath)
89
+ return roDb;
90
+ if (roDb) {
91
+ roDb.close();
92
+ roDb = null;
93
+ roDbPath = null;
94
+ }
95
+ }
96
+ else {
97
+ if (db && dbPath === resolvedPath)
98
+ return db;
99
+ if (db) {
100
+ db.close();
101
+ db = null;
102
+ dbPath = null;
103
+ }
85
104
  }
86
105
  if (!isSqliteAvailable()) {
87
106
  throw new Error("better-sqlite3 or sql.js required for graph. Install: npm install better-sqlite3");
@@ -89,8 +108,23 @@ function getDb(graphPath) {
89
108
  try {
90
109
  if (sqliteAvailable) {
91
110
  const Database = require("better-sqlite3");
111
+ if (readonly) {
112
+ const nativeDb = new Database(resolvedPath, {
113
+ readonly: true,
114
+ fileMustExist: true,
115
+ timeout: GRAPH_SQLITE_BUSY_TIMEOUT_MS,
116
+ });
117
+ roDb = nativeDb;
118
+ roDbPath = resolvedPath;
119
+ return nativeDb;
120
+ }
92
121
  mkdirSync(graphPath, { recursive: true });
93
- db = new Database(resolvedPath);
122
+ const nativeDb = new Database(resolvedPath, {
123
+ timeout: GRAPH_SQLITE_BUSY_TIMEOUT_MS,
124
+ });
125
+ nativeDb.pragma("journal_mode = WAL");
126
+ nativeDb.pragma(`busy_timeout = ${GRAPH_SQLITE_BUSY_TIMEOUT_MS}`);
127
+ db = nativeDb;
94
128
  dbPath = resolvedPath;
95
129
  }
96
130
  else if (sqlJsFallback) {
@@ -192,8 +226,8 @@ function initSchema(database) {
192
226
  export function ensureGraphDir(graphPath) {
193
227
  mkdirSync(graphPath, { recursive: true });
194
228
  }
195
- export function getDatabase(graphPath) {
196
- return getDb(graphPath);
229
+ export function getDatabase(graphPath, options) {
230
+ return getDb(graphPath, options);
197
231
  }
198
232
  export function closeDatabase() {
199
233
  if (db) {
@@ -201,6 +235,11 @@ export function closeDatabase() {
201
235
  db = null;
202
236
  dbPath = null;
203
237
  }
238
+ if (roDb) {
239
+ roDb.close();
240
+ roDb = null;
241
+ roDbPath = null;
242
+ }
204
243
  }
205
244
  export function insertAccount(database, address, owner, firstSeenBlock) {
206
245
  database
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Matches @economicagents/sdk/viem-transport — duplicated here to avoid sdk↔graph pkg cycle.
3
+ */
4
+ import { type Transport } from "viem";
5
+ export declare function transportFromRpcUrl(rpcUrl: string): Transport;
6
+ //# sourceMappingURL=viem-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viem-transport.d.ts","sourceRoot":"","sources":["../src/viem-transport.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,MAAM,CAAC;AAEvD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAO7D"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Matches @economicagents/sdk/viem-transport — duplicated here to avoid sdk↔graph pkg cycle.
3
+ */
4
+ import { http, webSocket } from "viem";
5
+ export function transportFromRpcUrl(rpcUrl) {
6
+ const trimmed = rpcUrl.trim();
7
+ const lower = trimmed.toLowerCase();
8
+ if (lower.startsWith("wss://") || lower.startsWith("ws://")) {
9
+ return webSocket(trimmed);
10
+ }
11
+ return http(trimmed);
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@economicagents/graph",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Economic graph for AEP - transaction graph, payments, credit events, credit scoring, recommendations",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -32,7 +32,9 @@
32
32
  "aep-graph": "./dist/cli.js"
33
33
  },
34
34
  "dependencies": {
35
- "viem": "^2.21.0"
35
+ "commander": "^12.1.0",
36
+ "viem": "^2.47.5",
37
+ "@economicagents/viem-rpc": "0.2.0"
36
38
  },
37
39
  "optionalDependencies": {
38
40
  "better-sqlite3": "^12.1.0"