@kb-labs/core-tenant 1.0.0 → 1.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kb-labs/core-tenant",
3
- "version": "1.0.0",
3
+ "version": "1.3.0",
4
4
  "description": "Multi-tenancy primitives for KB Labs",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,17 +14,26 @@
14
14
  },
15
15
  "files": [
16
16
  "dist",
17
- "src",
18
17
  "README.md"
19
18
  ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "clean": "rimraf dist",
22
+ "dev": "tsup --watch",
23
+ "type-check": "tsc --noEmit",
24
+ "lint": "eslint src --ext .ts",
25
+ "lint:fix": "eslint . --fix",
26
+ "test": "vitest run --passWithNoTests",
27
+ "test:watch": "vitest"
28
+ },
20
29
  "dependencies": {
21
- "@kb-labs/core-state-broker": "1.0.0"
30
+ "@kb-labs/core-state-broker": "workspace:*"
22
31
  },
23
32
  "devDependencies": {
24
33
  "tsup": "^8.5.0",
25
34
  "typescript": "^5.6.3",
26
35
  "rimraf": "^6.0.1",
27
- "@kb-labs/devkit": "link:../../../kb-labs-devkit",
36
+ "@kb-labs/devkit": "link:../../../../infra/kb-labs-devkit",
28
37
  "@types/node": "^24.3.3",
29
38
  "vitest": "^3.2.4"
30
39
  },
@@ -34,15 +43,5 @@
34
43
  },
35
44
  "publishConfig": {
36
45
  "access": "public"
37
- },
38
- "scripts": {
39
- "build": "tsup",
40
- "clean": "rimraf dist",
41
- "dev": "tsup --watch",
42
- "type-check": "tsc --noEmit",
43
- "lint": "eslint src --ext .ts",
44
- "lint:fix": "eslint . --fix",
45
- "test": "vitest run --passWithNoTests",
46
- "test:watch": "vitest"
47
46
  }
48
- }
47
+ }
package/src/index.ts DELETED
@@ -1,10 +0,0 @@
1
- /**
2
- * @module @kb-labs/tenant
3
- * Multi-tenancy primitives for KB Labs
4
- *
5
- * Provides tenant management, quotas, and rate limiting
6
- * using existing infrastructure (State Broker, LogContext, Prometheus)
7
- */
8
-
9
- export * from './types';
10
- export * from './rate-limiter';
@@ -1,153 +0,0 @@
1
- /**
2
- * @module @kb-labs/tenant/rate-limiter
3
- * Tenant rate limiter using existing State Broker infrastructure
4
- *
5
- * Benefits:
6
- * - Zero new dependencies
7
- * - TTL cleanup already implemented (30s interval)
8
- * - Consistent with state management patterns
9
- * - Same backend as plugin state (in-memory or HTTP daemon)
10
- */
11
-
12
- import type { StateBroker } from '@kb-labs/core-state-broker';
13
- import type { TenantQuotas, TenantTier } from './types';
14
- import { DEFAULT_QUOTAS } from './types';
15
-
16
- /**
17
- * Rate limit check result
18
- */
19
- export interface RateLimitResult {
20
- /** Whether the request is allowed */
21
- allowed: boolean;
22
- /** Remaining requests in current window */
23
- remaining: number;
24
- /** Timestamp when the limit resets (Unix ms) */
25
- resetAt: number;
26
- /** Limit for current window */
27
- limit: number;
28
- }
29
-
30
- /**
31
- * Rate limit resource types
32
- */
33
- export type RateLimitResource = 'requests' | 'workflows' | 'plugins';
34
-
35
- /**
36
- * Tenant rate limiter
37
- * Uses State Broker for distributed rate limiting with TTL
38
- */
39
- export class TenantRateLimiter {
40
- constructor(
41
- private broker: StateBroker,
42
- private quotas: Map<string, TenantQuotas> = new Map()
43
- ) {}
44
-
45
- /**
46
- * Check rate limit for a tenant
47
- *
48
- * @param tenantId - Tenant identifier
49
- * @param resource - Resource type to rate limit
50
- * @returns Rate limit check result
51
- *
52
- * @example
53
- * const result = await limiter.checkLimit('acme', 'requests');
54
- * if (!result.allowed) {
55
- * throw new Error(`Rate limit exceeded. Reset at ${result.resetAt}`);
56
- * }
57
- */
58
- async checkLimit(
59
- tenantId: string,
60
- resource: RateLimitResource
61
- ): Promise<RateLimitResult> {
62
- const quota = this.quotas.get(tenantId) ?? DEFAULT_QUOTAS.free;
63
-
64
- // Key pattern: ratelimit:tenant:default:requests:2025-01-15T10:30
65
- // Follows existing namespace:key pattern from State Broker
66
- const key = `ratelimit:tenant:${tenantId}:${resource}:${this.getWindow()}`;
67
-
68
- const current = (await this.broker.get<number>(key)) ?? 0;
69
- const limit = this.getLimit(quota, resource);
70
-
71
- if (current >= limit) {
72
- return {
73
- allowed: false,
74
- remaining: 0,
75
- resetAt: this.getResetTime(),
76
- limit,
77
- };
78
- }
79
-
80
- // Increment counter with 60s TTL
81
- // State Broker cleanup (30s interval) will remove expired entries
82
- await this.broker.set(key, current + 1, 60 * 1000);
83
-
84
- return {
85
- allowed: true,
86
- remaining: limit - current - 1,
87
- resetAt: this.getResetTime(),
88
- limit,
89
- };
90
- }
91
-
92
- /**
93
- * Set quotas for a tenant
94
- *
95
- * @param tenantId - Tenant identifier
96
- * @param quotas - Tenant quotas
97
- */
98
- setQuotas(tenantId: string, quotas: TenantQuotas): void {
99
- this.quotas.set(tenantId, quotas);
100
- }
101
-
102
- /**
103
- * Set quotas for a tenant tier
104
- *
105
- * @param tenantId - Tenant identifier
106
- * @param tier - Tenant tier
107
- */
108
- setTier(tenantId: string, tier: TenantTier): void {
109
- this.quotas.set(tenantId, { ...DEFAULT_QUOTAS[tier] });
110
- }
111
-
112
- /**
113
- * Get current window identifier (minute precision)
114
- * @returns ISO timestamp truncated to minutes
115
- */
116
- private getWindow(): string {
117
- return new Date().toISOString().slice(0, 16); // YYYY-MM-DDTHH:MM
118
- }
119
-
120
- /**
121
- * Get limit for resource type
122
- *
123
- * @param quota - Tenant quotas
124
- * @param resource - Resource type
125
- * @returns Limit value
126
- */
127
- private getLimit(quota: TenantQuotas, resource: RateLimitResource): number {
128
- switch (resource) {
129
- case 'requests':
130
- return quota.requestsPerMinute;
131
- case 'workflows':
132
- return quota.maxConcurrentWorkflows;
133
- case 'plugins':
134
- // Convert daily limit to per-minute (1440 minutes in a day)
135
- return quota.pluginExecutionsPerDay === -1
136
- ? Number.MAX_SAFE_INTEGER
137
- : Math.ceil(quota.pluginExecutionsPerDay / 1440);
138
- default:
139
- return 100; // default fallback
140
- }
141
- }
142
-
143
- /**
144
- * Get reset time for current window
145
- * @returns Unix timestamp in milliseconds
146
- */
147
- private getResetTime(): number {
148
- const now = new Date();
149
- now.setSeconds(0, 0);
150
- now.setMinutes(now.getMinutes() + 1);
151
- return now.getTime();
152
- }
153
- }
package/src/types.ts DELETED
@@ -1,97 +0,0 @@
1
- /**
2
- * @module @kb-labs/tenant/types
3
- * Multi-tenancy types and quota definitions
4
- */
5
-
6
- /**
7
- * Tenant tier levels
8
- */
9
- export type TenantTier = 'free' | 'pro' | 'enterprise';
10
-
11
- /**
12
- * Tenant resource quotas
13
- */
14
- export interface TenantQuotas {
15
- /** Requests per minute limit */
16
- requestsPerMinute: number;
17
- /** Requests per day limit (-1 = unlimited) */
18
- requestsPerDay: number;
19
- /** Maximum concurrent workflows */
20
- maxConcurrentWorkflows: number;
21
- /** Maximum storage in MB */
22
- maxStorageMB: number;
23
- /** Plugin executions per day (-1 = unlimited) */
24
- pluginExecutionsPerDay: number;
25
- }
26
-
27
- /**
28
- * Tenant configuration
29
- */
30
- export interface TenantConfig {
31
- /** Unique tenant identifier */
32
- id: string;
33
- /** Tenant tier */
34
- tier: TenantTier;
35
- /** Resource quotas */
36
- quotas: TenantQuotas;
37
- /** Creation timestamp */
38
- createdAt: string;
39
- /** Last update timestamp */
40
- updatedAt: string;
41
- }
42
-
43
- /**
44
- * Default quotas by tier
45
- */
46
- export const DEFAULT_QUOTAS: Record<TenantTier, TenantQuotas> = {
47
- free: {
48
- requestsPerMinute: 10,
49
- requestsPerDay: 1000,
50
- maxConcurrentWorkflows: 1,
51
- maxStorageMB: 10,
52
- pluginExecutionsPerDay: 100,
53
- },
54
- pro: {
55
- requestsPerMinute: 100,
56
- requestsPerDay: 100_000,
57
- maxConcurrentWorkflows: 10,
58
- maxStorageMB: 1000,
59
- pluginExecutionsPerDay: 10_000,
60
- },
61
- enterprise: {
62
- requestsPerMinute: 1000,
63
- requestsPerDay: -1, // unlimited
64
- maxConcurrentWorkflows: 100,
65
- maxStorageMB: 10_000,
66
- pluginExecutionsPerDay: -1, // unlimited
67
- },
68
- };
69
-
70
- /**
71
- * Get default tenant ID from environment
72
- * @returns Tenant ID (defaults to 'default' for single-tenant deployments)
73
- */
74
- export function getDefaultTenantId(): string {
75
- return process.env.KB_TENANT_ID || 'default';
76
- }
77
-
78
- /**
79
- * Get default tenant tier from environment
80
- * @returns Tenant tier (defaults to 'free')
81
- */
82
- export function getDefaultTenantTier(): TenantTier {
83
- const tier = process.env.KB_TENANT_DEFAULT_TIER?.toLowerCase();
84
- if (tier === 'pro' || tier === 'enterprise') {
85
- return tier;
86
- }
87
- return 'free';
88
- }
89
-
90
- /**
91
- * Get tenant quotas for a tier
92
- * @param tier - Tenant tier
93
- * @returns Quotas for the tier
94
- */
95
- export function getQuotasForTier(tier: TenantTier): TenantQuotas {
96
- return { ...DEFAULT_QUOTAS[tier] };
97
- }