nucleus-core-ts 0.9.709 → 0.9.710
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.js +1 -1
- package/dist/index.js +3 -3
- package/dist/src/ElysiaPlugin/routes/authorization/index.d.ts +6 -0
- package/dist/src/Managers/Redis/index.d.ts +6 -0
- package/dist/src/Services/Authorization/Quota/evaluate.d.ts +15 -0
- package/dist/src/Services/Authorization/Quota/index.d.ts +4 -0
- package/dist/src/Services/Authorization/Quota/quota.test.d.ts +1 -0
- package/dist/src/Services/Authorization/Quota/resolve.d.ts +16 -0
- package/dist/src/Services/Authorization/Quota/types.d.ts +59 -0
- package/dist/src/Services/Authorization/Quota/windows.d.ts +11 -0
- package/dist/src/Services/Authorization/SeedRunner/index.d.ts +6 -0
- package/package.json +1 -1
- package/src/system.tables.json +4 -0
|
@@ -10,6 +10,12 @@ export interface AuthorizationRoutesConfig {
|
|
|
10
10
|
discovery: EndpointDiscoveryConfig & {
|
|
11
11
|
token?: string;
|
|
12
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Reads integer usage counters (missing → 0) for the quota endpoint. Wired to
|
|
15
|
+
* RedisManager.readCounters when Redis is configured; when absent, `/authorization/quotas`
|
|
16
|
+
* still returns effective LIMITS but omits live usage.
|
|
17
|
+
*/
|
|
18
|
+
readCounters?: (keys: string[]) => Promise<number[]>;
|
|
13
19
|
}
|
|
14
20
|
/**
|
|
15
21
|
* Endpoint-discovery admin + manifest routes.
|
|
@@ -27,6 +27,12 @@ export declare class RedisManager {
|
|
|
27
27
|
update<T>(key: string, value: T, preserveTtl?: boolean): Promise<RedisResult<'OK'>>;
|
|
28
28
|
remove(key: string): Promise<RedisResult<number>>;
|
|
29
29
|
exists(key: string): Promise<RedisResult<boolean>>;
|
|
30
|
+
/**
|
|
31
|
+
* Read a batch of integer counter keys (missing / non-numeric → 0). Works in both
|
|
32
|
+
* direct and Dapr modes via the state-store abstraction. Used by the quota evaluator to
|
|
33
|
+
* gate usage; the counters themselves are maintained (INCR'd) by the spender.
|
|
34
|
+
*/
|
|
35
|
+
readCounters(keys: string[]): Promise<number[]>;
|
|
30
36
|
/**
|
|
31
37
|
* Returns the underlying ioredis client when running in direct mode.
|
|
32
38
|
* Returns null in Dapr mode — raw Redis commands (INFO, KEYS, …) are not
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CounterReader, QuotaAggregate, QuotaContext, QuotaEvaluation, QuotaPolicy } from './types';
|
|
2
|
+
/** Parse+validate a raw `claims.policy` jsonb value into a QuotaPolicy, or null if it isn't one. */
|
|
3
|
+
export declare function parseQuotaPolicy(raw: unknown): QuotaPolicy | null;
|
|
4
|
+
/**
|
|
5
|
+
* Combine the per-role limits a user holds for one quota claim into a single effective limit.
|
|
6
|
+
* `max` (default) = tier model (best role wins); `min` = strictest; `sum` = additive.
|
|
7
|
+
* Non-numeric scopes are ignored. Returns null when no numeric limit is present.
|
|
8
|
+
*/
|
|
9
|
+
export declare function aggregateLimit(scopes: readonly string[], mode: QuotaAggregate): number | null;
|
|
10
|
+
/**
|
|
11
|
+
* Read the current usage for a quota policy and compare it to the effective limit. Pure:
|
|
12
|
+
* the counter reader is injected. Enforcement decision is the caller's — for a spend of
|
|
13
|
+
* cost C, a HARD cap allows it iff `current + C <= limit` (lte) / `< limit` (lt).
|
|
14
|
+
*/
|
|
15
|
+
export declare function evaluateQuota(policy: QuotaPolicy, ctx: QuotaContext, limit: number, read: CounterReader): Promise<QuotaEvaluation>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { aggregateLimit, evaluateQuota, parseQuotaPolicy } from './evaluate';
|
|
2
|
+
export { type ResolvedQuota, resolveUserQuotas } from './resolve';
|
|
3
|
+
export type { CounterReader, QuotaAggregate, QuotaContext, QuotaEvaluation, QuotaOp, QuotaPolicy, QuotaWindow, } from './types';
|
|
4
|
+
export { bucketDatesForWindow, buildQuotaKeys } from './windows';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
|
2
|
+
import type { QuotaPolicy } from './types';
|
|
3
|
+
export interface ResolvedQuota {
|
|
4
|
+
action: string;
|
|
5
|
+
policy: QuotaPolicy;
|
|
6
|
+
/** effective limit after aggregating the per-role scopes for this claim */
|
|
7
|
+
limit: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a user's effective quota limits. For every QUOTA claim (`claims.policy.kind==='quota'`)
|
|
11
|
+
* granted through any of the user's roles, aggregate the per-role limits — each carried in that
|
|
12
|
+
* assignment's `role_claims.scope` — using the policy's `aggregate` rule (default `max`, the tier
|
|
13
|
+
* model). Active rows only. Reads whole small authz tables and filters in-memory, mirroring the
|
|
14
|
+
* existing manifest resolver (no dynamic-column WHERE).
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveUserQuotas(db: NodePgDatabase, schemaTables: Record<string, unknown>, userId: string): Promise<ResolvedQuota[]>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic usage-quota primitive. A quota is expressed as a CLAIM whose `policy` jsonb
|
|
3
|
+
* column carries this shape; the per-role LIMIT lives in the `role_claims.scope` of each
|
|
4
|
+
* assignment (so the same claim can be granted at different limits to different roles, and
|
|
5
|
+
* a user's effective limit is an aggregation across their roles). The counter itself is
|
|
6
|
+
* maintained by whoever does the spending (e.g. the LLM service) under the key described
|
|
7
|
+
* here — nucleus only READS it to gate. Deliberately not spend-specific: `metric` is
|
|
8
|
+
* free-form, so the same engine covers tokens, dollars, request counts, storage, etc.
|
|
9
|
+
*/
|
|
10
|
+
export type QuotaOp = 'lte' | 'lt';
|
|
11
|
+
/** How a user's per-role limits combine when they hold the claim via multiple roles. */
|
|
12
|
+
export type QuotaAggregate = 'max' | 'min' | 'sum';
|
|
13
|
+
export type QuotaWindow = {
|
|
14
|
+
type: 'rolling';
|
|
15
|
+
days: number;
|
|
16
|
+
} | {
|
|
17
|
+
type: 'calendar_day';
|
|
18
|
+
} | {
|
|
19
|
+
type: 'total';
|
|
20
|
+
};
|
|
21
|
+
export interface QuotaPolicy {
|
|
22
|
+
kind: 'quota';
|
|
23
|
+
source: 'redis';
|
|
24
|
+
/** Free-form metric label, e.g. 'tokens' | 'cost_usd' | 'requests'. */
|
|
25
|
+
metric: string;
|
|
26
|
+
window: QuotaWindow;
|
|
27
|
+
/**
|
|
28
|
+
* Redis counter key template. Placeholders: `{userId}`, `{tenant}`, `{date}`.
|
|
29
|
+
* Bucketed windows (rolling / calendar_day) substitute `{date}` (yyyy-mm-dd, UTC) once
|
|
30
|
+
* per bucket. `total` uses the template as-is (must not contain `{date}`).
|
|
31
|
+
* e.g. `usage:{tenant}:{userId}:tokens:{date}` or `usage:{tenant}:{userId}:tokens:total`.
|
|
32
|
+
*/
|
|
33
|
+
keyTemplate: string;
|
|
34
|
+
/** Comparison for a HARD cap. `lte`: usage may reach the limit; `lt`: must stay below. Default `lte`. */
|
|
35
|
+
op?: QuotaOp;
|
|
36
|
+
/** Multi-role limit resolution. Default `max` (tier model: the most generous role wins). */
|
|
37
|
+
aggregate?: QuotaAggregate;
|
|
38
|
+
/** Optional display unit, e.g. 'tokens' | 'usd'. */
|
|
39
|
+
unit?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface QuotaContext {
|
|
42
|
+
userId: string;
|
|
43
|
+
tenant?: string;
|
|
44
|
+
now: Date;
|
|
45
|
+
}
|
|
46
|
+
export interface QuotaEvaluation {
|
|
47
|
+
metric: string;
|
|
48
|
+
window: QuotaWindow;
|
|
49
|
+
limit: number;
|
|
50
|
+
current: number;
|
|
51
|
+
/** limit − current; negative when already over. */
|
|
52
|
+
remaining: number;
|
|
53
|
+
/** true when the cap is already reached (no room for another unit under `op`). */
|
|
54
|
+
over: boolean;
|
|
55
|
+
/** the concrete counter keys that were summed (for debugging / the status endpoint). */
|
|
56
|
+
keys: string[];
|
|
57
|
+
}
|
|
58
|
+
/** Reads integer counter values for a set of keys (missing → 0). Injected so the evaluator stays pure. */
|
|
59
|
+
export type CounterReader = (keys: string[]) => Promise<number[]>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { QuotaContext, QuotaPolicy, QuotaWindow } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The yyyy-mm-dd daily buckets a window spans, most-recent first, or `null` for `total`
|
|
4
|
+
* (which is a single cumulative counter with no date bucket). Rolling windows include today.
|
|
5
|
+
*/
|
|
6
|
+
export declare function bucketDatesForWindow(window: QuotaWindow, now: Date): string[] | null;
|
|
7
|
+
/**
|
|
8
|
+
* Concrete Redis counter keys for a policy + context. Substitutes `{userId}`/`{tenant}`,
|
|
9
|
+
* then `{date}` per bucket (or none for `total`). The spender maintains these; nucleus reads them.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildQuotaKeys(policy: QuotaPolicy, ctx: QuotaContext): string[];
|
|
@@ -10,6 +10,12 @@ type SeedConfig = {
|
|
|
10
10
|
path: string;
|
|
11
11
|
method: string;
|
|
12
12
|
description?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Optional generic policy dictionary (jsonb) — e.g. a QUOTA policy
|
|
15
|
+
* (`{ kind:'quota', metric, window, keyTemplate, ... }`). Reconciled on
|
|
16
|
+
* drift like `scope`, so editing a seeded quota claim's policy takes effect.
|
|
17
|
+
*/
|
|
18
|
+
policy?: Record<string, unknown> | null;
|
|
13
19
|
}>;
|
|
14
20
|
roleClaimAssignments?: Array<{
|
|
15
21
|
role: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.710",
|
|
4
4
|
"description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
|
|
5
5
|
"author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|