herald-sdk 0.5.4 → 0.5.6
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 +0 -1
- package/dist/index.cjs +17 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -4
- package/dist/index.d.ts +22 -4
- package/dist/index.js +17 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,7 +39,6 @@ const client = new HeraldClient(
|
|
|
39
39
|
// Check permission (cached per exact request for the TTL)
|
|
40
40
|
const resp = await client.checkPermission({
|
|
41
41
|
accessToken: 'user-browser-token', // issued by /api/auth/{realmId}/login
|
|
42
|
-
clientId: 'your-client-id',
|
|
43
42
|
rules: [{ resource: 'document', action: 'read' }],
|
|
44
43
|
})
|
|
45
44
|
console.log('allowed:', resp.allowed)
|
package/dist/index.cjs
CHANGED
|
@@ -21,7 +21,7 @@ var HeraldSdkError = class extends Error {
|
|
|
21
21
|
// src/client.ts
|
|
22
22
|
var TOKEN_EXPIRY_THRESHOLD_MS = 3e5;
|
|
23
23
|
function permissionCacheKey(req) {
|
|
24
|
-
return JSON.stringify({ accessToken: req.accessToken,
|
|
24
|
+
return JSON.stringify({ accessToken: req.accessToken, rules: req.rules });
|
|
25
25
|
}
|
|
26
26
|
async function handleResponse(response) {
|
|
27
27
|
const text = await response.text();
|
|
@@ -195,12 +195,25 @@ var HeraldClient = class {
|
|
|
195
195
|
createUser(realmId, request) {
|
|
196
196
|
return this.requestJson("POST", `/api/ext/realms/${encodeURIComponent(realmId)}/users`, { body: request });
|
|
197
197
|
}
|
|
198
|
+
/** List users in a realm, first page with the server defaults
|
|
199
|
+
* (page = 1, pageSize = 20). Use {@link listUsersPage} to page further. */
|
|
198
200
|
async listUsers(realmId) {
|
|
199
|
-
|
|
201
|
+
return (await this.listUsersPage(realmId)).items;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* List users in a realm with pagination. `page` is 1-based and defaults to
|
|
205
|
+
* 1 when undefined; `pageSize` defaults to 20 and is clamped to 100 by the
|
|
206
|
+
* server. The response echoes the applied paging and the cross-page total.
|
|
207
|
+
*/
|
|
208
|
+
async listUsersPage(realmId, page, pageSize) {
|
|
209
|
+
const query = {};
|
|
210
|
+
if (page !== void 0) query.page = String(page);
|
|
211
|
+
if (pageSize !== void 0) query.pageSize = String(pageSize);
|
|
212
|
+
return this.requestJson(
|
|
200
213
|
"GET",
|
|
201
|
-
`/api/ext/realms/${encodeURIComponent(realmId)}/users
|
|
214
|
+
`/api/ext/realms/${encodeURIComponent(realmId)}/users`,
|
|
215
|
+
{ query }
|
|
202
216
|
);
|
|
203
|
-
return body.items;
|
|
204
217
|
}
|
|
205
218
|
getUser(realmId, userId) {
|
|
206
219
|
return this.requestJson(
|
package/dist/index.cjs.map
CHANGED
|
@@ -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 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"]}
|
|
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 + 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 UserPage,\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. An empty `rules: []` is a valid key and\n * rule order is significant — `JSON.stringify` preserves both distinctions.\n * (The Rust cache key is the whole `PermissionCheckRequest`; `clientId` was\n * removed from the request because the backend body has no such field — the\n * client identity travels in the `X-API-Key` header.)\n */\nfunction permissionCacheKey(req: PermissionCheckRequest): string {\n return JSON.stringify({ accessToken: req.accessToken, 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 /** List users in a realm, first page with the server defaults\n * (page = 1, pageSize = 20). Use {@link listUsersPage} to page further. */\n async listUsers(realmId: string): Promise<UserInfo[]> {\n return (await this.listUsersPage(realmId)).items\n }\n\n /**\n * List users in a realm with pagination. `page` is 1-based and defaults to\n * 1 when undefined; `pageSize` defaults to 20 and is clamped to 100 by the\n * server. The response echoes the applied paging and the cross-page total.\n */\n async listUsersPage(realmId: string, page?: number, pageSize?: number): Promise<UserPage> {\n const query: Record<string, string> = {}\n if (page !== undefined) query.page = String(page)\n if (pageSize !== undefined) query.pageSize = String(pageSize)\n return this.requestJson<UserPage>(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/users`,\n { query },\n )\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
|
@@ -15,8 +15,9 @@ interface Rule {
|
|
|
15
15
|
interface PermissionCheckRequest {
|
|
16
16
|
/** Browser access token issued by `/api/auth/{realmId}/login`. */
|
|
17
17
|
accessToken: string;
|
|
18
|
-
rules
|
|
19
|
-
|
|
18
|
+
/** Required by the backend: a missing/empty `rules` array is a 400
|
|
19
|
+
* `rules_required`. */
|
|
20
|
+
rules: Rule[];
|
|
20
21
|
}
|
|
21
22
|
interface PermissionCheckResponse {
|
|
22
23
|
allowed: boolean;
|
|
@@ -203,6 +204,15 @@ interface UserInfo {
|
|
|
203
204
|
status: number;
|
|
204
205
|
createdAt: string;
|
|
205
206
|
}
|
|
207
|
+
/** One page of the paginated user list (`listUsersPage`). */
|
|
208
|
+
interface UserPage {
|
|
209
|
+
items: UserInfo[];
|
|
210
|
+
/** 1-based page number echo. */
|
|
211
|
+
page: number;
|
|
212
|
+
pageSize: number;
|
|
213
|
+
/** Total matching users across all pages. */
|
|
214
|
+
total: number;
|
|
215
|
+
}
|
|
206
216
|
/** Request body for creating a client app. */
|
|
207
217
|
interface CreateClientAppSdkRequest {
|
|
208
218
|
name: string;
|
|
@@ -237,7 +247,7 @@ interface ClientAppItem {
|
|
|
237
247
|
* the external API surface (`/api/ext/*`). `checkPermission` additionally
|
|
238
248
|
* mirrors the Rust caching behaviour exactly:
|
|
239
249
|
*
|
|
240
|
-
* - a TTL cache keyed by the full request (token +
|
|
250
|
+
* - a TTL cache keyed by the full request (token + rules);
|
|
241
251
|
* - a token→keys index so `invalidateCache(token)` drops every cached check
|
|
242
252
|
* for that token;
|
|
243
253
|
* - a 300s "token snapshot is stale" heuristic: once a token has been seen
|
|
@@ -294,7 +304,15 @@ declare class HeraldClient {
|
|
|
294
304
|
listRealms(): Promise<RealmItem[]>;
|
|
295
305
|
getRealm(realmId: string): Promise<RealmInfo>;
|
|
296
306
|
createUser(realmId: string, request: CreateUserSdkRequest): Promise<UserInfo>;
|
|
307
|
+
/** List users in a realm, first page with the server defaults
|
|
308
|
+
* (page = 1, pageSize = 20). Use {@link listUsersPage} to page further. */
|
|
297
309
|
listUsers(realmId: string): Promise<UserInfo[]>;
|
|
310
|
+
/**
|
|
311
|
+
* List users in a realm with pagination. `page` is 1-based and defaults to
|
|
312
|
+
* 1 when undefined; `pageSize` defaults to 20 and is clamped to 100 by the
|
|
313
|
+
* server. The response echoes the applied paging and the cross-page total.
|
|
314
|
+
*/
|
|
315
|
+
listUsersPage(realmId: string, page?: number, pageSize?: number): Promise<UserPage>;
|
|
298
316
|
getUser(realmId: string, userId: string): Promise<UserInfo>;
|
|
299
317
|
createClientApp(realmId: string, request: CreateClientAppSdkRequest): Promise<ClientAppInfo>;
|
|
300
318
|
listClientApps(realmId: string): Promise<ClientAppItem[]>;
|
|
@@ -331,4 +349,4 @@ declare class HeraldSdkError extends Error {
|
|
|
331
349
|
constructor(code: HeraldSdkErrorCode, status: number | undefined, body: string | undefined);
|
|
332
350
|
}
|
|
333
351
|
|
|
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 };
|
|
352
|
+
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 UserPage, type WalletByBucket };
|
package/dist/index.d.ts
CHANGED
|
@@ -15,8 +15,9 @@ interface Rule {
|
|
|
15
15
|
interface PermissionCheckRequest {
|
|
16
16
|
/** Browser access token issued by `/api/auth/{realmId}/login`. */
|
|
17
17
|
accessToken: string;
|
|
18
|
-
rules
|
|
19
|
-
|
|
18
|
+
/** Required by the backend: a missing/empty `rules` array is a 400
|
|
19
|
+
* `rules_required`. */
|
|
20
|
+
rules: Rule[];
|
|
20
21
|
}
|
|
21
22
|
interface PermissionCheckResponse {
|
|
22
23
|
allowed: boolean;
|
|
@@ -203,6 +204,15 @@ interface UserInfo {
|
|
|
203
204
|
status: number;
|
|
204
205
|
createdAt: string;
|
|
205
206
|
}
|
|
207
|
+
/** One page of the paginated user list (`listUsersPage`). */
|
|
208
|
+
interface UserPage {
|
|
209
|
+
items: UserInfo[];
|
|
210
|
+
/** 1-based page number echo. */
|
|
211
|
+
page: number;
|
|
212
|
+
pageSize: number;
|
|
213
|
+
/** Total matching users across all pages. */
|
|
214
|
+
total: number;
|
|
215
|
+
}
|
|
206
216
|
/** Request body for creating a client app. */
|
|
207
217
|
interface CreateClientAppSdkRequest {
|
|
208
218
|
name: string;
|
|
@@ -237,7 +247,7 @@ interface ClientAppItem {
|
|
|
237
247
|
* the external API surface (`/api/ext/*`). `checkPermission` additionally
|
|
238
248
|
* mirrors the Rust caching behaviour exactly:
|
|
239
249
|
*
|
|
240
|
-
* - a TTL cache keyed by the full request (token +
|
|
250
|
+
* - a TTL cache keyed by the full request (token + rules);
|
|
241
251
|
* - a token→keys index so `invalidateCache(token)` drops every cached check
|
|
242
252
|
* for that token;
|
|
243
253
|
* - a 300s "token snapshot is stale" heuristic: once a token has been seen
|
|
@@ -294,7 +304,15 @@ declare class HeraldClient {
|
|
|
294
304
|
listRealms(): Promise<RealmItem[]>;
|
|
295
305
|
getRealm(realmId: string): Promise<RealmInfo>;
|
|
296
306
|
createUser(realmId: string, request: CreateUserSdkRequest): Promise<UserInfo>;
|
|
307
|
+
/** List users in a realm, first page with the server defaults
|
|
308
|
+
* (page = 1, pageSize = 20). Use {@link listUsersPage} to page further. */
|
|
297
309
|
listUsers(realmId: string): Promise<UserInfo[]>;
|
|
310
|
+
/**
|
|
311
|
+
* List users in a realm with pagination. `page` is 1-based and defaults to
|
|
312
|
+
* 1 when undefined; `pageSize` defaults to 20 and is clamped to 100 by the
|
|
313
|
+
* server. The response echoes the applied paging and the cross-page total.
|
|
314
|
+
*/
|
|
315
|
+
listUsersPage(realmId: string, page?: number, pageSize?: number): Promise<UserPage>;
|
|
298
316
|
getUser(realmId: string, userId: string): Promise<UserInfo>;
|
|
299
317
|
createClientApp(realmId: string, request: CreateClientAppSdkRequest): Promise<ClientAppInfo>;
|
|
300
318
|
listClientApps(realmId: string): Promise<ClientAppItem[]>;
|
|
@@ -331,4 +349,4 @@ declare class HeraldSdkError extends Error {
|
|
|
331
349
|
constructor(code: HeraldSdkErrorCode, status: number | undefined, body: string | undefined);
|
|
332
350
|
}
|
|
333
351
|
|
|
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 };
|
|
352
|
+
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 UserPage, type WalletByBucket };
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var HeraldSdkError = class extends Error {
|
|
|
19
19
|
// src/client.ts
|
|
20
20
|
var TOKEN_EXPIRY_THRESHOLD_MS = 3e5;
|
|
21
21
|
function permissionCacheKey(req) {
|
|
22
|
-
return JSON.stringify({ accessToken: req.accessToken,
|
|
22
|
+
return JSON.stringify({ accessToken: req.accessToken, rules: req.rules });
|
|
23
23
|
}
|
|
24
24
|
async function handleResponse(response) {
|
|
25
25
|
const text = await response.text();
|
|
@@ -193,12 +193,25 @@ var HeraldClient = class {
|
|
|
193
193
|
createUser(realmId, request) {
|
|
194
194
|
return this.requestJson("POST", `/api/ext/realms/${encodeURIComponent(realmId)}/users`, { body: request });
|
|
195
195
|
}
|
|
196
|
+
/** List users in a realm, first page with the server defaults
|
|
197
|
+
* (page = 1, pageSize = 20). Use {@link listUsersPage} to page further. */
|
|
196
198
|
async listUsers(realmId) {
|
|
197
|
-
|
|
199
|
+
return (await this.listUsersPage(realmId)).items;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* List users in a realm with pagination. `page` is 1-based and defaults to
|
|
203
|
+
* 1 when undefined; `pageSize` defaults to 20 and is clamped to 100 by the
|
|
204
|
+
* server. The response echoes the applied paging and the cross-page total.
|
|
205
|
+
*/
|
|
206
|
+
async listUsersPage(realmId, page, pageSize) {
|
|
207
|
+
const query = {};
|
|
208
|
+
if (page !== void 0) query.page = String(page);
|
|
209
|
+
if (pageSize !== void 0) query.pageSize = String(pageSize);
|
|
210
|
+
return this.requestJson(
|
|
198
211
|
"GET",
|
|
199
|
-
`/api/ext/realms/${encodeURIComponent(realmId)}/users
|
|
212
|
+
`/api/ext/realms/${encodeURIComponent(realmId)}/users`,
|
|
213
|
+
{ query }
|
|
200
214
|
);
|
|
201
|
-
return body.items;
|
|
202
215
|
}
|
|
203
216
|
getUser(realmId, userId) {
|
|
204
217
|
return this.requestJson(
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/client.ts"],"names":[],"mappings":";AAsBO,IAAM,cAAA,GAAN,cAA6B,KAAA,CAAM;AAAA;AAAA,EAE/B,IAAA;AAAA;AAAA,EAEA,MAAA;AAAA;AAAA,EAEA,IAAA;AAAA,EAET,WAAA,CAAY,IAAA,EAA0B,MAAA,EAA4B,IAAA,EAA0B;AAC1F,IAAA,MAAM,QAAQ,MAAA,KAAW,MAAA,GAAY,GAAG,IAAI,CAAA,EAAA,EAAK,MAAM,CAAA,CAAA,CAAA,GAAM,IAAA;AAC7D,IAAA,KAAA,CAAM,IAAA,GAAO,qBAAqB,KAAK,CAAA,EAAA,EAAK,IAAI,CAAA,CAAA,GAAK,CAAA,kBAAA,EAAqB,KAAK,CAAA,CAAE,CAAA;AACjF,IAAA,IAAA,CAAK,IAAA,GAAO,gBAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AACF;;;ACEA,IAAM,yBAAA,GAA4B,GAAA;AAalC,SAAS,mBAAmB,GAAA,EAAqC;AAC/D,EAAA,OAAO,IAAA,CAAK,SAAA,CAAU,EAAE,WAAA,EAAa,GAAA,CAAI,WAAA,EAAa,QAAA,EAAU,GAAA,CAAI,QAAA,EAAU,KAAA,EAAO,GAAA,CAAI,KAAA,EAAO,CAAA;AAClG;AAGA,eAAe,eAAkB,QAAA,EAAgC;AAC/D,EAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,EAAA,MAAM,SAAS,QAAA,CAAS,MAAA;AACxB,EAAA,IAAI,WAAW,GAAA,EAAK,MAAM,IAAI,cAAA,CAAe,cAAA,EAAgB,QAAQ,IAAI,CAAA;AACzE,EAAA,IAAI,WAAW,GAAA,EAAK,MAAM,IAAI,cAAA,CAAe,WAAA,EAAa,QAAQ,IAAI,CAAA;AACtE,EAAA,IAAI,WAAW,GAAA,EAAK,MAAM,IAAI,cAAA,CAAe,WAAA,EAAa,QAAQ,IAAI,CAAA;AACtE,EAAA,IAAI,WAAW,GAAA,EAAK,MAAM,IAAI,cAAA,CAAe,uBAAA,EAAyB,QAAQ,IAAI,CAAA;AAClF,EAAA,IAAI,MAAA,IAAU,GAAA,IAAO,MAAA,GAAS,GAAA,EAAK;AACjC,IAAA,IAAI;AACF,MAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,IACxB,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,IAAI,eAAe,OAAA,EAAS,MAAA,EAAQ,sBAAsB,MAAA,CAAO,KAAK,CAAC,CAAA,CAAE,CAAA;AAAA,IACjF;AAAA,EACF;AACA,EAAA,MAAM,IAAI,cAAA,CAAe,WAAA,EAAa,MAAA,EAAQ,IAAI,CAAA;AACpD;AAOO,IAAM,eAAN,MAAmB;AAAA,EACP,OAAA;AAAA,EACA,MAAA;AAAA,EACA,UAAA;AAAA,EACA,eAAA,uBAAsB,GAAA,EAAwB;AAAA;AAAA,EAE9C,UAAA,uBAAiB,GAAA,EAAyB;AAAA;AAAA;AAAA,EAG1C,aAAA,uBAAoB,GAAA,EAAoB;AAAA,EAEzD,WAAA,CAAY,OAAA,EAAiB,MAAA,EAAgB,eAAA,EAA0B;AACrE,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA,CAAQ,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AACzC,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,UAAA,GAAA,CAAc,mBAAmB,GAAA,IAAO,GAAA;AAAA,EAC/C;AAAA,EAEA,MAAc,WAAA,CAAe,MAAA,EAAwB,IAAA,EAAc,OAAA,GAA0B,EAAC,EAAe;AAC3G,IAAA,IAAI,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,OAAO,GAAG,IAAI,CAAA,CAAA;AAChC,IAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,MAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,OAAA,CAAQ,KAAK,EAAE,QAAA,EAAS;AAC3D,MAAA,IAAI,MAAA,EAAQ,GAAA,IAAO,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA;AAAA,IAC/B;AACA,IAAA,MAAM,OAAA,GAAkC,EAAE,WAAA,EAAa,IAAA,CAAK,MAAA,EAAO;AACnE,IAAA,IAAI,OAAA,CAAQ,IAAA,KAAS,MAAA,EAAW,OAAA,CAAQ,cAAc,CAAA,GAAI,kBAAA;AAE1D,IAAA,IAAI,QAAA;AACJ,IAAA,IAAI;AACF,MAAA,QAAA,GAAW,MAAM,MAAM,GAAA,EAAK;AAAA,QAC1B,MAAA;AAAA,QACA,OAAA;AAAA,QACA,IAAA,EAAM,QAAQ,IAAA,KAAS,KAAA,CAAA,GAAY,KAAK,SAAA,CAAU,OAAA,CAAQ,IAAI,CAAA,GAAI,KAAA;AAAA,OACnE,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,IAAI,cAAA,CAAe,SAAA,EAAW,MAAA,EAAW,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IAC9D;AACA,IAAA,OAAO,eAAkB,QAAQ,CAAA;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,gBAAgB,GAAA,EAA+D;AACnF,IAAA,IAAI,IAAA,CAAK,cAAA,CAAe,GAAA,CAAI,WAAW,CAAA,EAAG;AACxC,MAAA,IAAA,CAAK,eAAA,CAAgB,IAAI,WAAW,CAAA;AAAA,IACtC;AAEA,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA;AACjC,IAAA,IAAI,QAAQ,OAAO,MAAA;AAEnB,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,WAAA,CAAqC,QAAQ,2BAAA,EAA6B;AAAA,MACpG,IAAA,EAAM;AAAA,KACP,CAAA;AAED,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,IAAA,CAAK,aAAA,CAAc,GAAA,CAAI,GAAA,CAAI,WAAA,EAAa,GAAG,CAAA;AAC3C,IAAA,MAAM,GAAA,GAAM,mBAAmB,GAAG,CAAA;AAClC,IAAA,IAAI,IAAA,GAAO,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,IAAI,WAAW,CAAA;AAC9C,IAAA,IAAI,CAAC,IAAA,EAAM;AACT,MAAA,IAAA,uBAAW,GAAA,EAAI;AACf,MAAA,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,GAAA,CAAI,WAAA,EAAa,IAAI,CAAA;AAAA,IAC3C;AACA,IAAA,IAAA,CAAK,IAAI,GAAG,CAAA;AACZ,IAAA,IAAA,CAAK,eAAA,CAAgB,IAAI,GAAA,EAAK,EAAE,UAAU,WAAA,EAAa,GAAA,GAAM,IAAA,CAAK,UAAA,EAAY,CAAA;AAC9E,IAAA,OAAO,QAAA;AAAA,EACT;AAAA;AAAA,EAGQ,eAAe,KAAA,EAAwB;AAC7C,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,aAAA,CAAc,GAAA,CAAI,KAAK,CAAA;AACvC,IAAA,OAAO,EAAA,KAAO,MAAA,IAAa,IAAA,CAAK,GAAA,KAAQ,EAAA,GAAK,yBAAA;AAAA,EAC/C;AAAA;AAAA;AAAA,EAIQ,UAAU,GAAA,EAAkE;AAClF,IAAA,MAAM,GAAA,GAAM,mBAAmB,GAAG,CAAA;AAClC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,eAAA,CAAgB,GAAA,CAAI,GAAG,CAAA;AAC1C,IAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AACnB,IAAA,IAAI,IAAA,CAAK,GAAA,EAAI,GAAI,KAAA,CAAM,WAAA,EAAa;AAClC,MAAA,IAAA,CAAK,eAAA,CAAgB,OAAO,GAAG,CAAA;AAC/B,MAAA,IAAA,CAAK,cAAA,CAAe,GAAA,CAAI,WAAA,EAAa,GAAG,CAAA;AACxC,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAO,KAAA,CAAM,QAAA;AAAA,EACf;AAAA,EAEQ,cAAA,CAAe,OAAe,GAAA,EAAmB;AACvD,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,KAAK,CAAA;AACtC,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,IAAA,CAAK,OAAO,GAAG,CAAA;AACf,IAAA,IAAI,KAAK,IAAA,KAAS,CAAA,EAAG,IAAA,CAAK,UAAA,CAAW,OAAO,KAAK,CAAA;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,KAAA,EAAqB;AACnC,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,KAAK,CAAA;AACtC,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,KAAA,MAAW,GAAA,IAAO,IAAA,EAAM,IAAA,CAAK,eAAA,CAAgB,OAAO,GAAG,CAAA;AACvD,IAAA,IAAA,CAAK,UAAA,CAAW,OAAO,KAAK,CAAA;AAAA,EAC9B;AAAA;AAAA;AAAA,EAKA,eAAA,CAAgB,SAAiB,WAAA,EAAkD;AACjF,IAAA,OAAO,IAAA,CAAK,WAAA,CAAY,KAAA,EAAO,CAAA,cAAA,EAAiB,kBAAA,CAAmB,OAAO,CAAC,CAAA,QAAA,EAAW,kBAAA,CAAmB,WAAW,CAAC,CAAA,aAAA,CAAe,CAAA;AAAA,EACtI;AAAA;AAAA;AAAA,EAKA,UAAA,CAAW,SAAiB,MAAA,EAAgD;AAC1E,IAAA,OAAO,KAAK,WAAA,CAAY,KAAA,EAAO,mBAAmB,kBAAA,CAAmB,OAAO,CAAC,CAAA,QAAA,CAAA,EAAY;AAAA,MACvF,KAAA,EAAO,EAAE,MAAA;AAAO,KACjB,CAAA;AAAA,EACH;AAAA;AAAA;AAAA,EAIA,cACE,OAAA,EACA,MAAA,EACA,WAAA,EACA,MAAA,EACA,aACA,cAAA,EACgC;AAChC,IAAA,OAAO,KAAK,WAAA,CAAY,MAAA,EAAQ,mBAAmB,kBAAA,CAAmB,OAAO,CAAC,CAAA,QAAA,CAAA,EAAY;AAAA,MACxF,IAAA,EAAM;AAAA,QACJ,MAAA;AAAA,QACA,WAAA;AAAA,QACA,MAAA;AAAA,QACA,WAAA;AAAA,QACA;AAAA;AACF,KACD,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,YACE,OAAA,EACA,MAAA,EACA,QAAA,EACA,MAAA,EACA,QACA,YAAA,EAC8B;AAC9B,IAAA,OAAO,KAAK,WAAA,CAAY,MAAA,EAAQ,mBAAmB,kBAAA,CAAmB,OAAO,CAAC,CAAA,MAAA,CAAA,EAAU;AAAA,MACtF,IAAA,EAAM;AAAA,QACJ,MAAA;AAAA,QACA,QAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA;AACF,KACD,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,cAAA,CAAe,SAAiB,aAAA,EAAmD;AACjF,IAAA,OAAO,IAAA,CAAK,WAAA;AAAA,MACV,KAAA;AAAA,MACA,mBAAmB,kBAAA,CAAmB,OAAO,CAAC,CAAA,cAAA,EAAiB,kBAAA,CAAmB,aAAa,CAAC,CAAA;AAAA,KAClG;AAAA,EACF;AAAA;AAAA,EAIA,YAAY,OAAA,EAAoD;AAC9D,IAAA,OAAO,KAAK,WAAA,CAAY,MAAA,EAAQ,mBAAmB,EAAE,IAAA,EAAM,SAAS,CAAA;AAAA,EACtE;AAAA,EAEA,MAAM,UAAA,GAAmC;AACvC,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,WAAA,CAAqC,OAAO,iBAAiB,CAAA;AACrF,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA,EAEA,SAAS,OAAA,EAAqC;AAC5C,IAAA,OAAO,KAAK,WAAA,CAAY,KAAA,EAAO,mBAAmB,kBAAA,CAAmB,OAAO,CAAC,CAAA,CAAE,CAAA;AAAA,EACjF;AAAA;AAAA,EAIA,UAAA,CAAW,SAAiB,OAAA,EAAkD;AAC5E,IAAA,OAAO,IAAA,CAAK,WAAA,CAAY,MAAA,EAAQ,CAAA,gBAAA,EAAmB,kBAAA,CAAmB,OAAO,CAAC,CAAA,MAAA,CAAA,EAAU,EAAE,IAAA,EAAM,OAAA,EAAS,CAAA;AAAA,EAC3G;AAAA,EAEA,MAAM,UAAU,OAAA,EAAsC;AACpD,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,WAAA;AAAA,MACtB,KAAA;AAAA,MACA,CAAA,gBAAA,EAAmB,kBAAA,CAAmB,OAAO,CAAC,CAAA,MAAA;AAAA,KAChD;AACA,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA,EAEA,OAAA,CAAQ,SAAiB,MAAA,EAAmC;AAC1D,IAAA,OAAO,IAAA,CAAK,WAAA;AAAA,MACV,KAAA;AAAA,MACA,mBAAmB,kBAAA,CAAmB,OAAO,CAAC,CAAA,OAAA,EAAU,kBAAA,CAAmB,MAAM,CAAC,CAAA;AAAA,KACpF;AAAA,EACF;AAAA;AAAA,EAIA,eAAA,CAAgB,SAAiB,OAAA,EAA4D;AAC3F,IAAA,OAAO,KAAK,WAAA,CAAY,MAAA,EAAQ,mBAAmB,kBAAA,CAAmB,OAAO,CAAC,CAAA,YAAA,CAAA,EAAgB;AAAA,MAC5F,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,eAAe,OAAA,EAA2C;AAC9D,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,WAAA;AAAA,MACtB,KAAA;AAAA,MACA,CAAA,gBAAA,EAAmB,kBAAA,CAAmB,OAAO,CAAC,CAAA,YAAA;AAAA,KAChD;AACA,IAAA,OAAO,IAAA,CAAK,UAAA;AAAA,EACd;AAAA,EAEA,YAAA,CAAa,SAAiB,WAAA,EAA6C;AACzE,IAAA,OAAO,IAAA,CAAK,WAAA;AAAA,MACV,KAAA;AAAA,MACA,mBAAmB,kBAAA,CAAmB,OAAO,CAAC,CAAA,aAAA,EAAgB,kBAAA,CAAmB,WAAW,CAAC,CAAA;AAAA,KAC/F;AAAA,EACF;AACF","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"]}
|
|
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 + 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 UserPage,\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. An empty `rules: []` is a valid key and\n * rule order is significant — `JSON.stringify` preserves both distinctions.\n * (The Rust cache key is the whole `PermissionCheckRequest`; `clientId` was\n * removed from the request because the backend body has no such field — the\n * client identity travels in the `X-API-Key` header.)\n */\nfunction permissionCacheKey(req: PermissionCheckRequest): string {\n return JSON.stringify({ accessToken: req.accessToken, 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 /** List users in a realm, first page with the server defaults\n * (page = 1, pageSize = 20). Use {@link listUsersPage} to page further. */\n async listUsers(realmId: string): Promise<UserInfo[]> {\n return (await this.listUsersPage(realmId)).items\n }\n\n /**\n * List users in a realm with pagination. `page` is 1-based and defaults to\n * 1 when undefined; `pageSize` defaults to 20 and is clamped to 100 by the\n * server. The response echoes the applied paging and the cross-page total.\n */\n async listUsersPage(realmId: string, page?: number, pageSize?: number): Promise<UserPage> {\n const query: Record<string, string> = {}\n if (page !== undefined) query.page = String(page)\n if (pageSize !== undefined) query.pageSize = String(pageSize)\n return this.requestJson<UserPage>(\n 'GET',\n `/api/ext/realms/${encodeURIComponent(realmId)}/users`,\n { query },\n )\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.
|
|
3
|
+
"version": "0.5.6",
|
|
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",
|