@dvmkit/sdk 0.0.0 → 0.1.0-rc.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/NOTICE +2 -0
- package/README.md +38 -2
- package/dist/chunk-27V2ILSR.js +291 -0
- package/dist/chunk-5GFED3GJ.js +955 -0
- package/dist/chunk-6JZIX5WW.js +1155 -0
- package/dist/chunk-7IH5SG2A.js +1038 -0
- package/dist/chunk-AT6V3SY7.js +102 -0
- package/dist/chunk-DCNT4PJS.js +733 -0
- package/dist/chunk-DMNLFNTW.js +135 -0
- package/dist/chunk-FROTD5XQ.js +70 -0
- package/dist/chunk-H25M54MI.js +149 -0
- package/dist/chunk-KQAJVVZT.js +712 -0
- package/dist/chunk-KXWROQGK.js +74 -0
- package/dist/chunk-L4OYF4DQ.js +67 -0
- package/dist/chunk-NTK5DJ6R.js +1256 -0
- package/dist/chunk-RPXHKMYE.js +3808 -0
- package/dist/chunk-S3XAHZQY.js +63 -0
- package/dist/chunk-YG7G4DPZ.js +25 -0
- package/dist/credit-ledger-EDMEZSA2.js +28 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.js +303 -0
- package/dist/job-store-C5n6bhap.d.ts +5090 -0
- package/dist/memory-credit-ledger-7TTZDSRS.js +9 -0
- package/dist/mpp-secret-state-WNAQQ6K4.js +127 -0
- package/dist/mpp-setup-MOBWGTWJ.js +30 -0
- package/dist/postgres-consumed-credential-store-VHBT4KEA.js +72 -0
- package/dist/postgres-job-store-J5F4GUWU.js +7 -0
- package/dist/postgres-kv-store-JFBDP5IP.js +7 -0
- package/dist/postgres-replay-store-UJXRT6VO.js +7 -0
- package/dist/pricing-4CEB34RM.js +48 -0
- package/dist/processed-payment-store-HAA4SFNK.js +11 -0
- package/dist/revenue-reporter-M35KP6V7.js +435 -0
- package/dist/server/index.d.ts +4108 -0
- package/dist/server/index.js +22538 -0
- package/dist/ssrf-BdHsrrIb.d.ts +325 -0
- package/dist/tempo-charge-store-6GJEMNUU.js +130 -0
- package/dist/tempo-session-store-FTEEGZXA.js +467 -0
- package/dist/testing/index.d.ts +135 -0
- package/dist/testing/index.js +151 -0
- package/dist/x402-35VLYFKZ.js +1272 -0
- package/package.json +89 -6
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withSdkInitLock
|
|
3
|
+
} from "./chunk-S3XAHZQY.js";
|
|
4
|
+
|
|
5
|
+
// src/sdk/server/processed-payment-store.ts
|
|
6
|
+
var ProcessedPaymentReplayError = class extends Error {
|
|
7
|
+
constructor(rail, paymentId) {
|
|
8
|
+
super(`payment ${paymentId} on rail ${rail} was already processed`);
|
|
9
|
+
this.rail = rail;
|
|
10
|
+
this.paymentId = paymentId;
|
|
11
|
+
this.name = "ProcessedPaymentReplayError";
|
|
12
|
+
}
|
|
13
|
+
rail;
|
|
14
|
+
paymentId;
|
|
15
|
+
};
|
|
16
|
+
var PostgresProcessedPaymentStore = class {
|
|
17
|
+
constructor(pool) {
|
|
18
|
+
this.pool = pool;
|
|
19
|
+
}
|
|
20
|
+
pool;
|
|
21
|
+
async withTx(fn) {
|
|
22
|
+
const client = await this.pool.connect();
|
|
23
|
+
try {
|
|
24
|
+
await client.query("BEGIN");
|
|
25
|
+
const result = await fn(client);
|
|
26
|
+
await client.query("COMMIT");
|
|
27
|
+
return result;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
try {
|
|
30
|
+
await client.query("ROLLBACK");
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
throw err;
|
|
34
|
+
} finally {
|
|
35
|
+
client.release();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Create the `processed_payments` table if absent. Call once at SDK boot. */
|
|
39
|
+
async init() {
|
|
40
|
+
await withSdkInitLock(this.pool, () => this.createTables());
|
|
41
|
+
}
|
|
42
|
+
/** The boot DDL itself — always runs under {@link withSdkInitLock} (internal-review). */
|
|
43
|
+
async createTables() {
|
|
44
|
+
await this.pool.query(`
|
|
45
|
+
CREATE TABLE IF NOT EXISTS processed_payments (
|
|
46
|
+
dvm_id TEXT NOT NULL,
|
|
47
|
+
rail TEXT NOT NULL,
|
|
48
|
+
payment_id TEXT NOT NULL,
|
|
49
|
+
credit_id TEXT NOT NULL,
|
|
50
|
+
draw_id TEXT NOT NULL,
|
|
51
|
+
job_id TEXT,
|
|
52
|
+
created_at BIGINT NOT NULL,
|
|
53
|
+
PRIMARY KEY (dvm_id, rail, payment_id)
|
|
54
|
+
);
|
|
55
|
+
`);
|
|
56
|
+
await this.pool.query(`UPDATE processed_payments SET rail = 'tempo' WHERE rail = 'mpp'`);
|
|
57
|
+
}
|
|
58
|
+
async record(args) {
|
|
59
|
+
const q = args.tx ?? this.pool;
|
|
60
|
+
try {
|
|
61
|
+
await q.query(
|
|
62
|
+
`INSERT INTO processed_payments (dvm_id, rail, payment_id, credit_id, draw_id, job_id, created_at)
|
|
63
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
64
|
+
[
|
|
65
|
+
args.dvmId,
|
|
66
|
+
args.rail,
|
|
67
|
+
args.paymentId,
|
|
68
|
+
args.creditId,
|
|
69
|
+
args.drawId,
|
|
70
|
+
args.jobId ?? null,
|
|
71
|
+
args.nowMs ?? Date.now()
|
|
72
|
+
]
|
|
73
|
+
);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (isUniqueViolation(err)) {
|
|
76
|
+
throw new ProcessedPaymentReplayError(args.rail, args.paymentId);
|
|
77
|
+
}
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async get(args) {
|
|
82
|
+
const { rows } = await this.pool.query(
|
|
83
|
+
`SELECT * FROM processed_payments WHERE dvm_id = $1 AND rail = $2 AND payment_id = $3`,
|
|
84
|
+
[args.dvmId, args.rail, args.paymentId]
|
|
85
|
+
);
|
|
86
|
+
if (rows.length === 0) return void 0;
|
|
87
|
+
const row = rows[0];
|
|
88
|
+
return {
|
|
89
|
+
dvmId: row.dvm_id,
|
|
90
|
+
rail: row.rail,
|
|
91
|
+
paymentId: row.payment_id,
|
|
92
|
+
creditId: row.credit_id,
|
|
93
|
+
drawId: row.draw_id,
|
|
94
|
+
jobId: row.job_id,
|
|
95
|
+
createdAt: Number(row.created_at)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
var MemoryProcessedPaymentStore = class {
|
|
100
|
+
markers = /* @__PURE__ */ new Map();
|
|
101
|
+
withTx(fn) {
|
|
102
|
+
return fn(void 0);
|
|
103
|
+
}
|
|
104
|
+
record(args) {
|
|
105
|
+
const key = markerKey(args);
|
|
106
|
+
if (this.markers.has(key)) {
|
|
107
|
+
return Promise.reject(new ProcessedPaymentReplayError(args.rail, args.paymentId));
|
|
108
|
+
}
|
|
109
|
+
this.markers.set(key, {
|
|
110
|
+
dvmId: args.dvmId,
|
|
111
|
+
rail: args.rail,
|
|
112
|
+
paymentId: args.paymentId,
|
|
113
|
+
creditId: args.creditId,
|
|
114
|
+
drawId: args.drawId,
|
|
115
|
+
jobId: args.jobId ?? null,
|
|
116
|
+
createdAt: args.nowMs ?? Date.now()
|
|
117
|
+
});
|
|
118
|
+
return Promise.resolve();
|
|
119
|
+
}
|
|
120
|
+
get(args) {
|
|
121
|
+
return Promise.resolve(this.markers.get(markerKey(args)));
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
function markerKey(args) {
|
|
125
|
+
return `${args.dvmId}\0${args.rail}\0${args.paymentId}`;
|
|
126
|
+
}
|
|
127
|
+
function isUniqueViolation(err) {
|
|
128
|
+
return typeof err === "object" && err !== null && err.code === "23505";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export {
|
|
132
|
+
ProcessedPaymentReplayError,
|
|
133
|
+
PostgresProcessedPaymentStore,
|
|
134
|
+
MemoryProcessedPaymentStore
|
|
135
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withSdkInitLock
|
|
3
|
+
} from "./chunk-S3XAHZQY.js";
|
|
4
|
+
|
|
5
|
+
// src/sdk/server/postgres-kv-store.ts
|
|
6
|
+
var PostgresKVStore = class {
|
|
7
|
+
pool;
|
|
8
|
+
constructor(pool) {
|
|
9
|
+
this.pool = pool;
|
|
10
|
+
}
|
|
11
|
+
/** Run the CREATE TABLE migration. Call once at startup. */
|
|
12
|
+
async init() {
|
|
13
|
+
await withSdkInitLock(this.pool, () => this.createTables());
|
|
14
|
+
}
|
|
15
|
+
/** The boot DDL itself — always runs under {@link withSdkInitLock} (internal-review). */
|
|
16
|
+
async createTables() {
|
|
17
|
+
await this.pool.query(`
|
|
18
|
+
-- Single-tenant KV table aligned with the platform's isolate storage schema.
|
|
19
|
+
-- The only structural difference is the absence of dvm_id (SDK is single-DVM-per-DB).
|
|
20
|
+
-- Any column addition should land on both schemas; see M06 / internal-review for the convergence precedent.
|
|
21
|
+
CREATE TABLE IF NOT EXISTS kv_store (
|
|
22
|
+
key TEXT PRIMARY KEY,
|
|
23
|
+
value JSONB NOT NULL,
|
|
24
|
+
expires_at BIGINT
|
|
25
|
+
);
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_kv_store_expires_at ON kv_store (expires_at)
|
|
27
|
+
WHERE expires_at IS NOT NULL;
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
30
|
+
async get(key) {
|
|
31
|
+
const { rows } = await this.pool.query(
|
|
32
|
+
"SELECT value FROM kv_store WHERE key = $1 AND (expires_at IS NULL OR expires_at > $2)",
|
|
33
|
+
[key, Date.now()]
|
|
34
|
+
);
|
|
35
|
+
if (rows.length === 0) return void 0;
|
|
36
|
+
return rows[0].value;
|
|
37
|
+
}
|
|
38
|
+
async set(key, value, opts) {
|
|
39
|
+
const expiresAt = opts?.ttl !== void 0 ? Date.now() + opts.ttl * 1e3 : null;
|
|
40
|
+
await this.pool.query(
|
|
41
|
+
`INSERT INTO kv_store (key, value, expires_at)
|
|
42
|
+
VALUES ($1, $2, $3)
|
|
43
|
+
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, expires_at = EXCLUDED.expires_at`,
|
|
44
|
+
[key, JSON.stringify(value), expiresAt]
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
async delete(key) {
|
|
48
|
+
await this.pool.query("DELETE FROM kv_store WHERE key = $1", [key]);
|
|
49
|
+
}
|
|
50
|
+
async list(prefix) {
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
if (prefix !== void 0) {
|
|
53
|
+
const escaped = prefix.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
54
|
+
const { rows: rows2 } = await this.pool.query(
|
|
55
|
+
"SELECT key FROM kv_store WHERE key LIKE $1 AND (expires_at IS NULL OR expires_at > $2)",
|
|
56
|
+
[`${escaped}%`, now]
|
|
57
|
+
);
|
|
58
|
+
return rows2.map((r) => r.key);
|
|
59
|
+
}
|
|
60
|
+
const { rows } = await this.pool.query(
|
|
61
|
+
"SELECT key FROM kv_store WHERE expires_at IS NULL OR expires_at > $1",
|
|
62
|
+
[now]
|
|
63
|
+
);
|
|
64
|
+
return rows.map((r) => r.key);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
PostgresKVStore
|
|
70
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// src/lib/pg-retry.ts
|
|
2
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3
|
+
var postgresRecovery = new AsyncLocalStorage();
|
|
4
|
+
function withPostgresRecoveryContext(context, operation) {
|
|
5
|
+
return postgresRecovery.run(context, operation);
|
|
6
|
+
}
|
|
7
|
+
function isTransportError(err) {
|
|
8
|
+
if (err === null || typeof err !== "object") return false;
|
|
9
|
+
const code = err.code;
|
|
10
|
+
const msg = err.message;
|
|
11
|
+
if (typeof code === "string" && /^[0-9A-Z]{5}$/.test(code)) return false;
|
|
12
|
+
return typeof msg === "string" && msg.includes("Connection terminated") || code === "ECONNRESET" || code === "ENOTFOUND";
|
|
13
|
+
}
|
|
14
|
+
var POSTGRES_APPLICATION_NAME_MAX_BYTES = 63;
|
|
15
|
+
function formatPostgresApplicationName(label, machineId = process.env.FLY_MACHINE_ID) {
|
|
16
|
+
const machineSuffix = machineId ? `@${machineId}` : "";
|
|
17
|
+
const boundedMachineSuffix = truncateUtf8(machineSuffix, POSTGRES_APPLICATION_NAME_MAX_BYTES);
|
|
18
|
+
const labelBudget = POSTGRES_APPLICATION_NAME_MAX_BYTES - Buffer.byteLength(boundedMachineSuffix, "utf8");
|
|
19
|
+
return `${truncateUtf8(label, labelBudget)}${boundedMachineSuffix}`;
|
|
20
|
+
}
|
|
21
|
+
var patchedPools = /* @__PURE__ */ new WeakSet();
|
|
22
|
+
var guardedClients = /* @__PURE__ */ new WeakSet();
|
|
23
|
+
function applyRetryToPool(pool, label = "pg-pool") {
|
|
24
|
+
if (patchedPools.has(pool)) return;
|
|
25
|
+
patchedPools.add(pool);
|
|
26
|
+
pool.options.application_name = formatPostgresApplicationName(label);
|
|
27
|
+
pool.on("error", (err) => {
|
|
28
|
+
console.error(`[${label}] idle client error:`, err.message);
|
|
29
|
+
});
|
|
30
|
+
const p = pool;
|
|
31
|
+
const origQuery = p.query.bind(pool);
|
|
32
|
+
p.query = (...args) => withTransportRecovery(pool, label, "pool.query", () => origQuery(...args));
|
|
33
|
+
const origConnect = p.connect.bind(pool);
|
|
34
|
+
p.connect = async (...args) => {
|
|
35
|
+
const client = await withTransportRecovery(
|
|
36
|
+
pool,
|
|
37
|
+
label,
|
|
38
|
+
"pool.connect",
|
|
39
|
+
() => origConnect(...args)
|
|
40
|
+
);
|
|
41
|
+
guardCheckedOutClient(client, label);
|
|
42
|
+
return client;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function withPostgresTransactionRecovery(pool, label, operation, callback) {
|
|
46
|
+
return withTransportRecovery(pool, label, operation, async () => {
|
|
47
|
+
const client = await pool.connect();
|
|
48
|
+
let releaseError;
|
|
49
|
+
try {
|
|
50
|
+
await client.query("BEGIN");
|
|
51
|
+
const value = await callback(client);
|
|
52
|
+
await client.query("COMMIT");
|
|
53
|
+
return value;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
await client.query("ROLLBACK").catch(() => void 0);
|
|
56
|
+
if (isTransportError(error)) releaseError = asError(error);
|
|
57
|
+
throw error;
|
|
58
|
+
} finally {
|
|
59
|
+
client.release(releaseError);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async function withTransportRecovery(pool, label, operation, callback) {
|
|
64
|
+
const context = postgresRecovery.getStore();
|
|
65
|
+
const maxAttempts = context?.drainPool ? hostPoolSize(pool) + 1 : 2;
|
|
66
|
+
const startedAt = Date.now();
|
|
67
|
+
let firstError;
|
|
68
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
69
|
+
try {
|
|
70
|
+
const value = await callback();
|
|
71
|
+
if (firstError) {
|
|
72
|
+
logTransportRecovery({
|
|
73
|
+
context,
|
|
74
|
+
label,
|
|
75
|
+
operation,
|
|
76
|
+
attempts: attempt,
|
|
77
|
+
elapsedMs: Date.now() - startedAt,
|
|
78
|
+
outcome: "recovered",
|
|
79
|
+
error: firstError.message
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (!isTransportError(error)) throw error;
|
|
85
|
+
firstError ??= asError(error);
|
|
86
|
+
const elapsedMs = Date.now() - startedAt;
|
|
87
|
+
if (attempt >= maxAttempts || context !== void 0 && Date.now() >= context.deadlineMs) {
|
|
88
|
+
logTransportRecovery({
|
|
89
|
+
context,
|
|
90
|
+
label,
|
|
91
|
+
operation,
|
|
92
|
+
attempts: attempt,
|
|
93
|
+
elapsedMs,
|
|
94
|
+
outcome: "exhausted",
|
|
95
|
+
error: asError(error).message
|
|
96
|
+
});
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function hostPoolSize(pool) {
|
|
103
|
+
const configured = pool.options.max;
|
|
104
|
+
return typeof configured === "number" && Number.isSafeInteger(configured) && configured > 0 ? configured : 10;
|
|
105
|
+
}
|
|
106
|
+
function logTransportRecovery(args) {
|
|
107
|
+
console.warn(
|
|
108
|
+
JSON.stringify({
|
|
109
|
+
level: "postgres_transport_recovery",
|
|
110
|
+
label: args.label,
|
|
111
|
+
route: args.context?.route,
|
|
112
|
+
channel_id: args.context?.channelId,
|
|
113
|
+
operation: args.operation,
|
|
114
|
+
attempts: args.attempts,
|
|
115
|
+
elapsed_ms: args.elapsedMs,
|
|
116
|
+
outcome: args.outcome,
|
|
117
|
+
error: args.error
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
function asError(error) {
|
|
122
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
123
|
+
}
|
|
124
|
+
function guardCheckedOutClient(client, label) {
|
|
125
|
+
if (client === null || typeof client !== "object") return;
|
|
126
|
+
const emitter = client;
|
|
127
|
+
if (typeof emitter.on !== "function" || guardedClients.has(client)) return;
|
|
128
|
+
guardedClients.add(client);
|
|
129
|
+
emitter.on("error", (err) => {
|
|
130
|
+
console.error(`[${label}] checked-out client error:`, err.message);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function truncateUtf8(value, maxBytes) {
|
|
134
|
+
if (maxBytes <= 0) return "";
|
|
135
|
+
if (Buffer.byteLength(value, "utf8") <= maxBytes) return value;
|
|
136
|
+
let result = "";
|
|
137
|
+
for (const character of value) {
|
|
138
|
+
const next = result + character;
|
|
139
|
+
if (Buffer.byteLength(next, "utf8") > maxBytes) break;
|
|
140
|
+
result = next;
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
withPostgresRecoveryContext,
|
|
147
|
+
applyRetryToPool,
|
|
148
|
+
withPostgresTransactionRecovery
|
|
149
|
+
};
|