@hardkas/toolkit 0.11.1-alpha → 0.11.4-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/dist/fee-estimator.d.ts +9 -0
- package/dist/fee-estimator.d.ts.map +1 -0
- package/dist/fee-estimator.js +32 -0
- package/dist/fee-estimator.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/indexer.d.ts +3 -1
- package/dist/indexer.d.ts.map +1 -1
- package/dist/indexer.js +13 -2
- package/dist/indexer.js.map +1 -1
- package/dist/jobs.d.ts +3 -0
- package/dist/jobs.d.ts.map +1 -1
- package/dist/jobs.js +16 -1
- package/dist/jobs.js.map +1 -1
- package/dist/payment.d.ts +4 -2
- package/dist/payment.d.ts.map +1 -1
- package/dist/payment.js +30 -12
- package/dist/payment.js.map +1 -1
- package/dist/query-api.d.ts +22 -0
- package/dist/query-api.d.ts.map +1 -0
- package/dist/query-api.js +116 -0
- package/dist/query-api.js.map +1 -0
- package/dist/snapshot/serialization.d.ts +0 -9
- package/dist/snapshot/serialization.d.ts.map +1 -1
- package/dist/snapshot/serialization.js +0 -12
- package/dist/snapshot/serialization.js.map +1 -1
- package/dist/stores/invoice-store.d.ts +15 -1
- package/dist/stores/invoice-store.d.ts.map +1 -1
- package/dist/stores/invoice-store.js.map +1 -1
- package/dist/subscriptions.d.ts +22 -0
- package/dist/subscriptions.d.ts.map +1 -0
- package/dist/subscriptions.js +91 -0
- package/dist/subscriptions.js.map +1 -0
- package/dist/wallet.d.ts +81 -2
- package/dist/wallet.d.ts.map +1 -1
- package/dist/wallet.js +328 -13
- package/dist/wallet.js.map +1 -1
- package/package.json +27 -11
- package/.turbo/turbo-build.log +0 -4
- package/src/dag/consensus.ts +0 -150
- package/src/dag/index.ts +0 -73
- package/src/dag/store.ts +0 -40
- package/src/dag/topology.ts +0 -108
- package/src/dag/types.ts +0 -20
- package/src/index.ts +0 -9
- package/src/indexer.ts +0 -128
- package/src/jobs.ts +0 -46
- package/src/payment.ts +0 -81
- package/src/silver/index.ts +0 -102
- package/src/silver/templates.ts +0 -27
- package/src/silver/types.ts +0 -37
- package/src/snapshot/backends/FsSnapshotBackend.ts +0 -100
- package/src/snapshot/backends/MemorySnapshotBackend.ts +0 -65
- package/src/snapshot/index.ts +0 -102
- package/src/snapshot/serialization.ts +0 -35
- package/src/snapshot/types.ts +0 -45
- package/src/stores/invoice-store.ts +0 -53
- package/src/stores/utxo-control-store.ts +0 -78
- package/src/utxos.ts +0 -451
- package/src/wallet.ts +0 -93
- package/test/silver.test.ts +0 -59
- package/test/snapshot.test.ts +0 -99
- package/test/toolkit.test.ts +0 -109
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -8
package/src/payment.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
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
|
-
}
|
package/src/silver/index.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { SILVER_TEMPLATES } from './templates.js';
|
|
2
|
-
import { SilverArtifact, SilverBuildResult, SilverClaims, SilverEvidence, SilverSimulationResult } from './types.js';
|
|
3
|
-
|
|
4
|
-
export class SilverTemplate {
|
|
5
|
-
constructor(public readonly name: string, private readonly rawSource: string) {}
|
|
6
|
-
|
|
7
|
-
public parameters(): string[] {
|
|
8
|
-
const regex = /<([^>]+)>/g;
|
|
9
|
-
const matches = [...this.rawSource.matchAll(regex)];
|
|
10
|
-
return Array.from(new Set(matches.map(m => m[1] as string)));
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
public fill(params: Record<string, string | number>): string {
|
|
14
|
-
let filledSource = this.rawSource;
|
|
15
|
-
const requiredParams = this.parameters();
|
|
16
|
-
|
|
17
|
-
for (const req of requiredParams) {
|
|
18
|
-
if (!(req in params)) {
|
|
19
|
-
throw new Error(`Missing parameter for template '${this.name}': <${req}>`);
|
|
20
|
-
}
|
|
21
|
-
filledSource = filledSource.replace(new RegExp(`<${req}>`, 'g'), String(params[req]));
|
|
22
|
-
}
|
|
23
|
-
return filledSource;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class SilverToolkit {
|
|
28
|
-
private constructor() {}
|
|
29
|
-
|
|
30
|
-
public static open(): SilverToolkit {
|
|
31
|
-
return new SilverToolkit();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
private getClaims(): SilverClaims {
|
|
35
|
-
return {
|
|
36
|
-
realSilverCompiler: false,
|
|
37
|
-
vmConsensusEquivalence: false,
|
|
38
|
-
mainnetReady: false,
|
|
39
|
-
productionSafe: false,
|
|
40
|
-
simulatedOnly: true
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
public templates(): string[] {
|
|
45
|
-
return Object.keys(SILVER_TEMPLATES);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
public template(name: string): SilverTemplate {
|
|
49
|
-
if (!SILVER_TEMPLATES[name]) {
|
|
50
|
-
throw new Error(`Silver template not found: ${name}`);
|
|
51
|
-
}
|
|
52
|
-
return new SilverTemplate(name, SILVER_TEMPLATES[name]);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
public async build(source: string): Promise<SilverBuildResult> {
|
|
56
|
-
// Mock bytecode compilation
|
|
57
|
-
const mockBytecode = Buffer.from(source).toString('hex');
|
|
58
|
-
return {
|
|
59
|
-
source,
|
|
60
|
-
bytecode: mockBytecode,
|
|
61
|
-
claims: this.getClaims()
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
public async simulate(build: SilverBuildResult, args?: string[]): Promise<SilverSimulationResult> {
|
|
66
|
-
// Mock VM execution
|
|
67
|
-
return {
|
|
68
|
-
success: true,
|
|
69
|
-
executionTrace: ["OP_TRUE", "OP_CHECKSIG"],
|
|
70
|
-
gasConsumed: 1000,
|
|
71
|
-
claims: this.getClaims()
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
public async artifact(build: SilverBuildResult, name?: string): Promise<SilverArtifact> {
|
|
76
|
-
const art: any = {
|
|
77
|
-
id: `silver-${Date.now()}`,
|
|
78
|
-
source: build.source,
|
|
79
|
-
bytecode: build.bytecode,
|
|
80
|
-
createdAt: new Date().toISOString(),
|
|
81
|
-
claims: this.getClaims()
|
|
82
|
-
};
|
|
83
|
-
if (name !== undefined) {
|
|
84
|
-
art.name = name;
|
|
85
|
-
}
|
|
86
|
-
return art as SilverArtifact;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public async evidence(build: SilverBuildResult, simulation: SilverSimulationResult): Promise<SilverEvidence> {
|
|
90
|
-
return {
|
|
91
|
-
schema: "hardkas.script-evidence.v1",
|
|
92
|
-
artifactId: `silver-${Date.now()}`,
|
|
93
|
-
simulationResult: {
|
|
94
|
-
success: simulation.success,
|
|
95
|
-
executionTrace: simulation.executionTrace,
|
|
96
|
-
gasConsumed: simulation.gasConsumed
|
|
97
|
-
},
|
|
98
|
-
timestamp: new Date().toISOString(),
|
|
99
|
-
claims: this.getClaims()
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
}
|
package/src/silver/templates.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export const SILVER_TEMPLATES: Record<string, string> = {
|
|
2
|
-
"op-true": `OP_TRUE`,
|
|
3
|
-
"timelock": `<locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP
|
|
4
|
-
<pubkey> OP_CHECKSIG`,
|
|
5
|
-
"escrow": `OP_IF
|
|
6
|
-
<buyer_pubkey> OP_CHECKSIGVERIFY
|
|
7
|
-
<arbiter_pubkey> OP_CHECKSIG
|
|
8
|
-
OP_ELSE
|
|
9
|
-
<seller_pubkey> OP_CHECKSIGVERIFY
|
|
10
|
-
<arbiter_pubkey> OP_CHECKSIG
|
|
11
|
-
OP_ENDIF`,
|
|
12
|
-
"htlc": `OP_IF
|
|
13
|
-
OP_SHA256 <secret_hash> OP_EQUALVERIFY
|
|
14
|
-
<receiver_pubkey> OP_CHECKSIG
|
|
15
|
-
OP_ELSE
|
|
16
|
-
<locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP
|
|
17
|
-
<sender_pubkey> OP_CHECKSIG
|
|
18
|
-
OP_ENDIF`,
|
|
19
|
-
"atomic-swap": `OP_IF
|
|
20
|
-
OP_SHA256 <secret_hash> OP_EQUALVERIFY
|
|
21
|
-
<participant_b_pubkey> OP_CHECKSIG
|
|
22
|
-
OP_ELSE
|
|
23
|
-
<locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP
|
|
24
|
-
<participant_a_pubkey> OP_CHECKSIG
|
|
25
|
-
OP_ENDIF`,
|
|
26
|
-
"multisig": `OP_2 <pubkey1> <pubkey2> <pubkey3> OP_3 OP_CHECKMULTISIG`
|
|
27
|
-
};
|
package/src/silver/types.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
export interface SilverClaims {
|
|
2
|
-
realSilverCompiler: boolean;
|
|
3
|
-
vmConsensusEquivalence: boolean;
|
|
4
|
-
mainnetReady: boolean;
|
|
5
|
-
productionSafe: boolean;
|
|
6
|
-
simulatedOnly: boolean;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface SilverBuildResult {
|
|
10
|
-
source: string;
|
|
11
|
-
bytecode: string;
|
|
12
|
-
claims: SilverClaims;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface SilverSimulationResult {
|
|
16
|
-
success: boolean;
|
|
17
|
-
executionTrace: string[];
|
|
18
|
-
gasConsumed: number;
|
|
19
|
-
claims: SilverClaims;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface SilverArtifact {
|
|
23
|
-
id: string;
|
|
24
|
-
name?: string;
|
|
25
|
-
source: string;
|
|
26
|
-
bytecode: string;
|
|
27
|
-
createdAt: string;
|
|
28
|
-
claims: SilverClaims;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface SilverEvidence {
|
|
32
|
-
schema: string;
|
|
33
|
-
artifactId: string;
|
|
34
|
-
simulationResult: Omit<SilverSimulationResult, 'claims'>;
|
|
35
|
-
timestamp: string;
|
|
36
|
-
claims: SilverClaims;
|
|
37
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { SnapshotBackend, SnapshotManifest, SnapshotState } from "../types.js";
|
|
2
|
-
import fs from "fs-extra";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import crypto from "crypto";
|
|
5
|
-
import { snapshotReplacer, snapshotReviver } from "../serialization.js";
|
|
6
|
-
|
|
7
|
-
export class FsSnapshotBackend implements SnapshotBackend {
|
|
8
|
-
private baseDir: string;
|
|
9
|
-
|
|
10
|
-
constructor(baseDir: string = ".hardkas-snapshots") {
|
|
11
|
-
this.baseDir = baseDir;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async save(id: string, name: string, state: Record<string, SnapshotState>): Promise<SnapshotManifest> {
|
|
15
|
-
const snapshotDir = path.join(this.baseDir, id);
|
|
16
|
-
await fs.ensureDir(snapshotDir);
|
|
17
|
-
|
|
18
|
-
const stateHashes: Record<string, string> = {};
|
|
19
|
-
const participants = Object.keys(state);
|
|
20
|
-
|
|
21
|
-
// Write each participant's state to a separate file
|
|
22
|
-
for (const [pId, pState] of Object.entries(state)) {
|
|
23
|
-
const data = JSON.stringify(pState, snapshotReplacer, 2);
|
|
24
|
-
stateHashes[pId] = crypto.createHash("sha256").update(data).digest("hex");
|
|
25
|
-
await fs.writeFile(path.join(snapshotDir, `${pId}.json`), data);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const manifest: SnapshotManifest = {
|
|
29
|
-
schema: "hardkas.snapshot.v1",
|
|
30
|
-
snapshotId: id,
|
|
31
|
-
name,
|
|
32
|
-
createdAt: Date.now(),
|
|
33
|
-
backend: "filesystem",
|
|
34
|
-
path: snapshotDir,
|
|
35
|
-
participants,
|
|
36
|
-
stateHashes,
|
|
37
|
-
claims: {
|
|
38
|
-
containsFullStateInline: false,
|
|
39
|
-
restorableLocalState: true,
|
|
40
|
-
networkStateCaptured: false
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// Write manifest
|
|
45
|
-
await fs.writeFile(path.join(snapshotDir, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
46
|
-
|
|
47
|
-
return manifest;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async load(id: string): Promise<{ manifest: SnapshotManifest; state: Record<string, SnapshotState> }> {
|
|
51
|
-
const snapshotDir = path.join(this.baseDir, id);
|
|
52
|
-
if (!await fs.pathExists(snapshotDir)) {
|
|
53
|
-
throw new Error(`Snapshot ${id} not found in filesystem backend at ${snapshotDir}`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const manifestData = await fs.readFile(path.join(snapshotDir, "manifest.json"), "utf8");
|
|
57
|
-
const manifest: SnapshotManifest = JSON.parse(manifestData);
|
|
58
|
-
|
|
59
|
-
const state: Record<string, SnapshotState> = {};
|
|
60
|
-
for (const pId of manifest.participants) {
|
|
61
|
-
const stateData = await fs.readFile(path.join(snapshotDir, `${pId}.json`), "utf8");
|
|
62
|
-
state[pId] = JSON.parse(stateData, snapshotReviver);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return { manifest, state };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async list(): Promise<SnapshotManifest[]> {
|
|
69
|
-
if (!await fs.pathExists(this.baseDir)) {
|
|
70
|
-
return [];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const entries = await fs.readdir(this.baseDir, { withFileTypes: true });
|
|
74
|
-
const manifests: SnapshotManifest[] = [];
|
|
75
|
-
|
|
76
|
-
for (const entry of entries) {
|
|
77
|
-
if (entry.isDirectory()) {
|
|
78
|
-
const manifestPath = path.join(this.baseDir, entry.name, "manifest.json");
|
|
79
|
-
if (await fs.pathExists(manifestPath)) {
|
|
80
|
-
const data = await fs.readFile(manifestPath, "utf8");
|
|
81
|
-
manifests.push(JSON.parse(data));
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return manifests;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async delete(id: string): Promise<void> {
|
|
90
|
-
const snapshotDir = path.join(this.baseDir, id);
|
|
91
|
-
if (await fs.pathExists(snapshotDir)) {
|
|
92
|
-
await fs.remove(snapshotDir);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async branch(baseId: string, newId: string, newName: string): Promise<SnapshotManifest> {
|
|
97
|
-
const { state } = await this.load(baseId);
|
|
98
|
-
return this.save(newId, newName, state);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { SnapshotBackend, SnapshotManifest, SnapshotState } from "../types.js";
|
|
2
|
-
import crypto from "crypto";
|
|
3
|
-
import { snapshotReplacer, snapshotReviver } from "../serialization.js";
|
|
4
|
-
|
|
5
|
-
export class MemorySnapshotBackend implements SnapshotBackend {
|
|
6
|
-
private snapshots = new Map<string, { manifest: SnapshotManifest; state: Record<string, SnapshotState> }>();
|
|
7
|
-
|
|
8
|
-
async save(id: string, name: string, state: Record<string, SnapshotState>): Promise<SnapshotManifest> {
|
|
9
|
-
const stateHashes: Record<string, string> = {};
|
|
10
|
-
const participants = Object.keys(state);
|
|
11
|
-
|
|
12
|
-
for (const [pId, pState] of Object.entries(state)) {
|
|
13
|
-
stateHashes[pId] = crypto.createHash("sha256").update(JSON.stringify(pState, snapshotReplacer)).digest("hex");
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const manifest: SnapshotManifest = {
|
|
17
|
-
schema: "hardkas.snapshot.v1",
|
|
18
|
-
snapshotId: id,
|
|
19
|
-
name,
|
|
20
|
-
createdAt: Date.now(),
|
|
21
|
-
backend: "memory",
|
|
22
|
-
participants,
|
|
23
|
-
stateHashes,
|
|
24
|
-
claims: {
|
|
25
|
-
containsFullStateInline: true,
|
|
26
|
-
restorableLocalState: true,
|
|
27
|
-
networkStateCaptured: false
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// Deep copy state to ensure isolation
|
|
32
|
-
const deepCopiedState = JSON.parse(JSON.stringify(state, snapshotReplacer), snapshotReviver);
|
|
33
|
-
this.snapshots.set(id, { manifest, state: deepCopiedState });
|
|
34
|
-
|
|
35
|
-
return manifest;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async load(id: string): Promise<{ manifest: SnapshotManifest; state: Record<string, SnapshotState> }> {
|
|
39
|
-
const entry = this.snapshots.get(id);
|
|
40
|
-
if (!entry) {
|
|
41
|
-
throw new Error(`Snapshot ${id} not found in memory backend`);
|
|
42
|
-
}
|
|
43
|
-
// Return a deep copy to prevent mutation of the snapshot itself
|
|
44
|
-
return {
|
|
45
|
-
manifest: JSON.parse(JSON.stringify(entry.manifest)),
|
|
46
|
-
state: JSON.parse(JSON.stringify(entry.state, snapshotReplacer), snapshotReviver)
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async list(): Promise<SnapshotManifest[]> {
|
|
51
|
-
return Array.from(this.snapshots.values()).map(s => s.manifest);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async delete(id: string): Promise<void> {
|
|
55
|
-
this.snapshots.delete(id);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async branch(baseId: string, newId: string, newName: string): Promise<SnapshotManifest> {
|
|
59
|
-
const entry = this.snapshots.get(baseId);
|
|
60
|
-
if (!entry) throw new Error(`Base snapshot ${baseId} not found`);
|
|
61
|
-
|
|
62
|
-
const newState = JSON.parse(JSON.stringify(entry.state, snapshotReplacer), snapshotReviver);
|
|
63
|
-
return this.save(newId, newName, newState);
|
|
64
|
-
}
|
|
65
|
-
}
|
package/src/snapshot/index.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { SnapshotBackend, SnapshotManifest, SnapshotParticipant, SnapshotState } from "./types.js";
|
|
2
|
-
import { MemorySnapshotBackend } from "./backends/MemorySnapshotBackend.js";
|
|
3
|
-
import { FsSnapshotBackend } from "./backends/FsSnapshotBackend.js";
|
|
4
|
-
import crypto from "crypto";
|
|
5
|
-
|
|
6
|
-
export interface SnapshotToolkitOptions {
|
|
7
|
-
backend?: "memory" | "filesystem";
|
|
8
|
-
dir?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class SnapshotToolkit {
|
|
12
|
-
private backend: SnapshotBackend;
|
|
13
|
-
private participants = new Map<string, SnapshotParticipant>();
|
|
14
|
-
|
|
15
|
-
private constructor(options: SnapshotToolkitOptions) {
|
|
16
|
-
if (options.backend === "filesystem") {
|
|
17
|
-
this.backend = new FsSnapshotBackend(options.dir);
|
|
18
|
-
} else {
|
|
19
|
-
this.backend = new MemorySnapshotBackend();
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
public static open(options: SnapshotToolkitOptions = { backend: "memory" }): SnapshotToolkit {
|
|
24
|
-
return new SnapshotToolkit(options);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public register(id: string, participant: SnapshotParticipant): void {
|
|
28
|
-
this.participants.set(id, participant);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
public async create(name: string): Promise<SnapshotManifest> {
|
|
32
|
-
const id = crypto.randomUUID();
|
|
33
|
-
const state: Record<string, SnapshotState> = {};
|
|
34
|
-
|
|
35
|
-
// Snapshot all participants
|
|
36
|
-
for (const [pId, participant] of this.participants.entries()) {
|
|
37
|
-
state[pId] = await participant.snapshot();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return this.backend.save(id, name, state);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
public async restore(id: string): Promise<void> {
|
|
44
|
-
const { state } = await this.backend.load(id);
|
|
45
|
-
|
|
46
|
-
// 1. load state & restore (but don't reload yet in case something fails)
|
|
47
|
-
for (const [pId, pState] of Object.entries(state)) {
|
|
48
|
-
const participant = this.participants.get(pId);
|
|
49
|
-
if (!participant) {
|
|
50
|
-
throw new Error(`Cannot restore state for participant '${pId}' because it is not registered`);
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
await participant.restore(pState);
|
|
54
|
-
} catch (err: any) {
|
|
55
|
-
throw new Error(`Failed to restore participant '${pId}': ${err.message}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// 2. reload all
|
|
60
|
-
for (const [pId, _] of Object.entries(state)) {
|
|
61
|
-
const participant = this.participants.get(pId)!;
|
|
62
|
-
try {
|
|
63
|
-
await participant.reload();
|
|
64
|
-
} catch (err: any) {
|
|
65
|
-
throw new Error(`Failed to reload participant '${pId}' after restoration: ${err.message}`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
public async branch(baseId: string, newName: string): Promise<SnapshotManifest> {
|
|
71
|
-
const newId = crypto.randomUUID();
|
|
72
|
-
return this.backend.branch(baseId, newId, newName);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
public async diff(idA: string, idB: string): Promise<Record<string, any>> {
|
|
76
|
-
const [a, b] = await Promise.all([this.backend.load(idA), this.backend.load(idB)]);
|
|
77
|
-
|
|
78
|
-
const differences: Record<string, any> = {};
|
|
79
|
-
const allParticipants = new Set([...a.manifest.participants, ...b.manifest.participants]);
|
|
80
|
-
|
|
81
|
-
for (const pId of allParticipants) {
|
|
82
|
-
const hashA = a.manifest.stateHashes[pId];
|
|
83
|
-
const hashB = b.manifest.stateHashes[pId];
|
|
84
|
-
if (hashA !== hashB) {
|
|
85
|
-
differences[pId] = {
|
|
86
|
-
a: a.state[pId] || null,
|
|
87
|
-
b: b.state[pId] || null
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return differences;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
public async compare(idA: string, idB: string): Promise<boolean> {
|
|
95
|
-
const diff = await this.diff(idA, idB);
|
|
96
|
-
return Object.keys(diff).length === 0;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
public async list(): Promise<SnapshotManifest[]> {
|
|
100
|
-
return this.backend.list();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { SnapshotState } from "./types.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Custom JSON Replacer to safely serialize BigInt values.
|
|
5
|
-
*/
|
|
6
|
-
export function snapshotReplacer(key: string, value: any): any {
|
|
7
|
-
if (typeof value === "bigint") {
|
|
8
|
-
return { $bigint: value.toString() };
|
|
9
|
-
}
|
|
10
|
-
return value;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Custom JSON Reviver to safely deserialize BigInt values.
|
|
15
|
-
*/
|
|
16
|
-
export function snapshotReviver(key: string, value: any): any {
|
|
17
|
-
if (value !== null && typeof value === "object" && typeof value.$bigint === "string") {
|
|
18
|
-
return BigInt(value.$bigint);
|
|
19
|
-
}
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Serializes a SnapshotState map to a string safely supporting BigInt.
|
|
25
|
-
*/
|
|
26
|
-
export function serializeState(state: Record<string, SnapshotState>): string {
|
|
27
|
-
return JSON.stringify(state, snapshotReplacer, 2);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Deserializes a SnapshotState map from a string safely supporting BigInt.
|
|
32
|
-
*/
|
|
33
|
-
export function deserializeState(data: string): Record<string, SnapshotState> {
|
|
34
|
-
return JSON.parse(data, snapshotReviver);
|
|
35
|
-
}
|
package/src/snapshot/types.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export interface SnapshotState {
|
|
2
|
-
[key: string]: any;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export interface SnapshotParticipant {
|
|
6
|
-
/**
|
|
7
|
-
* Extracts the current internal state of the toolkit/store
|
|
8
|
-
*/
|
|
9
|
-
snapshot(): Promise<SnapshotState>;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Restores the internal state from the provided snapshot state
|
|
13
|
-
*/
|
|
14
|
-
restore(state: SnapshotState): Promise<void>;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Reloads any caches, queues, or in-memory representations
|
|
18
|
-
* based on the newly restored state
|
|
19
|
-
*/
|
|
20
|
-
reload(): Promise<void>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface SnapshotManifest {
|
|
24
|
-
schema: "hardkas.snapshot.v1";
|
|
25
|
-
snapshotId: string;
|
|
26
|
-
name: string;
|
|
27
|
-
createdAt: number;
|
|
28
|
-
backend: "memory" | "filesystem";
|
|
29
|
-
path?: string;
|
|
30
|
-
participants: string[];
|
|
31
|
-
stateHashes: Record<string, string>;
|
|
32
|
-
claims: {
|
|
33
|
-
containsFullStateInline: boolean;
|
|
34
|
-
restorableLocalState: boolean;
|
|
35
|
-
networkStateCaptured: boolean;
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface SnapshotBackend {
|
|
40
|
-
save(id: string, name: string, state: Record<string, SnapshotState>): Promise<SnapshotManifest>;
|
|
41
|
-
load(id: string): Promise<{ manifest: SnapshotManifest; state: Record<string, SnapshotState> }>;
|
|
42
|
-
list(): Promise<SnapshotManifest[]>;
|
|
43
|
-
delete(id: string): Promise<void>;
|
|
44
|
-
branch(baseId: string, newId: string, newName: string): Promise<SnapshotManifest>;
|
|
45
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { DomainStoreJson } from '@hardkas/query-store';
|
|
2
|
-
|
|
3
|
-
export interface InvoiceRecord {
|
|
4
|
-
id: string;
|
|
5
|
-
merchantId: string;
|
|
6
|
-
amount: bigint;
|
|
7
|
-
currency: string;
|
|
8
|
-
status: 'pending' | 'paid' | 'cancelled';
|
|
9
|
-
uri: string;
|
|
10
|
-
createdAt: string;
|
|
11
|
-
updatedAt: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class InvoiceStoreJson {
|
|
15
|
-
private store: DomainStoreJson<InvoiceRecord>;
|
|
16
|
-
|
|
17
|
-
constructor(options: { filePath: string }) {
|
|
18
|
-
this.store = new DomainStoreJson<InvoiceRecord>(options);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
public save(invoice: InvoiceRecord): void {
|
|
22
|
-
invoice.updatedAt = new Date().toISOString();
|
|
23
|
-
this.store.save(invoice.id, invoice);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
public get(id: string): InvoiceRecord | undefined {
|
|
27
|
-
return this.store.get(id);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
public list(): InvoiceRecord[] {
|
|
31
|
-
return this.store.list();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
public listByMerchant(merchantId: string): InvoiceRecord[] {
|
|
35
|
-
return this.store.list().filter(i => i.merchantId === merchantId);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
public listPaid(): InvoiceRecord[] {
|
|
39
|
-
return this.store.list().filter(i => i.status === 'paid');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
public stats(merchantId?: string) {
|
|
43
|
-
let invoices = this.store.list();
|
|
44
|
-
if (merchantId) {
|
|
45
|
-
invoices = invoices.filter(i => i.merchantId === merchantId);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
totalInvoices: invoices.length,
|
|
50
|
-
paidInvoices: invoices.filter(i => i.status === 'paid').length
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
}
|