herald-sdk 0.5.1 → 0.5.2

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/README.md CHANGED
@@ -55,6 +55,10 @@ console.log('subscription status:', sub.status)
55
55
  const balance = await client.getBalance('realm-id', 'user-id')
56
56
  console.log('balance:', balance.balance)
57
57
 
58
+ // Get a single points transaction by ID
59
+ const tx = await client.getTransaction('realm-id', 'transaction-id')
60
+ console.log('type:', tx.transactionType, 'balanceAfter:', tx.balanceAfter)
61
+
58
62
  // Consume points (idempotent via idempotencyKey)
59
63
  const result = await client.consumePoints(
60
64
  'realm-id',
package/dist/index.cjs CHANGED
@@ -173,6 +173,13 @@ var HeraldClient = class {
173
173
  }
174
174
  });
175
175
  }
176
+ /** Single points transaction detail by transaction ID. */
177
+ getTransaction(realmId, transactionId) {
178
+ return this.requestJson(
179
+ "GET",
180
+ `/api/ext/points/${encodeURIComponent(realmId)}/transactions/${encodeURIComponent(transactionId)}`
181
+ );
182
+ }
176
183
  // --- Realms ---
177
184
  createRealm(request) {
178
185
  return this.requestJson("POST", "/api/ext/realms", { body: request });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Error type for the Herald Node SDK, mirroring the Rust `herald_sdk::Error`\n * variants (`sdk/rust/src/lib.rs`): every non-2xx or transport failure is\n * thrown as a `HeraldSdkError` carrying a stable machine-readable `code`.\n */\n\nexport type HeraldSdkErrorCode =\n /** Fetch-level transport failure (Rust: `Error::Reqwest`). */\n | 'network'\n /** 401 — invalid API key (Rust: `Error::Unauthorized`). */\n | 'unauthorized'\n /** 403 — e.g. cross-realm access or insufficient permission (Rust: `Error::Forbidden`). */\n | 'forbidden'\n /** 404 (Rust: `Error::NotFound`). */\n | 'not-found'\n /** 500 (Rust: `Error::InternalServerError`). */\n | 'internal-server-error'\n /** Any other non-2xx status (Rust: `Error::ApiError`). */\n | 'api-error'\n /** 2xx with a non-JSON body (Rust: `Error::SerdeJson`). */\n | 'parse'\n\nexport class HeraldSdkError extends Error {\n /** Stable machine-readable category; prefer this over string-matching the message. */\n readonly code: HeraldSdkErrorCode\n /** HTTP status when the error came from a response; undefined for network errors. */\n readonly status?: number\n /** Raw response body text when the error came from a response. */\n readonly body?: string\n\n constructor(code: HeraldSdkErrorCode, status: number | undefined, body: string | undefined) {\n const label = status !== undefined ? `${code} (${status})` : code\n super(body ? `Herald SDK error: ${label}: ${body}` : `Herald SDK error: ${label}`)\n this.name = 'HeraldSdkError'\n this.code = code\n this.status = status\n this.body = body\n }\n}\n","/**\n * Herald server SDK client — a 1:1 TypeScript port of the Rust `herald-sdk`\n * crate (`sdk/rust/src/lib.rs`), which is this SDK's source of truth.\n *\n * Auth model: every call carries the realm/service `X-API-Key` header against\n * the external API surface (`/api/ext/*`). `checkPermission` additionally\n * mirrors the Rust caching behaviour exactly:\n *\n * - a TTL cache keyed by the full request (token + clientId + rules);\n * - a token→keys index so `invalidateCache(token)` drops every cached check\n * for that token;\n * - a 300s \"token snapshot is stale\" heuristic: once a token has been seen\n * at least once more than 5 minutes ago, its cached entries are\n * invalidated before the next check (Rust `is_token_expired`).\n *\n * HTTP layer: native `fetch` (Node 18+), hand-rolled — same deliberate choice\n * as the Rust crate (no OpenAPI-generated client), keeping the package at zero\n * runtime dependencies and its types in lockstep with the crate's.\n */\n\nimport { HeraldSdkError } from './errors'\nimport type {\n ClientAppInfo,\n ClientAppItem,\n ConsumePointsResponse,\n CreateClientAppSdkRequest,\n CreateRealmSdkRequest,\n CreateUserSdkRequest,\n GrantPointsResponse,\n PermissionCheckRequest,\n PermissionCheckResponse,\n PointsBalanceResponse,\n RealmInfo,\n RealmItem,\n SubscriptionDetail,\n UserInfo,\n} from './types'\n\n/** Rust `is_token_expired` threshold: 5 minutes, independent of the cache TTL. */\nconst TOKEN_EXPIRY_THRESHOLD_MS = 300_000\n\ninterface CacheEntry {\n response: PermissionCheckResponse\n /** Lazy TTL: `Date.now()` after which the entry is treated as evicted. */\n expiresAtMs: number\n}\n\n/**\n * Cache key for a permission check. `rules: undefined` and `rules: []` are\n * DIFFERENT keys (Rust: `Option<Vec<Rule>>` participates in `Hash`/`Eq`), and\n * rule order is significant — `JSON.stringify` preserves both distinctions.\n */\nfunction permissionCacheKey(req: PermissionCheckRequest): string {\n return JSON.stringify({ accessToken: req.accessToken, clientId: req.clientId, rules: req.rules })\n}\n\n/** Map a `fetch` response onto the Rust `handle_response` semantics. */\nasync function handleResponse<T>(response: Response): Promise<T> {\n const text = await response.text()\n const status = response.status\n if (status === 401) throw new HeraldSdkError('unauthorized', status, text)\n if (status === 403) throw new HeraldSdkError('forbidden', status, text)\n if (status === 404) throw new HeraldSdkError('not-found', status, text)\n if (status === 500) throw new HeraldSdkError('internal-server-error', status, text)\n if (status >= 200 && status < 300) {\n try {\n return JSON.parse(text) as T\n } catch (cause) {\n throw new HeraldSdkError('parse', status, `invalid JSON body: ${String(cause)}`)\n }\n }\n throw new HeraldSdkError('api-error', status, text)\n}\n\ninterface RequestOptions {\n query?: Record<string, string>\n body?: unknown\n}\n\nexport class HeraldClient {\n private readonly baseUrl: string\n private readonly apiKey: string\n private readonly cacheTtlMs: number\n private readonly permissionCache = new Map<string, CacheEntry>()\n /** token → cache keys for its checks (Rust `token_index: DashMap`). */\n private readonly tokenIndex = new Map<string, Set<string>>()\n /** token → last successful check timestamp (Rust `token_cache`; the Rust\n * tuple also stored the response, but only the timestamp is ever read). */\n private readonly tokenLastSeen = new Map<string, number>()\n\n constructor(baseUrl: string, apiKey: string, cacheTtlSeconds?: number) {\n this.baseUrl = baseUrl.replace(/\\/+$/, '')\n this.apiKey = apiKey\n this.cacheTtlMs = (cacheTtlSeconds ?? 300) * 1000\n }\n\n private async requestJson<T>(method: 'GET' | 'POST', path: string, options: RequestOptions = {}): Promise<T> {\n let url = `${this.baseUrl}${path}`\n if (options.query) {\n const search = new URLSearchParams(options.query).toString()\n if (search) url += `?${search}`\n }\n const headers: Record<string, string> = { 'X-API-Key': this.apiKey }\n if (options.body !== undefined) headers['Content-Type'] = 'application/json'\n\n let response: Response\n try {\n response = await fetch(url, {\n method,\n headers,\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n })\n } catch (cause) {\n throw new HeraldSdkError('network', undefined, String(cause))\n }\n return handleResponse<T>(response)\n }\n\n // --- Permission check (with cache) ---\n\n /**\n * Check whether a user (identified by their browser access token) is allowed\n * an action. Results are cached per exact request for the client's TTL;\n * a token not checked for over 5 minutes has its cache invalidated first.\n */\n async checkPermission(req: PermissionCheckRequest): Promise<PermissionCheckResponse> {\n if (this.isTokenExpired(req.accessToken)) {\n this.invalidateCache(req.accessToken)\n }\n\n const cached = this.getCached(req)\n if (cached) return cached\n\n const response = await this.requestJson<PermissionCheckResponse>('POST', '/api/ext/permission/check', {\n body: req,\n })\n\n const now = Date.now()\n this.tokenLastSeen.set(req.accessToken, now)\n const key = permissionCacheKey(req)\n let keys = this.tokenIndex.get(req.accessToken)\n if (!keys) {\n keys = new Set()\n this.tokenIndex.set(req.accessToken, keys)\n }\n keys.add(key)\n this.permissionCache.set(key, { response, expiresAtMs: now + this.cacheTtlMs })\n return response\n }\n\n /** Rust `is_token_expired`: seen before, and more than 5 minutes ago. */\n private isTokenExpired(token: string): boolean {\n const at = this.tokenLastSeen.get(token)\n return at !== undefined && Date.now() - at > TOKEN_EXPIRY_THRESHOLD_MS\n }\n\n /** Cached response for an exact request, lazily evicting expired entries\n * (the Map counterpart of moka's TTL + eviction listener). */\n private getCached(req: PermissionCheckRequest): PermissionCheckResponse | undefined {\n const key = permissionCacheKey(req)\n const entry = this.permissionCache.get(key)\n if (!entry) return undefined\n if (Date.now() > entry.expiresAtMs) {\n this.permissionCache.delete(key)\n this.dropIndexEntry(req.accessToken, key)\n return undefined\n }\n return entry.response\n }\n\n private dropIndexEntry(token: string, key: string): void {\n const keys = this.tokenIndex.get(token)\n if (!keys) return\n keys.delete(key)\n if (keys.size === 0) this.tokenIndex.delete(token)\n }\n\n /** Drop every cached permission check for a token (e.g. after you know the\n * user's permissions changed). Unlike the Rust crate this is synchronous —\n * `await` it if you prefer; the return value is `void` either way. */\n invalidateCache(token: string): void {\n const keys = this.tokenIndex.get(token)\n if (!keys) return\n for (const key of keys) this.permissionCache.delete(key)\n this.tokenIndex.delete(token)\n }\n\n // --- Billing ---\n\n /** Subscription detail for a client app. */\n getSubscription(realmId: string, clientAppId: string): Promise<SubscriptionDetail> {\n return this.requestJson('GET', `/api/ext/bill/${encodeURIComponent(realmId)}/client/${encodeURIComponent(clientAppId)}/subscription`)\n }\n\n // --- Points ---\n\n /** User points balance. */\n getBalance(realmId: string, userId: string): Promise<PointsBalanceResponse> {\n return this.requestJson('GET', `/api/ext/points/${encodeURIComponent(realmId)}/balance`, {\n query: { userId },\n })\n }\n\n /** Consume points from a user's account. `idempotencyKey` prevents double\n * charges when the same logical consume is retried. */\n consumePoints(\n realmId: string,\n userId: string,\n clientAppId: string,\n amount: number,\n description?: string,\n idempotencyKey?: string,\n ): Promise<ConsumePointsResponse> {\n return this.requestJson('POST', `/api/ext/points/${encodeURIComponent(realmId)}/consume`, {\n body: {\n userId,\n clientAppId,\n amount,\n description,\n idempotencyKey,\n },\n })\n }\n\n /** Grant points to a user. `bucketId` is REQUIRED: every grant must target\n * an explicit Credit Bucket. `reason` must be non-empty; `validityDays`\n * omitted means permanent. */\n grantPoints(\n realmId: string,\n userId: string,\n bucketId: string,\n amount: number,\n reason: string,\n validityDays?: number,\n ): Promise<GrantPointsResponse> {\n return this.requestJson('POST', `/api/ext/points/${encodeURIComponent(realmId)}/grant`, {\n body: {\n userId,\n bucketId,\n amount,\n reason,\n validityDays,\n },\n })\n }\n\n // --- Realms ---\n\n createRealm(request: CreateRealmSdkRequest): Promise<RealmInfo> {\n return this.requestJson('POST', '/api/ext/realms', { body: request })\n }\n\n async listRealms(): Promise<RealmItem[]> {\n const body = await this.requestJson<{ realms: RealmItem[] }>('GET', '/api/ext/realms')\n return body.realms\n }\n\n getRealm(realmId: string): Promise<RealmInfo> {\n return this.requestJson('GET', `/api/ext/realms/${encodeURIComponent(realmId)}`)\n }\n\n // --- Users ---\n\n createUser(realmId: string, request: CreateUserSdkRequest): Promise<UserInfo> {\n return this.requestJson('POST', `/api/ext/realms/${encodeURIComponent(realmId)}/users`, { body: request })\n }\n\n async listUsers(realmId: string): Promise<UserInfo[]> {\n const body = await this.requestJson<{ items: UserInfo[] }>(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/users`,\n )\n return body.items\n }\n\n getUser(realmId: string, userId: string): Promise<UserInfo> {\n return this.requestJson(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/users/${encodeURIComponent(userId)}`,\n )\n }\n\n // --- Client apps ---\n\n createClientApp(realmId: string, request: CreateClientAppSdkRequest): Promise<ClientAppInfo> {\n return this.requestJson('POST', `/api/ext/realms/${encodeURIComponent(realmId)}/client-apps`, {\n body: request,\n })\n }\n\n async listClientApps(realmId: string): Promise<ClientAppItem[]> {\n const body = await this.requestJson<{ clientApps: ClientAppItem[] }>(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/client-apps`,\n )\n return body.clientApps\n }\n\n getClientApp(realmId: string, clientAppId: string): Promise<ClientAppInfo> {\n return this.requestJson(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/client-apps/${encodeURIComponent(clientAppId)}`,\n )\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Error type for the Herald Node SDK, mirroring the Rust `herald_sdk::Error`\n * variants (`sdk/rust/src/lib.rs`): every non-2xx or transport failure is\n * thrown as a `HeraldSdkError` carrying a stable machine-readable `code`.\n */\n\nexport type HeraldSdkErrorCode =\n /** Fetch-level transport failure (Rust: `Error::Reqwest`). */\n | 'network'\n /** 401 — invalid API key (Rust: `Error::Unauthorized`). */\n | 'unauthorized'\n /** 403 — e.g. cross-realm access or insufficient permission (Rust: `Error::Forbidden`). */\n | 'forbidden'\n /** 404 (Rust: `Error::NotFound`). */\n | 'not-found'\n /** 500 (Rust: `Error::InternalServerError`). */\n | 'internal-server-error'\n /** Any other non-2xx status (Rust: `Error::ApiError`). */\n | 'api-error'\n /** 2xx with a non-JSON body (Rust: `Error::SerdeJson`). */\n | 'parse'\n\nexport class HeraldSdkError extends Error {\n /** Stable machine-readable category; prefer this over string-matching the message. */\n readonly code: HeraldSdkErrorCode\n /** HTTP status when the error came from a response; undefined for network errors. */\n readonly status?: number\n /** Raw response body text when the error came from a response. */\n readonly body?: string\n\n constructor(code: HeraldSdkErrorCode, status: number | undefined, body: string | undefined) {\n const label = status !== undefined ? `${code} (${status})` : code\n super(body ? `Herald SDK error: ${label}: ${body}` : `Herald SDK error: ${label}`)\n this.name = 'HeraldSdkError'\n this.code = code\n this.status = status\n this.body = body\n }\n}\n","/**\n * Herald server SDK client — a 1:1 TypeScript port of the Rust `herald-sdk`\n * crate (`sdk/rust/src/lib.rs`), which is this SDK's source of truth.\n *\n * Auth model: every call carries the realm/service `X-API-Key` header against\n * the external API surface (`/api/ext/*`). `checkPermission` additionally\n * mirrors the Rust caching behaviour exactly:\n *\n * - a TTL cache keyed by the full request (token + clientId + rules);\n * - a token→keys index so `invalidateCache(token)` drops every cached check\n * for that token;\n * - a 300s \"token snapshot is stale\" heuristic: once a token has been seen\n * at least once more than 5 minutes ago, its cached entries are\n * invalidated before the next check (Rust `is_token_expired`).\n *\n * HTTP layer: native `fetch` (Node 18+), hand-rolled — same deliberate choice\n * as the Rust crate (no OpenAPI-generated client), keeping the package at zero\n * runtime dependencies and its types in lockstep with the crate's.\n */\n\nimport { HeraldSdkError } from './errors'\nimport type {\n ClientAppInfo,\n ClientAppItem,\n ConsumePointsResponse,\n CreateClientAppSdkRequest,\n CreateRealmSdkRequest,\n CreateUserSdkRequest,\n GrantPointsResponse,\n PermissionCheckRequest,\n PermissionCheckResponse,\n PointsBalanceResponse,\n RealmInfo,\n RealmItem,\n SubscriptionDetail,\n TransactionDetail,\n UserInfo,\n} from './types'\n\n/** Rust `is_token_expired` threshold: 5 minutes, independent of the cache TTL. */\nconst TOKEN_EXPIRY_THRESHOLD_MS = 300_000\n\ninterface CacheEntry {\n response: PermissionCheckResponse\n /** Lazy TTL: `Date.now()` after which the entry is treated as evicted. */\n expiresAtMs: number\n}\n\n/**\n * Cache key for a permission check. `rules: undefined` and `rules: []` are\n * DIFFERENT keys (Rust: `Option<Vec<Rule>>` participates in `Hash`/`Eq`), and\n * rule order is significant — `JSON.stringify` preserves both distinctions.\n */\nfunction permissionCacheKey(req: PermissionCheckRequest): string {\n return JSON.stringify({ accessToken: req.accessToken, clientId: req.clientId, rules: req.rules })\n}\n\n/** Map a `fetch` response onto the Rust `handle_response` semantics. */\nasync function handleResponse<T>(response: Response): Promise<T> {\n const text = await response.text()\n const status = response.status\n if (status === 401) throw new HeraldSdkError('unauthorized', status, text)\n if (status === 403) throw new HeraldSdkError('forbidden', status, text)\n if (status === 404) throw new HeraldSdkError('not-found', status, text)\n if (status === 500) throw new HeraldSdkError('internal-server-error', status, text)\n if (status >= 200 && status < 300) {\n try {\n return JSON.parse(text) as T\n } catch (cause) {\n throw new HeraldSdkError('parse', status, `invalid JSON body: ${String(cause)}`)\n }\n }\n throw new HeraldSdkError('api-error', status, text)\n}\n\ninterface RequestOptions {\n query?: Record<string, string>\n body?: unknown\n}\n\nexport class HeraldClient {\n private readonly baseUrl: string\n private readonly apiKey: string\n private readonly cacheTtlMs: number\n private readonly permissionCache = new Map<string, CacheEntry>()\n /** token → cache keys for its checks (Rust `token_index: DashMap`). */\n private readonly tokenIndex = new Map<string, Set<string>>()\n /** token → last successful check timestamp (Rust `token_cache`; the Rust\n * tuple also stored the response, but only the timestamp is ever read). */\n private readonly tokenLastSeen = new Map<string, number>()\n\n constructor(baseUrl: string, apiKey: string, cacheTtlSeconds?: number) {\n this.baseUrl = baseUrl.replace(/\\/+$/, '')\n this.apiKey = apiKey\n this.cacheTtlMs = (cacheTtlSeconds ?? 300) * 1000\n }\n\n private async requestJson<T>(method: 'GET' | 'POST', path: string, options: RequestOptions = {}): Promise<T> {\n let url = `${this.baseUrl}${path}`\n if (options.query) {\n const search = new URLSearchParams(options.query).toString()\n if (search) url += `?${search}`\n }\n const headers: Record<string, string> = { 'X-API-Key': this.apiKey }\n if (options.body !== undefined) headers['Content-Type'] = 'application/json'\n\n let response: Response\n try {\n response = await fetch(url, {\n method,\n headers,\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n })\n } catch (cause) {\n throw new HeraldSdkError('network', undefined, String(cause))\n }\n return handleResponse<T>(response)\n }\n\n // --- Permission check (with cache) ---\n\n /**\n * Check whether a user (identified by their browser access token) is allowed\n * an action. Results are cached per exact request for the client's TTL;\n * a token not checked for over 5 minutes has its cache invalidated first.\n */\n async checkPermission(req: PermissionCheckRequest): Promise<PermissionCheckResponse> {\n if (this.isTokenExpired(req.accessToken)) {\n this.invalidateCache(req.accessToken)\n }\n\n const cached = this.getCached(req)\n if (cached) return cached\n\n const response = await this.requestJson<PermissionCheckResponse>('POST', '/api/ext/permission/check', {\n body: req,\n })\n\n const now = Date.now()\n this.tokenLastSeen.set(req.accessToken, now)\n const key = permissionCacheKey(req)\n let keys = this.tokenIndex.get(req.accessToken)\n if (!keys) {\n keys = new Set()\n this.tokenIndex.set(req.accessToken, keys)\n }\n keys.add(key)\n this.permissionCache.set(key, { response, expiresAtMs: now + this.cacheTtlMs })\n return response\n }\n\n /** Rust `is_token_expired`: seen before, and more than 5 minutes ago. */\n private isTokenExpired(token: string): boolean {\n const at = this.tokenLastSeen.get(token)\n return at !== undefined && Date.now() - at > TOKEN_EXPIRY_THRESHOLD_MS\n }\n\n /** Cached response for an exact request, lazily evicting expired entries\n * (the Map counterpart of moka's TTL + eviction listener). */\n private getCached(req: PermissionCheckRequest): PermissionCheckResponse | undefined {\n const key = permissionCacheKey(req)\n const entry = this.permissionCache.get(key)\n if (!entry) return undefined\n if (Date.now() > entry.expiresAtMs) {\n this.permissionCache.delete(key)\n this.dropIndexEntry(req.accessToken, key)\n return undefined\n }\n return entry.response\n }\n\n private dropIndexEntry(token: string, key: string): void {\n const keys = this.tokenIndex.get(token)\n if (!keys) return\n keys.delete(key)\n if (keys.size === 0) this.tokenIndex.delete(token)\n }\n\n /** Drop every cached permission check for a token (e.g. after you know the\n * user's permissions changed). Unlike the Rust crate this is synchronous —\n * `await` it if you prefer; the return value is `void` either way. */\n invalidateCache(token: string): void {\n const keys = this.tokenIndex.get(token)\n if (!keys) return\n for (const key of keys) this.permissionCache.delete(key)\n this.tokenIndex.delete(token)\n }\n\n // --- Billing ---\n\n /** Subscription detail for a client app. */\n getSubscription(realmId: string, clientAppId: string): Promise<SubscriptionDetail> {\n return this.requestJson('GET', `/api/ext/bill/${encodeURIComponent(realmId)}/client/${encodeURIComponent(clientAppId)}/subscription`)\n }\n\n // --- Points ---\n\n /** User points balance. */\n getBalance(realmId: string, userId: string): Promise<PointsBalanceResponse> {\n return this.requestJson('GET', `/api/ext/points/${encodeURIComponent(realmId)}/balance`, {\n query: { userId },\n })\n }\n\n /** Consume points from a user's account. `idempotencyKey` prevents double\n * charges when the same logical consume is retried. */\n consumePoints(\n realmId: string,\n userId: string,\n clientAppId: string,\n amount: number,\n description?: string,\n idempotencyKey?: string,\n ): Promise<ConsumePointsResponse> {\n return this.requestJson('POST', `/api/ext/points/${encodeURIComponent(realmId)}/consume`, {\n body: {\n userId,\n clientAppId,\n amount,\n description,\n idempotencyKey,\n },\n })\n }\n\n /** Grant points to a user. `bucketId` is REQUIRED: every grant must target\n * an explicit Credit Bucket. `reason` must be non-empty; `validityDays`\n * omitted means permanent. */\n grantPoints(\n realmId: string,\n userId: string,\n bucketId: string,\n amount: number,\n reason: string,\n validityDays?: number,\n ): Promise<GrantPointsResponse> {\n return this.requestJson('POST', `/api/ext/points/${encodeURIComponent(realmId)}/grant`, {\n body: {\n userId,\n bucketId,\n amount,\n reason,\n validityDays,\n },\n })\n }\n\n /** Single points transaction detail by transaction ID. */\n getTransaction(realmId: string, transactionId: string): Promise<TransactionDetail> {\n return this.requestJson(\n 'GET',\n `/api/ext/points/${encodeURIComponent(realmId)}/transactions/${encodeURIComponent(transactionId)}`,\n )\n }\n\n // --- Realms ---\n\n createRealm(request: CreateRealmSdkRequest): Promise<RealmInfo> {\n return this.requestJson('POST', '/api/ext/realms', { body: request })\n }\n\n async listRealms(): Promise<RealmItem[]> {\n const body = await this.requestJson<{ realms: RealmItem[] }>('GET', '/api/ext/realms')\n return body.realms\n }\n\n getRealm(realmId: string): Promise<RealmInfo> {\n return this.requestJson('GET', `/api/ext/realms/${encodeURIComponent(realmId)}`)\n }\n\n // --- Users ---\n\n createUser(realmId: string, request: CreateUserSdkRequest): Promise<UserInfo> {\n return this.requestJson('POST', `/api/ext/realms/${encodeURIComponent(realmId)}/users`, { body: request })\n }\n\n async listUsers(realmId: string): Promise<UserInfo[]> {\n const body = await this.requestJson<{ items: UserInfo[] }>(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/users`,\n )\n return body.items\n }\n\n getUser(realmId: string, userId: string): Promise<UserInfo> {\n return this.requestJson(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/users/${encodeURIComponent(userId)}`,\n )\n }\n\n // --- Client apps ---\n\n createClientApp(realmId: string, request: CreateClientAppSdkRequest): Promise<ClientAppInfo> {\n return this.requestJson('POST', `/api/ext/realms/${encodeURIComponent(realmId)}/client-apps`, {\n body: request,\n })\n }\n\n async listClientApps(realmId: string): Promise<ClientAppItem[]> {\n const body = await this.requestJson<{ clientApps: ClientAppItem[] }>(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/client-apps`,\n )\n return body.clientApps\n }\n\n getClientApp(realmId: string, clientAppId: string): Promise<ClientAppInfo> {\n return this.requestJson(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/client-apps/${encodeURIComponent(clientAppId)}`,\n )\n }\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -85,6 +85,23 @@ interface GrantPointsResponse {
85
85
  balance: number;
86
86
  expiresAt?: string;
87
87
  }
88
+ /** Points transaction detail (single-transaction query).
89
+ *
90
+ * Absent optionals are serialized by the backend as `null` (not omitted),
91
+ * hence `| null` rather than `?`. */
92
+ interface TransactionDetail {
93
+ transactionId: string;
94
+ walletId: string;
95
+ userId: string;
96
+ transactionType: string;
97
+ amount: number;
98
+ balanceAfter: number;
99
+ description: string | null;
100
+ clientAppId: string | null;
101
+ subscriptionId: string | null;
102
+ externalRefId: string | null;
103
+ createdAt: string;
104
+ }
88
105
  /** Per-credit-type balances (`balancesByType`). */
89
106
  interface BalancesByType {
90
107
  topup?: number;
@@ -271,6 +288,8 @@ declare class HeraldClient {
271
288
  * an explicit Credit Bucket. `reason` must be non-empty; `validityDays`
272
289
  * omitted means permanent. */
273
290
  grantPoints(realmId: string, userId: string, bucketId: string, amount: number, reason: string, validityDays?: number): Promise<GrantPointsResponse>;
291
+ /** Single points transaction detail by transaction ID. */
292
+ getTransaction(realmId: string, transactionId: string): Promise<TransactionDetail>;
274
293
  createRealm(request: CreateRealmSdkRequest): Promise<RealmInfo>;
275
294
  listRealms(): Promise<RealmItem[]>;
276
295
  getRealm(realmId: string): Promise<RealmInfo>;
@@ -312,4 +331,4 @@ declare class HeraldSdkError extends Error {
312
331
  constructor(code: HeraldSdkErrorCode, status: number | undefined, body: string | undefined);
313
332
  }
314
333
 
315
- export { type AdminUserSdkInput, type AdminUserSdkOutput, type AllocationDetail, type BalancesByType, type BucketTransaction, type ClientAppInfo, type ClientAppItem, type ConsumePointsResponse, type CreateClientAppSdkRequest, type CreateRealmSdkRequest, type CreateUserSdkRequest, type GrantPointsResponse, HeraldClient, HeraldSdkError, type HeraldSdkErrorCode, type PermissionCheckRequest, type PermissionCheckResponse, type PointsBalanceResponse, type QuotaWindowView, type RealmInfo, type RealmItem, type Rule, type SubscriptionDetail, type UserInfo, type WalletByBucket };
334
+ export { type AdminUserSdkInput, type AdminUserSdkOutput, type AllocationDetail, type BalancesByType, type BucketTransaction, type ClientAppInfo, type ClientAppItem, type ConsumePointsResponse, type CreateClientAppSdkRequest, type CreateRealmSdkRequest, type CreateUserSdkRequest, type GrantPointsResponse, HeraldClient, HeraldSdkError, type HeraldSdkErrorCode, type PermissionCheckRequest, type PermissionCheckResponse, type PointsBalanceResponse, type QuotaWindowView, type RealmInfo, type RealmItem, type Rule, type SubscriptionDetail, type TransactionDetail, type UserInfo, type WalletByBucket };
package/dist/index.d.ts CHANGED
@@ -85,6 +85,23 @@ interface GrantPointsResponse {
85
85
  balance: number;
86
86
  expiresAt?: string;
87
87
  }
88
+ /** Points transaction detail (single-transaction query).
89
+ *
90
+ * Absent optionals are serialized by the backend as `null` (not omitted),
91
+ * hence `| null` rather than `?`. */
92
+ interface TransactionDetail {
93
+ transactionId: string;
94
+ walletId: string;
95
+ userId: string;
96
+ transactionType: string;
97
+ amount: number;
98
+ balanceAfter: number;
99
+ description: string | null;
100
+ clientAppId: string | null;
101
+ subscriptionId: string | null;
102
+ externalRefId: string | null;
103
+ createdAt: string;
104
+ }
88
105
  /** Per-credit-type balances (`balancesByType`). */
89
106
  interface BalancesByType {
90
107
  topup?: number;
@@ -271,6 +288,8 @@ declare class HeraldClient {
271
288
  * an explicit Credit Bucket. `reason` must be non-empty; `validityDays`
272
289
  * omitted means permanent. */
273
290
  grantPoints(realmId: string, userId: string, bucketId: string, amount: number, reason: string, validityDays?: number): Promise<GrantPointsResponse>;
291
+ /** Single points transaction detail by transaction ID. */
292
+ getTransaction(realmId: string, transactionId: string): Promise<TransactionDetail>;
274
293
  createRealm(request: CreateRealmSdkRequest): Promise<RealmInfo>;
275
294
  listRealms(): Promise<RealmItem[]>;
276
295
  getRealm(realmId: string): Promise<RealmInfo>;
@@ -312,4 +331,4 @@ declare class HeraldSdkError extends Error {
312
331
  constructor(code: HeraldSdkErrorCode, status: number | undefined, body: string | undefined);
313
332
  }
314
333
 
315
- export { type AdminUserSdkInput, type AdminUserSdkOutput, type AllocationDetail, type BalancesByType, type BucketTransaction, type ClientAppInfo, type ClientAppItem, type ConsumePointsResponse, type CreateClientAppSdkRequest, type CreateRealmSdkRequest, type CreateUserSdkRequest, type GrantPointsResponse, HeraldClient, HeraldSdkError, type HeraldSdkErrorCode, type PermissionCheckRequest, type PermissionCheckResponse, type PointsBalanceResponse, type QuotaWindowView, type RealmInfo, type RealmItem, type Rule, type SubscriptionDetail, type UserInfo, type WalletByBucket };
334
+ export { type AdminUserSdkInput, type AdminUserSdkOutput, type AllocationDetail, type BalancesByType, type BucketTransaction, type ClientAppInfo, type ClientAppItem, type ConsumePointsResponse, type CreateClientAppSdkRequest, type CreateRealmSdkRequest, type CreateUserSdkRequest, type GrantPointsResponse, HeraldClient, HeraldSdkError, type HeraldSdkErrorCode, type PermissionCheckRequest, type PermissionCheckResponse, type PointsBalanceResponse, type QuotaWindowView, type RealmInfo, type RealmItem, type Rule, type SubscriptionDetail, type TransactionDetail, type UserInfo, type WalletByBucket };
package/dist/index.js CHANGED
@@ -171,6 +171,13 @@ var HeraldClient = class {
171
171
  }
172
172
  });
173
173
  }
174
+ /** Single points transaction detail by transaction ID. */
175
+ getTransaction(realmId, transactionId) {
176
+ return this.requestJson(
177
+ "GET",
178
+ `/api/ext/points/${encodeURIComponent(realmId)}/transactions/${encodeURIComponent(transactionId)}`
179
+ );
180
+ }
174
181
  // --- Realms ---
175
182
  createRealm(request) {
176
183
  return this.requestJson("POST", "/api/ext/realms", { body: request });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Error type for the Herald Node SDK, mirroring the Rust `herald_sdk::Error`\n * variants (`sdk/rust/src/lib.rs`): every non-2xx or transport failure is\n * thrown as a `HeraldSdkError` carrying a stable machine-readable `code`.\n */\n\nexport type HeraldSdkErrorCode =\n /** Fetch-level transport failure (Rust: `Error::Reqwest`). */\n | 'network'\n /** 401 — invalid API key (Rust: `Error::Unauthorized`). */\n | 'unauthorized'\n /** 403 — e.g. cross-realm access or insufficient permission (Rust: `Error::Forbidden`). */\n | 'forbidden'\n /** 404 (Rust: `Error::NotFound`). */\n | 'not-found'\n /** 500 (Rust: `Error::InternalServerError`). */\n | 'internal-server-error'\n /** Any other non-2xx status (Rust: `Error::ApiError`). */\n | 'api-error'\n /** 2xx with a non-JSON body (Rust: `Error::SerdeJson`). */\n | 'parse'\n\nexport class HeraldSdkError extends Error {\n /** Stable machine-readable category; prefer this over string-matching the message. */\n readonly code: HeraldSdkErrorCode\n /** HTTP status when the error came from a response; undefined for network errors. */\n readonly status?: number\n /** Raw response body text when the error came from a response. */\n readonly body?: string\n\n constructor(code: HeraldSdkErrorCode, status: number | undefined, body: string | undefined) {\n const label = status !== undefined ? `${code} (${status})` : code\n super(body ? `Herald SDK error: ${label}: ${body}` : `Herald SDK error: ${label}`)\n this.name = 'HeraldSdkError'\n this.code = code\n this.status = status\n this.body = body\n }\n}\n","/**\n * Herald server SDK client — a 1:1 TypeScript port of the Rust `herald-sdk`\n * crate (`sdk/rust/src/lib.rs`), which is this SDK's source of truth.\n *\n * Auth model: every call carries the realm/service `X-API-Key` header against\n * the external API surface (`/api/ext/*`). `checkPermission` additionally\n * mirrors the Rust caching behaviour exactly:\n *\n * - a TTL cache keyed by the full request (token + clientId + rules);\n * - a token→keys index so `invalidateCache(token)` drops every cached check\n * for that token;\n * - a 300s \"token snapshot is stale\" heuristic: once a token has been seen\n * at least once more than 5 minutes ago, its cached entries are\n * invalidated before the next check (Rust `is_token_expired`).\n *\n * HTTP layer: native `fetch` (Node 18+), hand-rolled — same deliberate choice\n * as the Rust crate (no OpenAPI-generated client), keeping the package at zero\n * runtime dependencies and its types in lockstep with the crate's.\n */\n\nimport { HeraldSdkError } from './errors'\nimport type {\n ClientAppInfo,\n ClientAppItem,\n ConsumePointsResponse,\n CreateClientAppSdkRequest,\n CreateRealmSdkRequest,\n CreateUserSdkRequest,\n GrantPointsResponse,\n PermissionCheckRequest,\n PermissionCheckResponse,\n PointsBalanceResponse,\n RealmInfo,\n RealmItem,\n SubscriptionDetail,\n UserInfo,\n} from './types'\n\n/** Rust `is_token_expired` threshold: 5 minutes, independent of the cache TTL. */\nconst TOKEN_EXPIRY_THRESHOLD_MS = 300_000\n\ninterface CacheEntry {\n response: PermissionCheckResponse\n /** Lazy TTL: `Date.now()` after which the entry is treated as evicted. */\n expiresAtMs: number\n}\n\n/**\n * Cache key for a permission check. `rules: undefined` and `rules: []` are\n * DIFFERENT keys (Rust: `Option<Vec<Rule>>` participates in `Hash`/`Eq`), and\n * rule order is significant — `JSON.stringify` preserves both distinctions.\n */\nfunction permissionCacheKey(req: PermissionCheckRequest): string {\n return JSON.stringify({ accessToken: req.accessToken, clientId: req.clientId, rules: req.rules })\n}\n\n/** Map a `fetch` response onto the Rust `handle_response` semantics. */\nasync function handleResponse<T>(response: Response): Promise<T> {\n const text = await response.text()\n const status = response.status\n if (status === 401) throw new HeraldSdkError('unauthorized', status, text)\n if (status === 403) throw new HeraldSdkError('forbidden', status, text)\n if (status === 404) throw new HeraldSdkError('not-found', status, text)\n if (status === 500) throw new HeraldSdkError('internal-server-error', status, text)\n if (status >= 200 && status < 300) {\n try {\n return JSON.parse(text) as T\n } catch (cause) {\n throw new HeraldSdkError('parse', status, `invalid JSON body: ${String(cause)}`)\n }\n }\n throw new HeraldSdkError('api-error', status, text)\n}\n\ninterface RequestOptions {\n query?: Record<string, string>\n body?: unknown\n}\n\nexport class HeraldClient {\n private readonly baseUrl: string\n private readonly apiKey: string\n private readonly cacheTtlMs: number\n private readonly permissionCache = new Map<string, CacheEntry>()\n /** token → cache keys for its checks (Rust `token_index: DashMap`). */\n private readonly tokenIndex = new Map<string, Set<string>>()\n /** token → last successful check timestamp (Rust `token_cache`; the Rust\n * tuple also stored the response, but only the timestamp is ever read). */\n private readonly tokenLastSeen = new Map<string, number>()\n\n constructor(baseUrl: string, apiKey: string, cacheTtlSeconds?: number) {\n this.baseUrl = baseUrl.replace(/\\/+$/, '')\n this.apiKey = apiKey\n this.cacheTtlMs = (cacheTtlSeconds ?? 300) * 1000\n }\n\n private async requestJson<T>(method: 'GET' | 'POST', path: string, options: RequestOptions = {}): Promise<T> {\n let url = `${this.baseUrl}${path}`\n if (options.query) {\n const search = new URLSearchParams(options.query).toString()\n if (search) url += `?${search}`\n }\n const headers: Record<string, string> = { 'X-API-Key': this.apiKey }\n if (options.body !== undefined) headers['Content-Type'] = 'application/json'\n\n let response: Response\n try {\n response = await fetch(url, {\n method,\n headers,\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n })\n } catch (cause) {\n throw new HeraldSdkError('network', undefined, String(cause))\n }\n return handleResponse<T>(response)\n }\n\n // --- Permission check (with cache) ---\n\n /**\n * Check whether a user (identified by their browser access token) is allowed\n * an action. Results are cached per exact request for the client's TTL;\n * a token not checked for over 5 minutes has its cache invalidated first.\n */\n async checkPermission(req: PermissionCheckRequest): Promise<PermissionCheckResponse> {\n if (this.isTokenExpired(req.accessToken)) {\n this.invalidateCache(req.accessToken)\n }\n\n const cached = this.getCached(req)\n if (cached) return cached\n\n const response = await this.requestJson<PermissionCheckResponse>('POST', '/api/ext/permission/check', {\n body: req,\n })\n\n const now = Date.now()\n this.tokenLastSeen.set(req.accessToken, now)\n const key = permissionCacheKey(req)\n let keys = this.tokenIndex.get(req.accessToken)\n if (!keys) {\n keys = new Set()\n this.tokenIndex.set(req.accessToken, keys)\n }\n keys.add(key)\n this.permissionCache.set(key, { response, expiresAtMs: now + this.cacheTtlMs })\n return response\n }\n\n /** Rust `is_token_expired`: seen before, and more than 5 minutes ago. */\n private isTokenExpired(token: string): boolean {\n const at = this.tokenLastSeen.get(token)\n return at !== undefined && Date.now() - at > TOKEN_EXPIRY_THRESHOLD_MS\n }\n\n /** Cached response for an exact request, lazily evicting expired entries\n * (the Map counterpart of moka's TTL + eviction listener). */\n private getCached(req: PermissionCheckRequest): PermissionCheckResponse | undefined {\n const key = permissionCacheKey(req)\n const entry = this.permissionCache.get(key)\n if (!entry) return undefined\n if (Date.now() > entry.expiresAtMs) {\n this.permissionCache.delete(key)\n this.dropIndexEntry(req.accessToken, key)\n return undefined\n }\n return entry.response\n }\n\n private dropIndexEntry(token: string, key: string): void {\n const keys = this.tokenIndex.get(token)\n if (!keys) return\n keys.delete(key)\n if (keys.size === 0) this.tokenIndex.delete(token)\n }\n\n /** Drop every cached permission check for a token (e.g. after you know the\n * user's permissions changed). Unlike the Rust crate this is synchronous —\n * `await` it if you prefer; the return value is `void` either way. */\n invalidateCache(token: string): void {\n const keys = this.tokenIndex.get(token)\n if (!keys) return\n for (const key of keys) this.permissionCache.delete(key)\n this.tokenIndex.delete(token)\n }\n\n // --- Billing ---\n\n /** Subscription detail for a client app. */\n getSubscription(realmId: string, clientAppId: string): Promise<SubscriptionDetail> {\n return this.requestJson('GET', `/api/ext/bill/${encodeURIComponent(realmId)}/client/${encodeURIComponent(clientAppId)}/subscription`)\n }\n\n // --- Points ---\n\n /** User points balance. */\n getBalance(realmId: string, userId: string): Promise<PointsBalanceResponse> {\n return this.requestJson('GET', `/api/ext/points/${encodeURIComponent(realmId)}/balance`, {\n query: { userId },\n })\n }\n\n /** Consume points from a user's account. `idempotencyKey` prevents double\n * charges when the same logical consume is retried. */\n consumePoints(\n realmId: string,\n userId: string,\n clientAppId: string,\n amount: number,\n description?: string,\n idempotencyKey?: string,\n ): Promise<ConsumePointsResponse> {\n return this.requestJson('POST', `/api/ext/points/${encodeURIComponent(realmId)}/consume`, {\n body: {\n userId,\n clientAppId,\n amount,\n description,\n idempotencyKey,\n },\n })\n }\n\n /** Grant points to a user. `bucketId` is REQUIRED: every grant must target\n * an explicit Credit Bucket. `reason` must be non-empty; `validityDays`\n * omitted means permanent. */\n grantPoints(\n realmId: string,\n userId: string,\n bucketId: string,\n amount: number,\n reason: string,\n validityDays?: number,\n ): Promise<GrantPointsResponse> {\n return this.requestJson('POST', `/api/ext/points/${encodeURIComponent(realmId)}/grant`, {\n body: {\n userId,\n bucketId,\n amount,\n reason,\n validityDays,\n },\n })\n }\n\n // --- Realms ---\n\n createRealm(request: CreateRealmSdkRequest): Promise<RealmInfo> {\n return this.requestJson('POST', '/api/ext/realms', { body: request })\n }\n\n async listRealms(): Promise<RealmItem[]> {\n const body = await this.requestJson<{ realms: RealmItem[] }>('GET', '/api/ext/realms')\n return body.realms\n }\n\n getRealm(realmId: string): Promise<RealmInfo> {\n return this.requestJson('GET', `/api/ext/realms/${encodeURIComponent(realmId)}`)\n }\n\n // --- Users ---\n\n createUser(realmId: string, request: CreateUserSdkRequest): Promise<UserInfo> {\n return this.requestJson('POST', `/api/ext/realms/${encodeURIComponent(realmId)}/users`, { body: request })\n }\n\n async listUsers(realmId: string): Promise<UserInfo[]> {\n const body = await this.requestJson<{ items: UserInfo[] }>(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/users`,\n )\n return body.items\n }\n\n getUser(realmId: string, userId: string): Promise<UserInfo> {\n return this.requestJson(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/users/${encodeURIComponent(userId)}`,\n )\n }\n\n // --- Client apps ---\n\n createClientApp(realmId: string, request: CreateClientAppSdkRequest): Promise<ClientAppInfo> {\n return this.requestJson('POST', `/api/ext/realms/${encodeURIComponent(realmId)}/client-apps`, {\n body: request,\n })\n }\n\n async listClientApps(realmId: string): Promise<ClientAppItem[]> {\n const body = await this.requestJson<{ clientApps: ClientAppItem[] }>(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/client-apps`,\n )\n return body.clientApps\n }\n\n getClientApp(realmId: string, clientAppId: string): Promise<ClientAppInfo> {\n return this.requestJson(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/client-apps/${encodeURIComponent(clientAppId)}`,\n )\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Error type for the Herald Node SDK, mirroring the Rust `herald_sdk::Error`\n * variants (`sdk/rust/src/lib.rs`): every non-2xx or transport failure is\n * thrown as a `HeraldSdkError` carrying a stable machine-readable `code`.\n */\n\nexport type HeraldSdkErrorCode =\n /** Fetch-level transport failure (Rust: `Error::Reqwest`). */\n | 'network'\n /** 401 — invalid API key (Rust: `Error::Unauthorized`). */\n | 'unauthorized'\n /** 403 — e.g. cross-realm access or insufficient permission (Rust: `Error::Forbidden`). */\n | 'forbidden'\n /** 404 (Rust: `Error::NotFound`). */\n | 'not-found'\n /** 500 (Rust: `Error::InternalServerError`). */\n | 'internal-server-error'\n /** Any other non-2xx status (Rust: `Error::ApiError`). */\n | 'api-error'\n /** 2xx with a non-JSON body (Rust: `Error::SerdeJson`). */\n | 'parse'\n\nexport class HeraldSdkError extends Error {\n /** Stable machine-readable category; prefer this over string-matching the message. */\n readonly code: HeraldSdkErrorCode\n /** HTTP status when the error came from a response; undefined for network errors. */\n readonly status?: number\n /** Raw response body text when the error came from a response. */\n readonly body?: string\n\n constructor(code: HeraldSdkErrorCode, status: number | undefined, body: string | undefined) {\n const label = status !== undefined ? `${code} (${status})` : code\n super(body ? `Herald SDK error: ${label}: ${body}` : `Herald SDK error: ${label}`)\n this.name = 'HeraldSdkError'\n this.code = code\n this.status = status\n this.body = body\n }\n}\n","/**\n * Herald server SDK client — a 1:1 TypeScript port of the Rust `herald-sdk`\n * crate (`sdk/rust/src/lib.rs`), which is this SDK's source of truth.\n *\n * Auth model: every call carries the realm/service `X-API-Key` header against\n * the external API surface (`/api/ext/*`). `checkPermission` additionally\n * mirrors the Rust caching behaviour exactly:\n *\n * - a TTL cache keyed by the full request (token + clientId + rules);\n * - a token→keys index so `invalidateCache(token)` drops every cached check\n * for that token;\n * - a 300s \"token snapshot is stale\" heuristic: once a token has been seen\n * at least once more than 5 minutes ago, its cached entries are\n * invalidated before the next check (Rust `is_token_expired`).\n *\n * HTTP layer: native `fetch` (Node 18+), hand-rolled — same deliberate choice\n * as the Rust crate (no OpenAPI-generated client), keeping the package at zero\n * runtime dependencies and its types in lockstep with the crate's.\n */\n\nimport { HeraldSdkError } from './errors'\nimport type {\n ClientAppInfo,\n ClientAppItem,\n ConsumePointsResponse,\n CreateClientAppSdkRequest,\n CreateRealmSdkRequest,\n CreateUserSdkRequest,\n GrantPointsResponse,\n PermissionCheckRequest,\n PermissionCheckResponse,\n PointsBalanceResponse,\n RealmInfo,\n RealmItem,\n SubscriptionDetail,\n TransactionDetail,\n UserInfo,\n} from './types'\n\n/** Rust `is_token_expired` threshold: 5 minutes, independent of the cache TTL. */\nconst TOKEN_EXPIRY_THRESHOLD_MS = 300_000\n\ninterface CacheEntry {\n response: PermissionCheckResponse\n /** Lazy TTL: `Date.now()` after which the entry is treated as evicted. */\n expiresAtMs: number\n}\n\n/**\n * Cache key for a permission check. `rules: undefined` and `rules: []` are\n * DIFFERENT keys (Rust: `Option<Vec<Rule>>` participates in `Hash`/`Eq`), and\n * rule order is significant — `JSON.stringify` preserves both distinctions.\n */\nfunction permissionCacheKey(req: PermissionCheckRequest): string {\n return JSON.stringify({ accessToken: req.accessToken, clientId: req.clientId, rules: req.rules })\n}\n\n/** Map a `fetch` response onto the Rust `handle_response` semantics. */\nasync function handleResponse<T>(response: Response): Promise<T> {\n const text = await response.text()\n const status = response.status\n if (status === 401) throw new HeraldSdkError('unauthorized', status, text)\n if (status === 403) throw new HeraldSdkError('forbidden', status, text)\n if (status === 404) throw new HeraldSdkError('not-found', status, text)\n if (status === 500) throw new HeraldSdkError('internal-server-error', status, text)\n if (status >= 200 && status < 300) {\n try {\n return JSON.parse(text) as T\n } catch (cause) {\n throw new HeraldSdkError('parse', status, `invalid JSON body: ${String(cause)}`)\n }\n }\n throw new HeraldSdkError('api-error', status, text)\n}\n\ninterface RequestOptions {\n query?: Record<string, string>\n body?: unknown\n}\n\nexport class HeraldClient {\n private readonly baseUrl: string\n private readonly apiKey: string\n private readonly cacheTtlMs: number\n private readonly permissionCache = new Map<string, CacheEntry>()\n /** token → cache keys for its checks (Rust `token_index: DashMap`). */\n private readonly tokenIndex = new Map<string, Set<string>>()\n /** token → last successful check timestamp (Rust `token_cache`; the Rust\n * tuple also stored the response, but only the timestamp is ever read). */\n private readonly tokenLastSeen = new Map<string, number>()\n\n constructor(baseUrl: string, apiKey: string, cacheTtlSeconds?: number) {\n this.baseUrl = baseUrl.replace(/\\/+$/, '')\n this.apiKey = apiKey\n this.cacheTtlMs = (cacheTtlSeconds ?? 300) * 1000\n }\n\n private async requestJson<T>(method: 'GET' | 'POST', path: string, options: RequestOptions = {}): Promise<T> {\n let url = `${this.baseUrl}${path}`\n if (options.query) {\n const search = new URLSearchParams(options.query).toString()\n if (search) url += `?${search}`\n }\n const headers: Record<string, string> = { 'X-API-Key': this.apiKey }\n if (options.body !== undefined) headers['Content-Type'] = 'application/json'\n\n let response: Response\n try {\n response = await fetch(url, {\n method,\n headers,\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n })\n } catch (cause) {\n throw new HeraldSdkError('network', undefined, String(cause))\n }\n return handleResponse<T>(response)\n }\n\n // --- Permission check (with cache) ---\n\n /**\n * Check whether a user (identified by their browser access token) is allowed\n * an action. Results are cached per exact request for the client's TTL;\n * a token not checked for over 5 minutes has its cache invalidated first.\n */\n async checkPermission(req: PermissionCheckRequest): Promise<PermissionCheckResponse> {\n if (this.isTokenExpired(req.accessToken)) {\n this.invalidateCache(req.accessToken)\n }\n\n const cached = this.getCached(req)\n if (cached) return cached\n\n const response = await this.requestJson<PermissionCheckResponse>('POST', '/api/ext/permission/check', {\n body: req,\n })\n\n const now = Date.now()\n this.tokenLastSeen.set(req.accessToken, now)\n const key = permissionCacheKey(req)\n let keys = this.tokenIndex.get(req.accessToken)\n if (!keys) {\n keys = new Set()\n this.tokenIndex.set(req.accessToken, keys)\n }\n keys.add(key)\n this.permissionCache.set(key, { response, expiresAtMs: now + this.cacheTtlMs })\n return response\n }\n\n /** Rust `is_token_expired`: seen before, and more than 5 minutes ago. */\n private isTokenExpired(token: string): boolean {\n const at = this.tokenLastSeen.get(token)\n return at !== undefined && Date.now() - at > TOKEN_EXPIRY_THRESHOLD_MS\n }\n\n /** Cached response for an exact request, lazily evicting expired entries\n * (the Map counterpart of moka's TTL + eviction listener). */\n private getCached(req: PermissionCheckRequest): PermissionCheckResponse | undefined {\n const key = permissionCacheKey(req)\n const entry = this.permissionCache.get(key)\n if (!entry) return undefined\n if (Date.now() > entry.expiresAtMs) {\n this.permissionCache.delete(key)\n this.dropIndexEntry(req.accessToken, key)\n return undefined\n }\n return entry.response\n }\n\n private dropIndexEntry(token: string, key: string): void {\n const keys = this.tokenIndex.get(token)\n if (!keys) return\n keys.delete(key)\n if (keys.size === 0) this.tokenIndex.delete(token)\n }\n\n /** Drop every cached permission check for a token (e.g. after you know the\n * user's permissions changed). Unlike the Rust crate this is synchronous —\n * `await` it if you prefer; the return value is `void` either way. */\n invalidateCache(token: string): void {\n const keys = this.tokenIndex.get(token)\n if (!keys) return\n for (const key of keys) this.permissionCache.delete(key)\n this.tokenIndex.delete(token)\n }\n\n // --- Billing ---\n\n /** Subscription detail for a client app. */\n getSubscription(realmId: string, clientAppId: string): Promise<SubscriptionDetail> {\n return this.requestJson('GET', `/api/ext/bill/${encodeURIComponent(realmId)}/client/${encodeURIComponent(clientAppId)}/subscription`)\n }\n\n // --- Points ---\n\n /** User points balance. */\n getBalance(realmId: string, userId: string): Promise<PointsBalanceResponse> {\n return this.requestJson('GET', `/api/ext/points/${encodeURIComponent(realmId)}/balance`, {\n query: { userId },\n })\n }\n\n /** Consume points from a user's account. `idempotencyKey` prevents double\n * charges when the same logical consume is retried. */\n consumePoints(\n realmId: string,\n userId: string,\n clientAppId: string,\n amount: number,\n description?: string,\n idempotencyKey?: string,\n ): Promise<ConsumePointsResponse> {\n return this.requestJson('POST', `/api/ext/points/${encodeURIComponent(realmId)}/consume`, {\n body: {\n userId,\n clientAppId,\n amount,\n description,\n idempotencyKey,\n },\n })\n }\n\n /** Grant points to a user. `bucketId` is REQUIRED: every grant must target\n * an explicit Credit Bucket. `reason` must be non-empty; `validityDays`\n * omitted means permanent. */\n grantPoints(\n realmId: string,\n userId: string,\n bucketId: string,\n amount: number,\n reason: string,\n validityDays?: number,\n ): Promise<GrantPointsResponse> {\n return this.requestJson('POST', `/api/ext/points/${encodeURIComponent(realmId)}/grant`, {\n body: {\n userId,\n bucketId,\n amount,\n reason,\n validityDays,\n },\n })\n }\n\n /** Single points transaction detail by transaction ID. */\n getTransaction(realmId: string, transactionId: string): Promise<TransactionDetail> {\n return this.requestJson(\n 'GET',\n `/api/ext/points/${encodeURIComponent(realmId)}/transactions/${encodeURIComponent(transactionId)}`,\n )\n }\n\n // --- Realms ---\n\n createRealm(request: CreateRealmSdkRequest): Promise<RealmInfo> {\n return this.requestJson('POST', '/api/ext/realms', { body: request })\n }\n\n async listRealms(): Promise<RealmItem[]> {\n const body = await this.requestJson<{ realms: RealmItem[] }>('GET', '/api/ext/realms')\n return body.realms\n }\n\n getRealm(realmId: string): Promise<RealmInfo> {\n return this.requestJson('GET', `/api/ext/realms/${encodeURIComponent(realmId)}`)\n }\n\n // --- Users ---\n\n createUser(realmId: string, request: CreateUserSdkRequest): Promise<UserInfo> {\n return this.requestJson('POST', `/api/ext/realms/${encodeURIComponent(realmId)}/users`, { body: request })\n }\n\n async listUsers(realmId: string): Promise<UserInfo[]> {\n const body = await this.requestJson<{ items: UserInfo[] }>(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/users`,\n )\n return body.items\n }\n\n getUser(realmId: string, userId: string): Promise<UserInfo> {\n return this.requestJson(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/users/${encodeURIComponent(userId)}`,\n )\n }\n\n // --- Client apps ---\n\n createClientApp(realmId: string, request: CreateClientAppSdkRequest): Promise<ClientAppInfo> {\n return this.requestJson('POST', `/api/ext/realms/${encodeURIComponent(realmId)}/client-apps`, {\n body: request,\n })\n }\n\n async listClientApps(realmId: string): Promise<ClientAppItem[]> {\n const body = await this.requestJson<{ clientApps: ClientAppItem[] }>(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/client-apps`,\n )\n return body.clientApps\n }\n\n getClientApp(realmId: string, clientAppId: string): Promise<ClientAppInfo> {\n return this.requestJson(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/client-apps/${encodeURIComponent(clientAppId)}`,\n )\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "herald-sdk",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Node.js SDK for Herald — multi-tenant auth, billing & points API client. Server-side TypeScript counterpart of the Rust herald-sdk crate. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",