@artblocks/abx-indexer 0.1.0-alpha.4 → 0.1.0-alpha.41

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/CHANGELOG.md ADDED
@@ -0,0 +1,62 @@
1
+ # @artblocks/abx-indexer
2
+
3
+ ## 0.1.0-alpha.41
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [b9197bc]
8
+ - @artblocks/abx-sdk@0.1.0-alpha.40
9
+
10
+ ## 0.1.0-alpha.40
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies [3d7fe3c]
15
+ - Updated dependencies [7b462e2]
16
+ - @artblocks/abx-sdk@0.1.0-alpha.39
17
+
18
+ ## 0.1.0-alpha.39
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [c2400a7]
23
+ - @artblocks/abx-sdk@0.1.0-alpha.38
24
+
25
+ ## 0.1.0-alpha.38
26
+
27
+ ### Patch Changes
28
+
29
+ - Updated dependencies [4512962]
30
+ - @artblocks/abx-sdk@0.1.0-alpha.37
31
+
32
+ ## 0.1.0-alpha.37
33
+
34
+ ### Patch Changes
35
+
36
+ - Updated dependencies [5522192]
37
+ - @artblocks/abx-sdk@0.1.0-alpha.36
38
+
39
+ ## 0.1.0-alpha.36
40
+
41
+ ### Patch Changes
42
+
43
+ - Updated dependencies [21e3d6c]
44
+ - Updated dependencies [a73922c]
45
+ - @artblocks/abx-sdk@0.1.0-alpha.35
46
+
47
+ ## 0.1.0-alpha.35
48
+
49
+ ### Patch Changes
50
+
51
+ - Updated dependencies [3ddba71]
52
+ - Updated dependencies [9065128]
53
+ - @artblocks/abx-sdk@0.1.0-alpha.34
54
+
55
+ ## 0.1.0-alpha.34
56
+
57
+ ### Patch Changes
58
+
59
+ - 9242a05: Prepare package metadata, release notes, and public-facing source comments for the initial public
60
+ source release.
61
+ - Updated dependencies [9242a05]
62
+ - @artblocks/abx-sdk@0.1.0-alpha.33
package/dist/index.d.ts CHANGED
@@ -4,6 +4,6 @@
4
4
  * Replays the event spine from chain into a disposable, rebuildable projection.
5
5
  * The canonical re-index: how every service onboards or exits a project.
6
6
  */
7
- export { SelfHostIndexer, type IndexResult } from './indexer.js';
7
+ export { SelfHostIndexer, DEFAULT_INCREMENTAL_VACUUM_PAGES, DEFAULT_VACUUM_INTERVAL_MS, type IndexResult, type ReindexOptions, } from './indexer.js';
8
8
  export { SqliteStore, type Store, type ProjectRegistration, type EffectStatusRow, type EffectArtifactRow, } from './store.js';
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAC,eAAe,EAAE,KAAK,WAAW,EAAC,MAAM,cAAc,CAAC;AAC/D,OAAO,EACL,WAAW,EACX,KAAK,KAAK,EACV,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,iBAAiB,GACvB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,eAAe,EACf,gCAAgC,EAChC,0BAA0B,EAC1B,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,WAAW,EACX,KAAK,KAAK,EACV,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,iBAAiB,GACvB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -4,6 +4,6 @@
4
4
  * Replays the event spine from chain into a disposable, rebuildable projection.
5
5
  * The canonical re-index: how every service onboards or exits a project.
6
6
  */
7
- export { SelfHostIndexer } from './indexer.js';
7
+ export { SelfHostIndexer, DEFAULT_INCREMENTAL_VACUUM_PAGES, DEFAULT_VACUUM_INTERVAL_MS, } from './indexer.js';
8
8
  export { SqliteStore, } from './store.js';
9
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAC,eAAe,EAAmB,MAAM,cAAc,CAAC;AAC/D,OAAO,EACL,WAAW,GAKZ,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,eAAe,EACf,gCAAgC,EAChC,0BAA0B,GAG3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,WAAW,GAKZ,MAAM,YAAY,CAAC"}
package/dist/indexer.d.ts CHANGED
@@ -1,10 +1,45 @@
1
- import { type Address, type ProjectState, type PublicClient } from '@artblocks/abx-sdk';
1
+ import { type Address, type GetLogsAdaptiveOptions, type ProjectState, type PublicClient, type ReconstructBlockTag } from '@artblocks/abx-sdk';
2
+ import type { Log } from 'viem';
2
3
  import { type IndexStatusRow, type Store, type ProjectRegistration } from './store.js';
4
+ export interface ReindexOptions {
5
+ full?: boolean;
6
+ readUriDocuments?: boolean;
7
+ rpcUrl?: string;
8
+ /** Already-fetched logs for this project covering {@link toBlock}. Skips the
9
+ * per-project `eth_getLogs` — the watch loop that scanned the registered set
10
+ * holds the delta and should be the only getLogs. */
11
+ logs?: readonly Log[];
12
+ /** Inclusive scan head `logs` cover. Required with `logs`. */
13
+ toBlock?: bigint;
14
+ /**
15
+ * Stop the scan boundary at this block tag instead of chain head (the default, unchanged
16
+ * behavior). Resolved to a concrete block number BEFORE scanning (`resolveBlockTag` in the SDK),
17
+ * so the stored watermark (`ProjectState.toBlock`) is always a literal number, never the tag —
18
+ * see {@link ReconstructBlockTag}. Ignored when `logs` is supplied: that path's own `toBlock` is
19
+ * the caller's already-resolved scan head, from a watch loop that has nothing to do with a
20
+ * per-project tag choice.
21
+ */
22
+ blockTag?: ReconstructBlockTag;
23
+ /** See {@link GetLogsAdaptiveOptions.onChunk} — the only progress signal during a large scan. */
24
+ onChunk?: GetLogsAdaptiveOptions['onChunk'];
25
+ }
3
26
  export interface IndexResult {
4
27
  state: ProjectState;
5
28
  elapsedMs: number;
6
29
  mode: 'full' | 'incremental';
7
30
  }
31
+ /** Bounded per-pass reclaim for {@link SelfHostIndexer.runIncrementalVacuum} — ~100 pages is ~400KB
32
+ * at SQLite's default 4096-byte page size, cheap enough to run synchronously without a request or a
33
+ * watch tick noticing, while a long-running node with a large freelist still drains it over several
34
+ * passes instead of never. */
35
+ export declare const DEFAULT_INCREMENTAL_VACUUM_PAGES = 100;
36
+ /** Default cadence for automatic SQLite maintenance (see
37
+ * {@link SelfHostIndexer.startVacuumMaintenance}). Deliberately far coarser than the chain watcher's
38
+ * poll (`ABX_WATCH_INTERVAL_MS`, 12s by default): freed pages accumulate slowly (a reclaim, a
39
+ * dropped projection, a pruned bound artifact), so there is nothing to gain from touching the file
40
+ * every tick — and this is process-internal scheduling, not an operator-facing knob, so it isn't
41
+ * read from the environment the way the watcher's interval is. */
42
+ export declare const DEFAULT_VACUUM_INTERVAL_MS: number;
8
43
  /**
9
44
  * The reference indexer (Layer 3). It does one thing well: replay the event
10
45
  * spine from chain into the store. Because every state change rides a standard,
@@ -19,6 +54,16 @@ export declare class SelfHostIndexer {
19
54
  /** Catch-up runs in flight, per lowercased address — see {@link reindexShared}. */
20
55
  private inFlight;
21
56
  constructor(store?: Store);
57
+ /**
58
+ * The pooled read client for a chain, or — with `rpcUrl` — one pinned to a SINGLE endpoint.
59
+ *
60
+ * The default (no `rpcUrl`) is a viem `fallback` across every configured endpoint, which fails
61
+ * over on an *error*. It does not fail over on a successful empty answer, which is exactly what an
62
+ * endpoint that has pruned its log history returns for an old block: `[]`, HTTP 200. Pinning one
63
+ * endpoint is how a caller can re-run the same scan against each in turn and find one that holds
64
+ * the history (see the CLI's empty-scan recovery). Pooled per (chain, endpoint) so the pinned
65
+ * clients don't evict the shared one.
66
+ */
22
67
  private client;
23
68
  /** The RPC client for a chain — exposed for reads that aren't a reindex (e.g. deploy-block
24
69
  * discovery in the admin control plane). Reuses the same pooled client as `reindex`. */
@@ -34,9 +79,7 @@ export declare class SelfHostIndexer {
34
79
  * replay. `{full: true}` forces a replay from the deploy block (the durability
35
80
  * proof, and what runs automatically when there's no prior state). Idempotent.
36
81
  */
37
- reindex(address: Address, opts?: {
38
- full?: boolean;
39
- }): Promise<IndexResult>;
82
+ reindex(address: Address, opts?: ReindexOptions): Promise<IndexResult>;
40
83
  /**
41
84
  * {@link reindex}, but concurrent calls for the same project COALESCE into one run.
42
85
  *
@@ -46,9 +89,7 @@ export declare class SelfHostIndexer {
46
89
  * the bottleneck — so the second caller joins the first run instead. A caller that needs a *full*
47
90
  * replay while an incremental is in flight gets its replay queued after it, never dropped.
48
91
  */
49
- reindexShared(address: Address, opts?: {
50
- full?: boolean;
51
- }): Promise<IndexResult>;
92
+ reindexShared(address: Address, opts?: ReindexOptions): Promise<IndexResult>;
52
93
  /** Is a catch-up for this project running right now? (Lets a caller answer "still backfilling"
53
94
  * without starting more work.) */
54
95
  isCatchingUp(address: Address): boolean;
@@ -61,14 +102,44 @@ export declare class SelfHostIndexer {
61
102
  indexStatus(address: Address): IndexStatusRow;
62
103
  private track;
63
104
  /** Re-index every registered project. */
64
- reindexAll(opts?: {
65
- full?: boolean;
66
- }): Promise<IndexResult[]>;
105
+ reindexAll(opts?: ReindexOptions): Promise<IndexResult[]>;
67
106
  getProject(address: Address): ProjectState | null;
68
107
  /** Throw away a project's reconstructed projection, keeping its registration — the next
69
108
  * {@link reindex} then rebuilds it from the deploy block. The projection is a disposable cache of
70
109
  * chain state; this is how you prove it (`abx demo` deletes and replays to show identical state). */
71
110
  dropProjection(address: Address): void;
72
111
  listProjects(): ProjectState[];
112
+ /**
113
+ * Run one bounded `PRAGMA incremental_vacuum` pass, delegating to {@link SqliteStore.runIncrementalVacuum}.
114
+ * A no-op (returns 0) for any `Store` implementation that isn't a `SqliteStore` — vacuuming is a
115
+ * SQLite-specific concern, so a Postgres-backed deploy (which owns its own manual VACUUM story)
116
+ * simply has nothing to do here. Exposed on its own (not just via {@link startVacuumMaintenance})
117
+ * so a caller can also run one pass on demand — e.g. right after `abx vacuum convert`, to reclaim
118
+ * whatever the full VACUUM already didn't (it shouldn't leave anything, but this makes "did it
119
+ * work" independently checkable).
120
+ */
121
+ runIncrementalVacuum(maxPages?: number): number;
122
+ /**
123
+ * Start automatic SQLite maintenance: {@link runIncrementalVacuum} on its own
124
+ * timer, decoupled from the chain watcher's poll loop (`packages/token-api/src/watcher.ts`) so a
125
+ * reclaim pass can never sit inline with a request or a watch tick — it runs BETWEEN them, on
126
+ * whatever cadence the caller (or the default) picks, and a slow pass only ever delays the next
127
+ * pass, never a request being served concurrently (`node:sqlite`'s `DatabaseSync` is synchronous,
128
+ * but each pass is bounded — see `SqliteStore.runIncrementalVacuum` — precisely so that's cheap).
129
+ *
130
+ * `abx serve` is the intended caller, started once alongside `startChainWatcher`. Safe to call
131
+ * unconditionally: for a non-`SqliteStore` backend this still returns a working `stop()`, it just
132
+ * schedules calls that are themselves no-ops (see {@link runIncrementalVacuum}).
133
+ *
134
+ * A failed pass is logged and swallowed — maintenance must never be the thing that takes a node
135
+ * down; the freelist simply waits for the next pass.
136
+ */
137
+ startVacuumMaintenance(opts?: {
138
+ intervalMs?: number;
139
+ maxPages?: number;
140
+ log?: (line: string) => void;
141
+ }): {
142
+ stop(): void;
143
+ };
73
144
  }
74
145
  //# sourceMappingURL=indexer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../src/indexer.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,OAAO,EACZ,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAc,KAAK,cAAc,EAAE,KAAK,KAAK,EAAE,KAAK,mBAAmB,EAAC,MAAM,YAAY,CAAC;AAElG,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,qBAAa,eAAe;IAC1B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,OAAO,CAAC,OAAO,CAAmC;IAClD,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAqE;gBAEzE,KAAK,CAAC,EAAE,KAAK;IAIzB,OAAO,CAAC,MAAM;IAMd;6FACyF;IACzF,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY;IAI5C;;6DAEyD;IACzD,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,mBAAmB,EAAE,cAAc,CAAC,GAAG,mBAAmB;IAO7E;;;;;;OAMG;IACG,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAiDlF;;;;;;;;OAQG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAYlF;uCACmC;IACnC,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO;IAIvC;;;;;OAKG;IACH,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc;IAU7C,OAAO,CAAC,KAAK;IAoBb,yCAAyC;IACnC,UAAU,CAAC,IAAI,GAAE;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAQrE,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI;IAIjD;;0GAEsG;IACtG,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAItC,YAAY,IAAI,YAAY,EAAE;CAG/B"}
1
+ {"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../src/indexer.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,OAAO,EACZ,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,MAAM,CAAC;AAC9B,OAAO,EAAc,KAAK,cAAc,EAAE,KAAK,KAAK,EAAE,KAAK,mBAAmB,EAAC,MAAM,YAAY,CAAC;AAElG,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;0DAEsD;IACtD,IAAI,CAAC,EAAE,SAAS,GAAG,EAAE,CAAC;IACtB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,iGAAiG;IACjG,OAAO,CAAC,EAAE,sBAAsB,CAAC,SAAS,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC;CAC9B;AAED;;;+BAG+B;AAC/B,eAAO,MAAM,gCAAgC,MAAM,CAAC;AAEpD;;;;;mEAKmE;AACnE,eAAO,MAAM,0BAA0B,QAAa,CAAC;AAErD;;;;;;;GAOG;AACH,qBAAa,eAAe;IAC1B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,OAAO,CAAC,OAAO,CAAmC;IAClD,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAqE;gBAEzE,KAAK,CAAC,EAAE,KAAK;IAIzB;;;;;;;;;OASG;IACH,OAAO,CAAC,MAAM;IAOd;6FACyF;IACzF,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY;IAI5C;;6DAEyD;IACzD,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,mBAAmB,EAAE,cAAc,CAAC,GAAG,mBAAmB;IAO7E;;;;;;OAMG;IACG,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,WAAW,CAAC;IA2FhF;;;;;;;;OAQG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,WAAW,CAAC;IAYhF;uCACmC;IACnC,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO;IAIvC;;;;;OAKG;IACH,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc;IAU7C,OAAO,CAAC,KAAK;IAoBb,yCAAyC;IACnC,UAAU,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAQnE,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI;IAIjD;;0GAEsG;IACtG,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAItC,YAAY,IAAI,YAAY,EAAE;IAO9B;;;;;;;;OAQG;IACH,oBAAoB,CAAC,QAAQ,GAAE,MAAyC,GAAG,MAAM;IAIjF;;;;;;;;;;;;;;OAcG;IACH,sBAAsB,CAAC,IAAI,GAAE;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;KAAM,GAAG;QAAC,IAAI,IAAI,IAAI,CAAA;KAAC;CAiB1H"}
package/dist/indexer.js CHANGED
@@ -1,5 +1,17 @@
1
- import { classifyIndexError, makePublicClient, reconstructIncremental, reconstructProject, } from '@artblocks/abx-sdk';
1
+ import { classifyIndexError, makePublicClient, reconstructFromLogs, reconstructIncremental, reconstructProject, } from '@artblocks/abx-sdk';
2
2
  import { SqliteStore } from './store.js';
3
+ /** Bounded per-pass reclaim for {@link SelfHostIndexer.runIncrementalVacuum} — ~100 pages is ~400KB
4
+ * at SQLite's default 4096-byte page size, cheap enough to run synchronously without a request or a
5
+ * watch tick noticing, while a long-running node with a large freelist still drains it over several
6
+ * passes instead of never. */
7
+ export const DEFAULT_INCREMENTAL_VACUUM_PAGES = 100;
8
+ /** Default cadence for automatic SQLite maintenance (see
9
+ * {@link SelfHostIndexer.startVacuumMaintenance}). Deliberately far coarser than the chain watcher's
10
+ * poll (`ABX_WATCH_INTERVAL_MS`, 12s by default): freed pages accumulate slowly (a reclaim, a
11
+ * dropped projection, a pruned bound artifact), so there is nothing to gain from touching the file
12
+ * every tick — and this is process-internal scheduling, not an operator-facing knob, so it isn't
13
+ * read from the environment the way the watcher's interval is. */
14
+ export const DEFAULT_VACUUM_INTERVAL_MS = 5 * 60_000;
3
15
  /**
4
16
  * The reference indexer (Layer 3). It does one thing well: replay the event
5
17
  * spine from chain into the store. Because every state change rides a standard,
@@ -16,10 +28,21 @@ export class SelfHostIndexer {
16
28
  constructor(store) {
17
29
  this.store = store ?? new SqliteStore();
18
30
  }
19
- client(chainKey) {
20
- let c = this.clients.get(chainKey);
31
+ /**
32
+ * The pooled read client for a chain, or — with `rpcUrl` — one pinned to a SINGLE endpoint.
33
+ *
34
+ * The default (no `rpcUrl`) is a viem `fallback` across every configured endpoint, which fails
35
+ * over on an *error*. It does not fail over on a successful empty answer, which is exactly what an
36
+ * endpoint that has pruned its log history returns for an old block: `[]`, HTTP 200. Pinning one
37
+ * endpoint is how a caller can re-run the same scan against each in turn and find one that holds
38
+ * the history (see the CLI's empty-scan recovery). Pooled per (chain, endpoint) so the pinned
39
+ * clients don't evict the shared one.
40
+ */
41
+ client(chainKey, rpcUrl) {
42
+ const key = rpcUrl ? `${chainKey}|${rpcUrl}` : chainKey;
43
+ let c = this.clients.get(key);
21
44
  if (!c)
22
- this.clients.set(chainKey, (c = makePublicClient({ chainKey })));
45
+ this.clients.set(key, (c = makePublicClient({ chainKey, rpcUrl })));
23
46
  return c;
24
47
  }
25
48
  /** The RPC client for a chain — exposed for reads that aren't a reindex (e.g. deploy-block
@@ -64,15 +87,54 @@ export class SelfHostIndexer {
64
87
  let state;
65
88
  let mode;
66
89
  try {
67
- if (incremental) {
68
- state = await reconstructIncremental(this.client(reg.chainKey), prior, { factory });
90
+ if (incremental && opts.logs !== undefined) {
91
+ if (opts.toBlock === undefined) {
92
+ throw new Error('reindex({logs}) requires toBlock — the inclusive scan head those logs cover');
93
+ }
94
+ state = await reconstructFromLogs(this.client(reg.chainKey, opts.rpcUrl), prior, opts.logs, {
95
+ factory,
96
+ readUriDocuments: opts.readUriDocuments,
97
+ toBlock: opts.toBlock,
98
+ });
99
+ mode = 'incremental';
100
+ }
101
+ else if (incremental) {
102
+ state = await reconstructIncremental(this.client(reg.chainKey, opts.rpcUrl), prior, {
103
+ factory,
104
+ readUriDocuments: opts.readUriDocuments,
105
+ onChunk: opts.onChunk,
106
+ toBlock: opts.blockTag,
107
+ });
69
108
  mode = 'incremental';
70
109
  }
71
110
  else {
72
- state = await reconstructProject(this.client(reg.chainKey), {
111
+ state = await reconstructProject(this.client(reg.chainKey, opts.rpcUrl), {
73
112
  address,
74
113
  fromBlock: BigInt(reg.fromBlock),
75
114
  factory,
115
+ readUriDocuments: opts.readUriDocuments,
116
+ onChunk: opts.onChunk,
117
+ toBlock: opts.blockTag,
118
+ });
119
+ mode = 'full';
120
+ }
121
+ // Self-heal, never loop: an incremental fold that yields FEWER events than what's already
122
+ // stored signals a damaged projection. `eventCount` can only grow with append-only chain
123
+ // history, so a decrease is
124
+ // never legitimate — discard the bad fold before it's written, and repair with exactly ONE
125
+ // full replay from the deploy block. That result is written unconditionally, even in the
126
+ // (pathological) case where it too looks smaller: a full reconstruct from chain is
127
+ // definitionally correct, so there is nothing left to fall back to and no second attempt.
128
+ if (mode === 'incremental' && prior && state.eventCount < prior.eventCount) {
129
+ console.error(`[indexer] ${address}: an incremental fold produced ${state.eventCount} events, fewer than the ` +
130
+ `${prior.eventCount} already stored — discarding it and falling back to one full reconstruct.`);
131
+ state = await reconstructProject(this.client(reg.chainKey, opts.rpcUrl), {
132
+ address,
133
+ fromBlock: BigInt(reg.fromBlock),
134
+ factory,
135
+ readUriDocuments: opts.readUriDocuments,
136
+ onChunk: opts.onChunk,
137
+ toBlock: opts.blockTag,
76
138
  });
77
139
  mode = 'full';
78
140
  }
@@ -88,7 +150,9 @@ export class SelfHostIndexer {
88
150
  });
89
151
  throw err;
90
152
  }
91
- this.store.putProject(state);
153
+ // A full replay is the repair path — it purges + rebuilds the append-only event log so a
154
+ // reorg-replaced event can't survive as a stale row (incremental writes never delete).
155
+ this.store.putProject(state, { fullReplay: mode === 'full' });
92
156
  const now = new Date().toISOString();
93
157
  this.store.setIndexStatus(address, { status: 'live', error: null, attempts: 0, lastAttemptAt: now, lastIndexedAt: now });
94
158
  return { state, elapsedMs: Date.now() - started, mode };
@@ -172,5 +236,53 @@ export class SelfHostIndexer {
172
236
  listProjects() {
173
237
  return this.store.listProjects();
174
238
  }
239
+ // ── SQLite maintenance — the bounded-automatic half; the explicit one-time
240
+ // conversion for a pre-existing store lives on `SqliteStore.vacuumConvert` / `abx vacuum convert`.
241
+ /**
242
+ * Run one bounded `PRAGMA incremental_vacuum` pass, delegating to {@link SqliteStore.runIncrementalVacuum}.
243
+ * A no-op (returns 0) for any `Store` implementation that isn't a `SqliteStore` — vacuuming is a
244
+ * SQLite-specific concern, so a Postgres-backed deploy (which owns its own manual VACUUM story)
245
+ * simply has nothing to do here. Exposed on its own (not just via {@link startVacuumMaintenance})
246
+ * so a caller can also run one pass on demand — e.g. right after `abx vacuum convert`, to reclaim
247
+ * whatever the full VACUUM already didn't (it shouldn't leave anything, but this makes "did it
248
+ * work" independently checkable).
249
+ */
250
+ runIncrementalVacuum(maxPages = DEFAULT_INCREMENTAL_VACUUM_PAGES) {
251
+ return this.store instanceof SqliteStore ? this.store.runIncrementalVacuum(maxPages) : 0;
252
+ }
253
+ /**
254
+ * Start automatic SQLite maintenance: {@link runIncrementalVacuum} on its own
255
+ * timer, decoupled from the chain watcher's poll loop (`packages/token-api/src/watcher.ts`) so a
256
+ * reclaim pass can never sit inline with a request or a watch tick — it runs BETWEEN them, on
257
+ * whatever cadence the caller (or the default) picks, and a slow pass only ever delays the next
258
+ * pass, never a request being served concurrently (`node:sqlite`'s `DatabaseSync` is synchronous,
259
+ * but each pass is bounded — see `SqliteStore.runIncrementalVacuum` — precisely so that's cheap).
260
+ *
261
+ * `abx serve` is the intended caller, started once alongside `startChainWatcher`. Safe to call
262
+ * unconditionally: for a non-`SqliteStore` backend this still returns a working `stop()`, it just
263
+ * schedules calls that are themselves no-ops (see {@link runIncrementalVacuum}).
264
+ *
265
+ * A failed pass is logged and swallowed — maintenance must never be the thing that takes a node
266
+ * down; the freelist simply waits for the next pass.
267
+ */
268
+ startVacuumMaintenance(opts = {}) {
269
+ const intervalMs = opts.intervalMs ?? DEFAULT_VACUUM_INTERVAL_MS;
270
+ const maxPages = opts.maxPages ?? DEFAULT_INCREMENTAL_VACUUM_PAGES;
271
+ const log = opts.log ?? ((line) => console.error(`[maintenance] ${line}`));
272
+ const timer = setInterval(() => {
273
+ try {
274
+ const reclaimed = this.runIncrementalVacuum(maxPages);
275
+ if (reclaimed > 0)
276
+ log(`incremental_vacuum reclaimed ${reclaimed} page(s)`);
277
+ }
278
+ catch (err) {
279
+ log(`incremental_vacuum failed (will retry next pass): ${err.message}`);
280
+ }
281
+ }, intervalMs);
282
+ // Never keeps the process alive on its own — a plain background convenience, not a reason `abx
283
+ // serve` (or a test) would hang waiting for it.
284
+ timer.unref?.();
285
+ return { stop: () => clearInterval(timer) };
286
+ }
175
287
  }
176
288
  //# sourceMappingURL=indexer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"indexer.js","sourceRoot":"","sources":["../src/indexer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"indexer.js","sourceRoot":"","sources":["../src/indexer.ts"],"names":[],"mappings":"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"}
package/dist/store.d.ts CHANGED
@@ -22,11 +22,12 @@ export interface ProjectRegistration {
22
22
  registeredAt: string;
23
23
  }
24
24
  /** One producer-published effect artifact — a row in the data plane's enumeration registry
25
- * (`specs/protocol/data-plane.md`). The resolver lists a token's rows and keeps only those whose
25
+ * (`site/content/docs/protocol/data-plane.mdx`). The resolver lists a token's rows and keeps only those whose
26
26
  * `key` matches the recomputed address at the CURRENT settled inputsHash, so stale rows go
27
27
  * silently unlisted (self-invalidation). `locator === null` means the bytes live in custody
28
- * (the shared backend at `key`, or resolver-held published bytes). Producer-published, NOT
29
- * chain-derived: rows survive a projection wipe and self-heal from a runner's next sweep. */
28
+ * (a co-located producer's bytes in the shared backend at `key`, or a bound output's `bytes` below).
29
+ * Producer-registered, NOT chain-derived: rows survive a projection wipe and self-heal from a
30
+ * runner's next sweep. */
30
31
  export interface EffectArtifactRow {
31
32
  /** renderArtifactKey (keccak hex; encodes effectKey + settled inputsHash). */
32
33
  key: string;
@@ -38,10 +39,26 @@ export interface EffectArtifactRow {
38
39
  /** The effect's DECLARED output mimeType — what the manifest and byte route serve. */
39
40
  contentType: string | null;
40
41
  locator: string | null;
42
+ /**
43
+ * A **bound** output's content (`site/content/docs/protocol/effects.mdx → Bound vs referenced`): the bytes that
44
+ * stitch into the metadata JSON — `render/traits` today. Capped at
45
+ * {@link BOUND_ARTIFACT_MAX_BYTES}; `null` for every **referenced** output, whose bytes stay with
46
+ * the producer and reach us only as `locator`.
47
+ *
48
+ * These live HERE, next to the row, and deliberately not in a `StorageBackend`: byte custody holds
49
+ * *source* bytes an on-chain keccak commits to (switchable, never lapsable), while these are
50
+ * re-creatable projection state whose loss is a re-render. One write instead of two also removes a
51
+ * way for the row and its content to disagree.
52
+ */
53
+ bytes?: Uint8Array | null;
41
54
  updatedAt?: string;
42
55
  }
56
+ /** The cap a serving node MUST accept per bound output, and MUST refuse above
57
+ * (`site/content/docs/using-abx/remote-services.mdx → The mode is decided by the binding`). ~100× a real
58
+ * traits payload: generous for what it is for, far too small to become blob storage. */
59
+ export declare const BOUND_ARTIFACT_MAX_BYTES: number;
43
60
  /**
44
- * Where one project sits in the indexing lifecycle (`specs/self-host-toolkit/remote-services.md` →
61
+ * Where one project sits in the indexing lifecycle (`site/content/docs/using-abx/remote-services.mdx` →
45
62
  * The indexing lifecycle) — what the control plane's `status`/list routes report and what
46
63
  * `abx status` prints, for a managed provider and for your own node in the same words.
47
64
  *
@@ -119,6 +136,20 @@ export interface Store {
119
136
  /** Every registered artifact row for (project, token) — the manifest's effect-source listing.
120
137
  * Callers filter by recomputing the current-inputsHash key per row (stale rows unlisted). */
121
138
  listEffectArtifacts(address: string, tokenId: string): EffectArtifactRow[];
139
+ /**
140
+ * Drop the **bound** content of every superseded row for (project, token, effectKey, outputKey) —
141
+ * everything whose `inputs_hash` isn't `currentHash`.
142
+ *
143
+ * The spec makes this a **MAY**, not a MUST (`site/content/docs/protocol/effects.mdx → Bound vs referenced`):
144
+ * what's normative is that superseded content is never *served* or stitched, which leaves it with
145
+ * no legal reader. Dropping it is therefore free of consequence, and this node drops eagerly — on
146
+ * each bound registration — because that is what turns "held bytes" into
147
+ * `cap × minted × bound outputs` rather than a number that grows with every param change. A node
148
+ * that chose to keep history would be equally conforming and simply carry the cost.
149
+ *
150
+ * The rows themselves survive as provenance; only the unreadable content goes.
151
+ */
152
+ pruneBoundArtifactBytes(address: string, tokenId: string, effectKey: string, outputKey: string, currentHash: string): void;
122
153
  /** Merge a lifecycle patch for one project (preserve-on-omit, clear-on-`null`) and return the
123
154
  * stored row. Creates the row on first write. */
124
155
  setIndexStatus(address: string, patch: IndexStatusPatch): IndexStatusRow;
@@ -134,7 +165,12 @@ export interface Store {
134
165
  /** Clear one artifact key's status — the run finished; artifact presence takes over as truth. */
135
166
  clearEffectStatus(key: string): void;
136
167
  listEffectStatuses(address: string): EffectStatusRow[];
137
- putProject(state: ProjectState): void;
168
+ /** Persist a folded projection. `fullReplay: true` (the repair path — `abx index --full`) purges
169
+ * the project's append-only event log first, so a reorg-replaced event can't survive as a stale
170
+ * row; routine incremental writes leave the log append-only. */
171
+ putProject(state: ProjectState, opts?: {
172
+ fullReplay?: boolean;
173
+ }): void;
138
174
  getProject(address: string): ProjectState | null;
139
175
  listProjects(): ProjectState[];
140
176
  /** Drop the rebuildable projection (keeps registrations). Proves replay. */
@@ -167,12 +203,31 @@ export declare class SqliteStore implements Store {
167
203
  /** Idempotently add columns introduced after a DB was first created (CREATE IF NOT
168
204
  * EXISTS won't backfill them). Keeps an existing projection working across upgrades. */
169
205
  private migrate;
206
+ /**
207
+ * One-time data migration, gated on `PRAGMA user_version` (unused before this — starts at 0 on
208
+ * every existing store). `migrate()` above only ever ADDs columns; recomputing `seq` rewrites
209
+ * VALUES in an existing column, which a `PRAGMA table_info` presence check can't gate — hence a
210
+ * separate versioned step.
211
+ *
212
+ * `events.seq` used to be the array index at write time (positional — renumbers on every re-fold).
213
+ * This recomputes every existing row to the chain-derived value `(block_number << 32) | log_index`
214
+ * — the same formula {@link eventSeq} uses going forward — so an upgraded store's history becomes
215
+ * append-only-compatible without re-indexing from chain. No collisions are possible: `(block,
216
+ * logIndex)` is already unique per project (the table's own history proves it — it was a valid
217
+ * event log before this ran), and the shift preserves that uniqueness.
218
+ *
219
+ * A fresh install has no rows yet, so the `UPDATE` is a no-op — but `user_version` still advances,
220
+ * so this never re-scans an empty table on every open. Wrapped in a transaction: a crash mid-way
221
+ * leaves `user_version` at 0 and retries the whole thing next open, never a half-migrated table.
222
+ */
223
+ private migrateSeq;
170
224
  register(reg: ProjectRegistration): void;
171
225
  listRegistrations(): ProjectRegistration[];
172
226
  getRegistration(address: string): ProjectRegistration | null;
173
227
  dropProjection(address: string): void;
174
228
  deregister(address: string): void;
175
229
  putEffectArtifact(row: EffectArtifactRow): void;
230
+ pruneBoundArtifactBytes(address: string, tokenId: string, effectKey: string, outputKey: string, currentHash: string): void;
176
231
  getEffectArtifact(key: string): EffectArtifactRow | null;
177
232
  listEffectArtifacts(address: string, tokenId: string): EffectArtifactRow[];
178
233
  putMeta(key: string, value: string): void;
@@ -189,11 +244,78 @@ export declare class SqliteStore implements Store {
189
244
  putEffectStatus(row: EffectStatusRow): void;
190
245
  clearEffectStatus(key: string): void;
191
246
  listEffectStatuses(address: string): EffectStatusRow[];
192
- putProject(state: ProjectState): void;
247
+ /**
248
+ * Persist a reconstructed `ProjectState`. O(delta), not O(history): the routine case — one new
249
+ * mint on an otherwise-unchanged project — writes one project row and one token row, appends
250
+ * whatever events are new, and touches nothing else. A delete-and-reinsert approach would rewrite
251
+ * the entire projection (every token, every event) on every call.
252
+ *
253
+ * `contract_uri`/`token_uri` are bound NULL unconditionally regardless of what `state` carries —
254
+ * see the schema comments on those columns. This is deliberate even when a caller passed populated
255
+ * values (e.g. a reconstruct run with `readUriDocuments: true`): the store's job is never to cache
256
+ * a value with no settled state, only to serve it live when asked.
257
+ */
258
+ putProject(state: ProjectState, opts?: {
259
+ fullReplay?: boolean;
260
+ }): void;
193
261
  getProject(address: string): ProjectState | null;
194
262
  listProjects(): ProjectState[];
195
263
  wipeProjections(): void;
196
264
  destroy(): void;
265
+ /**
266
+ * This store's actual `PRAGMA auto_vacuum` mode, right now. A store opened from a file created
267
+ * before `SCHEMA` started setting `PRAGMA auto_vacuum = INCREMENTAL` BEFORE its first `CREATE
268
+ * TABLE` (see the comment on that pragma above) is permanently stuck at `'none'` — the pragma
269
+ * silently no-ops on a database that already has tables — until {@link vacuumConvert} runs. This
270
+ * is the detection half of the two-part approach: every other read/write path in this class is
271
+ * indifferent to the mode, so nothing breaks for a store still at `'none'`; only reclamation cares.
272
+ */
273
+ autoVacuumMode(): 'none' | 'full' | 'incremental';
274
+ /** Diagnostics behind `abx vacuum status`: {@link autoVacuumMode} alongside the raw page/freelist
275
+ * counts a caller (or a test) can watch move as maintenance runs. */
276
+ vacuumStats(): {
277
+ mode: 'none' | 'full' | 'incremental';
278
+ freelistPages: number;
279
+ pageCount: number;
280
+ };
281
+ /**
282
+ * The one-time, EXPLICIT conversion for a store stuck at `auto_vacuum='none'`: sets the pragma —
283
+ * on its own a no-op here, same as it was at construction, since the database already has tables
284
+ * — and then runs a full `VACUUM`, the only operation that actually rebuilds the file under the
285
+ * new mode. This is deliberately not run automatically anywhere in this class or in
286
+ * {@link SelfHostIndexer}: a full `VACUUM` rewrites the ENTIRE database file and can briefly need
287
+ * up to ~2x its on-disk size while it runs, so it must be an operator's explicit choice, made with
288
+ * that cost understood — `abx vacuum convert` (and the docs it points at) is that explicit surface.
289
+ * A no-op, returning the mode unchanged, once the store is already `'full'` or `'incremental'`.
290
+ */
291
+ vacuumConvert(): 'none' | 'full' | 'incremental';
292
+ /**
293
+ * Bounded `PRAGMA incremental_vacuum(N)`: reclaims at most `maxPages` freed pages THIS call, never
294
+ * the whole freelist at once. `node:sqlite`'s `DatabaseSync` is SYNCHRONOUS and shares this process
295
+ * with token-api reads on a co-located `abx serve` — an unbounded reclaim would block every request
296
+ * in flight for however long a large freelist takes to drain. Bounding it is what makes it safe to
297
+ * call automatically between watch-loop ticks (see {@link SelfHostIndexer.runIncrementalVacuum})
298
+ * rather than only from the explicit `abx vacuum convert` command above.
299
+ *
300
+ * A no-op (returns 0, touches nothing) before the store has been converted to `'incremental'` mode
301
+ * — `incremental_vacuum` only does anything in that mode, and running it blind on a `'none'` store
302
+ * would be silent maintenance with no effect, which is worse than an honest no-op.
303
+ *
304
+ * Returns the number of pages actually REQUESTED of SQLite this call (capped by the freelist's
305
+ * current size), not a guarantee it moved exactly that many — draining an already-small freelist
306
+ * legitimately returns fewer than `maxPages`.
307
+ */
308
+ runIncrementalVacuum(maxPages: number): number;
309
+ /**
310
+ * Drop one project's projection — `extensions` / `tokens` / `projects` — but deliberately NOT
311
+ * `events`. The event log is append-only and chain-derived (`seq` is `(block << 32) | logIndex`,
312
+ * so a re-fold always lands on the same rows): a re-index after this re-derives the identical
313
+ * event set from chain and re-inserts it as a no-op (`ON CONFLICT DO NOTHING`), so nothing is lost
314
+ * by leaving the rows in place, and `abx demo`'s "drop + replay" proof doesn't depend on the log
315
+ * being gone. `deregister`/forget-the-project-entirely paths delete events explicitly — this
316
+ * helper is for `dropProjection`, which keeps the registration precisely so the next reindex can
317
+ * rebuild from it.
318
+ */
197
319
  private deleteProjection;
198
320
  private hydrate;
199
321
  }