@kb-labs/core-tenant 2.94.0 → 2.98.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/types.ts
2
2
  var DEFAULT_QUOTAS = {
3
3
  free: {
4
- requestsPerMinute: 10,
4
+ requestsPerMinute: 100,
5
5
  requestsPerDay: 1e3,
6
6
  maxConcurrentWorkflows: 1,
7
7
  maxStorageMB: 10,
@@ -9,7 +9,7 @@ var DEFAULT_QUOTAS = {
9
9
  tokensPerDay: 1e5
10
10
  },
11
11
  pro: {
12
- requestsPerMinute: 100,
12
+ requestsPerMinute: 500,
13
13
  requestsPerDay: 1e5,
14
14
  maxConcurrentWorkflows: 10,
15
15
  maxStorageMB: 1e3,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/rate-limiter.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @module @kb-labs/tenant/types\n * Multi-tenancy types and quota definitions\n */\n\n/**\n * Tenant tier levels\n */\nexport type TenantTier = 'free' | 'pro' | 'enterprise';\n\n/**\n * Tenant resource quotas\n */\nexport interface TenantQuotas {\n /** Requests per minute limit */\n requestsPerMinute: number;\n /** Requests per day limit (-1 = unlimited) */\n requestsPerDay: number;\n /** Maximum concurrent workflows */\n maxConcurrentWorkflows: number;\n /** Maximum storage in MB */\n maxStorageMB: number;\n /** Plugin executions per day (-1 = unlimited) */\n pluginExecutionsPerDay: number;\n /** LLM tokens per day across all requests (-1 = unlimited) */\n tokensPerDay: number;\n}\n\n/**\n * Tenant configuration\n */\nexport interface TenantConfig {\n /** Unique tenant identifier */\n id: string;\n /** Tenant tier */\n tier: TenantTier;\n /** Resource quotas */\n quotas: TenantQuotas;\n /** Creation timestamp */\n createdAt: string;\n /** Last update timestamp */\n updatedAt: string;\n}\n\n/**\n * Default quotas by tier\n */\nexport const DEFAULT_QUOTAS: Record<TenantTier, TenantQuotas> = {\n free: {\n requestsPerMinute: 10,\n requestsPerDay: 1000,\n maxConcurrentWorkflows: 1,\n maxStorageMB: 10,\n pluginExecutionsPerDay: 100,\n tokensPerDay: 100_000,\n },\n pro: {\n requestsPerMinute: 100,\n requestsPerDay: 100_000,\n maxConcurrentWorkflows: 10,\n maxStorageMB: 1000,\n pluginExecutionsPerDay: 10_000,\n tokensPerDay: 10_000_000,\n },\n enterprise: {\n requestsPerMinute: 1000,\n requestsPerDay: -1,\n maxConcurrentWorkflows: 100,\n maxStorageMB: 10_000,\n pluginExecutionsPerDay: -1,\n tokensPerDay: -1,\n },\n};\n\n/**\n * Get default tenant ID from environment\n * @returns Tenant ID (defaults to 'default' for single-tenant deployments)\n */\nexport function getDefaultTenantId(): string {\n return process.env.KB_TENANT_ID || 'default';\n}\n\n/**\n * Get default tenant tier from environment\n * @returns Tenant tier (defaults to 'free')\n */\nexport function getDefaultTenantTier(): TenantTier {\n const tier = process.env.KB_TENANT_DEFAULT_TIER?.toLowerCase();\n if (tier === 'pro' || tier === 'enterprise') {\n return tier;\n }\n return 'free';\n}\n\n/**\n * Get tenant quotas for a tier\n * @param tier - Tenant tier\n * @returns Quotas for the tier\n */\nexport function getQuotasForTier(tier: TenantTier): TenantQuotas {\n return { ...DEFAULT_QUOTAS[tier] };\n}\n","/**\n * @module @kb-labs/tenant/rate-limiter\n * Tenant rate limiter backed by platform.cache (ICache).\n *\n * Uses the same cache adapter as all other platform components —\n * no extra dependencies, swappable backend (memory / Redis / etc.).\n */\n\nimport type { ICache } from '@kb-labs/core-platform';\nimport type { TenantQuotas, TenantTier } from './types.js';\nimport { DEFAULT_QUOTAS } from './types.js';\n\n/**\n * Rate limit check result\n */\nexport interface RateLimitResult {\n /** Whether the request is allowed */\n allowed: boolean;\n /** Remaining requests in current window */\n remaining: number;\n /** Timestamp when the limit resets (Unix ms) */\n resetAt: number;\n /** Limit for current window */\n limit: number;\n}\n\n/**\n * Token consumption result\n */\nexport interface TokenLimitResult {\n /** Whether token budget allows the request */\n allowed: boolean;\n /** Tokens consumed so far today */\n consumed: number;\n /** Daily token limit (-1 = unlimited) */\n limit: number;\n /** Timestamp when the daily window resets (start of next UTC day, Unix ms) */\n resetAt: number;\n}\n\n/**\n * Rate limit resource types\n */\nexport type RateLimitResource = 'requests' | 'workflows' | 'plugins';\n\n/**\n * Tenant rate limiter backed by platform.cache.\n *\n * All counters use cache TTL for cleanup — no background timers needed.\n */\nexport class TenantRateLimiter {\n constructor(\n private cache: ICache,\n private quotas: Map<string, TenantQuotas> = new Map()\n ) {}\n\n /**\n * Check (and increment) the per-minute rate limit for a tenant.\n *\n * @param tenantId - Tenant identifier\n * @param resource - Resource type to rate limit\n * @returns Rate limit check result\n */\n async checkLimit(\n tenantId: string,\n resource: RateLimitResource\n ): Promise<RateLimitResult> {\n const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;\n const limit = this.getLimit(quota, resource);\n const window = this.getMinuteWindow();\n // key: ratelimit:tenant:<id>:<resource>:<YYYY-MM-DDTHH:MM>\n const key = `ratelimit:tenant:${tenantId}:${resource}:${window}`;\n const ttl = 60_000; // 1 minute\n\n const current = (await this.cache.get<number>(key)) ?? 0;\n\n if (current >= limit) {\n return {\n allowed: false,\n remaining: 0,\n resetAt: this.getMinuteResetTime(),\n limit,\n };\n }\n\n await this.cache.set(key, current + 1, ttl);\n\n return {\n allowed: true,\n remaining: limit - current - 1,\n resetAt: this.getMinuteResetTime(),\n limit,\n };\n }\n\n /**\n * Record consumed tokens and check whether the daily token budget is exceeded.\n *\n * Call this after a successful LLM response when the token count is known.\n * Returns allowed=false when the budget was already exceeded BEFORE this call\n * (i.e. the previous total was already >= limit). The current call's tokens\n * are still recorded so the counter stays accurate.\n *\n * @param tenantId - Tenant identifier\n * @param tokens - Number of tokens consumed in this request (prompt + completion)\n */\n async trackTokens(tenantId: string, tokens: number): Promise<TokenLimitResult> {\n const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;\n const limit = quota.tokensPerDay;\n const day = this.getDayWindow();\n // key: ratelimit:tenant:<id>:tokens:<YYYY-MM-DD>\n const key = `ratelimit:tenant:${tenantId}:tokens:${day}`;\n const ttl = 25 * 60 * 60 * 1000; // 25 h — outlasts the day window with margin\n\n const consumed = (await this.cache.get<number>(key)) ?? 0;\n const newTotal = consumed + tokens;\n await this.cache.set(key, newTotal, ttl);\n\n if (limit === -1) {\n return { allowed: true, consumed: newTotal, limit, resetAt: this.getDayResetTime() };\n }\n\n return {\n allowed: consumed < limit, // allowed if budget wasn't exhausted before this call\n consumed: newTotal,\n limit,\n resetAt: this.getDayResetTime(),\n };\n }\n\n /**\n * Read current token consumption without incrementing.\n */\n async getTokenUsage(tenantId: string): Promise<TokenLimitResult> {\n const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;\n const limit = quota.tokensPerDay;\n const day = this.getDayWindow();\n const key = `ratelimit:tenant:${tenantId}:tokens:${day}`;\n const consumed = (await this.cache.get<number>(key)) ?? 0;\n\n return {\n allowed: limit === -1 || consumed < limit,\n consumed,\n limit,\n resetAt: this.getDayResetTime(),\n };\n }\n\n /** Set quotas for a specific tenant (overrides tier default). */\n setQuotas(tenantId: string, quotas: TenantQuotas): void {\n this.quotas.set(tenantId, quotas);\n }\n\n /** Set quotas for a tenant by tier. */\n setTier(tenantId: string, tier: TenantTier): void {\n this.quotas.set(tenantId, { ...DEFAULT_QUOTAS[tier] });\n }\n\n // ── private helpers ────────────────────────────────────────────────────────\n\n /** Current minute window identifier: YYYY-MM-DDTHH:MM */\n private getMinuteWindow(): string {\n return new Date().toISOString().slice(0, 16);\n }\n\n /** Unix ms when the current minute window ends. */\n private getMinuteResetTime(): number {\n const now = new Date();\n now.setSeconds(0, 0);\n now.setMinutes(now.getMinutes() + 1);\n return now.getTime();\n }\n\n /** Current day window identifier: YYYY-MM-DD (UTC) */\n private getDayWindow(): string {\n return new Date().toISOString().slice(0, 10);\n }\n\n /** Unix ms for start of next UTC day. */\n private getDayResetTime(): number {\n const d = new Date();\n d.setUTCHours(24, 0, 0, 0);\n return d.getTime();\n }\n\n private getLimit(quota: TenantQuotas, resource: RateLimitResource): number {\n switch (resource) {\n case 'requests':\n return quota.requestsPerMinute;\n case 'workflows':\n return quota.maxConcurrentWorkflows;\n case 'plugins':\n return quota.pluginExecutionsPerDay === -1\n ? Number.MAX_SAFE_INTEGER\n : Math.ceil(quota.pluginExecutionsPerDay / 1440);\n default:\n return 100;\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/rate-limiter.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @module @kb-labs/tenant/types\n * Multi-tenancy types and quota definitions\n */\n\n/**\n * Tenant tier levels\n */\nexport type TenantTier = 'free' | 'pro' | 'enterprise';\n\n/**\n * Tenant resource quotas\n */\nexport interface TenantQuotas {\n /** Requests per minute limit */\n requestsPerMinute: number;\n /** Requests per day limit (-1 = unlimited) */\n requestsPerDay: number;\n /** Maximum concurrent workflows */\n maxConcurrentWorkflows: number;\n /** Maximum storage in MB */\n maxStorageMB: number;\n /** Plugin executions per day (-1 = unlimited) */\n pluginExecutionsPerDay: number;\n /** LLM tokens per day across all requests (-1 = unlimited) */\n tokensPerDay: number;\n}\n\n/**\n * Tenant configuration\n */\nexport interface TenantConfig {\n /** Unique tenant identifier */\n id: string;\n /** Tenant tier */\n tier: TenantTier;\n /** Resource quotas */\n quotas: TenantQuotas;\n /** Creation timestamp */\n createdAt: string;\n /** Last update timestamp */\n updatedAt: string;\n}\n\n/**\n * Default quotas by tier\n */\nexport const DEFAULT_QUOTAS: Record<TenantTier, TenantQuotas> = {\n free: {\n requestsPerMinute: 100,\n requestsPerDay: 1000,\n maxConcurrentWorkflows: 1,\n maxStorageMB: 10,\n pluginExecutionsPerDay: 100,\n tokensPerDay: 100_000,\n },\n pro: {\n requestsPerMinute: 500,\n requestsPerDay: 100_000,\n maxConcurrentWorkflows: 10,\n maxStorageMB: 1000,\n pluginExecutionsPerDay: 10_000,\n tokensPerDay: 10_000_000,\n },\n enterprise: {\n requestsPerMinute: 1000,\n requestsPerDay: -1,\n maxConcurrentWorkflows: 100,\n maxStorageMB: 10_000,\n pluginExecutionsPerDay: -1,\n tokensPerDay: -1,\n },\n};\n\n/**\n * Get default tenant ID from environment\n * @returns Tenant ID (defaults to 'default' for single-tenant deployments)\n */\nexport function getDefaultTenantId(): string {\n return process.env.KB_TENANT_ID || 'default';\n}\n\n/**\n * Get default tenant tier from environment\n * @returns Tenant tier (defaults to 'free')\n */\nexport function getDefaultTenantTier(): TenantTier {\n const tier = process.env.KB_TENANT_DEFAULT_TIER?.toLowerCase();\n if (tier === 'pro' || tier === 'enterprise') {\n return tier;\n }\n return 'free';\n}\n\n/**\n * Get tenant quotas for a tier\n * @param tier - Tenant tier\n * @returns Quotas for the tier\n */\nexport function getQuotasForTier(tier: TenantTier): TenantQuotas {\n return { ...DEFAULT_QUOTAS[tier] };\n}\n","/**\n * @module @kb-labs/tenant/rate-limiter\n * Tenant rate limiter backed by platform.cache (ICache).\n *\n * Uses the same cache adapter as all other platform components —\n * no extra dependencies, swappable backend (memory / Redis / etc.).\n */\n\nimport type { ICache } from '@kb-labs/core-platform';\nimport type { TenantQuotas, TenantTier } from './types.js';\nimport { DEFAULT_QUOTAS } from './types.js';\n\n/**\n * Rate limit check result\n */\nexport interface RateLimitResult {\n /** Whether the request is allowed */\n allowed: boolean;\n /** Remaining requests in current window */\n remaining: number;\n /** Timestamp when the limit resets (Unix ms) */\n resetAt: number;\n /** Limit for current window */\n limit: number;\n}\n\n/**\n * Token consumption result\n */\nexport interface TokenLimitResult {\n /** Whether token budget allows the request */\n allowed: boolean;\n /** Tokens consumed so far today */\n consumed: number;\n /** Daily token limit (-1 = unlimited) */\n limit: number;\n /** Timestamp when the daily window resets (start of next UTC day, Unix ms) */\n resetAt: number;\n}\n\n/**\n * Rate limit resource types\n */\nexport type RateLimitResource = 'requests' | 'workflows' | 'plugins';\n\n/**\n * Tenant rate limiter backed by platform.cache.\n *\n * All counters use cache TTL for cleanup — no background timers needed.\n */\nexport class TenantRateLimiter {\n constructor(\n private cache: ICache,\n private quotas: Map<string, TenantQuotas> = new Map()\n ) {}\n\n /**\n * Check (and increment) the per-minute rate limit for a tenant.\n *\n * @param tenantId - Tenant identifier\n * @param resource - Resource type to rate limit\n * @returns Rate limit check result\n */\n async checkLimit(\n tenantId: string,\n resource: RateLimitResource\n ): Promise<RateLimitResult> {\n const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;\n const limit = this.getLimit(quota, resource);\n const window = this.getMinuteWindow();\n // key: ratelimit:tenant:<id>:<resource>:<YYYY-MM-DDTHH:MM>\n const key = `ratelimit:tenant:${tenantId}:${resource}:${window}`;\n const ttl = 60_000; // 1 minute\n\n const current = (await this.cache.get<number>(key)) ?? 0;\n\n if (current >= limit) {\n return {\n allowed: false,\n remaining: 0,\n resetAt: this.getMinuteResetTime(),\n limit,\n };\n }\n\n await this.cache.set(key, current + 1, ttl);\n\n return {\n allowed: true,\n remaining: limit - current - 1,\n resetAt: this.getMinuteResetTime(),\n limit,\n };\n }\n\n /**\n * Record consumed tokens and check whether the daily token budget is exceeded.\n *\n * Call this after a successful LLM response when the token count is known.\n * Returns allowed=false when the budget was already exceeded BEFORE this call\n * (i.e. the previous total was already >= limit). The current call's tokens\n * are still recorded so the counter stays accurate.\n *\n * @param tenantId - Tenant identifier\n * @param tokens - Number of tokens consumed in this request (prompt + completion)\n */\n async trackTokens(tenantId: string, tokens: number): Promise<TokenLimitResult> {\n const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;\n const limit = quota.tokensPerDay;\n const day = this.getDayWindow();\n // key: ratelimit:tenant:<id>:tokens:<YYYY-MM-DD>\n const key = `ratelimit:tenant:${tenantId}:tokens:${day}`;\n const ttl = 25 * 60 * 60 * 1000; // 25 h — outlasts the day window with margin\n\n const consumed = (await this.cache.get<number>(key)) ?? 0;\n const newTotal = consumed + tokens;\n await this.cache.set(key, newTotal, ttl);\n\n if (limit === -1) {\n return { allowed: true, consumed: newTotal, limit, resetAt: this.getDayResetTime() };\n }\n\n return {\n allowed: consumed < limit, // allowed if budget wasn't exhausted before this call\n consumed: newTotal,\n limit,\n resetAt: this.getDayResetTime(),\n };\n }\n\n /**\n * Read current token consumption without incrementing.\n */\n async getTokenUsage(tenantId: string): Promise<TokenLimitResult> {\n const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;\n const limit = quota.tokensPerDay;\n const day = this.getDayWindow();\n const key = `ratelimit:tenant:${tenantId}:tokens:${day}`;\n const consumed = (await this.cache.get<number>(key)) ?? 0;\n\n return {\n allowed: limit === -1 || consumed < limit,\n consumed,\n limit,\n resetAt: this.getDayResetTime(),\n };\n }\n\n /** Set quotas for a specific tenant (overrides tier default). */\n setQuotas(tenantId: string, quotas: TenantQuotas): void {\n this.quotas.set(tenantId, quotas);\n }\n\n /** Set quotas for a tenant by tier. */\n setTier(tenantId: string, tier: TenantTier): void {\n this.quotas.set(tenantId, { ...DEFAULT_QUOTAS[tier] });\n }\n\n // ── private helpers ────────────────────────────────────────────────────────\n\n /** Current minute window identifier: YYYY-MM-DDTHH:MM */\n private getMinuteWindow(): string {\n return new Date().toISOString().slice(0, 16);\n }\n\n /** Unix ms when the current minute window ends. */\n private getMinuteResetTime(): number {\n const now = new Date();\n now.setSeconds(0, 0);\n now.setMinutes(now.getMinutes() + 1);\n return now.getTime();\n }\n\n /** Current day window identifier: YYYY-MM-DD (UTC) */\n private getDayWindow(): string {\n return new Date().toISOString().slice(0, 10);\n }\n\n /** Unix ms for start of next UTC day. */\n private getDayResetTime(): number {\n const d = new Date();\n d.setUTCHours(24, 0, 0, 0);\n return d.getTime();\n }\n\n private getLimit(quota: TenantQuotas, resource: RateLimitResource): number {\n switch (resource) {\n case 'requests':\n return quota.requestsPerMinute;\n case 'workflows':\n return quota.maxConcurrentWorkflows;\n case 'plugins':\n return quota.pluginExecutionsPerDay === -1\n ? Number.MAX_SAFE_INTEGER\n : Math.ceil(quota.pluginExecutionsPerDay / 1440);\n default:\n return 100;\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kb-labs/core-tenant",
3
3
  "description": "Multi-tenancy primitives for KB Labs",
4
- "version": "2.94.0",
4
+ "version": "2.98.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -17,15 +17,15 @@
17
17
  "README.md"
18
18
  ],
19
19
  "dependencies": {
20
- "@kb-labs/core-platform": "2.94.0"
20
+ "@kb-labs/core-platform": "2.98.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^24.3.3",
24
24
  "rimraf": "^6.0.1",
25
25
  "tsup": "^8.5.0",
26
26
  "typescript": "^5.6.3",
27
- "vitest": "^3.2.4",
28
- "@kb-labs/devkit": "2.94.0"
27
+ "vitest": "^3.2.6",
28
+ "@kb-labs/devkit": "2.98.0"
29
29
  },
30
30
  "engines": {
31
31
  "node": ">=18.18.0",