@juli-os/ledger 0.2.0 → 0.3.1

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/dist/index.d.ts CHANGED
@@ -4,5 +4,3 @@ export { TERMINAL_WORKFLOW_STATUSES, STEP_KINDS, AGENT_STEP_KINDS, INSTRUCTABLE_
4
4
  export { createUsageStore, type UsageRecord, type UsageStats, type TimelinePoint, type UsageStore, } from './platform/ledger/usage.ts';
5
5
  export { findPrice, priceUsage, type TierWhen, type PriceTier, type ModelPrice, PRICING_SOURCE, PRICING_RETRIEVED_AT, BIGMODEL_PRICES, type UsageRow, type CostBreakdown, type ModelUsageCost, type UsageCost, } from './platform/ledger/pricing.ts';
6
6
  export { emptyCaseFile, parseCaseFile, serializeCaseFile, withOriginal, appendSection, appendExecution, appendDecision, appendReference, originalEmail, type CaseDecision, type ArtifactRef, type CaseFileDoc, } from './platform/ledger/casefile.ts';
7
- export { rfc3339, systemClock, randomIds, type Clock, type IdGen } from './platform/shared/clock.ts';
8
- export { asRecord, safeParse, type JsonRecord, type JsonValue } from './platform/shared/json.ts';
package/dist/index.js CHANGED
@@ -1,5 +1,65 @@
1
- // src/platform/ledger/store.ts
1
+ // node_modules/@juli-os/basin/dist/index.js
2
2
  import { DatabaseSync } from "node:sqlite";
3
+ var openDatabase = (path) => new DatabaseSync(path);
4
+ var currentVersion = (db) => {
5
+ const row = db.prepare("PRAGMA user_version").get();
6
+ return Number(row?.["user_version"] ?? 0);
7
+ };
8
+ var writeVersion = (db, version) => {
9
+ db.exec(`PRAGMA user_version = ${Math.floor(version)}`);
10
+ };
11
+ var validateMigrations = (migrations) => {
12
+ let prev = 0;
13
+ for (const m of migrations) {
14
+ if (!Number.isInteger(m.version) || m.version < 1) {
15
+ throw new Error(`\u8FC1\u79FB\u7248\u672C\u5FC5\u987B\u662F >=1 \u7684\u6574\u6570\uFF0C\u6536\u5230 ${m.version}\uFF08${m.name}\uFF09`);
16
+ }
17
+ if (m.version <= prev) {
18
+ throw new Error(`\u8FC1\u79FB\u7248\u672C\u5FC5\u987B\u4E25\u683C\u9012\u589E\uFF1Av${m.version}\uFF08${m.name}\uFF09\u8DDF\u5728 v${prev} \u4E4B\u540E`);
19
+ }
20
+ prev = m.version;
21
+ }
22
+ };
23
+ var defineMigration = (version, name, up) => {
24
+ if (!Number.isInteger(version) || version < 1) {
25
+ throw new Error(`\u8FC1\u79FB\u7248\u672C\u5FC5\u987B\u662F >=1 \u7684\u6574\u6570\uFF0C\u6536\u5230 ${version}\uFF08${name}\uFF09`);
26
+ }
27
+ return { version, name, up };
28
+ };
29
+ var sqlMigration = (version, name, script) => defineMigration(version, name, (db) => {
30
+ db.exec(script);
31
+ });
32
+ var migrate = (db, migrations) => {
33
+ validateMigrations(migrations);
34
+ const from = currentVersion(db);
35
+ const applied = [];
36
+ for (const m of migrations) {
37
+ if (m.version <= from) continue;
38
+ db.exec("BEGIN IMMEDIATE");
39
+ try {
40
+ m.up(db);
41
+ writeVersion(db, m.version);
42
+ db.exec("COMMIT");
43
+ applied.push({ version: m.version, name: m.name });
44
+ } catch (e) {
45
+ db.exec("ROLLBACK");
46
+ throw new Error(`\u8FC1\u79FB v${m.version}\uFF08${m.name}\uFF09\u5931\u8D25\uFF0C\u5DF2\u56DE\u6EDA\uFF1A${e instanceof Error ? e.message : String(e)}`);
47
+ }
48
+ }
49
+ return { from, to: currentVersion(db), applied };
50
+ };
51
+ var tableColumns = (db, table) => {
52
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table)) {
53
+ throw new Error(`\u8868\u540D\u53EA\u5141\u8BB8\u5B57\u6BCD\u6570\u5B57\u4E0B\u5212\u7EBF\uFF0C\u6536\u5230 ${table}`);
54
+ }
55
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
56
+ return rows.map((r) => String(r["name"]));
57
+ };
58
+ var addColumnIfMissing = (db, table, column, ddl) => {
59
+ if (tableColumns(db, table).includes(column)) return false;
60
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl}`);
61
+ return true;
62
+ };
3
63
 
4
64
  // src/platform/shared/json.ts
5
65
  var safeParse = (raw) => {
@@ -222,6 +282,18 @@ CREATE TABLE IF NOT EXISTS relays (
222
282
  CREATE INDEX IF NOT EXISTS idx_events_entity ON events(entity_type, entity_id);
223
283
  CREATE INDEX IF NOT EXISTS idx_runs_session ON runs(session, status);
224
284
  `;
285
+ var STORE_MIGRATIONS = [
286
+ sqlMigration(1, "create lifecycle tables", MIGRATIONS),
287
+ defineMigration(2, "owner/thread/tenant columns + indexes + step timing", (db) => {
288
+ addColumnIfMissing(db, "workflows", "owner", "owner TEXT");
289
+ addColumnIfMissing(db, "workflows", "thread_id", "thread_id TEXT");
290
+ addColumnIfMissing(db, "workflows", "tenant", "tenant TEXT NOT NULL DEFAULT 'internal'");
291
+ db.exec("CREATE INDEX IF NOT EXISTS idx_workflows_thread ON workflows(thread_id)");
292
+ db.exec("CREATE INDEX IF NOT EXISTS idx_workflows_tenant ON workflows(tenant, created_at)");
293
+ addColumnIfMissing(db, "workflow_steps", "started_at", "started_at TEXT");
294
+ addColumnIfMissing(db, "workflow_steps", "completed_at", "completed_at TEXT");
295
+ })
296
+ ];
225
297
  var str = (v) => typeof v === "string" ? v : v === null || v === void 0 ? "" : String(v);
226
298
  var num = (v) => typeof v === "number" ? v : Number(v ?? 0);
227
299
  var jsonFieldPattern = (field, value) => {
@@ -299,23 +371,8 @@ var STEP_COLS = `id, workflow_id, seq, kind, title, status, COALESCE(input,'') A
299
371
  var createLedgerStore = (dbPath, deps = {}) => {
300
372
  const clock = deps.clock ?? systemClock;
301
373
  const ids = deps.ids ?? randomIds;
302
- const db = new DatabaseSync(dbPath);
303
- db.exec(MIGRATIONS);
304
- const wfCols = db.prepare("PRAGMA table_info(workflows)").all().map((c) => c.name);
305
- if (!wfCols.includes("owner")) {
306
- db.exec("ALTER TABLE workflows ADD COLUMN owner TEXT");
307
- }
308
- if (!wfCols.includes("thread_id")) {
309
- db.exec("ALTER TABLE workflows ADD COLUMN thread_id TEXT");
310
- }
311
- if (!wfCols.includes("tenant")) {
312
- db.exec("ALTER TABLE workflows ADD COLUMN tenant TEXT NOT NULL DEFAULT 'internal'");
313
- }
314
- db.exec("CREATE INDEX IF NOT EXISTS idx_workflows_thread ON workflows(thread_id)");
315
- db.exec("CREATE INDEX IF NOT EXISTS idx_workflows_tenant ON workflows(tenant, created_at)");
316
- const stepCols = db.prepare("PRAGMA table_info(workflow_steps)").all().map((c) => c.name);
317
- if (!stepCols.includes("started_at")) db.exec("ALTER TABLE workflow_steps ADD COLUMN started_at TEXT");
318
- if (!stepCols.includes("completed_at")) db.exec("ALTER TABLE workflow_steps ADD COLUMN completed_at TEXT");
374
+ const db = openDatabase(dbPath);
375
+ migrate(db, STORE_MIGRATIONS);
319
376
  const now = () => rfc3339(clock.now());
320
377
  const mergeStepJson = (id, col, patch) => {
321
378
  const row = db.prepare(`SELECT COALESCE(${col},'{}') AS v FROM workflow_steps WHERE id=?`).get(id);
@@ -770,7 +827,6 @@ var createLedgerStore = (dbPath, deps = {}) => {
770
827
  };
771
828
 
772
829
  // src/platform/ledger/usage.ts
773
- import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
774
830
  import { readdirSync, openSync, readSync, fstatSync, closeSync } from "node:fs";
775
831
  import { join } from "node:path";
776
832
 
@@ -1017,6 +1073,14 @@ CREATE TABLE IF NOT EXISTS usage_meta (
1017
1073
  value TEXT NOT NULL DEFAULT ''
1018
1074
  );
1019
1075
  `;
1076
+ var USAGE_MIGRATIONS = [
1077
+ sqlMigration(1, "create usage tables", MIGRATIONS2),
1078
+ defineMigration(2, "cached_tokens/tmux_session columns + session index", (db) => {
1079
+ addColumnIfMissing(db, "prompt_usage", "cached_tokens", "cached_tokens INTEGER NOT NULL DEFAULT 0");
1080
+ addColumnIfMissing(db, "prompt_usage", "tmux_session", "tmux_session TEXT NOT NULL DEFAULT ''");
1081
+ db.exec("CREATE INDEX IF NOT EXISTS idx_usage_session ON prompt_usage(tmux_session, id)");
1082
+ })
1083
+ ];
1020
1084
  var n = (v) => typeof v === "number" ? v : Number(v ?? 0);
1021
1085
  var windowConds = (tmuxSession, windows) => {
1022
1086
  const conds = windows.map(() => "(created_at >= ? AND created_at <= ?)").join(" OR ");
@@ -1039,20 +1103,8 @@ var windowUsageRows = (db, tmuxSession, windows) => {
1039
1103
  };
1040
1104
  var REPLAY_COOLDOWN_MS = 24 * 36e5;
1041
1105
  var createUsageStore = (dbPath, now = () => /* @__PURE__ */ new Date()) => {
1042
- const db = new DatabaseSync2(dbPath);
1043
- db.exec(MIGRATIONS2);
1044
- try {
1045
- db.exec("ALTER TABLE prompt_usage ADD COLUMN cached_tokens INTEGER NOT NULL DEFAULT 0");
1046
- } catch {
1047
- }
1048
- try {
1049
- db.exec(`ALTER TABLE prompt_usage ADD COLUMN tmux_session TEXT NOT NULL DEFAULT ''`);
1050
- } catch {
1051
- }
1052
- try {
1053
- db.exec(`CREATE INDEX IF NOT EXISTS idx_usage_session ON prompt_usage(tmux_session, id)`);
1054
- } catch {
1055
- }
1106
+ const db = openDatabase(dbPath);
1107
+ migrate(db, USAGE_MIGRATIONS);
1056
1108
  return {
1057
1109
  record(r) {
1058
1110
  db.prepare(`INSERT INTO prompt_usage (source, note, model, input_tokens, output_tokens, cached_tokens, duration_ms, error, created_at)
@@ -1323,7 +1375,6 @@ export {
1323
1375
  appendExecution,
1324
1376
  appendReference,
1325
1377
  appendSection,
1326
- asRecord,
1327
1378
  createLedgerStore,
1328
1379
  createUsageStore,
1329
1380
  emptyCaseFile,
@@ -1331,10 +1382,6 @@ export {
1331
1382
  originalEmail,
1332
1383
  parseCaseFile,
1333
1384
  priceUsage,
1334
- randomIds,
1335
- rfc3339,
1336
- safeParse,
1337
1385
  serializeCaseFile,
1338
- systemClock,
1339
1386
  withOriginal
1340
1387
  };
@@ -4,8 +4,68 @@
4
4
  import { join as join2 } from "node:path";
5
5
  import { mkdirSync } from "node:fs";
6
6
 
7
- // src/platform/ledger/store.ts
7
+ // node_modules/@juli-os/basin/dist/index.js
8
8
  import { DatabaseSync } from "node:sqlite";
9
+ var openDatabase = (path) => new DatabaseSync(path);
10
+ var currentVersion = (db) => {
11
+ const row = db.prepare("PRAGMA user_version").get();
12
+ return Number(row?.["user_version"] ?? 0);
13
+ };
14
+ var writeVersion = (db, version) => {
15
+ db.exec(`PRAGMA user_version = ${Math.floor(version)}`);
16
+ };
17
+ var validateMigrations = (migrations) => {
18
+ let prev = 0;
19
+ for (const m of migrations) {
20
+ if (!Number.isInteger(m.version) || m.version < 1) {
21
+ throw new Error(`\u8FC1\u79FB\u7248\u672C\u5FC5\u987B\u662F >=1 \u7684\u6574\u6570\uFF0C\u6536\u5230 ${m.version}\uFF08${m.name}\uFF09`);
22
+ }
23
+ if (m.version <= prev) {
24
+ throw new Error(`\u8FC1\u79FB\u7248\u672C\u5FC5\u987B\u4E25\u683C\u9012\u589E\uFF1Av${m.version}\uFF08${m.name}\uFF09\u8DDF\u5728 v${prev} \u4E4B\u540E`);
25
+ }
26
+ prev = m.version;
27
+ }
28
+ };
29
+ var defineMigration = (version, name, up) => {
30
+ if (!Number.isInteger(version) || version < 1) {
31
+ throw new Error(`\u8FC1\u79FB\u7248\u672C\u5FC5\u987B\u662F >=1 \u7684\u6574\u6570\uFF0C\u6536\u5230 ${version}\uFF08${name}\uFF09`);
32
+ }
33
+ return { version, name, up };
34
+ };
35
+ var sqlMigration = (version, name, script) => defineMigration(version, name, (db) => {
36
+ db.exec(script);
37
+ });
38
+ var migrate = (db, migrations) => {
39
+ validateMigrations(migrations);
40
+ const from = currentVersion(db);
41
+ const applied = [];
42
+ for (const m of migrations) {
43
+ if (m.version <= from) continue;
44
+ db.exec("BEGIN IMMEDIATE");
45
+ try {
46
+ m.up(db);
47
+ writeVersion(db, m.version);
48
+ db.exec("COMMIT");
49
+ applied.push({ version: m.version, name: m.name });
50
+ } catch (e) {
51
+ db.exec("ROLLBACK");
52
+ throw new Error(`\u8FC1\u79FB v${m.version}\uFF08${m.name}\uFF09\u5931\u8D25\uFF0C\u5DF2\u56DE\u6EDA\uFF1A${e instanceof Error ? e.message : String(e)}`);
53
+ }
54
+ }
55
+ return { from, to: currentVersion(db), applied };
56
+ };
57
+ var tableColumns = (db, table) => {
58
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table)) {
59
+ throw new Error(`\u8868\u540D\u53EA\u5141\u8BB8\u5B57\u6BCD\u6570\u5B57\u4E0B\u5212\u7EBF\uFF0C\u6536\u5230 ${table}`);
60
+ }
61
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
62
+ return rows.map((r) => String(r["name"]));
63
+ };
64
+ var addColumnIfMissing = (db, table, column, ddl) => {
65
+ if (tableColumns(db, table).includes(column)) return false;
66
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl}`);
67
+ return true;
68
+ };
9
69
 
10
70
  // src/platform/shared/json.ts
11
71
  var safeParse = (raw) => {
@@ -180,6 +240,18 @@ CREATE TABLE IF NOT EXISTS relays (
180
240
  CREATE INDEX IF NOT EXISTS idx_events_entity ON events(entity_type, entity_id);
181
241
  CREATE INDEX IF NOT EXISTS idx_runs_session ON runs(session, status);
182
242
  `;
243
+ var STORE_MIGRATIONS = [
244
+ sqlMigration(1, "create lifecycle tables", MIGRATIONS),
245
+ defineMigration(2, "owner/thread/tenant columns + indexes + step timing", (db) => {
246
+ addColumnIfMissing(db, "workflows", "owner", "owner TEXT");
247
+ addColumnIfMissing(db, "workflows", "thread_id", "thread_id TEXT");
248
+ addColumnIfMissing(db, "workflows", "tenant", "tenant TEXT NOT NULL DEFAULT 'internal'");
249
+ db.exec("CREATE INDEX IF NOT EXISTS idx_workflows_thread ON workflows(thread_id)");
250
+ db.exec("CREATE INDEX IF NOT EXISTS idx_workflows_tenant ON workflows(tenant, created_at)");
251
+ addColumnIfMissing(db, "workflow_steps", "started_at", "started_at TEXT");
252
+ addColumnIfMissing(db, "workflow_steps", "completed_at", "completed_at TEXT");
253
+ })
254
+ ];
183
255
  var str = (v) => typeof v === "string" ? v : v === null || v === void 0 ? "" : String(v);
184
256
  var num = (v) => typeof v === "number" ? v : Number(v ?? 0);
185
257
  var jsonFieldPattern = (field, value) => {
@@ -257,23 +329,8 @@ var STEP_COLS = `id, workflow_id, seq, kind, title, status, COALESCE(input,'') A
257
329
  var createLedgerStore = (dbPath, deps = {}) => {
258
330
  const clock = deps.clock ?? systemClock;
259
331
  const ids = deps.ids ?? randomIds;
260
- const db = new DatabaseSync(dbPath);
261
- db.exec(MIGRATIONS);
262
- const wfCols = db.prepare("PRAGMA table_info(workflows)").all().map((c) => c.name);
263
- if (!wfCols.includes("owner")) {
264
- db.exec("ALTER TABLE workflows ADD COLUMN owner TEXT");
265
- }
266
- if (!wfCols.includes("thread_id")) {
267
- db.exec("ALTER TABLE workflows ADD COLUMN thread_id TEXT");
268
- }
269
- if (!wfCols.includes("tenant")) {
270
- db.exec("ALTER TABLE workflows ADD COLUMN tenant TEXT NOT NULL DEFAULT 'internal'");
271
- }
272
- db.exec("CREATE INDEX IF NOT EXISTS idx_workflows_thread ON workflows(thread_id)");
273
- db.exec("CREATE INDEX IF NOT EXISTS idx_workflows_tenant ON workflows(tenant, created_at)");
274
- const stepCols = db.prepare("PRAGMA table_info(workflow_steps)").all().map((c) => c.name);
275
- if (!stepCols.includes("started_at")) db.exec("ALTER TABLE workflow_steps ADD COLUMN started_at TEXT");
276
- if (!stepCols.includes("completed_at")) db.exec("ALTER TABLE workflow_steps ADD COLUMN completed_at TEXT");
332
+ const db = openDatabase(dbPath);
333
+ migrate(db, STORE_MIGRATIONS);
277
334
  const now = () => rfc3339(clock.now());
278
335
  const mergeStepJson = (id, col, patch) => {
279
336
  const row = db.prepare(`SELECT COALESCE(${col},'{}') AS v FROM workflow_steps WHERE id=?`).get(id);
@@ -728,7 +785,6 @@ var createLedgerStore = (dbPath, deps = {}) => {
728
785
  };
729
786
 
730
787
  // src/platform/ledger/usage.ts
731
- import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
732
788
  import { readdirSync, openSync, readSync, fstatSync, closeSync } from "node:fs";
733
789
  import { join } from "node:path";
734
790
 
@@ -975,6 +1031,14 @@ CREATE TABLE IF NOT EXISTS usage_meta (
975
1031
  value TEXT NOT NULL DEFAULT ''
976
1032
  );
977
1033
  `;
1034
+ var USAGE_MIGRATIONS = [
1035
+ sqlMigration(1, "create usage tables", MIGRATIONS2),
1036
+ defineMigration(2, "cached_tokens/tmux_session columns + session index", (db) => {
1037
+ addColumnIfMissing(db, "prompt_usage", "cached_tokens", "cached_tokens INTEGER NOT NULL DEFAULT 0");
1038
+ addColumnIfMissing(db, "prompt_usage", "tmux_session", "tmux_session TEXT NOT NULL DEFAULT ''");
1039
+ db.exec("CREATE INDEX IF NOT EXISTS idx_usage_session ON prompt_usage(tmux_session, id)");
1040
+ })
1041
+ ];
978
1042
  var n = (v) => typeof v === "number" ? v : Number(v ?? 0);
979
1043
  var windowConds = (tmuxSession, windows) => {
980
1044
  const conds = windows.map(() => "(created_at >= ? AND created_at <= ?)").join(" OR ");
@@ -997,20 +1061,8 @@ var windowUsageRows = (db, tmuxSession, windows) => {
997
1061
  };
998
1062
  var REPLAY_COOLDOWN_MS = 24 * 36e5;
999
1063
  var createUsageStore = (dbPath, now = () => /* @__PURE__ */ new Date()) => {
1000
- const db = new DatabaseSync2(dbPath);
1001
- db.exec(MIGRATIONS2);
1002
- try {
1003
- db.exec("ALTER TABLE prompt_usage ADD COLUMN cached_tokens INTEGER NOT NULL DEFAULT 0");
1004
- } catch {
1005
- }
1006
- try {
1007
- db.exec(`ALTER TABLE prompt_usage ADD COLUMN tmux_session TEXT NOT NULL DEFAULT ''`);
1008
- } catch {
1009
- }
1010
- try {
1011
- db.exec(`CREATE INDEX IF NOT EXISTS idx_usage_session ON prompt_usage(tmux_session, id)`);
1012
- } catch {
1013
- }
1064
+ const db = openDatabase(dbPath);
1065
+ migrate(db, USAGE_MIGRATIONS);
1014
1066
  return {
1015
1067
  record(r) {
1016
1068
  db.prepare(`INSERT INTO prompt_usage (source, note, model, input_tokens, output_tokens, cached_tokens, duration_ms, error, created_at)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@juli-os/ledger",
3
- "version": "0.2.0",
4
- "description": "juli-os L1 layer: the ledger of record for agent work — workflows, steps, case files, and token usage in one zero-dependency SQLite layer.",
3
+ "version": "0.3.1",
4
+ "description": "juli-os L1 layer: the ledger of record for agent work — workflows, steps, case files, and token usage in one SQLite layer (schema via @juli-os/basin).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -46,5 +46,8 @@
46
46
  "repository": {
47
47
  "type": "git",
48
48
  "url": "git+https://github.com/juli-os/ledger.git"
49
+ },
50
+ "dependencies": {
51
+ "@juli-os/basin": "^0.1.0"
49
52
  }
50
- }
53
+ }