feat: add engine and store snapshot/restore for restart resilience

This commit is contained in:
Codex
2026-02-18 12:57:25 +00:00
parent 76f673fe4c
commit 9b3cdbff1a
6 changed files with 117 additions and 15 deletions

View File

@@ -1,8 +1,9 @@
"use strict";
class AudioAccessStore {
constructor() {
this.grants = new Map();
constructor(initialState) {
const entries = Object.entries((initialState && initialState.grants) || {});
this.grants = new Map(entries.map(([audioId, userIds]) => [audioId, new Set(userIds)]));
}
_key(audioId) {
@@ -71,6 +72,15 @@ class AudioAccessStore {
this.grantAccess(audio.id, userId);
return { unlocked: true, charged: audio.creditsCharged };
}
exportState() {
const grants = {};
for (const [audioId, users] of this.grants.entries()) {
grants[audioId] = Array.from(users.values());
}
return { grants };
}
}
module.exports = {

View File

@@ -6,17 +6,21 @@ const { calculateCredits } = require("./credits");
const { extractArticleFromParent } = require("./article");
class XArtAudioEngine {
constructor({ creditConfig }) {
constructor({ creditConfig, initialState }) {
this.creditConfig = creditConfig;
this.wallets = new WalletStore();
this.access = new AudioAccessStore();
this.wallets = new WalletStore(initialState && initialState.wallets);
this.access = new AudioAccessStore(initialState && initialState.access);
this.jobs = new Map();
this.assets = new Map();
this.mentions = new Map();
this.jobs = new Map(Object.entries((initialState && initialState.jobs) || {}));
this.assets = new Map(Object.entries((initialState && initialState.assets) || {}));
this.mentions = new Map(Object.entries((initialState && initialState.mentions) || {}));
this.nextJobId = 1;
this.nextAssetId = 1;
this.nextJobId = Number.isInteger(initialState && initialState.nextJobId)
? initialState.nextJobId
: 1;
this.nextAssetId = Number.isInteger(initialState && initialState.nextAssetId)
? initialState.nextAssetId
: 1;
}
topUpCredits(userId, amount, idempotencyKey) {
@@ -166,6 +170,18 @@ class XArtAudioEngine {
wallet: this.wallets,
});
}
exportState() {
return {
wallets: this.wallets.exportState(),
access: this.access.exportState(),
jobs: Object.fromEntries(this.jobs.entries()),
assets: Object.fromEntries(this.assets.entries()),
mentions: Object.fromEntries(this.mentions.entries()),
nextJobId: this.nextJobId,
nextAssetId: this.nextAssetId,
};
}
}
module.exports = {

View File

@@ -1,11 +1,19 @@
"use strict";
class WalletStore {
constructor() {
this.balances = new Map();
this.transactions = [];
this.byIdempotencyKey = new Map();
this.nextId = 1;
constructor(initialState) {
this.balances = new Map(Object.entries((initialState && initialState.balances) || {}));
this.transactions = Array.isArray(initialState && initialState.transactions)
? [...initialState.transactions]
: [];
this.byIdempotencyKey = new Map(
this.transactions
.filter((tx) => tx.idempotencyKey)
.map((tx) => [tx.idempotencyKey, tx]),
);
this.nextId = Number.isInteger(initialState && initialState.nextId)
? initialState.nextId
: (this.transactions.length + 1);
}
getBalance(userId) {
@@ -63,6 +71,14 @@ class WalletStore {
this.balances.set(userId, newBalance);
return tx;
}
exportState() {
return {
balances: Object.fromEntries(this.balances.entries()),
transactions: [...this.transactions],
nextId: this.nextId,
};
}
}
module.exports = {