@kb-labs/core-tenant 2.13.0 → 2.14.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.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../Users/kirillbaranov/Desktop/kb-labs-workspace/core/tenant/src/types.ts","../../../../../../Users/kirillbaranov/Desktop/kb-labs-workspace/core/tenant/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}\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 },\n pro: {\n requestsPerMinute: 100,\n requestsPerDay: 100_000,\n maxConcurrentWorkflows: 10,\n maxStorageMB: 1000,\n pluginExecutionsPerDay: 10_000,\n },\n enterprise: {\n requestsPerMinute: 1000,\n requestsPerDay: -1, // unlimited\n maxConcurrentWorkflows: 100,\n maxStorageMB: 10_000,\n pluginExecutionsPerDay: -1, // unlimited\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 using existing State Broker infrastructure\n *\n * Benefits:\n * - Zero new dependencies\n * - TTL cleanup already implemented (30s interval)\n * - Consistent with state management patterns\n * - Same backend as plugin state (in-memory or HTTP daemon)\n */\n\nimport type { StateBroker } from '@kb-labs/core-state-broker';\nimport type { TenantQuotas, TenantTier } from './types';\nimport { DEFAULT_QUOTAS } from './types';\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 * Rate limit resource types\n */\nexport type RateLimitResource = 'requests' | 'workflows' | 'plugins';\n\n/**\n * Tenant rate limiter\n * Uses State Broker for distributed rate limiting with TTL\n */\nexport class TenantRateLimiter {\n constructor(\n private broker: StateBroker,\n private quotas: Map<string, TenantQuotas> = new Map()\n ) {}\n\n /**\n * Check 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 * @example\n * const result = await limiter.checkLimit('acme', 'requests');\n * if (!result.allowed) {\n * throw new Error(`Rate limit exceeded. Reset at ${result.resetAt}`);\n * }\n */\n async checkLimit(\n tenantId: string,\n resource: RateLimitResource\n ): Promise<RateLimitResult> {\n const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;\n\n // Key pattern: ratelimit:tenant:default:requests:2025-01-15T10:30\n // Follows existing namespace:key pattern from State Broker\n const key = `ratelimit:tenant:${tenantId}:${resource}:${this.getWindow()}`;\n\n const current = (await this.broker.get<number>(key)) ?? 0;\n const limit = this.getLimit(quota, resource);\n\n if (current >= limit) {\n return {\n allowed: false,\n remaining: 0,\n resetAt: this.getResetTime(),\n limit,\n };\n }\n\n // Increment counter with 60s TTL\n // State Broker cleanup (30s interval) will remove expired entries\n await this.broker.set(key, current + 1, 60 * 1000);\n\n return {\n allowed: true,\n remaining: limit - current - 1,\n resetAt: this.getResetTime(),\n limit,\n };\n }\n\n /**\n * Set quotas for a tenant\n *\n * @param tenantId - Tenant identifier\n * @param quotas - Tenant quotas\n */\n setQuotas(tenantId: string, quotas: TenantQuotas): void {\n this.quotas.set(tenantId, quotas);\n }\n\n /**\n * Set quotas for a tenant tier\n *\n * @param tenantId - Tenant identifier\n * @param tier - Tenant tier\n */\n setTier(tenantId: string, tier: TenantTier): void {\n this.quotas.set(tenantId, { ...DEFAULT_QUOTAS[tier] });\n }\n\n /**\n * Get current window identifier (minute precision)\n * @returns ISO timestamp truncated to minutes\n */\n private getWindow(): string {\n return new Date().toISOString().slice(0, 16); // YYYY-MM-DDTHH:MM\n }\n\n /**\n * Get limit for resource type\n *\n * @param quota - Tenant quotas\n * @param resource - Resource type\n * @returns Limit value\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 // Convert daily limit to per-minute (1440 minutes in a day)\n return quota.pluginExecutionsPerDay === -1\n ? Number.MAX_SAFE_INTEGER\n : Math.ceil(quota.pluginExecutionsPerDay / 1440);\n default:\n return 100; // default fallback\n }\n }\n\n /**\n * Get reset time for current window\n * @returns Unix timestamp in milliseconds\n */\n private getResetTime(): number {\n const now = new Date();\n now.setSeconds(0, 0);\n now.setMinutes(now.getMinutes() + 1);\n return now.getTime();\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}\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 },\n pro: {\n requestsPerMinute: 100,\n requestsPerDay: 100_000,\n maxConcurrentWorkflows: 10,\n maxStorageMB: 1000,\n pluginExecutionsPerDay: 10_000,\n },\n enterprise: {\n requestsPerMinute: 1000,\n requestsPerDay: -1, // unlimited\n maxConcurrentWorkflows: 100,\n maxStorageMB: 10_000,\n pluginExecutionsPerDay: -1, // unlimited\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 using existing State Broker infrastructure\n *\n * Benefits:\n * - Zero new dependencies\n * - TTL cleanup already implemented (30s interval)\n * - Consistent with state management patterns\n * - Same backend as plugin state (in-memory or HTTP daemon)\n */\n\nimport type { StateBroker } from '@kb-labs/core-state-broker';\nimport type { TenantQuotas, TenantTier } from './types';\nimport { DEFAULT_QUOTAS } from './types';\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 * Rate limit resource types\n */\nexport type RateLimitResource = 'requests' | 'workflows' | 'plugins';\n\n/**\n * Tenant rate limiter\n * Uses State Broker for distributed rate limiting with TTL\n */\nexport class TenantRateLimiter {\n constructor(\n private broker: StateBroker,\n private quotas: Map<string, TenantQuotas> = new Map()\n ) {}\n\n /**\n * Check 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 * @example\n * const result = await limiter.checkLimit('acme', 'requests');\n * if (!result.allowed) {\n * throw new Error(`Rate limit exceeded. Reset at ${result.resetAt}`);\n * }\n */\n async checkLimit(\n tenantId: string,\n resource: RateLimitResource\n ): Promise<RateLimitResult> {\n const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;\n\n // Key pattern: ratelimit:tenant:default:requests:2025-01-15T10:30\n // Follows existing namespace:key pattern from State Broker\n const key = `ratelimit:tenant:${tenantId}:${resource}:${this.getWindow()}`;\n\n const current = (await this.broker.get<number>(key)) ?? 0;\n const limit = this.getLimit(quota, resource);\n\n if (current >= limit) {\n return {\n allowed: false,\n remaining: 0,\n resetAt: this.getResetTime(),\n limit,\n };\n }\n\n // Increment counter with 60s TTL\n // State Broker cleanup (30s interval) will remove expired entries\n await this.broker.set(key, current + 1, 60 * 1000);\n\n return {\n allowed: true,\n remaining: limit - current - 1,\n resetAt: this.getResetTime(),\n limit,\n };\n }\n\n /**\n * Set quotas for a tenant\n *\n * @param tenantId - Tenant identifier\n * @param quotas - Tenant quotas\n */\n setQuotas(tenantId: string, quotas: TenantQuotas): void {\n this.quotas.set(tenantId, quotas);\n }\n\n /**\n * Set quotas for a tenant tier\n *\n * @param tenantId - Tenant identifier\n * @param tier - Tenant tier\n */\n setTier(tenantId: string, tier: TenantTier): void {\n this.quotas.set(tenantId, { ...DEFAULT_QUOTAS[tier] });\n }\n\n /**\n * Get current window identifier (minute precision)\n * @returns ISO timestamp truncated to minutes\n */\n private getWindow(): string {\n return new Date().toISOString().slice(0, 16); // YYYY-MM-DDTHH:MM\n }\n\n /**\n * Get limit for resource type\n *\n * @param quota - Tenant quotas\n * @param resource - Resource type\n * @returns Limit value\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 // Convert daily limit to per-minute (1440 minutes in a day)\n return quota.pluginExecutionsPerDay === -1\n ? Number.MAX_SAFE_INTEGER\n : Math.ceil(quota.pluginExecutionsPerDay / 1440);\n default:\n return 100; // default fallback\n }\n }\n\n /**\n * Get reset time for current window\n * @returns Unix timestamp in milliseconds\n */\n private getResetTime(): number {\n const now = new Date();\n now.setSeconds(0, 0);\n now.setMinutes(now.getMinutes() + 1);\n return now.getTime();\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kb-labs/core-tenant",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "Multi-tenancy primitives for KB Labs",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,7 +17,7 @@
17
17
  "README.md"
18
18
  ],
19
19
  "dependencies": {
20
- "@kb-labs/core-state-broker": "2.13.0"
20
+ "@kb-labs/core-state-broker": "2.14.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "tsup": "^8.5.0",
@@ -25,7 +25,7 @@
25
25
  "rimraf": "^6.0.1",
26
26
  "@types/node": "^24.3.3",
27
27
  "vitest": "^3.2.4",
28
- "@kb-labs/devkit": "2.13.0"
28
+ "@kb-labs/devkit": "2.14.0"
29
29
  },
30
30
  "engines": {
31
31
  "node": ">=18.18.0",