@subscribe.dev/client
Version:
JavaScript/TypeScript client for SubscribeDev API - A drop-in for AI generation across 100+ models with built-in billing and rate limiting
579 lines (578 loc) • 19.9 kB
JavaScript
import D from "p-queue";
import C from "p-retry";
function U(f) {
return f && f.__esModule && Object.prototype.hasOwnProperty.call(f, "default") ? f.default : f;
}
var P = { exports: {} };
(function(f) {
var e = Object.prototype.hasOwnProperty, t = "~";
function s() {
}
Object.create && (s.prototype = /* @__PURE__ */ Object.create(null), new s().__proto__ || (t = !1));
function c(u, r, i) {
this.fn = u, this.context = r, this.once = i || !1;
}
function o(u, r, i, a, g) {
if (typeof i != "function")
throw new TypeError("The listener must be a function");
var p = new c(i, a || u, g), d = t ? t + r : r;
return u._events[d] ? u._events[d].fn ? u._events[d] = [u._events[d], p] : u._events[d].push(p) : (u._events[d] = p, u._eventsCount++), u;
}
function m(u, r) {
--u._eventsCount === 0 ? u._events = new s() : delete u._events[r];
}
function l() {
this._events = new s(), this._eventsCount = 0;
}
l.prototype.eventNames = function() {
var r = [], i, a;
if (this._eventsCount === 0) return r;
for (a in i = this._events)
e.call(i, a) && r.push(t ? a.slice(1) : a);
return Object.getOwnPropertySymbols ? r.concat(Object.getOwnPropertySymbols(i)) : r;
}, l.prototype.listeners = function(r) {
var i = t ? t + r : r, a = this._events[i];
if (!a) return [];
if (a.fn) return [a.fn];
for (var g = 0, p = a.length, d = new Array(p); g < p; g++)
d[g] = a[g].fn;
return d;
}, l.prototype.listenerCount = function(r) {
var i = t ? t + r : r, a = this._events[i];
return a ? a.fn ? 1 : a.length : 0;
}, l.prototype.emit = function(r, i, a, g, p, d) {
var v = t ? t + r : r;
if (!this._events[v]) return !1;
var n = this._events[v], y = arguments.length, w, h;
if (n.fn) {
switch (n.once && this.removeListener(r, n.fn, void 0, !0), y) {
case 1:
return n.fn.call(n.context), !0;
case 2:
return n.fn.call(n.context, i), !0;
case 3:
return n.fn.call(n.context, i, a), !0;
case 4:
return n.fn.call(n.context, i, a, g), !0;
case 5:
return n.fn.call(n.context, i, a, g, p), !0;
case 6:
return n.fn.call(n.context, i, a, g, p, d), !0;
}
for (h = 1, w = new Array(y - 1); h < y; h++)
w[h - 1] = arguments[h];
n.fn.apply(n.context, w);
} else {
var R = n.length, E;
for (h = 0; h < R; h++)
switch (n[h].once && this.removeListener(r, n[h].fn, void 0, !0), y) {
case 1:
n[h].fn.call(n[h].context);
break;
case 2:
n[h].fn.call(n[h].context, i);
break;
case 3:
n[h].fn.call(n[h].context, i, a);
break;
case 4:
n[h].fn.call(n[h].context, i, a, g);
break;
default:
if (!w) for (E = 1, w = new Array(y - 1); E < y; E++)
w[E - 1] = arguments[E];
n[h].fn.apply(n[h].context, w);
}
}
return !0;
}, l.prototype.on = function(r, i, a) {
return o(this, r, i, a, !1);
}, l.prototype.once = function(r, i, a) {
return o(this, r, i, a, !0);
}, l.prototype.removeListener = function(r, i, a, g) {
var p = t ? t + r : r;
if (!this._events[p]) return this;
if (!i)
return m(this, p), this;
var d = this._events[p];
if (d.fn)
d.fn === i && (!g || d.once) && (!a || d.context === a) && m(this, p);
else {
for (var v = 0, n = [], y = d.length; v < y; v++)
(d[v].fn !== i || g && !d[v].once || a && d[v].context !== a) && n.push(d[v]);
n.length ? this._events[p] = n.length === 1 ? n[0] : n : m(this, p);
}
return this;
}, l.prototype.removeAllListeners = function(r) {
var i;
return r ? (i = t ? t + r : r, this._events[i] && m(this, i)) : (this._events = new s(), this._eventsCount = 0), this;
}, l.prototype.off = l.prototype.removeListener, l.prototype.addListener = l.prototype.on, l.prefixed = t, l.EventEmitter = l, f.exports = l;
})(P);
var k = P.exports;
const q = /* @__PURE__ */ U(k);
class b extends Error {
constructor(e, t, s, c, o) {
super(e), this.name = "SubscribeDevError", this.code = t, this.statusCode = s, this.details = c, this.requestId = o;
}
}
class A extends b {
constructor(e, t, s, c) {
super(e, "INSUFFICIENT_BALANCE", 402, {
remainingCredits: t,
requiredCredits: s
}, c), this.name = "InsufficientBalanceError";
}
}
class x extends b {
constructor(e, t, s, c) {
super(e, "RATE_LIMIT_EXCEEDED", 429, {
resetTime: t,
retryAfter: s
}, c), this.name = "RateLimitError", this.resetTime = t, this.retryAfter = s;
}
}
class O extends b {
constructor(e, t) {
super(e, "AUTHENTICATION_FAILED", 401, void 0, t), this.name = "AuthenticationError";
}
}
class L extends b {
constructor(e, t) {
super(e, "ACCESS_DENIED", 403, void 0, t), this.name = "AccessDeniedError";
}
}
class I extends b {
constructor(e, t) {
super(e, "NOT_FOUND", 404, void 0, t), this.name = "NotFoundError";
}
}
class S extends b {
constructor(e, t, s) {
super(e, "VALIDATION_ERROR", 400, t, s), this.name = "ValidationError";
}
}
class _ extends b {
constructor(e, t) {
super(e, "USER_UNSUBSCRIBED", 402, void 0, t), this.name = "UnsubscribedError";
}
}
class T extends q {
constructor(e) {
if (super(), this.config = {
baseUrl: e.baseUrl || "https://subscribedev.volterapp-dev.com",
timeout: e.timeout || 3e5,
// 5 minutes
maxRetries: e.maxRetries || 2,
debug: e.debug || !1,
...e
}, this.queue = new D({
concurrency: 10,
// Max concurrent requests
interval: 6e4,
// 1 minute
intervalCap: 60
// Max requests per minute
}), this.fetch = globalThis.fetch?.bind(globalThis), !this.fetch)
throw new Error("fetch is not available. Please provide a fetch polyfill.");
this.debug("SubscribeDevClient initialized", { baseUrl: this.config.baseUrl });
}
get pricingUrl() {
return `${this.config.baseUrl}/subscribe`;
}
/**
* Run a prediction (main method matching Replicate.js API)
* Note: Now returns completed predictions directly - no polling needed
*/
async run(e, t) {
const { input: s, response_format: c } = t;
this.debug("Starting prediction", { model: e, input: s, response_format: c });
const o = {
model: e,
input: t.input
};
t.response_format && (o.response_format = t.response_format);
const m = await this.makeRequest("POST", "/v1/run", o);
return this.debug("Prediction completed", {
hasOutput: !!m.output
}), this.emit("usage-changed", { operation: "run", model: e }), m;
}
/**
* Create a new prediction
*/
async createPrediction(e, t) {
return this.queue.add(async () => await C(
async () => {
const s = {
model: e,
input: t.input
};
t.response_format && (s.response_format = t.response_format);
const c = await this.makeRequest("POST", "/v1/run", s);
this.emit("usage-changed", { operation: "createPrediction", model: e });
const o = (/* @__PURE__ */ new Date()).toISOString();
return {
id: `pred_${Math.random().toString(36).substring(2, 15)}`,
created_at: o,
started_at: o,
completed_at: o,
status: "succeeded",
input: t.input,
output: c.output,
urls: {
get: `${this.config.baseUrl}/v1/predictions/pred_${Math.random().toString(36).substring(2, 15)}`
}
};
},
{
retries: this.config.maxRetries,
onFailedAttempt: (s) => {
this.debug("Prediction creation failed, retrying", {
attempt: s.attemptNumber,
error: s.message
});
},
// @ts-ignore: Don't retry on UnsubscribedError
shouldRetry: (s) => !(s instanceof _),
// Reduce retry delay to prevent timeouts
factor: 1.5,
minTimeout: 500,
maxTimeout: 2e3
}
));
}
/**
* Get prediction status
* @deprecated Since predictions now complete synchronously, this is mainly for historical lookups
*/
async getPrediction(e) {
return console.warn("[DEPRECATED] getPrediction() is deprecated. Predictions now complete synchronously in run()."), this.queue.add(async () => {
const t = await this.makeRequest("GET", `/v1/predictions/${e}`);
return this.transformResponse(t);
});
}
/**
* Cancel a prediction
*/
async cancelPrediction(e) {
return this.queue.add(async () => {
const t = await this.makeRequest("POST", `/v1/predictions/${e}/cancel`);
return this.transformResponse(t);
});
}
/**
* Wait for prediction completion with optional progress callback
* @deprecated Since predictions now complete synchronously, this method is no longer needed
*/
async waitForPrediction(e, t) {
console.warn("[DEPRECATED] waitForPrediction() is deprecated. Predictions now complete synchronously in run()."), this.debug("Waiting for prediction completion", { id: e });
let s = 0;
const c = 1800;
for (; s < c; ) {
const o = await this.getPrediction(e);
if (t && t(o), ["succeeded", "failed", "canceled"].includes(o.status))
return this.debug("Prediction completed", { id: e, status: o.status }), o;
const m = Math.min(1e3 + s * 100, 5e3);
await new Promise((l) => setTimeout(l, m)), s++;
}
throw new b(
"Prediction timeout - exceeded maximum wait time",
"PREDICTION_TIMEOUT",
408
);
}
/**
* Get account balance information
*/
async getBalance() {
const t = (await this.makeRequest("GET", "/v1/subscriptions/usage")).credits?.userBalance;
return {
allocatedCredits: t?.allocatedCredits || 0,
usedCredits: t?.usedCredits || 0,
remainingCredits: t?.remainingCredits || 0
};
}
/**
* Get transaction history
*/
async getTransactions(e = {}) {
const t = new URLSearchParams();
e.limit && t.set("limit", e.limit.toString()), e.offset && t.set("offset", e.offset.toString()), e.status && t.set("status", e.status), e.model && t.set("model", e.model), e.startDate && t.set("start_date", e.startDate), e.endDate && t.set("end_date", e.endDate);
const s = `/v1/transactions${t.toString() ? "?" + t.toString() : ""}`;
return this.makeRequest("GET", s);
}
/**
* Get rate limit information
*/
async getRateLimits() {
return this.makeRequest("GET", "/v1/rate-limits");
}
/**
* Get user storage data using auth context
*/
async getStorage(e = {}) {
const t = new URLSearchParams();
e.appVersion && t.set("appVersion", e.appVersion);
const s = `/v1/storage${t.toString() ? "?" + t.toString() : ""}`;
return this.makeRequest("GET", s);
}
/**
* Update user storage data using auth context
*/
async setStorage(e, t = {}) {
const s = new URLSearchParams();
t.appVersion && s.set("appVersion", t.appVersion);
const c = `/v1/storage${s.toString() ? "?" + s.toString() : ""}`;
return this.makeRequest("PUT", c, { sessionData: e });
}
/**
* Delete user storage data using auth context
*/
async deleteStorage(e = {}) {
const t = new URLSearchParams();
e.appVersion && t.set("appVersion", e.appVersion);
const s = `/v1/storage${t.toString() ? "?" + t.toString() : ""}`;
await this.makeRequest("DELETE", s);
}
/**
* Browser convenience method: Persist session data locally and sync to server
* Automatically saves the entire session object to localStorage and server
*/
async persistSessionData(e = {}) {
if (typeof globalThis < "u" && typeof globalThis.localStorage > "u")
throw new Error("persistSessionData is only available in browser environments");
const t = await this.getStorage(e), s = `subscribeDev_session_${this.config.apiKey.slice(-8)}`;
return globalThis.localStorage.setItem(s, JSON.stringify(t.sessionData)), t;
}
/**
* Browser convenience method: Get session data from server and update localStorage
* Always fetches from server to ensure data is synchronized
*/
async getSessionData(e = {}) {
const s = (await this.getStorage(e)).sessionData || {};
if (typeof globalThis < "u" && typeof globalThis.localStorage < "u") {
const c = `subscribeDev_session_${this.config.apiKey.slice(-8)}`;
globalThis.localStorage.setItem(c, JSON.stringify(s));
}
return s;
}
/**
* Get user's subscription status using userKey authentication
* Requires userKey to be provided in client configuration
*/
async getSubscriptionStatus() {
if (console.log("[SubscribeDevClient] getSubscriptionStatus called"), !this.config.userKey)
throw console.log("[SubscribeDevClient] No userKey provided, throwing ValidationError"), new S(
"User key is required to get subscription status. Provide userKey in client configuration.",
{ method: "getSubscriptionStatus" }
);
console.log("[SubscribeDevClient] Making request to /v1/subscriptions/plan with userKey:", this.config.userKey.substring(0, 10) + "...");
try {
const e = await this.makeRequest("GET", "/v1/subscriptions/plan");
console.log("[SubscribeDevClient] Received response:", {
hasUserPlan: !!e.userPlan,
planId: e.userPlan?.planId,
status: e.userPlan?.status,
hasPlanDetails: !!e.userPlan?.plan
});
const t = e.userPlan;
if (!t || t.status === "none" || !t.plan)
return console.log("[SubscribeDevClient] User has no active subscription, returning none status"), {
hasActiveSubscription: !1,
status: t?.status || "none"
};
const s = {
hasActiveSubscription: t.status === "active",
plan: t.plan ? {
id: t.plan.id || t.planId,
name: t.plan.name,
price: t.plan.price,
tokenLimit: t.plan.tokenLimit || t.plan.token_limit,
subtitle: t.plan.subtitle,
description: t.plan.description,
features: t.plan.features
} : void 0,
status: t.status,
startedAt: t.startedAt,
endsAt: t.endsAt
};
return console.log("[SubscribeDevClient] Returning subscription status:", {
hasActiveSubscription: s.hasActiveSubscription,
status: s.status,
planName: s.plan?.name
}), s;
} catch (e) {
throw console.log("[SubscribeDevClient] Error in getSubscriptionStatus:", {
errorType: e.constructor.name,
message: e.message
}), e;
}
}
/**
* Get available subscription plans for the project
*/
async getSubscriptionPlans() {
return this.makeRequest("GET", "/v1/subscriptions/status");
}
/**
* Create a checkout session for subscription payment
* Requires userKey to be provided in client configuration
*/
async createCheckoutSession(e) {
if (!this.config.userKey)
throw new S(
"User key is required to create checkout session. Provide userKey in client configuration.",
{ method: "createCheckoutSession" }
);
return this.makeRequest("POST", "/v1/subscriptions/checkout", e);
}
/**
* Sign up for the free plan
* Requires userKey to be provided in client configuration
*/
async signupForFree(e) {
if (!this.config.userKey)
throw new S(
"User key is required to sign up for free plan. Provide userKey in client configuration.",
{ method: "signupForFree" }
);
return this.makeRequest("POST", "/v1/subscriptions/free-signup", e);
}
/**
* Cancel user subscription
* Requires userKey to be provided in client configuration
*/
async cancelSubscription(e, t = !0) {
if (!this.config.userKey)
throw new S(
"User key is required to cancel subscription. Provide userKey in client configuration.",
{ method: "cancelSubscription" }
);
return this.makeRequest("POST", `/v1/subscriptions/cancel/${e}`, { cancelAtPeriodEnd: t });
}
/**
* Get comprehensive usage limits and current consumption
* Requires userKey to be provided in client configuration
*/
async getUsageLimits() {
if (!this.config.userKey)
throw new S(
"User key is required to get usage limits. Provide userKey in client configuration.",
{ method: "getUsageLimits" }
);
return this.makeRequest("GET", "/v1/subscriptions/usage");
}
/**
* Get comprehensive usage information including credits, rate limits, and consumption
* Alias for getUsageLimits() - Requires userKey to be provided in client configuration
*/
async getUsage() {
return this.getUsageLimits();
}
/**
* Make authenticated HTTP request to SubscribeDev API
*/
async makeRequest(e, t, s) {
const c = `${this.config.baseUrl}${t}`, o = {
Authorization: `Bearer ${this.config.apiKey}`,
"Content-Type": "application/json",
"User-Agent": "@subscribe.dev/client/0.1.0"
};
this.config.userKey && (o["X-User-Token"] = this.config.userKey);
const m = {
method: e,
headers: o,
signal: AbortSignal.timeout(this.config.timeout)
};
s && ["POST", "PUT", "PATCH"].includes(e) && (m.body = JSON.stringify(s)), this.debug("Making request", { method: e, url: c, headers: { ...o, Authorization: "[redacted]" } });
const l = await this.fetch(c, m), u = await l.text();
let r;
try {
r = u ? JSON.parse(u) : null;
} catch {
r = { error: { message: u } };
}
return l.ok || this.handleErrorResponse(l, r), this.debug("Request successful", { status: l.status, data: r }), r;
}
/**
* Handle error responses and throw appropriate error types
*/
handleErrorResponse(e, t) {
const s = e.headers.get("x-request-id") || void 0, c = t?.error || {}, o = c.message || `HTTP ${e.status}`;
switch (e.status) {
case 400:
throw new S(o, c.details, s);
case 401:
throw new O(o, s);
case 402:
throw c.code === "USER_UNSUBSCRIBED" || o.toLowerCase().includes("unsubscribed") || o.toLowerCase().includes("subscription") ? new _(o, s) : new A(
o,
c.details?.remainingCredits,
c.details?.requiredCredits,
s
);
case 403:
throw new L(o, s);
case 404:
throw new I(o, s);
case 429:
throw new x(
o,
c.details?.resetTime,
c.details?.retryAfter,
s
);
default:
throw new b(
o,
c.code || "UNKNOWN_ERROR",
e.status,
c.details,
s
);
}
}
/**
* Transform API response to match Replicate.js format
*/
transformResponse(e) {
return {
id: e.id,
version: e.version,
created_at: e.created_at,
started_at: e.started_at,
completed_at: e.completed_at,
status: e.status,
input: e.input,
output: e.output,
error: e.error,
logs: e.logs,
metrics: e.metrics,
// URLs are no longer included in completed predictions (use output field instead)
urls: e.urls || void 0
};
}
/**
* Debug logging
*/
debug(e, t) {
this.config.debug && console.log(`[SubscribeDevClient] ${e}`, t || "");
}
/**
* Static method to create client (for convenience)
*/
static create(e) {
return new T(e);
}
}
export {
L as AccessDeniedError,
O as AuthenticationError,
A as InsufficientBalanceError,
I as NotFoundError,
x as RateLimitError,
T as SubscribeDevClient,
b as SubscribeDevError,
_ as UnsubscribedError,
S as ValidationError
};
//# sourceMappingURL=index.js.map