@objectstack/core 4.0.3 → 4.0.5

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.
Files changed (75) hide show
  1. package/README.md +95 -10
  2. package/dist/index.cjs +169 -507
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +24 -223
  5. package/dist/index.d.ts +24 -223
  6. package/dist/index.js +175 -505
  7. package/dist/index.js.map +1 -1
  8. package/dist/logger.cjs +177 -0
  9. package/dist/logger.cjs.map +1 -0
  10. package/dist/logger.d.cts +26 -0
  11. package/dist/logger.d.ts +26 -0
  12. package/dist/logger.js +158 -0
  13. package/dist/logger.js.map +1 -0
  14. package/package.json +36 -15
  15. package/.turbo/turbo-build.log +0 -22
  16. package/ADVANCED_FEATURES.md +0 -380
  17. package/API_REGISTRY.md +0 -392
  18. package/CHANGELOG.md +0 -465
  19. package/PHASE2_IMPLEMENTATION.md +0 -388
  20. package/REFACTORING_SUMMARY.md +0 -40
  21. package/examples/api-registry-example.ts +0 -559
  22. package/examples/kernel-features-example.ts +0 -311
  23. package/examples/phase2-integration.ts +0 -357
  24. package/src/api-registry-plugin.test.ts +0 -393
  25. package/src/api-registry-plugin.ts +0 -89
  26. package/src/api-registry.test.ts +0 -1089
  27. package/src/api-registry.ts +0 -739
  28. package/src/contracts/data-engine.ts +0 -57
  29. package/src/contracts/http-server.ts +0 -151
  30. package/src/contracts/logger.ts +0 -72
  31. package/src/dependency-resolver.test.ts +0 -287
  32. package/src/dependency-resolver.ts +0 -390
  33. package/src/fallbacks/fallbacks.test.ts +0 -281
  34. package/src/fallbacks/index.ts +0 -26
  35. package/src/fallbacks/memory-cache.ts +0 -34
  36. package/src/fallbacks/memory-i18n.ts +0 -112
  37. package/src/fallbacks/memory-job.ts +0 -23
  38. package/src/fallbacks/memory-metadata.ts +0 -50
  39. package/src/fallbacks/memory-queue.ts +0 -28
  40. package/src/health-monitor.test.ts +0 -81
  41. package/src/health-monitor.ts +0 -318
  42. package/src/hot-reload.ts +0 -382
  43. package/src/index.ts +0 -50
  44. package/src/kernel-base.ts +0 -273
  45. package/src/kernel.test.ts +0 -624
  46. package/src/kernel.ts +0 -631
  47. package/src/lite-kernel.test.ts +0 -248
  48. package/src/lite-kernel.ts +0 -137
  49. package/src/logger.test.ts +0 -116
  50. package/src/logger.ts +0 -355
  51. package/src/namespace-resolver.test.ts +0 -130
  52. package/src/namespace-resolver.ts +0 -188
  53. package/src/package-manager.test.ts +0 -225
  54. package/src/package-manager.ts +0 -428
  55. package/src/plugin-loader.test.ts +0 -421
  56. package/src/plugin-loader.ts +0 -484
  57. package/src/qa/adapter.ts +0 -16
  58. package/src/qa/http-adapter.ts +0 -116
  59. package/src/qa/index.ts +0 -5
  60. package/src/qa/runner.ts +0 -189
  61. package/src/security/index.ts +0 -50
  62. package/src/security/permission-manager.test.ts +0 -256
  63. package/src/security/permission-manager.ts +0 -338
  64. package/src/security/plugin-config-validator.test.ts +0 -276
  65. package/src/security/plugin-config-validator.ts +0 -193
  66. package/src/security/plugin-permission-enforcer.test.ts +0 -251
  67. package/src/security/plugin-permission-enforcer.ts +0 -436
  68. package/src/security/plugin-signature-verifier.ts +0 -403
  69. package/src/security/sandbox-runtime.ts +0 -462
  70. package/src/security/security-scanner.ts +0 -367
  71. package/src/types.ts +0 -120
  72. package/src/utils/env.test.ts +0 -62
  73. package/src/utils/env.ts +0 -53
  74. package/tsconfig.json +0 -10
  75. package/vitest.config.ts +0 -10
@@ -1,338 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import type {
4
- Permission,
5
- PermissionSet,
6
- PermissionAction,
7
- ResourceType
8
- } from '@objectstack/spec/kernel';
9
- import type { ObjectLogger } from '../logger.js';
10
-
11
- /**
12
- * Permission Grant
13
- * Represents a granted permission at runtime
14
- */
15
- export interface PermissionGrant {
16
- permissionId: string;
17
- pluginId: string;
18
- grantedAt: Date;
19
- grantedBy?: string;
20
- expiresAt?: Date;
21
- conditions?: Record<string, any>;
22
- }
23
-
24
- /**
25
- * Permission Check Result
26
- */
27
- export interface PermissionCheckResult {
28
- allowed: boolean;
29
- reason?: string;
30
- requiredPermission?: string;
31
- grantedPermissions?: string[];
32
- }
33
-
34
- /**
35
- * Plugin Permission Manager
36
- *
37
- * Manages fine-grained permissions for plugin security and access control
38
- */
39
- export class PluginPermissionManager {
40
- private logger: ObjectLogger;
41
-
42
- // Plugin permission definitions
43
- private permissionSets = new Map<string, PermissionSet>();
44
-
45
- // Granted permissions (pluginId -> Set of permission IDs)
46
- private grants = new Map<string, Set<string>>();
47
-
48
- // Permission grant details
49
- private grantDetails = new Map<string, PermissionGrant>();
50
-
51
- constructor(logger: ObjectLogger) {
52
- this.logger = logger.child({ component: 'PermissionManager' });
53
- }
54
-
55
- /**
56
- * Register permission requirements for a plugin
57
- */
58
- registerPermissions(pluginId: string, permissionSet: PermissionSet): void {
59
- this.permissionSets.set(pluginId, permissionSet);
60
-
61
- this.logger.info('Permissions registered for plugin', {
62
- pluginId,
63
- permissionCount: permissionSet.permissions.length
64
- });
65
- }
66
-
67
- /**
68
- * Grant a permission to a plugin
69
- */
70
- grantPermission(
71
- pluginId: string,
72
- permissionId: string,
73
- grantedBy?: string,
74
- expiresAt?: Date
75
- ): void {
76
- // Verify permission exists in plugin's declared permissions
77
- const permissionSet = this.permissionSets.get(pluginId);
78
- if (!permissionSet) {
79
- throw new Error(`No permissions registered for plugin: ${pluginId}`);
80
- }
81
-
82
- const permission = permissionSet.permissions.find(p => p.id === permissionId);
83
- if (!permission) {
84
- throw new Error(`Permission ${permissionId} not declared by plugin ${pluginId}`);
85
- }
86
-
87
- // Create grant
88
- if (!this.grants.has(pluginId)) {
89
- this.grants.set(pluginId, new Set());
90
- }
91
- this.grants.get(pluginId)!.add(permissionId);
92
-
93
- // Store grant details
94
- const grantKey = `${pluginId}:${permissionId}`;
95
- this.grantDetails.set(grantKey, {
96
- permissionId,
97
- pluginId,
98
- grantedAt: new Date(),
99
- grantedBy,
100
- expiresAt,
101
- });
102
-
103
- this.logger.info('Permission granted', {
104
- pluginId,
105
- permissionId,
106
- grantedBy
107
- });
108
- }
109
-
110
- /**
111
- * Revoke a permission from a plugin
112
- */
113
- revokePermission(pluginId: string, permissionId: string): void {
114
- const grants = this.grants.get(pluginId);
115
- if (grants) {
116
- grants.delete(permissionId);
117
-
118
- const grantKey = `${pluginId}:${permissionId}`;
119
- this.grantDetails.delete(grantKey);
120
-
121
- this.logger.info('Permission revoked', { pluginId, permissionId });
122
- }
123
- }
124
-
125
- /**
126
- * Grant all permissions for a plugin
127
- */
128
- grantAllPermissions(pluginId: string, grantedBy?: string): void {
129
- const permissionSet = this.permissionSets.get(pluginId);
130
- if (!permissionSet) {
131
- throw new Error(`No permissions registered for plugin: ${pluginId}`);
132
- }
133
-
134
- for (const permission of permissionSet.permissions) {
135
- this.grantPermission(pluginId, permission.id, grantedBy);
136
- }
137
-
138
- this.logger.info('All permissions granted', { pluginId, grantedBy });
139
- }
140
-
141
- /**
142
- * Check if a plugin has a specific permission
143
- */
144
- hasPermission(pluginId: string, permissionId: string): boolean {
145
- const grants = this.grants.get(pluginId);
146
- if (!grants) {
147
- return false;
148
- }
149
-
150
- // Check if granted
151
- if (!grants.has(permissionId)) {
152
- return false;
153
- }
154
-
155
- // Check expiration
156
- const grantKey = `${pluginId}:${permissionId}`;
157
- const grantDetails = this.grantDetails.get(grantKey);
158
- if (grantDetails?.expiresAt && grantDetails.expiresAt < new Date()) {
159
- this.revokePermission(pluginId, permissionId);
160
- return false;
161
- }
162
-
163
- return true;
164
- }
165
-
166
- /**
167
- * Check if plugin can perform an action on a resource
168
- */
169
- checkAccess(
170
- pluginId: string,
171
- resource: ResourceType,
172
- action: PermissionAction,
173
- resourceId?: string
174
- ): PermissionCheckResult {
175
- const permissionSet = this.permissionSets.get(pluginId);
176
- if (!permissionSet) {
177
- return {
178
- allowed: false,
179
- reason: 'No permissions registered for plugin',
180
- };
181
- }
182
-
183
- // Find matching permissions
184
- const matchingPermissions = permissionSet.permissions.filter(p => {
185
- // Check resource type
186
- if (p.resource !== resource) {
187
- return false;
188
- }
189
-
190
- // Check action
191
- if (!p.actions.includes(action)) {
192
- return false;
193
- }
194
-
195
- // Check resource filter if specified
196
- if (resourceId && p.filter?.resourceIds) {
197
- if (!p.filter.resourceIds.includes(resourceId)) {
198
- return false;
199
- }
200
- }
201
-
202
- return true;
203
- });
204
-
205
- if (matchingPermissions.length === 0) {
206
- return {
207
- allowed: false,
208
- reason: `No permission found for ${action} on ${resource}`,
209
- };
210
- }
211
-
212
- // Check if any matching permission is granted
213
- const grantedPermissions = matchingPermissions.filter(p =>
214
- this.hasPermission(pluginId, p.id)
215
- );
216
-
217
- if (grantedPermissions.length === 0) {
218
- return {
219
- allowed: false,
220
- reason: 'Required permissions not granted',
221
- requiredPermission: matchingPermissions[0].id,
222
- };
223
- }
224
-
225
- return {
226
- allowed: true,
227
- grantedPermissions: grantedPermissions.map(p => p.id),
228
- };
229
- }
230
-
231
- /**
232
- * Get all permissions for a plugin
233
- */
234
- getPluginPermissions(pluginId: string): Permission[] {
235
- const permissionSet = this.permissionSets.get(pluginId);
236
- return permissionSet?.permissions || [];
237
- }
238
-
239
- /**
240
- * Get granted permissions for a plugin
241
- */
242
- getGrantedPermissions(pluginId: string): string[] {
243
- const grants = this.grants.get(pluginId);
244
- return grants ? Array.from(grants) : [];
245
- }
246
-
247
- /**
248
- * Get required but not granted permissions
249
- */
250
- getMissingPermissions(pluginId: string): Permission[] {
251
- const permissionSet = this.permissionSets.get(pluginId);
252
- if (!permissionSet) {
253
- return [];
254
- }
255
-
256
- const granted = this.grants.get(pluginId) || new Set();
257
-
258
- return permissionSet.permissions.filter(p =>
259
- p.required && !granted.has(p.id)
260
- );
261
- }
262
-
263
- /**
264
- * Check if all required permissions are granted
265
- */
266
- hasAllRequiredPermissions(pluginId: string): boolean {
267
- return this.getMissingPermissions(pluginId).length === 0;
268
- }
269
-
270
- /**
271
- * Get permission grant details
272
- */
273
- getGrantDetails(pluginId: string, permissionId: string): PermissionGrant | undefined {
274
- const grantKey = `${pluginId}:${permissionId}`;
275
- return this.grantDetails.get(grantKey);
276
- }
277
-
278
- /**
279
- * Validate permission against scope constraints
280
- */
281
- validatePermissionScope(
282
- permission: Permission,
283
- context: {
284
- tenantId?: string;
285
- userId?: string;
286
- resourceId?: string;
287
- }
288
- ): boolean {
289
- switch (permission.scope) {
290
- case 'global':
291
- return true;
292
-
293
- case 'tenant':
294
- return !!context.tenantId;
295
-
296
- case 'user':
297
- return !!context.userId;
298
-
299
- case 'resource':
300
- return !!context.resourceId;
301
-
302
- case 'plugin':
303
- return true;
304
-
305
- default:
306
- return false;
307
- }
308
- }
309
-
310
- /**
311
- * Clear all permissions for a plugin
312
- */
313
- clearPluginPermissions(pluginId: string): void {
314
- this.permissionSets.delete(pluginId);
315
-
316
- const grants = this.grants.get(pluginId);
317
- if (grants) {
318
- for (const permissionId of grants) {
319
- const grantKey = `${pluginId}:${permissionId}`;
320
- this.grantDetails.delete(grantKey);
321
- }
322
- this.grants.delete(pluginId);
323
- }
324
-
325
- this.logger.info('All permissions cleared', { pluginId });
326
- }
327
-
328
- /**
329
- * Shutdown permission manager
330
- */
331
- shutdown(): void {
332
- this.permissionSets.clear();
333
- this.grants.clear();
334
- this.grantDetails.clear();
335
-
336
- this.logger.info('Permission manager shutdown complete');
337
- }
338
- }
@@ -1,276 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { z } from 'zod';
3
- import { PluginConfigValidator } from './plugin-config-validator.js';
4
- import { createLogger } from '../logger.js';
5
- import type { PluginMetadata } from '../plugin-loader.js';
6
-
7
- describe('PluginConfigValidator', () => {
8
- let validator: PluginConfigValidator;
9
- let logger: ReturnType<typeof createLogger>;
10
-
11
- beforeEach(() => {
12
- logger = createLogger({ level: 'error' });
13
- validator = new PluginConfigValidator(logger);
14
- });
15
-
16
- describe('validatePluginConfig', () => {
17
- it('should validate valid configuration', () => {
18
- const configSchema = z.object({
19
- port: z.number().min(1000).max(65535),
20
- host: z.string(),
21
- debug: z.boolean().default(false),
22
- });
23
-
24
- const plugin: PluginMetadata = {
25
- name: 'com.test.plugin',
26
- version: '1.0.0',
27
- configSchema,
28
- init: async () => {},
29
- };
30
-
31
- const config = {
32
- port: 3000,
33
- host: 'localhost',
34
- debug: true,
35
- };
36
-
37
- const validatedConfig = validator.validatePluginConfig(plugin, config);
38
-
39
- expect(validatedConfig).toEqual(config);
40
- });
41
-
42
- it('should apply defaults for missing optional fields', () => {
43
- const configSchema = z.object({
44
- port: z.number().default(3000),
45
- host: z.string().default('localhost'),
46
- debug: z.boolean().default(false),
47
- });
48
-
49
- const plugin: PluginMetadata = {
50
- name: 'com.test.plugin',
51
- version: '1.0.0',
52
- configSchema,
53
- init: async () => {},
54
- };
55
-
56
- const config = {
57
- port: 8080,
58
- };
59
-
60
- const validatedConfig = validator.validatePluginConfig(plugin, config);
61
-
62
- expect(validatedConfig).toEqual({
63
- port: 8080,
64
- host: 'localhost',
65
- debug: false,
66
- });
67
- });
68
-
69
- it('should throw error for invalid configuration', () => {
70
- const configSchema = z.object({
71
- port: z.number().min(1000).max(65535),
72
- host: z.string(),
73
- });
74
-
75
- const plugin: PluginMetadata = {
76
- name: 'com.test.plugin',
77
- version: '1.0.0',
78
- configSchema,
79
- init: async () => {},
80
- };
81
-
82
- const config = {
83
- port: 100, // Invalid: < 1000
84
- host: 'localhost',
85
- };
86
-
87
- expect(() => validator.validatePluginConfig(plugin, config)).toThrow();
88
- });
89
-
90
- it('should provide detailed error messages', () => {
91
- const configSchema = z.object({
92
- port: z.number().min(1000),
93
- host: z.string().min(1),
94
- });
95
-
96
- const plugin: PluginMetadata = {
97
- name: 'com.test.plugin',
98
- version: '1.0.0',
99
- configSchema,
100
- init: async () => {},
101
- };
102
-
103
- const config = {
104
- port: 100,
105
- host: '',
106
- };
107
-
108
- try {
109
- validator.validatePluginConfig(plugin, config);
110
- expect.fail('Should have thrown validation error');
111
- } catch (error) {
112
- const errorMessage = (error as Error).message;
113
- expect(errorMessage).toContain('com.test.plugin');
114
- expect(errorMessage).toContain('port');
115
- expect(errorMessage).toContain('host');
116
- }
117
- });
118
-
119
- it('should skip validation when no schema is provided', () => {
120
- const plugin: PluginMetadata = {
121
- name: 'com.test.plugin',
122
- version: '1.0.0',
123
- init: async () => {},
124
- };
125
-
126
- const config = { anything: 'goes' };
127
-
128
- const validatedConfig = validator.validatePluginConfig(plugin, config);
129
-
130
- expect(validatedConfig).toEqual(config);
131
- });
132
- });
133
-
134
- describe('validatePartialConfig', () => {
135
- it('should validate partial configuration', () => {
136
- const configSchema = z.object({
137
- port: z.number().min(1000),
138
- host: z.string(),
139
- debug: z.boolean(),
140
- });
141
-
142
- const plugin: PluginMetadata = {
143
- name: 'com.test.plugin',
144
- version: '1.0.0',
145
- configSchema,
146
- init: async () => {},
147
- };
148
-
149
- const partialConfig = {
150
- port: 8080,
151
- };
152
-
153
- const validatedConfig = validator.validatePartialConfig(plugin, partialConfig);
154
-
155
- expect(validatedConfig).toEqual({ port: 8080 });
156
- });
157
- });
158
-
159
- describe('getDefaultConfig', () => {
160
- it('should extract default configuration', () => {
161
- const configSchema = z.object({
162
- port: z.number().default(3000),
163
- host: z.string().default('localhost'),
164
- debug: z.boolean().default(false),
165
- });
166
-
167
- const plugin: PluginMetadata = {
168
- name: 'com.test.plugin',
169
- version: '1.0.0',
170
- configSchema,
171
- init: async () => {},
172
- };
173
-
174
- const defaults = validator.getDefaultConfig(plugin);
175
-
176
- expect(defaults).toEqual({
177
- port: 3000,
178
- host: 'localhost',
179
- debug: false,
180
- });
181
- });
182
-
183
- it('should return undefined when schema requires fields', () => {
184
- const configSchema = z.object({
185
- port: z.number(),
186
- host: z.string(),
187
- });
188
-
189
- const plugin: PluginMetadata = {
190
- name: 'com.test.plugin',
191
- version: '1.0.0',
192
- configSchema,
193
- init: async () => {},
194
- };
195
-
196
- const defaults = validator.getDefaultConfig(plugin);
197
-
198
- expect(defaults).toBeUndefined();
199
- });
200
- });
201
-
202
- describe('isConfigValid', () => {
203
- it('should return true for valid config', () => {
204
- const configSchema = z.object({
205
- port: z.number(),
206
- });
207
-
208
- const plugin: PluginMetadata = {
209
- name: 'com.test.plugin',
210
- version: '1.0.0',
211
- configSchema,
212
- init: async () => {},
213
- };
214
-
215
- const isValid = validator.isConfigValid(plugin, { port: 3000 });
216
-
217
- expect(isValid).toBe(true);
218
- });
219
-
220
- it('should return false for invalid config', () => {
221
- const configSchema = z.object({
222
- port: z.number(),
223
- });
224
-
225
- const plugin: PluginMetadata = {
226
- name: 'com.test.plugin',
227
- version: '1.0.0',
228
- configSchema,
229
- init: async () => {},
230
- };
231
-
232
- const isValid = validator.isConfigValid(plugin, { port: 'invalid' });
233
-
234
- expect(isValid).toBe(false);
235
- });
236
- });
237
-
238
- describe('getConfigErrors', () => {
239
- it('should return errors for invalid config', () => {
240
- const configSchema = z.object({
241
- port: z.number().min(1000),
242
- host: z.string().min(1),
243
- });
244
-
245
- const plugin: PluginMetadata = {
246
- name: 'com.test.plugin',
247
- version: '1.0.0',
248
- configSchema,
249
- init: async () => {},
250
- };
251
-
252
- const errors = validator.getConfigErrors(plugin, { port: 100, host: '' });
253
-
254
- expect(errors).toHaveLength(2);
255
- expect(errors[0].path).toBe('port');
256
- expect(errors[1].path).toBe('host');
257
- });
258
-
259
- it('should return empty array for valid config', () => {
260
- const configSchema = z.object({
261
- port: z.number(),
262
- });
263
-
264
- const plugin: PluginMetadata = {
265
- name: 'com.test.plugin',
266
- version: '1.0.0',
267
- configSchema,
268
- init: async () => {},
269
- };
270
-
271
- const errors = validator.getConfigErrors(plugin, { port: 3000 });
272
-
273
- expect(errors).toEqual([]);
274
- });
275
- });
276
- });