@hardkas/toolkit 0.11.1-alpha → 0.11.2-alpha

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/src/indexer.ts CHANGED
@@ -1,128 +1,134 @@
1
- import { EventSubscriber, BackendPlugin } from '@hardkas/core';
2
- import { ProjectionStoreJson } from '@hardkas/query-store';
3
- import { ArtifactIndexStoreJson } from '@hardkas/artifacts';
4
- import { DagApi } from './dag/index.js';
5
- import { SnapshotParticipant } from './snapshot/types.js';
6
-
7
- export interface IndexerBackendPlugin extends BackendPlugin {
8
- balance?(address: string): Promise<bigint>;
9
- history?(address: string): Promise<unknown[]>;
10
- utxos?(address: string): Promise<unknown[]>;
11
- }
12
-
13
- export interface IndexerToolkitOptions {
14
- dataDir?: string;
15
- backend?: IndexerBackendPlugin;
16
- }
17
-
18
- export class IndexerToolkit implements SnapshotParticipant {
19
- private subscriber: EventSubscriber;
20
- private projection?: ProjectionStoreJson;
21
- private artifactIndex?: ArtifactIndexStoreJson;
22
- public readonly dag: DagApi;
23
- public readonly backend?: IndexerBackendPlugin | undefined;
24
-
25
- private constructor(private readonly options: IndexerToolkitOptions) {
26
- this.subscriber = new EventSubscriber();
27
- this.backend = options.backend;
28
-
29
- if (!this.backend || !this.backend.capabilities.externalState) {
30
- this.projection = new ProjectionStoreJson({
31
- dirPath: options.dataDir ? `${options.dataDir}/projections` : '.hardkas/indexer/projections',
32
- namespace: 'indexer'
33
- });
34
- this.artifactIndex = new ArtifactIndexStoreJson({
35
- filePath: options.dataDir ? `${options.dataDir}/artifacts.json` : '.hardkas/indexer/artifacts.json'
36
- });
37
- }
38
-
39
- this.dag = new DagApi();
40
- }
41
-
42
- public static open(options: IndexerToolkitOptions = {}): IndexerToolkit {
43
- return new IndexerToolkit(options);
44
- }
45
-
46
- public async connect(): Promise<void> {
47
- if (this.backend?.connect) {
48
- await this.backend.connect();
49
- }
50
- }
51
-
52
- public async watch(address: string): Promise<void> {
53
- // Polling wrapper as per V1 rules.
54
- this.subscriber.subscribe({
55
- source: {}, // mock source for facade
56
- type: "payment",
57
- intervalMs: 1000,
58
- watchedAddresses: [address],
59
- handler: async (evt) => {
60
- // Internally updates projections if address is involved
61
- }
62
- });
63
- }
64
-
65
- public async balance(address: string): Promise<bigint> {
66
- if (this.backend?.balance) {
67
- return this.backend.balance(address);
68
- }
69
- const balances = this.projection?.get('balances') || {};
70
- return BigInt(balances[address] || 0);
71
- }
72
-
73
- public async history(address: string): Promise<any[]> {
74
- if (this.backend?.history) {
75
- return this.backend.history(address);
76
- }
77
- const history = this.projection?.get('history') || {};
78
- return history[address] || [];
79
- }
80
-
81
- public async findReceipts(opts: { tags: string[] }): Promise<any[]> {
82
- if (!this.artifactIndex) return [];
83
- return this.artifactIndex.find(opts);
84
- }
85
-
86
- public async ingestArtifact(artifact: any): Promise<void> {
87
- if (!this.artifactIndex) return;
88
- this.artifactIndex.index({
89
- hash: artifact.id || 'mock_hash',
90
- schema: artifact.schema || 'unknown.v1',
91
- timestamp: new Date().toISOString(),
92
- filePath: `/mock/path/${artifact.id}.json`,
93
- tags: artifact.tags || []
94
- });
95
- }
96
-
97
- public async snapshot(): Promise<any> {
98
- if (this.backend && !this.backend.capabilities.snapshots && this.backend.capabilities.externalState) {
99
- return {
100
- backend: this.backend.name,
101
- externalState: true,
102
- snapshots: false,
103
- dag: await this.dag.snapshot()
104
- };
105
- }
106
- return {
107
- projection: this.projection?.getAll() || {},
108
- artifactIndex: this.artifactIndex?.getAll() || [],
109
- dag: await this.dag.snapshot()
110
- };
111
- }
112
-
113
- public async restore(state: any): Promise<void> {
114
- if (state.externalState) {
115
- // Cannot restore local state over an external backend. Just restore DAG.
116
- if (state.dag) await this.dag.restore(state.dag);
117
- return;
118
- }
119
- if (state.projection && this.projection) this.projection.setAll(state.projection);
120
- if (state.artifactIndex && this.artifactIndex) this.artifactIndex.setAll(state.artifactIndex);
121
- if (state.dag) await this.dag.restore(state.dag);
122
- }
123
-
124
- public async reload(): Promise<void> {
125
- await this.dag.reload();
126
- // Projection and artifactIndex read synchronously from memory in this version
127
- }
128
- }
1
+ import { EventSubscriber, BackendPlugin } from '@hardkas/core';
2
+ import { ProjectionStoreJson } from '@hardkas/query-store';
3
+ import { ArtifactIndexStoreJson } from '@hardkas/artifacts';
4
+ import { DagApi } from './dag/index.js';
5
+ import { SnapshotParticipant } from './snapshot/types.js';
6
+
7
+ export interface IndexerBackendPlugin extends BackendPlugin {
8
+ balance?(address: string): Promise<bigint>;
9
+ history?(address: string): Promise<unknown[]>;
10
+ utxos?(address: string): Promise<unknown[]>;
11
+ }
12
+
13
+ export interface IndexerToolkitOptions {
14
+ dataDir?: string;
15
+ backend?: IndexerBackendPlugin;
16
+ }
17
+
18
+ export type WatchHandler = (evt: any) => Promise<void> | void;
19
+
20
+ export class IndexerToolkit implements SnapshotParticipant {
21
+ private subscriber: EventSubscriber;
22
+ private projection?: ProjectionStoreJson;
23
+ private artifactIndex?: ArtifactIndexStoreJson;
24
+ public readonly dag: DagApi;
25
+ public readonly backend?: IndexerBackendPlugin | undefined;
26
+
27
+ private constructor(private readonly options: IndexerToolkitOptions) {
28
+ this.subscriber = new EventSubscriber();
29
+ this.backend = options.backend;
30
+
31
+ if (!this.backend || !this.backend.capabilities.externalState) {
32
+ this.projection = new ProjectionStoreJson({
33
+ dirPath: options.dataDir ? `${options.dataDir}/projections` : '.hardkas/indexer/projections',
34
+ namespace: 'indexer'
35
+ });
36
+ this.artifactIndex = new ArtifactIndexStoreJson({
37
+ filePath: options.dataDir ? `${options.dataDir}/artifacts.json` : '.hardkas/indexer/artifacts.json'
38
+ });
39
+ }
40
+
41
+ this.dag = new DagApi();
42
+ }
43
+
44
+ public static open(options: IndexerToolkitOptions = {}): IndexerToolkit {
45
+ return new IndexerToolkit(options);
46
+ }
47
+
48
+ public async connect(): Promise<void> {
49
+ if (this.backend?.connect) {
50
+ await this.backend.connect();
51
+ }
52
+ }
53
+
54
+ public async watch(address: string | string[], handler?: WatchHandler): Promise<void> {
55
+ const addresses = Array.isArray(address) ? address : [address];
56
+ // Polling wrapper as per V1 rules.
57
+ this.subscriber.subscribe({
58
+ source: {}, // mock source for facade
59
+ type: "payment",
60
+ intervalMs: 1000,
61
+ watchedAddresses: addresses,
62
+ handler: async (evt) => {
63
+ // Internally updates projections if address is involved
64
+ if (handler) {
65
+ await handler(evt);
66
+ }
67
+ }
68
+ });
69
+ }
70
+
71
+ public async balance(address: string): Promise<bigint> {
72
+ if (this.backend?.balance) {
73
+ return this.backend.balance(address);
74
+ }
75
+ const balances = this.projection?.get('balances') || {};
76
+ return BigInt(balances[address] || 0);
77
+ }
78
+
79
+ public async history(address: string): Promise<any[]> {
80
+ if (this.backend?.history) {
81
+ return this.backend.history(address);
82
+ }
83
+ const history = this.projection?.get('history') || {};
84
+ return history[address] || [];
85
+ }
86
+
87
+ public async findReceipts(opts: { tags: string[] }): Promise<any[]> {
88
+ if (!this.artifactIndex) return [];
89
+ return this.artifactIndex.find(opts);
90
+ }
91
+
92
+ public async ingestArtifact(artifact: any): Promise<void> {
93
+ if (!this.artifactIndex) return;
94
+ this.artifactIndex.index({
95
+ hash: artifact.id || 'mock_hash',
96
+ schema: artifact.schema || 'unknown.v1',
97
+ timestamp: new Date().toISOString(),
98
+ filePath: `/mock/path/${artifact.id}.json`,
99
+ tags: artifact.tags || []
100
+ });
101
+ }
102
+
103
+ public async snapshot(): Promise<any> {
104
+ if (this.backend && !this.backend.capabilities.snapshots && this.backend.capabilities.externalState) {
105
+ return {
106
+ backend: this.backend.name,
107
+ externalState: true,
108
+ snapshots: false,
109
+ dag: await this.dag.snapshot()
110
+ };
111
+ }
112
+ return {
113
+ projection: this.projection?.getAll() || {},
114
+ artifactIndex: this.artifactIndex?.getAll() || [],
115
+ dag: await this.dag.snapshot()
116
+ };
117
+ }
118
+
119
+ public async restore(state: any): Promise<void> {
120
+ if (state.externalState) {
121
+ // Cannot restore local state over an external backend. Just restore DAG.
122
+ if (state.dag) await this.dag.restore(state.dag);
123
+ return;
124
+ }
125
+ if (state.projection && this.projection) this.projection.setAll(state.projection);
126
+ if (state.artifactIndex && this.artifactIndex) this.artifactIndex.setAll(state.artifactIndex);
127
+ if (state.dag) await this.dag.restore(state.dag);
128
+ }
129
+
130
+ public async reload(): Promise<void> {
131
+ await this.dag.reload();
132
+ // Projection and artifactIndex read synchronously from memory in this version
133
+ }
134
+ }
package/src/jobs.ts CHANGED
@@ -1,46 +1,63 @@
1
- import { JobRunner, JobStoreJson, JobHandler, JobRecord } from '@hardkas/jobs';
2
- import { SnapshotParticipant, SnapshotState } from './snapshot/types.js';
3
-
4
- export interface JobsToolkitOptions {
5
- storePath?: string;
6
- }
7
-
8
- export class JobsToolkit implements SnapshotParticipant {
9
- private runner: JobRunner;
10
- private store: JobStoreJson;
11
-
12
- private constructor(options: JobsToolkitOptions) {
13
- this.store = new JobStoreJson({ filePath: options.storePath || '.hardkas-data/jobs.json' });
14
- this.runner = new JobRunner({ store: this.store });
15
- }
16
-
17
- public static open(options: JobsToolkitOptions = {}): JobsToolkit {
18
- return new JobsToolkit(options);
19
- }
20
-
21
- public registerHandler(type: string, handler: JobHandler): void {
22
- this.runner.registerHandler(type, handler);
23
- }
24
-
25
- public async enqueue(type: string, args?: any): Promise<string> {
26
- return this.runner.enqueue(type, args);
27
- }
28
-
29
- public async getJob(id: string): Promise<JobRecord | undefined> {
30
- return this.runner.getJob(id);
31
- }
32
-
33
- public async snapshot(): Promise<SnapshotState> {
34
- return this.store.getAll();
35
- }
36
-
37
- public async restore(state: SnapshotState): Promise<void> {
38
- this.store.setAll(state as Record<string, JobRecord>);
39
- }
40
-
41
- public async reload(): Promise<void> {
42
- // Runner reads from store on each operation natively in current design,
43
- // but if it had internal queues/caches, we'd clear them here.
44
- // E.g., this.runner.clearCache();
45
- }
46
- }
1
+ import { JobRunner, JobStoreJson, JobHandler, JobRecord, JobStore } from '@hardkas/jobs';
2
+ import { SnapshotParticipant, SnapshotState } from './snapshot/types.js';
3
+
4
+ export interface JobsToolkitOptions {
5
+ storePath?: string;
6
+ storage?: any; // e.g. SqliteStorage
7
+ }
8
+
9
+ export class JobsToolkit implements SnapshotParticipant {
10
+ private runner: JobRunner;
11
+ private store: JobStore;
12
+
13
+ private constructor(options: JobsToolkitOptions) {
14
+ if (options.storage) {
15
+ this.store = options.storage.createJobStore();
16
+ } else {
17
+ this.store = new JobStoreJson({ filePath: options.storePath || '.hardkas-data/jobs.json' });
18
+ }
19
+ this.runner = new JobRunner({ store: this.store });
20
+ }
21
+
22
+ public static open(options: JobsToolkitOptions = {}): JobsToolkit {
23
+ return new JobsToolkit(options);
24
+ }
25
+
26
+ public registerHandler(type: string, handler: JobHandler): void {
27
+ this.runner.registerHandler(type, handler);
28
+ }
29
+
30
+ public async enqueue(type: string, args?: any): Promise<string> {
31
+ return this.runner.enqueue(type, args);
32
+ }
33
+
34
+ public async resumePendingJobs(): Promise<void> {
35
+ return this.runner.resumePendingJobs();
36
+ }
37
+
38
+ public async getJob(id: string): Promise<JobRecord | undefined> {
39
+ return this.runner.getJob(id);
40
+ }
41
+
42
+ public async snapshot(): Promise<SnapshotState> {
43
+ return this.store.getAll();
44
+ }
45
+
46
+ public resetAll() {
47
+ // Not all stores support this, but if we really need it, we should add clear() to JobStore.
48
+ // For now, it's a test utility.
49
+ if ('setAll' in this.store) {
50
+ (this.store as any).setAll({});
51
+ }
52
+ }
53
+
54
+ public async restore(state: SnapshotState): Promise<void> {
55
+ this.store.setAll(state as Record<string, JobRecord>);
56
+ }
57
+
58
+ public async reload(): Promise<void> {
59
+ // Runner reads from store on each operation natively in current design,
60
+ // but if it had internal queues/caches, we'd clear them here.
61
+ // E.g., this.runner.clearCache();
62
+ }
63
+ }
package/src/payment.ts CHANGED
@@ -1,81 +1,100 @@
1
- import { buildKaspaUri } from '@hardkas/tx-builder';
2
- import { EvidenceBatchExporter } from '@hardkas/artifacts';
3
- import { InvoiceStoreJson, InvoiceRecord } from './stores/invoice-store.js';
4
-
5
- export interface PaymentToolkitOptions {
6
- storePath?: string;
7
- }
8
-
9
- export class PaymentToolkit {
10
- private store: InvoiceStoreJson;
11
-
12
- private constructor(
13
- public readonly merchantId: string,
14
- private readonly options: PaymentToolkitOptions
15
- ) {
16
- this.store = new InvoiceStoreJson({ filePath: options.storePath || '.hardkas-data/invoices.json' });
17
- }
18
-
19
- public static openMerchant(merchantId: string, options: PaymentToolkitOptions = {}): PaymentToolkit {
20
- return new PaymentToolkit(merchantId, options);
21
- }
22
-
23
- public async createInvoice(opts: { amount: bigint; currency: string }): Promise<InvoiceRecord> {
24
- const id = `inv_${Date.now()}`;
25
- const result = buildKaspaUri({
26
- address: `kaspa:${this.merchantId}`,
27
- amountSompi: opts.amount * 100_000_000n, // convert to sompi
28
- message: `Invoice ${id}`
29
- });
30
-
31
- const invoice: InvoiceRecord = {
32
- id,
33
- merchantId: this.merchantId,
34
- amount: opts.amount,
35
- currency: opts.currency,
36
- status: 'pending',
37
- uri: result.uri,
38
- createdAt: new Date().toISOString(),
39
- updatedAt: new Date().toISOString()
40
- };
41
-
42
- this.store.save(invoice);
43
- return invoice;
44
- }
45
-
46
- public async getInvoice(id: string): Promise<InvoiceRecord | undefined> {
47
- return this.store.get(id);
48
- }
49
-
50
- public async listInvoices(): Promise<InvoiceRecord[]> {
51
- return this.store.listByMerchant(this.merchantId);
52
- }
53
-
54
- public async stats(): Promise<{ totalInvoices: number; paidInvoices: number }> {
55
- return this.store.stats(this.merchantId);
56
- }
57
-
58
- public async check(invoiceId: string): Promise<string> {
59
- const inv = this.store.get(invoiceId);
60
- return inv ? inv.status : 'not_found';
61
- }
62
-
63
- public async receipt(invoiceId: string): Promise<any> {
64
- return {
65
- schema: "paymentReceipt.v1",
66
- invoiceId,
67
- merchantId: this.merchantId,
68
- status: "paid",
69
- timestamp: new Date().toISOString()
70
- };
71
- }
72
-
73
- // For lab testing/simulation manually paying an invoice
74
- public async simulatePay(invoiceId: string): Promise<void> {
75
- const inv = this.store.get(invoiceId);
76
- if (inv) {
77
- inv.status = 'paid';
78
- this.store.save(inv);
79
- }
80
- }
81
- }
1
+ import { buildKaspaUri } from '@hardkas/tx-builder';
2
+ import { EvidenceBatchExporter } from '@hardkas/artifacts';
3
+ import { InvoiceStoreJson, InvoiceRecord, InvoiceStore } from './stores/invoice-store.js';
4
+
5
+ export interface PaymentToolkitOptions {
6
+ storePath?: string;
7
+ storage?: any;
8
+ }
9
+
10
+ export class PaymentToolkit {
11
+ private store: InvoiceStore;
12
+
13
+ private constructor(
14
+ public readonly merchantId: string,
15
+ private readonly options: PaymentToolkitOptions
16
+ ) {
17
+ if (options.storage) {
18
+ this.store = options.storage.createInvoiceStore();
19
+ } else {
20
+ this.store = new InvoiceStoreJson({ filePath: options.storePath || '.hardkas-data/invoices.json' });
21
+ }
22
+ }
23
+
24
+ public static openMerchant(merchantId: string, options: PaymentToolkitOptions = {}): PaymentToolkit {
25
+ return new PaymentToolkit(merchantId, options);
26
+ }
27
+
28
+ public async createInvoice(opts: { amount?: bigint; amountSompi?: bigint; currency?: string }): Promise<InvoiceRecord> {
29
+ if (opts.amount === undefined && opts.amountSompi === undefined) {
30
+ throw new Error("Must provide either amount or amountSompi");
31
+ }
32
+
33
+ const sompi = opts.amountSompi ?? (opts.amount! * 100_000_000n);
34
+ const kas = opts.amount ?? (opts.amountSompi! / 100_000_000n);
35
+ const curr = opts.currency ?? 'KAS';
36
+
37
+ const id = `inv_${Date.now()}`;
38
+ const result = buildKaspaUri({
39
+ address: `kaspa:${this.merchantId}`,
40
+ amountSompi: sompi,
41
+ message: `Invoice ${id}`
42
+ });
43
+
44
+ const invoice: InvoiceRecord = {
45
+ id,
46
+ merchantId: this.merchantId,
47
+ amount: kas,
48
+ currency: curr,
49
+ status: 'pending',
50
+ uri: result.uri,
51
+ createdAt: new Date().toISOString(),
52
+ updatedAt: new Date().toISOString()
53
+ };
54
+
55
+ await this.store.save(invoice);
56
+ return invoice;
57
+ }
58
+
59
+ public async getInvoice(id: string): Promise<InvoiceRecord | undefined> {
60
+ return await this.store.get(id);
61
+ }
62
+
63
+ public async listInvoices(): Promise<InvoiceRecord[]> {
64
+ return await this.store.listByMerchant(this.merchantId);
65
+ }
66
+
67
+ public async stats(): Promise<{ totalInvoices: number; paidInvoices: number }> {
68
+ return await this.store.stats(this.merchantId);
69
+ }
70
+
71
+ public async check(invoiceId: string): Promise<string> {
72
+ const inv = await this.store.get(invoiceId);
73
+ return inv ? inv.status : 'not_found';
74
+ }
75
+
76
+ public async receipt(invoiceId: string): Promise<any> {
77
+ const inv = await this.store.get(invoiceId);
78
+ if (!inv) throw new Error("Invoice not found");
79
+ if (inv.status !== "paid") throw new Error("Invoice is not paid");
80
+
81
+ return {
82
+ schema: "paymentReceipt.v1",
83
+ invoiceId,
84
+ merchantId: this.merchantId,
85
+ status: "paid",
86
+ timestamp: new Date().toISOString(),
87
+ amount: inv.amount,
88
+ currency: inv.currency
89
+ };
90
+ }
91
+
92
+ // For lab testing/simulation manually paying an invoice
93
+ public async simulatePay(invoiceId: string): Promise<void> {
94
+ const inv = await this.store.get(invoiceId);
95
+ if (inv) {
96
+ inv.status = 'paid';
97
+ await this.store.save(inv);
98
+ }
99
+ }
100
+ }