billsdk 0.3.2 → 0.4.0
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/client/index.d.ts +7 -1
- package/dist/client/index.js +52 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/react/index.js +52 -2
- package/dist/client/react/index.js.map +1 -1
- package/dist/index.js +299 -4
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/client/index.d.ts
CHANGED
|
@@ -31,7 +31,13 @@ interface FetchOptions {
|
|
|
31
31
|
headers?: Record<string, string>;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* Create a configured fetch function
|
|
34
|
+
* Create a configured fetch function with automatic CSRF token handling.
|
|
35
|
+
*
|
|
36
|
+
* On the first mutating request (POST/PUT/PATCH/DELETE), the client
|
|
37
|
+
* automatically fetches a CSRF token from `GET /csrf-token` and caches it.
|
|
38
|
+
* The token is sent as the `x-billsdk-csrf` header on all subsequent
|
|
39
|
+
* mutating requests. The matching cookie is sent automatically via
|
|
40
|
+
* `credentials: "include"`.
|
|
35
41
|
*/
|
|
36
42
|
declare function createFetch(config?: ClientConfig): <T>(path: string, options?: FetchOptions) => Promise<T>;
|
|
37
43
|
/**
|
package/dist/client/index.js
CHANGED
|
@@ -73,9 +73,38 @@ function computed(sourceAtom, transform) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
// src/client/proxy.ts
|
|
76
|
+
var MUTATING_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
76
77
|
function createFetch(config = {}) {
|
|
77
78
|
const baseFetch = config.fetch ?? fetch;
|
|
78
79
|
const baseURL = config.baseURL ?? "/api/billing";
|
|
80
|
+
const credentials = config.credentials ?? "include";
|
|
81
|
+
let csrfToken = null;
|
|
82
|
+
let csrfPromise = null;
|
|
83
|
+
async function fetchCsrfToken() {
|
|
84
|
+
const response = await baseFetch(`${baseURL}/csrf-token`, {
|
|
85
|
+
method: "GET",
|
|
86
|
+
credentials
|
|
87
|
+
});
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
throw new Error(`Failed to fetch CSRF token: ${response.status}`);
|
|
90
|
+
}
|
|
91
|
+
const data = await response.json();
|
|
92
|
+
return data.csrfToken;
|
|
93
|
+
}
|
|
94
|
+
async function ensureCsrfToken() {
|
|
95
|
+
if (csrfToken) return csrfToken;
|
|
96
|
+
if (!csrfPromise) {
|
|
97
|
+
csrfPromise = fetchCsrfToken().then((token) => {
|
|
98
|
+
csrfToken = token;
|
|
99
|
+
csrfPromise = null;
|
|
100
|
+
return token;
|
|
101
|
+
}).catch((err) => {
|
|
102
|
+
csrfPromise = null;
|
|
103
|
+
throw err;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return csrfPromise;
|
|
107
|
+
}
|
|
79
108
|
return async (path, options = {}) => {
|
|
80
109
|
const method = options.method ?? "GET";
|
|
81
110
|
let url = `${baseURL}${path}`;
|
|
@@ -96,17 +125,38 @@ function createFetch(config = {}) {
|
|
|
96
125
|
...config.headers,
|
|
97
126
|
...options.headers
|
|
98
127
|
};
|
|
128
|
+
if (MUTATING_METHODS.has(method)) {
|
|
129
|
+
const token = await ensureCsrfToken();
|
|
130
|
+
headers["x-billsdk-csrf"] = token;
|
|
131
|
+
}
|
|
99
132
|
const fetchOptions = {
|
|
100
133
|
method,
|
|
101
134
|
headers,
|
|
102
|
-
credentials
|
|
135
|
+
credentials
|
|
103
136
|
};
|
|
104
137
|
if (options.body && ["POST", "PUT", "PATCH"].includes(method)) {
|
|
105
138
|
fetchOptions.body = JSON.stringify(options.body);
|
|
106
139
|
}
|
|
107
140
|
const response = await baseFetch(url, fetchOptions);
|
|
108
141
|
if (!response.ok) {
|
|
109
|
-
const errorData = await response.json().catch(() => ({ error: { message: "Unknown error" } }));
|
|
142
|
+
const errorData = await response.json().catch(() => ({ error: { code: "", message: "Unknown error" } }));
|
|
143
|
+
if (response.status === 403 && MUTATING_METHODS.has(method) && csrfToken && errorData?.error?.code === "INVALID_CSRF_TOKEN") {
|
|
144
|
+
csrfToken = null;
|
|
145
|
+
csrfPromise = null;
|
|
146
|
+
const newToken = await ensureCsrfToken();
|
|
147
|
+
headers["x-billsdk-csrf"] = newToken;
|
|
148
|
+
const retryResponse = await baseFetch(url, {
|
|
149
|
+
...fetchOptions,
|
|
150
|
+
headers
|
|
151
|
+
});
|
|
152
|
+
if (!retryResponse.ok) {
|
|
153
|
+
const retryError = await retryResponse.json().catch(() => ({ error: { message: "Unknown error" } }));
|
|
154
|
+
throw new Error(
|
|
155
|
+
retryError?.error?.message ?? `Request failed: ${retryResponse.status}`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return retryResponse.json();
|
|
159
|
+
}
|
|
110
160
|
throw new Error(
|
|
111
161
|
errorData?.error?.message ?? `Request failed: ${response.status}`
|
|
112
162
|
);
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/atoms.ts","../../src/client/proxy.ts","../../src/client/vanilla.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { AsyncAtom, Atom } from \"./types\";\n\n/**\n * Create a simple atom for reactive state\n */\nexport function atom<T>(initialValue: T): Atom<T> {\n let value = initialValue;\n const listeners = new Set<(value: T) => void>();\n\n return {\n get() {\n return value;\n },\n\n set(newValue: T) {\n value = newValue;\n for (const listener of listeners) {\n listener(value);\n }\n },\n\n subscribe(callback: (value: T) => void) {\n listeners.add(callback);\n // Call immediately with current value\n callback(value);\n // Return unsubscribe function\n return () => {\n listeners.delete(callback);\n };\n },\n };\n}\n\n/**\n * Create an async atom that fetches data\n */\nexport function asyncAtom<T>(\n fetcher: () => Promise<T>,\n options: { autoFetch?: boolean } = {},\n): AsyncAtom<T> {\n const valueAtom = atom<T | null>(null);\n const loadingAtom = atom(false);\n const errorAtom = atom<Error | null>(null);\n\n const refresh = async () => {\n loadingAtom.set(true);\n errorAtom.set(null);\n\n try {\n const data = await fetcher();\n valueAtom.set(data);\n } catch (err) {\n errorAtom.set(err instanceof Error ? err : new Error(String(err)));\n } finally {\n loadingAtom.set(false);\n }\n };\n\n // Auto-fetch on creation if enabled\n if (options.autoFetch !== false) {\n refresh();\n }\n\n return {\n get() {\n return valueAtom.get();\n },\n\n isLoading() {\n return loadingAtom.get();\n },\n\n error() {\n return errorAtom.get();\n },\n\n refresh,\n\n subscribe(callback: (value: T | null) => void) {\n return valueAtom.subscribe(callback);\n },\n };\n}\n\n/**\n * Computed atom that derives from other atoms\n */\nexport function computed<T, R>(\n sourceAtom: Atom<T>,\n transform: (value: T) => R,\n): Atom<R> {\n const computedAtom = atom(transform(sourceAtom.get()));\n\n sourceAtom.subscribe((value) => {\n computedAtom.set(transform(value));\n });\n\n return {\n get() {\n return computedAtom.get();\n },\n set() {\n throw new Error(\"Cannot set a computed atom directly\");\n },\n subscribe: computedAtom.subscribe,\n };\n}\n","import type { ClientConfig } from \"./types\";\n\n/**\n * HTTP methods\n */\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\n/**\n * Fetch options\n */\ninterface FetchOptions {\n method?: HttpMethod;\n body?: unknown;\n query?: Record<string, string | undefined>;\n headers?: Record<string, string>;\n}\n\n/**\n * Create a configured fetch function\n */\nexport function createFetch(config: ClientConfig = {}) {\n const baseFetch = config.fetch ?? fetch;\n const baseURL = config.baseURL ?? \"/api/billing\";\n\n return async <T>(path: string, options: FetchOptions = {}): Promise<T> => {\n const method = options.method ?? \"GET\";\n let url = `${baseURL}${path}`;\n\n // Add query params\n if (options.query) {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(options.query)) {\n if (value !== undefined) {\n params.set(key, value);\n }\n }\n const queryString = params.toString();\n if (queryString) {\n url += `?${queryString}`;\n }\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...config.headers,\n ...options.headers,\n };\n\n const fetchOptions: RequestInit = {\n method,\n headers,\n credentials: config.credentials ?? \"include\",\n };\n\n if (options.body && [\"POST\", \"PUT\", \"PATCH\"].includes(method)) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n const response = await baseFetch(url, fetchOptions);\n\n if (!response.ok) {\n const errorData = (await response\n .json()\n .catch(() => ({ error: { message: \"Unknown error\" } }))) as {\n error?: { message?: string };\n };\n throw new Error(\n errorData?.error?.message ?? `Request failed: ${response.status}`,\n );\n }\n\n return response.json() as Promise<T>;\n };\n}\n\n/**\n * Path segments for the proxy\n */\ntype PathSegment = string;\n\n/**\n * Create a proxy that converts method calls to API requests\n *\n * @example\n * ```typescript\n * const api = createProxy($fetch);\n * await api.customer.get({ query: { externalId: \"123\" } });\n * await api.plans.list();\n * ```\n */\nexport function createProxy<T extends ReturnType<typeof createFetch>>(\n $fetch: T,\n pathMethods: Record<string, HttpMethod> = {},\n): Record<string, unknown> {\n // Default path methods\n const _methods: Record<string, HttpMethod> = {\n \"/customer:GET\": \"GET\",\n \"/customer:POST\": \"POST\",\n \"/plans:GET\": \"GET\",\n \"/plan:GET\": \"GET\",\n \"/subscription:GET\": \"GET\",\n \"/health:GET\": \"GET\",\n ...pathMethods,\n };\n\n function createNestedProxy(segments: PathSegment[]): unknown {\n return new Proxy(() => {}, {\n get(_, prop: string) {\n // Handle special methods\n if (prop === \"then\" || prop === \"catch\" || prop === \"finally\") {\n return undefined;\n }\n\n // Add segment and return nested proxy\n return createNestedProxy([...segments, prop]);\n },\n\n apply(_, __, args: unknown[]) {\n // Convert segments to path\n // e.g., [\"customer\", \"get\"] -> \"/customer\" with GET method\n const lastSegment = segments[segments.length - 1] ?? \"\";\n const pathSegments = segments.slice(0, -1);\n\n // Determine method from last segment or default\n let method: HttpMethod = \"GET\";\n if (lastSegment === \"get\" || lastSegment === \"list\") {\n method = \"GET\";\n } else if (lastSegment === \"create\" || lastSegment === \"post\") {\n method = \"POST\";\n } else if (lastSegment === \"update\" || lastSegment === \"put\") {\n method = \"PUT\";\n } else if (lastSegment === \"patch\") {\n method = \"PATCH\";\n } else if (lastSegment === \"delete\" || lastSegment === \"remove\") {\n method = \"DELETE\";\n } else {\n // If last segment isn't a method, include it in path\n pathSegments.push(lastSegment);\n }\n\n // Build path\n const path =\n \"/\" +\n pathSegments\n .join(\"/\")\n .replace(/([A-Z])/g, \"-$1\")\n .toLowerCase();\n\n // Get options from args\n const options = (args[0] as FetchOptions) ?? {};\n\n return $fetch(path, { ...options, method });\n },\n });\n }\n\n return createNestedProxy([]) as Record<string, unknown>;\n}\n","import type { Customer, Plan } from \"../types/models\";\nimport { asyncAtom, atom } from \"./atoms\";\nimport { createFetch } from \"./proxy\";\nimport type {\n AsyncAtom,\n CancelSubscriptionInput,\n CancelSubscriptionResponse,\n ClientConfig,\n CreateSubscriptionInput,\n CreateSubscriptionResponse,\n HealthResponse,\n SubscriptionResponse,\n} from \"./types\";\n\n/**\n * Billing client interface\n */\nexport interface BillingClient {\n // Reactive atoms\n $customer: AsyncAtom<Customer | null>;\n $subscription: AsyncAtom<SubscriptionResponse | null>;\n $plans: AsyncAtom<Plan[]>;\n\n // API methods\n customer: {\n get(options: {\n query: { externalId: string };\n }): Promise<{ customer: Customer | null }>;\n create(options: {\n body: { externalId: string; email: string; name?: string };\n }): Promise<{ customer: Customer }>;\n };\n\n plans: {\n list(): Promise<{ plans: Plan[] }>;\n };\n\n plan: {\n get(options: {\n query: { id?: string; code?: string };\n }): Promise<{ plan: Plan | null; prices: unknown[] }>;\n };\n\n subscription: {\n get(options: {\n query: { customerId: string };\n }): Promise<SubscriptionResponse>;\n create(options: {\n body: CreateSubscriptionInput;\n }): Promise<CreateSubscriptionResponse>;\n cancel(options: {\n body: CancelSubscriptionInput;\n }): Promise<CancelSubscriptionResponse>;\n };\n\n health: {\n get(): Promise<HealthResponse>;\n };\n\n // Utility methods\n setCustomerId(customerId: string): void;\n refresh(): Promise<void>;\n}\n\n/**\n * Create a billing client\n *\n * @example\n * ```typescript\n * import { createBillingClient } from \"@billsdk/core/client\";\n *\n * // Uses default baseURL: \"/api/billing\"\n * const billing = createBillingClient();\n *\n * // Set current customer\n * billing.setCustomerId(\"user_123\");\n *\n * // Reactive state\n * billing.$customer.subscribe((customer) => {\n * console.log(\"Customer:\", customer);\n * });\n *\n * // Direct API calls\n * const plans = await billing.plans.list();\n * ```\n */\nexport function createBillingClient(config: ClientConfig = {}): BillingClient {\n const $fetch = createFetch(config);\n\n // Current customer ID atom\n const customerIdAtom = atom<string | null>(null);\n\n // Async atoms for reactive state\n const $customer = asyncAtom<Customer | null>(\n async () => {\n const customerId = customerIdAtom.get();\n if (!customerId) return null;\n\n const response = await $fetch<{ customer: Customer | null }>(\n \"/customer\",\n {\n method: \"GET\",\n query: { externalId: customerId },\n },\n );\n return response.customer;\n },\n { autoFetch: false },\n );\n\n const $subscription = asyncAtom<SubscriptionResponse | null>(\n async () => {\n const customerId = customerIdAtom.get();\n if (!customerId) return null;\n\n const response = await $fetch<SubscriptionResponse>(\"/subscription\", {\n method: \"GET\",\n query: { customerId },\n });\n return response;\n },\n { autoFetch: false },\n );\n\n const $plans = asyncAtom<Plan[]>(\n async () => {\n const response = await $fetch<{ plans: Plan[] }>(\"/plans\", {\n method: \"GET\",\n });\n return response.plans;\n },\n { autoFetch: true },\n );\n\n return {\n // Atoms\n $customer,\n $subscription,\n $plans,\n\n // API methods (typed wrapper around proxy)\n customer: {\n async get(options) {\n return $fetch(\"/customer\", { method: \"GET\", query: options.query });\n },\n async create(options) {\n return $fetch(\"/customer\", { method: \"POST\", body: options.body });\n },\n },\n\n plans: {\n async list() {\n return $fetch(\"/plans\", { method: \"GET\" });\n },\n },\n\n plan: {\n async get(options) {\n return $fetch(\"/plan\", { method: \"GET\", query: options.query });\n },\n },\n\n subscription: {\n async get(options) {\n return $fetch(\"/subscription\", { method: \"GET\", query: options.query });\n },\n async create(options) {\n return $fetch(\"/subscription\", { method: \"POST\", body: options.body });\n },\n async cancel(options) {\n return $fetch(\"/subscription/cancel\", {\n method: \"POST\",\n body: options.body,\n });\n },\n },\n\n health: {\n async get() {\n return $fetch(\"/health\", { method: \"GET\" });\n },\n },\n\n // Utility methods\n setCustomerId(customerId: string) {\n customerIdAtom.set(customerId);\n // Refresh customer-dependent atoms\n $customer.refresh();\n $subscription.refresh();\n },\n\n async refresh() {\n await Promise.all([\n $customer.refresh(),\n $subscription.refresh(),\n $plans.refresh(),\n ]);\n },\n };\n}\n\nexport default createBillingClient;\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/client/atoms.ts","../../src/client/proxy.ts","../../src/client/vanilla.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { AsyncAtom, Atom } from \"./types\";\n\n/**\n * Create a simple atom for reactive state\n */\nexport function atom<T>(initialValue: T): Atom<T> {\n let value = initialValue;\n const listeners = new Set<(value: T) => void>();\n\n return {\n get() {\n return value;\n },\n\n set(newValue: T) {\n value = newValue;\n for (const listener of listeners) {\n listener(value);\n }\n },\n\n subscribe(callback: (value: T) => void) {\n listeners.add(callback);\n // Call immediately with current value\n callback(value);\n // Return unsubscribe function\n return () => {\n listeners.delete(callback);\n };\n },\n };\n}\n\n/**\n * Create an async atom that fetches data\n */\nexport function asyncAtom<T>(\n fetcher: () => Promise<T>,\n options: { autoFetch?: boolean } = {},\n): AsyncAtom<T> {\n const valueAtom = atom<T | null>(null);\n const loadingAtom = atom(false);\n const errorAtom = atom<Error | null>(null);\n\n const refresh = async () => {\n loadingAtom.set(true);\n errorAtom.set(null);\n\n try {\n const data = await fetcher();\n valueAtom.set(data);\n } catch (err) {\n errorAtom.set(err instanceof Error ? err : new Error(String(err)));\n } finally {\n loadingAtom.set(false);\n }\n };\n\n // Auto-fetch on creation if enabled\n if (options.autoFetch !== false) {\n refresh();\n }\n\n return {\n get() {\n return valueAtom.get();\n },\n\n isLoading() {\n return loadingAtom.get();\n },\n\n error() {\n return errorAtom.get();\n },\n\n refresh,\n\n subscribe(callback: (value: T | null) => void) {\n return valueAtom.subscribe(callback);\n },\n };\n}\n\n/**\n * Computed atom that derives from other atoms\n */\nexport function computed<T, R>(\n sourceAtom: Atom<T>,\n transform: (value: T) => R,\n): Atom<R> {\n const computedAtom = atom(transform(sourceAtom.get()));\n\n sourceAtom.subscribe((value) => {\n computedAtom.set(transform(value));\n });\n\n return {\n get() {\n return computedAtom.get();\n },\n set() {\n throw new Error(\"Cannot set a computed atom directly\");\n },\n subscribe: computedAtom.subscribe,\n };\n}\n","import type { ClientConfig } from \"./types\";\n\n/**\n * HTTP methods\n */\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\n/**\n * Fetch options\n */\ninterface FetchOptions {\n method?: HttpMethod;\n body?: unknown;\n query?: Record<string, string | undefined>;\n headers?: Record<string, string>;\n}\n\nconst MUTATING_METHODS = new Set([\"POST\", \"PUT\", \"PATCH\", \"DELETE\"]);\n\n/**\n * Create a configured fetch function with automatic CSRF token handling.\n *\n * On the first mutating request (POST/PUT/PATCH/DELETE), the client\n * automatically fetches a CSRF token from `GET /csrf-token` and caches it.\n * The token is sent as the `x-billsdk-csrf` header on all subsequent\n * mutating requests. The matching cookie is sent automatically via\n * `credentials: \"include\"`.\n */\nexport function createFetch(config: ClientConfig = {}) {\n const baseFetch = config.fetch ?? fetch;\n const baseURL = config.baseURL ?? \"/api/billing\";\n const credentials = config.credentials ?? \"include\";\n\n // CSRF token cache (per createFetch instance)\n let csrfToken: string | null = null;\n let csrfPromise: Promise<string> | null = null;\n\n async function fetchCsrfToken(): Promise<string> {\n const response = await baseFetch(`${baseURL}/csrf-token`, {\n method: \"GET\",\n credentials,\n });\n if (!response.ok) {\n throw new Error(`Failed to fetch CSRF token: ${response.status}`);\n }\n const data = (await response.json()) as { csrfToken: string };\n return data.csrfToken;\n }\n\n async function ensureCsrfToken(): Promise<string> {\n if (csrfToken) return csrfToken;\n\n // Deduplicate concurrent requests for the token\n if (!csrfPromise) {\n csrfPromise = fetchCsrfToken()\n .then((token) => {\n csrfToken = token;\n csrfPromise = null;\n return token;\n })\n .catch((err) => {\n // Clear so next request retries instead of returning a rejected promise forever\n csrfPromise = null;\n throw err;\n });\n }\n\n return csrfPromise;\n }\n\n return async <T>(path: string, options: FetchOptions = {}): Promise<T> => {\n const method = options.method ?? \"GET\";\n let url = `${baseURL}${path}`;\n\n // Add query params\n if (options.query) {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(options.query)) {\n if (value !== undefined) {\n params.set(key, value);\n }\n }\n const queryString = params.toString();\n if (queryString) {\n url += `?${queryString}`;\n }\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...config.headers,\n ...options.headers,\n };\n\n // Auto-inject CSRF token for mutating requests\n if (MUTATING_METHODS.has(method)) {\n const token = await ensureCsrfToken();\n headers[\"x-billsdk-csrf\"] = token;\n }\n\n const fetchOptions: RequestInit = {\n method,\n headers,\n credentials,\n };\n\n if (options.body && [\"POST\", \"PUT\", \"PATCH\"].includes(method)) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n const response = await baseFetch(url, fetchOptions);\n\n if (!response.ok) {\n // Parse error body once (body can only be consumed once)\n const errorData = (await response\n .json()\n .catch(() => ({ error: { code: \"\", message: \"Unknown error\" } }))) as {\n error?: { code?: string; message?: string };\n };\n\n // If CSRF token was rejected, clear cache and retry once\n if (\n response.status === 403 &&\n MUTATING_METHODS.has(method) &&\n csrfToken &&\n errorData?.error?.code === \"INVALID_CSRF_TOKEN\"\n ) {\n // Clear token and retry\n csrfToken = null;\n csrfPromise = null;\n const newToken = await ensureCsrfToken();\n headers[\"x-billsdk-csrf\"] = newToken;\n\n const retryResponse = await baseFetch(url, {\n ...fetchOptions,\n headers,\n });\n if (!retryResponse.ok) {\n const retryError = (await retryResponse\n .json()\n .catch(() => ({ error: { message: \"Unknown error\" } }))) as {\n error?: { message?: string };\n };\n throw new Error(\n retryError?.error?.message ??\n `Request failed: ${retryResponse.status}`,\n );\n }\n return retryResponse.json() as Promise<T>;\n }\n\n throw new Error(\n errorData?.error?.message ?? `Request failed: ${response.status}`,\n );\n }\n\n return response.json() as Promise<T>;\n };\n}\n\n/**\n * Path segments for the proxy\n */\ntype PathSegment = string;\n\n/**\n * Create a proxy that converts method calls to API requests\n *\n * @example\n * ```typescript\n * const api = createProxy($fetch);\n * await api.customer.get({ query: { externalId: \"123\" } });\n * await api.plans.list();\n * ```\n */\nexport function createProxy<T extends ReturnType<typeof createFetch>>(\n $fetch: T,\n pathMethods: Record<string, HttpMethod> = {},\n): Record<string, unknown> {\n // Default path methods\n const _methods: Record<string, HttpMethod> = {\n \"/customer:GET\": \"GET\",\n \"/customer:POST\": \"POST\",\n \"/plans:GET\": \"GET\",\n \"/plan:GET\": \"GET\",\n \"/subscription:GET\": \"GET\",\n \"/health:GET\": \"GET\",\n ...pathMethods,\n };\n\n function createNestedProxy(segments: PathSegment[]): unknown {\n return new Proxy(() => {}, {\n get(_, prop: string) {\n // Handle special methods\n if (prop === \"then\" || prop === \"catch\" || prop === \"finally\") {\n return undefined;\n }\n\n // Add segment and return nested proxy\n return createNestedProxy([...segments, prop]);\n },\n\n apply(_, __, args: unknown[]) {\n // Convert segments to path\n // e.g., [\"customer\", \"get\"] -> \"/customer\" with GET method\n const lastSegment = segments[segments.length - 1] ?? \"\";\n const pathSegments = segments.slice(0, -1);\n\n // Determine method from last segment or default\n let method: HttpMethod = \"GET\";\n if (lastSegment === \"get\" || lastSegment === \"list\") {\n method = \"GET\";\n } else if (lastSegment === \"create\" || lastSegment === \"post\") {\n method = \"POST\";\n } else if (lastSegment === \"update\" || lastSegment === \"put\") {\n method = \"PUT\";\n } else if (lastSegment === \"patch\") {\n method = \"PATCH\";\n } else if (lastSegment === \"delete\" || lastSegment === \"remove\") {\n method = \"DELETE\";\n } else {\n // If last segment isn't a method, include it in path\n pathSegments.push(lastSegment);\n }\n\n // Build path\n const path =\n \"/\" +\n pathSegments\n .join(\"/\")\n .replace(/([A-Z])/g, \"-$1\")\n .toLowerCase();\n\n // Get options from args\n const options = (args[0] as FetchOptions) ?? {};\n\n return $fetch(path, { ...options, method });\n },\n });\n }\n\n return createNestedProxy([]) as Record<string, unknown>;\n}\n","import type { Customer, Plan } from \"../types/models\";\nimport { asyncAtom, atom } from \"./atoms\";\nimport { createFetch } from \"./proxy\";\nimport type {\n AsyncAtom,\n CancelSubscriptionInput,\n CancelSubscriptionResponse,\n ClientConfig,\n CreateSubscriptionInput,\n CreateSubscriptionResponse,\n HealthResponse,\n SubscriptionResponse,\n} from \"./types\";\n\n/**\n * Billing client interface\n */\nexport interface BillingClient {\n // Reactive atoms\n $customer: AsyncAtom<Customer | null>;\n $subscription: AsyncAtom<SubscriptionResponse | null>;\n $plans: AsyncAtom<Plan[]>;\n\n // API methods\n customer: {\n get(options: {\n query: { externalId: string };\n }): Promise<{ customer: Customer | null }>;\n create(options: {\n body: { externalId: string; email: string; name?: string };\n }): Promise<{ customer: Customer }>;\n };\n\n plans: {\n list(): Promise<{ plans: Plan[] }>;\n };\n\n plan: {\n get(options: {\n query: { id?: string; code?: string };\n }): Promise<{ plan: Plan | null; prices: unknown[] }>;\n };\n\n subscription: {\n get(options: {\n query: { customerId: string };\n }): Promise<SubscriptionResponse>;\n create(options: {\n body: CreateSubscriptionInput;\n }): Promise<CreateSubscriptionResponse>;\n cancel(options: {\n body: CancelSubscriptionInput;\n }): Promise<CancelSubscriptionResponse>;\n };\n\n health: {\n get(): Promise<HealthResponse>;\n };\n\n // Utility methods\n setCustomerId(customerId: string): void;\n refresh(): Promise<void>;\n}\n\n/**\n * Create a billing client\n *\n * @example\n * ```typescript\n * import { createBillingClient } from \"@billsdk/core/client\";\n *\n * // Uses default baseURL: \"/api/billing\"\n * const billing = createBillingClient();\n *\n * // Set current customer\n * billing.setCustomerId(\"user_123\");\n *\n * // Reactive state\n * billing.$customer.subscribe((customer) => {\n * console.log(\"Customer:\", customer);\n * });\n *\n * // Direct API calls\n * const plans = await billing.plans.list();\n * ```\n */\nexport function createBillingClient(config: ClientConfig = {}): BillingClient {\n const $fetch = createFetch(config);\n\n // Current customer ID atom\n const customerIdAtom = atom<string | null>(null);\n\n // Async atoms for reactive state\n const $customer = asyncAtom<Customer | null>(\n async () => {\n const customerId = customerIdAtom.get();\n if (!customerId) return null;\n\n const response = await $fetch<{ customer: Customer | null }>(\n \"/customer\",\n {\n method: \"GET\",\n query: { externalId: customerId },\n },\n );\n return response.customer;\n },\n { autoFetch: false },\n );\n\n const $subscription = asyncAtom<SubscriptionResponse | null>(\n async () => {\n const customerId = customerIdAtom.get();\n if (!customerId) return null;\n\n const response = await $fetch<SubscriptionResponse>(\"/subscription\", {\n method: \"GET\",\n query: { customerId },\n });\n return response;\n },\n { autoFetch: false },\n );\n\n const $plans = asyncAtom<Plan[]>(\n async () => {\n const response = await $fetch<{ plans: Plan[] }>(\"/plans\", {\n method: \"GET\",\n });\n return response.plans;\n },\n { autoFetch: true },\n );\n\n return {\n // Atoms\n $customer,\n $subscription,\n $plans,\n\n // API methods (typed wrapper around proxy)\n customer: {\n async get(options) {\n return $fetch(\"/customer\", { method: \"GET\", query: options.query });\n },\n async create(options) {\n return $fetch(\"/customer\", { method: \"POST\", body: options.body });\n },\n },\n\n plans: {\n async list() {\n return $fetch(\"/plans\", { method: \"GET\" });\n },\n },\n\n plan: {\n async get(options) {\n return $fetch(\"/plan\", { method: \"GET\", query: options.query });\n },\n },\n\n subscription: {\n async get(options) {\n return $fetch(\"/subscription\", { method: \"GET\", query: options.query });\n },\n async create(options) {\n return $fetch(\"/subscription\", { method: \"POST\", body: options.body });\n },\n async cancel(options) {\n return $fetch(\"/subscription/cancel\", {\n method: \"POST\",\n body: options.body,\n });\n },\n },\n\n health: {\n async get() {\n return $fetch(\"/health\", { method: \"GET\" });\n },\n },\n\n // Utility methods\n setCustomerId(customerId: string) {\n customerIdAtom.set(customerId);\n // Refresh customer-dependent atoms\n $customer.refresh();\n $subscription.refresh();\n },\n\n async refresh() {\n await Promise.all([\n $customer.refresh(),\n $subscription.refresh(),\n $plans.refresh(),\n ]);\n },\n };\n}\n\nexport default createBillingClient;\n"]}
|
|
@@ -60,9 +60,38 @@ function asyncAtom(fetcher, options = {}) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// src/client/proxy.ts
|
|
63
|
+
var MUTATING_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
63
64
|
function createFetch(config = {}) {
|
|
64
65
|
const baseFetch = config.fetch ?? fetch;
|
|
65
66
|
const baseURL = config.baseURL ?? "/api/billing";
|
|
67
|
+
const credentials = config.credentials ?? "include";
|
|
68
|
+
let csrfToken = null;
|
|
69
|
+
let csrfPromise = null;
|
|
70
|
+
async function fetchCsrfToken() {
|
|
71
|
+
const response = await baseFetch(`${baseURL}/csrf-token`, {
|
|
72
|
+
method: "GET",
|
|
73
|
+
credentials
|
|
74
|
+
});
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
throw new Error(`Failed to fetch CSRF token: ${response.status}`);
|
|
77
|
+
}
|
|
78
|
+
const data = await response.json();
|
|
79
|
+
return data.csrfToken;
|
|
80
|
+
}
|
|
81
|
+
async function ensureCsrfToken() {
|
|
82
|
+
if (csrfToken) return csrfToken;
|
|
83
|
+
if (!csrfPromise) {
|
|
84
|
+
csrfPromise = fetchCsrfToken().then((token) => {
|
|
85
|
+
csrfToken = token;
|
|
86
|
+
csrfPromise = null;
|
|
87
|
+
return token;
|
|
88
|
+
}).catch((err) => {
|
|
89
|
+
csrfPromise = null;
|
|
90
|
+
throw err;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return csrfPromise;
|
|
94
|
+
}
|
|
66
95
|
return async (path, options = {}) => {
|
|
67
96
|
const method = options.method ?? "GET";
|
|
68
97
|
let url = `${baseURL}${path}`;
|
|
@@ -83,17 +112,38 @@ function createFetch(config = {}) {
|
|
|
83
112
|
...config.headers,
|
|
84
113
|
...options.headers
|
|
85
114
|
};
|
|
115
|
+
if (MUTATING_METHODS.has(method)) {
|
|
116
|
+
const token = await ensureCsrfToken();
|
|
117
|
+
headers["x-billsdk-csrf"] = token;
|
|
118
|
+
}
|
|
86
119
|
const fetchOptions = {
|
|
87
120
|
method,
|
|
88
121
|
headers,
|
|
89
|
-
credentials
|
|
122
|
+
credentials
|
|
90
123
|
};
|
|
91
124
|
if (options.body && ["POST", "PUT", "PATCH"].includes(method)) {
|
|
92
125
|
fetchOptions.body = JSON.stringify(options.body);
|
|
93
126
|
}
|
|
94
127
|
const response = await baseFetch(url, fetchOptions);
|
|
95
128
|
if (!response.ok) {
|
|
96
|
-
const errorData = await response.json().catch(() => ({ error: { message: "Unknown error" } }));
|
|
129
|
+
const errorData = await response.json().catch(() => ({ error: { code: "", message: "Unknown error" } }));
|
|
130
|
+
if (response.status === 403 && MUTATING_METHODS.has(method) && csrfToken && errorData?.error?.code === "INVALID_CSRF_TOKEN") {
|
|
131
|
+
csrfToken = null;
|
|
132
|
+
csrfPromise = null;
|
|
133
|
+
const newToken = await ensureCsrfToken();
|
|
134
|
+
headers["x-billsdk-csrf"] = newToken;
|
|
135
|
+
const retryResponse = await baseFetch(url, {
|
|
136
|
+
...fetchOptions,
|
|
137
|
+
headers
|
|
138
|
+
});
|
|
139
|
+
if (!retryResponse.ok) {
|
|
140
|
+
const retryError = await retryResponse.json().catch(() => ({ error: { message: "Unknown error" } }));
|
|
141
|
+
throw new Error(
|
|
142
|
+
retryError?.error?.message ?? `Request failed: ${retryResponse.status}`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return retryResponse.json();
|
|
146
|
+
}
|
|
97
147
|
throw new Error(
|
|
98
148
|
errorData?.error?.message ?? `Request failed: ${response.status}`
|
|
99
149
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/client/atoms.ts","../../../src/client/proxy.ts","../../../src/client/react/react-store.ts","../../../src/client/react/index.ts"],"names":["atom"],"mappings":"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","file":"index.js","sourcesContent":["import type { AsyncAtom, Atom } from \"./types\";\n\n/**\n * Create a simple atom for reactive state\n */\nexport function atom<T>(initialValue: T): Atom<T> {\n let value = initialValue;\n const listeners = new Set<(value: T) => void>();\n\n return {\n get() {\n return value;\n },\n\n set(newValue: T) {\n value = newValue;\n for (const listener of listeners) {\n listener(value);\n }\n },\n\n subscribe(callback: (value: T) => void) {\n listeners.add(callback);\n // Call immediately with current value\n callback(value);\n // Return unsubscribe function\n return () => {\n listeners.delete(callback);\n };\n },\n };\n}\n\n/**\n * Create an async atom that fetches data\n */\nexport function asyncAtom<T>(\n fetcher: () => Promise<T>,\n options: { autoFetch?: boolean } = {},\n): AsyncAtom<T> {\n const valueAtom = atom<T | null>(null);\n const loadingAtom = atom(false);\n const errorAtom = atom<Error | null>(null);\n\n const refresh = async () => {\n loadingAtom.set(true);\n errorAtom.set(null);\n\n try {\n const data = await fetcher();\n valueAtom.set(data);\n } catch (err) {\n errorAtom.set(err instanceof Error ? err : new Error(String(err)));\n } finally {\n loadingAtom.set(false);\n }\n };\n\n // Auto-fetch on creation if enabled\n if (options.autoFetch !== false) {\n refresh();\n }\n\n return {\n get() {\n return valueAtom.get();\n },\n\n isLoading() {\n return loadingAtom.get();\n },\n\n error() {\n return errorAtom.get();\n },\n\n refresh,\n\n subscribe(callback: (value: T | null) => void) {\n return valueAtom.subscribe(callback);\n },\n };\n}\n\n/**\n * Computed atom that derives from other atoms\n */\nexport function computed<T, R>(\n sourceAtom: Atom<T>,\n transform: (value: T) => R,\n): Atom<R> {\n const computedAtom = atom(transform(sourceAtom.get()));\n\n sourceAtom.subscribe((value) => {\n computedAtom.set(transform(value));\n });\n\n return {\n get() {\n return computedAtom.get();\n },\n set() {\n throw new Error(\"Cannot set a computed atom directly\");\n },\n subscribe: computedAtom.subscribe,\n };\n}\n","import type { ClientConfig } from \"./types\";\n\n/**\n * HTTP methods\n */\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\n/**\n * Fetch options\n */\ninterface FetchOptions {\n method?: HttpMethod;\n body?: unknown;\n query?: Record<string, string | undefined>;\n headers?: Record<string, string>;\n}\n\n/**\n * Create a configured fetch function\n */\nexport function createFetch(config: ClientConfig = {}) {\n const baseFetch = config.fetch ?? fetch;\n const baseURL = config.baseURL ?? \"/api/billing\";\n\n return async <T>(path: string, options: FetchOptions = {}): Promise<T> => {\n const method = options.method ?? \"GET\";\n let url = `${baseURL}${path}`;\n\n // Add query params\n if (options.query) {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(options.query)) {\n if (value !== undefined) {\n params.set(key, value);\n }\n }\n const queryString = params.toString();\n if (queryString) {\n url += `?${queryString}`;\n }\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...config.headers,\n ...options.headers,\n };\n\n const fetchOptions: RequestInit = {\n method,\n headers,\n credentials: config.credentials ?? \"include\",\n };\n\n if (options.body && [\"POST\", \"PUT\", \"PATCH\"].includes(method)) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n const response = await baseFetch(url, fetchOptions);\n\n if (!response.ok) {\n const errorData = (await response\n .json()\n .catch(() => ({ error: { message: \"Unknown error\" } }))) as {\n error?: { message?: string };\n };\n throw new Error(\n errorData?.error?.message ?? `Request failed: ${response.status}`,\n );\n }\n\n return response.json() as Promise<T>;\n };\n}\n\n/**\n * Path segments for the proxy\n */\ntype PathSegment = string;\n\n/**\n * Create a proxy that converts method calls to API requests\n *\n * @example\n * ```typescript\n * const api = createProxy($fetch);\n * await api.customer.get({ query: { externalId: \"123\" } });\n * await api.plans.list();\n * ```\n */\nexport function createProxy<T extends ReturnType<typeof createFetch>>(\n $fetch: T,\n pathMethods: Record<string, HttpMethod> = {},\n): Record<string, unknown> {\n // Default path methods\n const _methods: Record<string, HttpMethod> = {\n \"/customer:GET\": \"GET\",\n \"/customer:POST\": \"POST\",\n \"/plans:GET\": \"GET\",\n \"/plan:GET\": \"GET\",\n \"/subscription:GET\": \"GET\",\n \"/health:GET\": \"GET\",\n ...pathMethods,\n };\n\n function createNestedProxy(segments: PathSegment[]): unknown {\n return new Proxy(() => {}, {\n get(_, prop: string) {\n // Handle special methods\n if (prop === \"then\" || prop === \"catch\" || prop === \"finally\") {\n return undefined;\n }\n\n // Add segment and return nested proxy\n return createNestedProxy([...segments, prop]);\n },\n\n apply(_, __, args: unknown[]) {\n // Convert segments to path\n // e.g., [\"customer\", \"get\"] -> \"/customer\" with GET method\n const lastSegment = segments[segments.length - 1] ?? \"\";\n const pathSegments = segments.slice(0, -1);\n\n // Determine method from last segment or default\n let method: HttpMethod = \"GET\";\n if (lastSegment === \"get\" || lastSegment === \"list\") {\n method = \"GET\";\n } else if (lastSegment === \"create\" || lastSegment === \"post\") {\n method = \"POST\";\n } else if (lastSegment === \"update\" || lastSegment === \"put\") {\n method = \"PUT\";\n } else if (lastSegment === \"patch\") {\n method = \"PATCH\";\n } else if (lastSegment === \"delete\" || lastSegment === \"remove\") {\n method = \"DELETE\";\n } else {\n // If last segment isn't a method, include it in path\n pathSegments.push(lastSegment);\n }\n\n // Build path\n const path =\n \"/\" +\n pathSegments\n .join(\"/\")\n .replace(/([A-Z])/g, \"-$1\")\n .toLowerCase();\n\n // Get options from args\n const options = (args[0] as FetchOptions) ?? {};\n\n return $fetch(path, { ...options, method });\n },\n });\n }\n\n return createNestedProxy([]) as Record<string, unknown>;\n}\n","import { useCallback, useRef, useSyncExternalStore } from \"react\";\nimport type { AsyncAtom, Atom } from \"../types\";\n\n/**\n * Hook to subscribe to an atom in React\n * Similar to nanostores' useStore\n */\nexport function useStore<T>(atom: Atom<T>): T {\n const snapshotRef = useRef<T>(atom.get());\n\n const subscribe = useCallback(\n (onChange: () => void) => {\n const emitChange = (value: T) => {\n if (snapshotRef.current === value) return;\n snapshotRef.current = value;\n onChange();\n };\n\n // Subscribe to atom changes\n return atom.subscribe(emitChange);\n },\n [atom],\n );\n\n const get = () => snapshotRef.current;\n\n return useSyncExternalStore(subscribe, get, get);\n}\n\n/**\n * Hook to subscribe to an async atom in React\n * Returns { data, isLoading, error, refresh }\n */\nexport function useAsyncStore<T>(atom: AsyncAtom<T>): {\n data: T | null;\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n} {\n const snapshotRef = useRef<T | null>(atom.get());\n\n const subscribe = useCallback(\n (onChange: () => void) => {\n const emitChange = (value: T | null) => {\n if (snapshotRef.current === value) return;\n snapshotRef.current = value;\n onChange();\n };\n\n return atom.subscribe(emitChange);\n },\n [atom],\n );\n\n const get = () => snapshotRef.current;\n\n const data = useSyncExternalStore(subscribe, get, get);\n\n return {\n data,\n isLoading: atom.isLoading(),\n error: atom.error(),\n refresh: atom.refresh,\n };\n}\n","import type { Customer, Plan } from \"../../types/models\";\nimport { asyncAtom, atom } from \"../atoms\";\nimport { createFetch } from \"../proxy\";\nimport type {\n AsyncAtom,\n CancelSubscriptionInput,\n CancelSubscriptionResponse,\n ClientConfig,\n CreateSubscriptionInput,\n CreateSubscriptionResponse,\n HealthResponse,\n SubscriptionResponse,\n} from \"../types\";\nimport { useAsyncStore } from \"./react-store\";\n\n/**\n * React billing client interface\n */\nexport interface BillingClientReact {\n // React hooks (auto-subscribing)\n useCustomer: () => {\n data: Customer | null;\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n };\n useSubscription: () => {\n data: SubscriptionResponse | null;\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n };\n usePlans: () => {\n data: Plan[];\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n };\n\n // Raw atoms (for advanced usage)\n $customer: AsyncAtom<Customer | null>;\n $subscription: AsyncAtom<SubscriptionResponse | null>;\n $plans: AsyncAtom<Plan[]>;\n\n // API methods\n customer: {\n get(options: {\n query: { externalId: string };\n }): Promise<{ customer: Customer | null }>;\n create(options: {\n body: { externalId: string; email: string; name?: string };\n }): Promise<{ customer: Customer }>;\n };\n\n plans: {\n list(): Promise<{ plans: Plan[] }>;\n };\n\n plan: {\n get(options: {\n query: { id?: string; code?: string };\n }): Promise<{ plan: Plan | null; prices: unknown[] }>;\n };\n\n subscription: {\n get(options: {\n query: { customerId: string };\n }): Promise<SubscriptionResponse>;\n create(options: {\n body: CreateSubscriptionInput;\n }): Promise<CreateSubscriptionResponse>;\n cancel(options: {\n body: CancelSubscriptionInput;\n }): Promise<CancelSubscriptionResponse>;\n };\n\n health: {\n get(): Promise<HealthResponse>;\n };\n\n // Utility methods\n setCustomerId(customerId: string): void;\n refresh(): Promise<void>;\n}\n\n/**\n * Create a React billing client\n *\n * @example\n * ```typescript\n * import { createBillingClient } from \"@billsdk/core/react\";\n *\n * // Uses default baseURL: \"/api/billing\"\n * export const billing = createBillingClient();\n *\n * // In a component\n * function Dashboard() {\n * const { data: subscription, isLoading } = billing.useSubscription();\n *\n * if (isLoading) return <Loading />;\n * return <div>Plan: {subscription?.plan?.name}</div>;\n * }\n * ```\n */\nexport function createBillingClient(\n config: ClientConfig = {},\n): BillingClientReact {\n const $fetch = createFetch(config);\n\n // Current customer ID atom\n const customerIdAtom = atom<string | null>(null);\n\n // Async atoms for reactive state\n const $customer = asyncAtom<Customer | null>(\n async () => {\n const customerId = customerIdAtom.get();\n if (!customerId) return null;\n\n const response = await $fetch<{ customer: Customer | null }>(\n \"/customer\",\n {\n method: \"GET\",\n query: { externalId: customerId },\n },\n );\n return response.customer;\n },\n { autoFetch: false },\n );\n\n const $subscription = asyncAtom<SubscriptionResponse | null>(\n async () => {\n const customerId = customerIdAtom.get();\n if (!customerId) return null;\n\n const response = await $fetch<SubscriptionResponse>(\"/subscription\", {\n method: \"GET\",\n query: { customerId },\n });\n return response;\n },\n { autoFetch: false },\n );\n\n const $plans = asyncAtom<Plan[]>(\n async () => {\n const response = await $fetch<{ plans: Plan[] }>(\"/plans\", {\n method: \"GET\",\n });\n return response.plans;\n },\n { autoFetch: true },\n );\n\n return {\n // React hooks\n useCustomer: () => useAsyncStore($customer),\n useSubscription: () => useAsyncStore($subscription),\n usePlans: () =>\n useAsyncStore($plans) as {\n data: Plan[];\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n },\n\n // Raw atoms\n $customer,\n $subscription,\n $plans,\n\n // API methods (typed wrapper around proxy)\n customer: {\n async get(options) {\n return $fetch(\"/customer\", { method: \"GET\", query: options.query });\n },\n async create(options) {\n return $fetch(\"/customer\", { method: \"POST\", body: options.body });\n },\n },\n\n plans: {\n async list() {\n return $fetch(\"/plans\", { method: \"GET\" });\n },\n },\n\n plan: {\n async get(options) {\n return $fetch(\"/plan\", { method: \"GET\", query: options.query });\n },\n },\n\n subscription: {\n async get(options) {\n return $fetch(\"/subscription\", { method: \"GET\", query: options.query });\n },\n async create(options) {\n return $fetch(\"/subscription\", { method: \"POST\", body: options.body });\n },\n async cancel(options) {\n return $fetch(\"/subscription/cancel\", {\n method: \"POST\",\n body: options.body,\n });\n },\n },\n\n health: {\n async get() {\n return $fetch(\"/health\", { method: \"GET\" });\n },\n },\n\n // Utility methods\n setCustomerId(customerId: string) {\n customerIdAtom.set(customerId);\n // Refresh customer-dependent atoms\n $customer.refresh();\n $subscription.refresh();\n },\n\n async refresh() {\n await Promise.all([\n $customer.refresh(),\n $subscription.refresh(),\n $plans.refresh(),\n ]);\n },\n };\n}\n\n// Re-export types\nexport type * from \"../types\";\n// Re-export store hooks for advanced usage\nexport { useAsyncStore, useStore } from \"./react-store\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/client/atoms.ts","../../../src/client/proxy.ts","../../../src/client/react/react-store.ts","../../../src/client/react/index.ts"],"names":["atom"],"mappings":"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","file":"index.js","sourcesContent":["import type { AsyncAtom, Atom } from \"./types\";\n\n/**\n * Create a simple atom for reactive state\n */\nexport function atom<T>(initialValue: T): Atom<T> {\n let value = initialValue;\n const listeners = new Set<(value: T) => void>();\n\n return {\n get() {\n return value;\n },\n\n set(newValue: T) {\n value = newValue;\n for (const listener of listeners) {\n listener(value);\n }\n },\n\n subscribe(callback: (value: T) => void) {\n listeners.add(callback);\n // Call immediately with current value\n callback(value);\n // Return unsubscribe function\n return () => {\n listeners.delete(callback);\n };\n },\n };\n}\n\n/**\n * Create an async atom that fetches data\n */\nexport function asyncAtom<T>(\n fetcher: () => Promise<T>,\n options: { autoFetch?: boolean } = {},\n): AsyncAtom<T> {\n const valueAtom = atom<T | null>(null);\n const loadingAtom = atom(false);\n const errorAtom = atom<Error | null>(null);\n\n const refresh = async () => {\n loadingAtom.set(true);\n errorAtom.set(null);\n\n try {\n const data = await fetcher();\n valueAtom.set(data);\n } catch (err) {\n errorAtom.set(err instanceof Error ? err : new Error(String(err)));\n } finally {\n loadingAtom.set(false);\n }\n };\n\n // Auto-fetch on creation if enabled\n if (options.autoFetch !== false) {\n refresh();\n }\n\n return {\n get() {\n return valueAtom.get();\n },\n\n isLoading() {\n return loadingAtom.get();\n },\n\n error() {\n return errorAtom.get();\n },\n\n refresh,\n\n subscribe(callback: (value: T | null) => void) {\n return valueAtom.subscribe(callback);\n },\n };\n}\n\n/**\n * Computed atom that derives from other atoms\n */\nexport function computed<T, R>(\n sourceAtom: Atom<T>,\n transform: (value: T) => R,\n): Atom<R> {\n const computedAtom = atom(transform(sourceAtom.get()));\n\n sourceAtom.subscribe((value) => {\n computedAtom.set(transform(value));\n });\n\n return {\n get() {\n return computedAtom.get();\n },\n set() {\n throw new Error(\"Cannot set a computed atom directly\");\n },\n subscribe: computedAtom.subscribe,\n };\n}\n","import type { ClientConfig } from \"./types\";\n\n/**\n * HTTP methods\n */\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\n/**\n * Fetch options\n */\ninterface FetchOptions {\n method?: HttpMethod;\n body?: unknown;\n query?: Record<string, string | undefined>;\n headers?: Record<string, string>;\n}\n\nconst MUTATING_METHODS = new Set([\"POST\", \"PUT\", \"PATCH\", \"DELETE\"]);\n\n/**\n * Create a configured fetch function with automatic CSRF token handling.\n *\n * On the first mutating request (POST/PUT/PATCH/DELETE), the client\n * automatically fetches a CSRF token from `GET /csrf-token` and caches it.\n * The token is sent as the `x-billsdk-csrf` header on all subsequent\n * mutating requests. The matching cookie is sent automatically via\n * `credentials: \"include\"`.\n */\nexport function createFetch(config: ClientConfig = {}) {\n const baseFetch = config.fetch ?? fetch;\n const baseURL = config.baseURL ?? \"/api/billing\";\n const credentials = config.credentials ?? \"include\";\n\n // CSRF token cache (per createFetch instance)\n let csrfToken: string | null = null;\n let csrfPromise: Promise<string> | null = null;\n\n async function fetchCsrfToken(): Promise<string> {\n const response = await baseFetch(`${baseURL}/csrf-token`, {\n method: \"GET\",\n credentials,\n });\n if (!response.ok) {\n throw new Error(`Failed to fetch CSRF token: ${response.status}`);\n }\n const data = (await response.json()) as { csrfToken: string };\n return data.csrfToken;\n }\n\n async function ensureCsrfToken(): Promise<string> {\n if (csrfToken) return csrfToken;\n\n // Deduplicate concurrent requests for the token\n if (!csrfPromise) {\n csrfPromise = fetchCsrfToken()\n .then((token) => {\n csrfToken = token;\n csrfPromise = null;\n return token;\n })\n .catch((err) => {\n // Clear so next request retries instead of returning a rejected promise forever\n csrfPromise = null;\n throw err;\n });\n }\n\n return csrfPromise;\n }\n\n return async <T>(path: string, options: FetchOptions = {}): Promise<T> => {\n const method = options.method ?? \"GET\";\n let url = `${baseURL}${path}`;\n\n // Add query params\n if (options.query) {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(options.query)) {\n if (value !== undefined) {\n params.set(key, value);\n }\n }\n const queryString = params.toString();\n if (queryString) {\n url += `?${queryString}`;\n }\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...config.headers,\n ...options.headers,\n };\n\n // Auto-inject CSRF token for mutating requests\n if (MUTATING_METHODS.has(method)) {\n const token = await ensureCsrfToken();\n headers[\"x-billsdk-csrf\"] = token;\n }\n\n const fetchOptions: RequestInit = {\n method,\n headers,\n credentials,\n };\n\n if (options.body && [\"POST\", \"PUT\", \"PATCH\"].includes(method)) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n const response = await baseFetch(url, fetchOptions);\n\n if (!response.ok) {\n // Parse error body once (body can only be consumed once)\n const errorData = (await response\n .json()\n .catch(() => ({ error: { code: \"\", message: \"Unknown error\" } }))) as {\n error?: { code?: string; message?: string };\n };\n\n // If CSRF token was rejected, clear cache and retry once\n if (\n response.status === 403 &&\n MUTATING_METHODS.has(method) &&\n csrfToken &&\n errorData?.error?.code === \"INVALID_CSRF_TOKEN\"\n ) {\n // Clear token and retry\n csrfToken = null;\n csrfPromise = null;\n const newToken = await ensureCsrfToken();\n headers[\"x-billsdk-csrf\"] = newToken;\n\n const retryResponse = await baseFetch(url, {\n ...fetchOptions,\n headers,\n });\n if (!retryResponse.ok) {\n const retryError = (await retryResponse\n .json()\n .catch(() => ({ error: { message: \"Unknown error\" } }))) as {\n error?: { message?: string };\n };\n throw new Error(\n retryError?.error?.message ??\n `Request failed: ${retryResponse.status}`,\n );\n }\n return retryResponse.json() as Promise<T>;\n }\n\n throw new Error(\n errorData?.error?.message ?? `Request failed: ${response.status}`,\n );\n }\n\n return response.json() as Promise<T>;\n };\n}\n\n/**\n * Path segments for the proxy\n */\ntype PathSegment = string;\n\n/**\n * Create a proxy that converts method calls to API requests\n *\n * @example\n * ```typescript\n * const api = createProxy($fetch);\n * await api.customer.get({ query: { externalId: \"123\" } });\n * await api.plans.list();\n * ```\n */\nexport function createProxy<T extends ReturnType<typeof createFetch>>(\n $fetch: T,\n pathMethods: Record<string, HttpMethod> = {},\n): Record<string, unknown> {\n // Default path methods\n const _methods: Record<string, HttpMethod> = {\n \"/customer:GET\": \"GET\",\n \"/customer:POST\": \"POST\",\n \"/plans:GET\": \"GET\",\n \"/plan:GET\": \"GET\",\n \"/subscription:GET\": \"GET\",\n \"/health:GET\": \"GET\",\n ...pathMethods,\n };\n\n function createNestedProxy(segments: PathSegment[]): unknown {\n return new Proxy(() => {}, {\n get(_, prop: string) {\n // Handle special methods\n if (prop === \"then\" || prop === \"catch\" || prop === \"finally\") {\n return undefined;\n }\n\n // Add segment and return nested proxy\n return createNestedProxy([...segments, prop]);\n },\n\n apply(_, __, args: unknown[]) {\n // Convert segments to path\n // e.g., [\"customer\", \"get\"] -> \"/customer\" with GET method\n const lastSegment = segments[segments.length - 1] ?? \"\";\n const pathSegments = segments.slice(0, -1);\n\n // Determine method from last segment or default\n let method: HttpMethod = \"GET\";\n if (lastSegment === \"get\" || lastSegment === \"list\") {\n method = \"GET\";\n } else if (lastSegment === \"create\" || lastSegment === \"post\") {\n method = \"POST\";\n } else if (lastSegment === \"update\" || lastSegment === \"put\") {\n method = \"PUT\";\n } else if (lastSegment === \"patch\") {\n method = \"PATCH\";\n } else if (lastSegment === \"delete\" || lastSegment === \"remove\") {\n method = \"DELETE\";\n } else {\n // If last segment isn't a method, include it in path\n pathSegments.push(lastSegment);\n }\n\n // Build path\n const path =\n \"/\" +\n pathSegments\n .join(\"/\")\n .replace(/([A-Z])/g, \"-$1\")\n .toLowerCase();\n\n // Get options from args\n const options = (args[0] as FetchOptions) ?? {};\n\n return $fetch(path, { ...options, method });\n },\n });\n }\n\n return createNestedProxy([]) as Record<string, unknown>;\n}\n","import { useCallback, useRef, useSyncExternalStore } from \"react\";\nimport type { AsyncAtom, Atom } from \"../types\";\n\n/**\n * Hook to subscribe to an atom in React\n * Similar to nanostores' useStore\n */\nexport function useStore<T>(atom: Atom<T>): T {\n const snapshotRef = useRef<T>(atom.get());\n\n const subscribe = useCallback(\n (onChange: () => void) => {\n const emitChange = (value: T) => {\n if (snapshotRef.current === value) return;\n snapshotRef.current = value;\n onChange();\n };\n\n // Subscribe to atom changes\n return atom.subscribe(emitChange);\n },\n [atom],\n );\n\n const get = () => snapshotRef.current;\n\n return useSyncExternalStore(subscribe, get, get);\n}\n\n/**\n * Hook to subscribe to an async atom in React\n * Returns { data, isLoading, error, refresh }\n */\nexport function useAsyncStore<T>(atom: AsyncAtom<T>): {\n data: T | null;\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n} {\n const snapshotRef = useRef<T | null>(atom.get());\n\n const subscribe = useCallback(\n (onChange: () => void) => {\n const emitChange = (value: T | null) => {\n if (snapshotRef.current === value) return;\n snapshotRef.current = value;\n onChange();\n };\n\n return atom.subscribe(emitChange);\n },\n [atom],\n );\n\n const get = () => snapshotRef.current;\n\n const data = useSyncExternalStore(subscribe, get, get);\n\n return {\n data,\n isLoading: atom.isLoading(),\n error: atom.error(),\n refresh: atom.refresh,\n };\n}\n","import type { Customer, Plan } from \"../../types/models\";\nimport { asyncAtom, atom } from \"../atoms\";\nimport { createFetch } from \"../proxy\";\nimport type {\n AsyncAtom,\n CancelSubscriptionInput,\n CancelSubscriptionResponse,\n ClientConfig,\n CreateSubscriptionInput,\n CreateSubscriptionResponse,\n HealthResponse,\n SubscriptionResponse,\n} from \"../types\";\nimport { useAsyncStore } from \"./react-store\";\n\n/**\n * React billing client interface\n */\nexport interface BillingClientReact {\n // React hooks (auto-subscribing)\n useCustomer: () => {\n data: Customer | null;\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n };\n useSubscription: () => {\n data: SubscriptionResponse | null;\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n };\n usePlans: () => {\n data: Plan[];\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n };\n\n // Raw atoms (for advanced usage)\n $customer: AsyncAtom<Customer | null>;\n $subscription: AsyncAtom<SubscriptionResponse | null>;\n $plans: AsyncAtom<Plan[]>;\n\n // API methods\n customer: {\n get(options: {\n query: { externalId: string };\n }): Promise<{ customer: Customer | null }>;\n create(options: {\n body: { externalId: string; email: string; name?: string };\n }): Promise<{ customer: Customer }>;\n };\n\n plans: {\n list(): Promise<{ plans: Plan[] }>;\n };\n\n plan: {\n get(options: {\n query: { id?: string; code?: string };\n }): Promise<{ plan: Plan | null; prices: unknown[] }>;\n };\n\n subscription: {\n get(options: {\n query: { customerId: string };\n }): Promise<SubscriptionResponse>;\n create(options: {\n body: CreateSubscriptionInput;\n }): Promise<CreateSubscriptionResponse>;\n cancel(options: {\n body: CancelSubscriptionInput;\n }): Promise<CancelSubscriptionResponse>;\n };\n\n health: {\n get(): Promise<HealthResponse>;\n };\n\n // Utility methods\n setCustomerId(customerId: string): void;\n refresh(): Promise<void>;\n}\n\n/**\n * Create a React billing client\n *\n * @example\n * ```typescript\n * import { createBillingClient } from \"@billsdk/core/react\";\n *\n * // Uses default baseURL: \"/api/billing\"\n * export const billing = createBillingClient();\n *\n * // In a component\n * function Dashboard() {\n * const { data: subscription, isLoading } = billing.useSubscription();\n *\n * if (isLoading) return <Loading />;\n * return <div>Plan: {subscription?.plan?.name}</div>;\n * }\n * ```\n */\nexport function createBillingClient(\n config: ClientConfig = {},\n): BillingClientReact {\n const $fetch = createFetch(config);\n\n // Current customer ID atom\n const customerIdAtom = atom<string | null>(null);\n\n // Async atoms for reactive state\n const $customer = asyncAtom<Customer | null>(\n async () => {\n const customerId = customerIdAtom.get();\n if (!customerId) return null;\n\n const response = await $fetch<{ customer: Customer | null }>(\n \"/customer\",\n {\n method: \"GET\",\n query: { externalId: customerId },\n },\n );\n return response.customer;\n },\n { autoFetch: false },\n );\n\n const $subscription = asyncAtom<SubscriptionResponse | null>(\n async () => {\n const customerId = customerIdAtom.get();\n if (!customerId) return null;\n\n const response = await $fetch<SubscriptionResponse>(\"/subscription\", {\n method: \"GET\",\n query: { customerId },\n });\n return response;\n },\n { autoFetch: false },\n );\n\n const $plans = asyncAtom<Plan[]>(\n async () => {\n const response = await $fetch<{ plans: Plan[] }>(\"/plans\", {\n method: \"GET\",\n });\n return response.plans;\n },\n { autoFetch: true },\n );\n\n return {\n // React hooks\n useCustomer: () => useAsyncStore($customer),\n useSubscription: () => useAsyncStore($subscription),\n usePlans: () =>\n useAsyncStore($plans) as {\n data: Plan[];\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n },\n\n // Raw atoms\n $customer,\n $subscription,\n $plans,\n\n // API methods (typed wrapper around proxy)\n customer: {\n async get(options) {\n return $fetch(\"/customer\", { method: \"GET\", query: options.query });\n },\n async create(options) {\n return $fetch(\"/customer\", { method: \"POST\", body: options.body });\n },\n },\n\n plans: {\n async list() {\n return $fetch(\"/plans\", { method: \"GET\" });\n },\n },\n\n plan: {\n async get(options) {\n return $fetch(\"/plan\", { method: \"GET\", query: options.query });\n },\n },\n\n subscription: {\n async get(options) {\n return $fetch(\"/subscription\", { method: \"GET\", query: options.query });\n },\n async create(options) {\n return $fetch(\"/subscription\", { method: \"POST\", body: options.body });\n },\n async cancel(options) {\n return $fetch(\"/subscription/cancel\", {\n method: \"POST\",\n body: options.body,\n });\n },\n },\n\n health: {\n async get() {\n return $fetch(\"/health\", { method: \"GET\" });\n },\n },\n\n // Utility methods\n setCustomerId(customerId: string) {\n customerIdAtom.set(customerId);\n // Refresh customer-dependent atoms\n $customer.refresh();\n $subscription.refresh();\n },\n\n async refresh() {\n await Promise.all([\n $customer.refresh(),\n $subscription.refresh(),\n $plans.refresh(),\n ]);\n },\n };\n}\n\n// Re-export types\nexport type * from \"../types\";\n// Re-export store hooks for advanced usage\nexport { useAsyncStore, useStore } from \"./react-store\";\n"]}
|