@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,484 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import { Plugin, PluginContext } from './types.js';
4
- import type { Logger } from '@objectstack/spec/contracts';
5
- import { z } from 'zod';
6
- import { PluginConfigValidator } from './security/plugin-config-validator.js';
7
-
8
- /**
9
- * Service Lifecycle Types
10
- * Defines how services are instantiated and managed
11
- */
12
- export enum ServiceLifecycle {
13
- /** Single instance shared across all requests */
14
- SINGLETON = 'singleton',
15
- /** New instance created for each request */
16
- TRANSIENT = 'transient',
17
- /** New instance per scope (e.g., per HTTP request) */
18
- SCOPED = 'scoped',
19
- }
20
-
21
- /**
22
- * Service Factory
23
- * Function that creates a service instance
24
- */
25
- export type ServiceFactory<T = any> = (ctx: PluginContext) => T | Promise<T>;
26
-
27
- /**
28
- * Service Registration Options
29
- */
30
- export interface ServiceRegistration {
31
- name: string;
32
- factory: ServiceFactory;
33
- lifecycle: ServiceLifecycle;
34
- dependencies?: string[];
35
- }
36
-
37
- /**
38
- * Plugin Metadata with Enhanced Features
39
- */
40
- export interface PluginMetadata extends Plugin {
41
- /** Semantic version (e.g., "1.0.0") */
42
- version: string;
43
-
44
- /** Configuration schema for validation */
45
- configSchema?: z.ZodSchema;
46
-
47
- /** Plugin signature for security verification */
48
- signature?: string;
49
-
50
- /** Plugin health check function */
51
- healthCheck?(): Promise<PluginHealthStatus>;
52
-
53
- /** Startup timeout in milliseconds (default: 30000) */
54
- startupTimeout?: number;
55
-
56
- /** Whether plugin supports hot reload */
57
- hotReloadable?: boolean;
58
- }
59
-
60
- /**
61
- * Plugin Health Status
62
- */
63
- export interface PluginHealthStatus {
64
- healthy: boolean;
65
- message?: string;
66
- details?: Record<string, any>;
67
- lastCheck?: Date;
68
- }
69
-
70
- /**
71
- * Plugin Load Result
72
- */
73
- export interface PluginLoadResult {
74
- success: boolean;
75
- plugin?: PluginMetadata;
76
- error?: Error;
77
- loadTime?: number;
78
- }
79
-
80
- /**
81
- * Plugin Startup Result
82
- */
83
- export interface PluginStartupResult {
84
- success: boolean;
85
- pluginName: string;
86
- startTime?: number;
87
- error?: Error;
88
- timedOut?: boolean;
89
- }
90
-
91
- /**
92
- * Version Compatibility Result
93
- */
94
- export interface VersionCompatibility {
95
- compatible: boolean;
96
- pluginVersion: string;
97
- requiredVersion?: string;
98
- message?: string;
99
- }
100
-
101
- /**
102
- * Enhanced Plugin Loader
103
- * Provides advanced plugin loading capabilities with validation, security, and lifecycle management
104
- */
105
- export class PluginLoader {
106
- private logger: Logger;
107
- private context?: PluginContext;
108
- private configValidator: PluginConfigValidator;
109
- private loadedPlugins: Map<string, PluginMetadata> = new Map();
110
- private serviceFactories: Map<string, ServiceRegistration> = new Map();
111
- private serviceInstances: Map<string, any> = new Map();
112
- private scopedServices: Map<string, Map<string, any>> = new Map();
113
- private creating: Set<string> = new Set();
114
-
115
- constructor(logger: Logger) {
116
- this.logger = logger;
117
- this.configValidator = new PluginConfigValidator(logger);
118
- }
119
-
120
- /**
121
- * Set the plugin context for service factories
122
- */
123
- setContext(context: PluginContext): void {
124
- this.context = context;
125
- }
126
-
127
- /**
128
- * Get a synchronous service instance if it exists (Sync Helper)
129
- */
130
- getServiceInstance<T>(name: string): T | undefined {
131
- return this.serviceInstances.get(name) as T;
132
- }
133
-
134
- /**
135
- * Load a plugin asynchronously with validation
136
- */
137
- async loadPlugin(plugin: Plugin): Promise<PluginLoadResult> {
138
- const startTime = Date.now();
139
-
140
- try {
141
- this.logger.info(`Loading plugin: ${plugin.name}`);
142
-
143
- // Convert to PluginMetadata
144
- const metadata = this.toPluginMetadata(plugin);
145
-
146
- // Validate plugin structure
147
- this.validatePluginStructure(metadata);
148
-
149
- // Check version compatibility
150
- const versionCheck = this.checkVersionCompatibility(metadata);
151
- if (!versionCheck.compatible) {
152
- throw new Error(`Version incompatible: ${versionCheck.message}`);
153
- }
154
-
155
- // Validate configuration if schema is provided
156
- if (metadata.configSchema) {
157
- this.validatePluginConfig(metadata);
158
- }
159
-
160
- // Verify signature if provided
161
- if (metadata.signature) {
162
- await this.verifyPluginSignature(metadata);
163
- }
164
-
165
- // Store loaded plugin
166
- this.loadedPlugins.set(metadata.name, metadata);
167
-
168
- const loadTime = Date.now() - startTime;
169
- this.logger.info(`Plugin loaded: ${plugin.name} (${loadTime}ms)`);
170
-
171
- return {
172
- success: true,
173
- plugin: metadata,
174
- loadTime,
175
- };
176
- } catch (error) {
177
- this.logger.error(`Failed to load plugin: ${plugin.name}`, error as Error);
178
- return {
179
- success: false,
180
- error: error as Error,
181
- loadTime: Date.now() - startTime,
182
- };
183
- }
184
- }
185
-
186
- /**
187
- * Register a service with factory function
188
- */
189
- registerServiceFactory(registration: ServiceRegistration): void {
190
- if (this.serviceFactories.has(registration.name)) {
191
- throw new Error(`Service factory '${registration.name}' already registered`);
192
- }
193
-
194
- this.serviceFactories.set(registration.name, registration);
195
- this.logger.debug(`Service factory registered: ${registration.name} (${registration.lifecycle})`);
196
- }
197
-
198
- /**
199
- * Get or create a service instance based on lifecycle type
200
- */
201
- async getService<T>(name: string, scopeId?: string): Promise<T> {
202
- const registration = this.serviceFactories.get(name);
203
-
204
- if (!registration) {
205
- // Fall back to static service instances
206
- const instance = this.serviceInstances.get(name);
207
- if (!instance) {
208
- throw new Error(`Service '${name}' not found`);
209
- }
210
- return instance as T;
211
- }
212
-
213
- switch (registration.lifecycle) {
214
- case ServiceLifecycle.SINGLETON:
215
- return await this.getSingletonService<T>(registration);
216
-
217
- case ServiceLifecycle.TRANSIENT:
218
- return await this.createTransientService<T>(registration);
219
-
220
- case ServiceLifecycle.SCOPED:
221
- if (!scopeId) {
222
- throw new Error(`Scope ID required for scoped service '${name}'`);
223
- }
224
- return await this.getScopedService<T>(registration, scopeId);
225
-
226
- default:
227
- throw new Error(`Unknown service lifecycle: ${registration.lifecycle}`);
228
- }
229
- }
230
-
231
- /**
232
- * Register a static service instance (legacy support)
233
- */
234
- registerService(name: string, service: any): void {
235
- if (this.serviceInstances.has(name)) {
236
- throw new Error(`Service '${name}' already registered`);
237
- }
238
- this.serviceInstances.set(name, service);
239
- }
240
-
241
- /**
242
- * Replace an existing service instance.
243
- * Used by optimization plugins to swap kernel internals.
244
- * @throws Error if service does not exist
245
- */
246
- replaceService(name: string, service: any): void {
247
- if (!this.hasService(name)) {
248
- throw new Error(`Service '${name}' not found`);
249
- }
250
- this.serviceInstances.set(name, service);
251
- }
252
-
253
- /**
254
- * Check if a service is registered (either as instance or factory)
255
- */
256
- hasService(name: string): boolean {
257
- return this.serviceInstances.has(name) || this.serviceFactories.has(name);
258
- }
259
-
260
- /**
261
- * Detect circular dependencies in service factories
262
- * Note: This only detects cycles in service dependencies, not plugin dependencies.
263
- * Plugin dependency cycles are detected in the kernel's resolveDependencies method.
264
- */
265
- detectCircularDependencies(): string[] {
266
- const cycles: string[] = [];
267
- const visited = new Set<string>();
268
- const visiting = new Set<string>();
269
-
270
- const visit = (serviceName: string, path: string[] = []) => {
271
- if (visiting.has(serviceName)) {
272
- const cycle = [...path, serviceName].join(' -> ');
273
- cycles.push(cycle);
274
- return;
275
- }
276
-
277
- if (visited.has(serviceName)) {
278
- return;
279
- }
280
-
281
- visiting.add(serviceName);
282
-
283
- const registration = this.serviceFactories.get(serviceName);
284
- if (registration?.dependencies) {
285
- for (const dep of registration.dependencies) {
286
- visit(dep, [...path, serviceName]);
287
- }
288
- }
289
-
290
- visiting.delete(serviceName);
291
- visited.add(serviceName);
292
- };
293
-
294
- for (const serviceName of this.serviceFactories.keys()) {
295
- visit(serviceName);
296
- }
297
-
298
- return cycles;
299
- }
300
-
301
- /**
302
- * Check plugin health
303
- */
304
- async checkPluginHealth(pluginName: string): Promise<PluginHealthStatus> {
305
- const plugin = this.loadedPlugins.get(pluginName);
306
-
307
- if (!plugin) {
308
- return {
309
- healthy: false,
310
- message: 'Plugin not found',
311
- lastCheck: new Date(),
312
- };
313
- }
314
-
315
- if (!plugin.healthCheck) {
316
- return {
317
- healthy: true,
318
- message: 'No health check defined',
319
- lastCheck: new Date(),
320
- };
321
- }
322
-
323
- try {
324
- const status = await plugin.healthCheck();
325
- return {
326
- ...status,
327
- lastCheck: new Date(),
328
- };
329
- } catch (error) {
330
- return {
331
- healthy: false,
332
- message: `Health check failed: ${(error as Error).message}`,
333
- lastCheck: new Date(),
334
- };
335
- }
336
- }
337
-
338
- /**
339
- * Clear scoped services for a scope
340
- */
341
- clearScope(scopeId: string): void {
342
- this.scopedServices.delete(scopeId);
343
- this.logger.debug(`Cleared scope: ${scopeId}`);
344
- }
345
-
346
- /**
347
- * Get all loaded plugins
348
- */
349
- getLoadedPlugins(): Map<string, PluginMetadata> {
350
- return new Map(this.loadedPlugins);
351
- }
352
-
353
- // Private helper methods
354
-
355
- private toPluginMetadata(plugin: Plugin): PluginMetadata {
356
- // Fix: Do not use object spread {...plugin} as it destroys the prototype chain for Class-based plugins.
357
- // Instead, cast the original object and inject default values if missing.
358
- const metadata = plugin as PluginMetadata;
359
-
360
- if (!metadata.version) {
361
- metadata.version = '0.0.0';
362
- }
363
-
364
- return metadata;
365
- }
366
-
367
- private validatePluginStructure(plugin: PluginMetadata): void {
368
- if (!plugin.name) {
369
- throw new Error('Plugin name is required');
370
- }
371
-
372
- if (!plugin.init) {
373
- throw new Error('Plugin init function is required');
374
- }
375
-
376
- if (!this.isValidSemanticVersion(plugin.version)) {
377
- throw new Error(`Invalid semantic version: ${plugin.version}`);
378
- }
379
- }
380
-
381
- private checkVersionCompatibility(plugin: PluginMetadata): VersionCompatibility {
382
- // Basic semantic version compatibility check
383
- // In a real implementation, this would check against kernel version
384
- const version = plugin.version;
385
-
386
- if (!this.isValidSemanticVersion(version)) {
387
- return {
388
- compatible: false,
389
- pluginVersion: version,
390
- message: 'Invalid semantic version format',
391
- };
392
- }
393
-
394
- return {
395
- compatible: true,
396
- pluginVersion: version,
397
- };
398
- }
399
-
400
- private isValidSemanticVersion(version: string): boolean {
401
- const semverRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/;
402
- return semverRegex.test(version);
403
- }
404
-
405
- private validatePluginConfig(plugin: PluginMetadata, config?: any): void {
406
- if (!plugin.configSchema) {
407
- return;
408
- }
409
-
410
- if (config === undefined) {
411
- // In loadPlugin, we often don't have the config yet.
412
- // We skip validation here or valid against empty object if schema allows?
413
- // For now, let's keep the logging behavior but note it's delegating
414
- this.logger.debug(`Plugin ${plugin.name} has configuration schema (config validation postponed)`);
415
- return;
416
- }
417
-
418
- this.configValidator.validatePluginConfig(plugin, config);
419
- }
420
-
421
- private async verifyPluginSignature(plugin: PluginMetadata): Promise<void> {
422
- if (!plugin.signature) {
423
- return;
424
- }
425
-
426
- // Plugin signature verification is now implemented in PluginSignatureVerifier
427
- // This is a placeholder that logs the verification would happen
428
- // The actual verification should be done by the caller with proper security config
429
- this.logger.debug(`Plugin ${plugin.name} has signature (use PluginSignatureVerifier for verification)`);
430
- }
431
-
432
- private async getSingletonService<T>(registration: ServiceRegistration): Promise<T> {
433
- let instance = this.serviceInstances.get(registration.name);
434
-
435
- if (!instance) {
436
- // Create instance (would need context)
437
- instance = await this.createServiceInstance(registration);
438
- this.serviceInstances.set(registration.name, instance);
439
- this.logger.debug(`Singleton service created: ${registration.name}`);
440
- }
441
-
442
- return instance as T;
443
- }
444
-
445
- private async createTransientService<T>(registration: ServiceRegistration): Promise<T> {
446
- const instance = await this.createServiceInstance(registration);
447
- this.logger.debug(`Transient service created: ${registration.name}`);
448
- return instance as T;
449
- }
450
-
451
- private async getScopedService<T>(registration: ServiceRegistration, scopeId: string): Promise<T> {
452
- if (!this.scopedServices.has(scopeId)) {
453
- this.scopedServices.set(scopeId, new Map());
454
- }
455
-
456
- const scope = this.scopedServices.get(scopeId)!;
457
- let instance = scope.get(registration.name);
458
-
459
- if (!instance) {
460
- instance = await this.createServiceInstance(registration);
461
- scope.set(registration.name, instance);
462
- this.logger.debug(`Scoped service created: ${registration.name} (scope: ${scopeId})`);
463
- }
464
-
465
- return instance as T;
466
- }
467
-
468
- private async createServiceInstance(registration: ServiceRegistration): Promise<any> {
469
- if (!this.context) {
470
- throw new Error(`[PluginLoader] Context not set - cannot create service '${registration.name}'`);
471
- }
472
-
473
- if (this.creating.has(registration.name)) {
474
- throw new Error(`Circular dependency detected: ${Array.from(this.creating).join(' -> ')} -> ${registration.name}`);
475
- }
476
-
477
- this.creating.add(registration.name);
478
- try {
479
- return await registration.factory(this.context);
480
- } finally {
481
- this.creating.delete(registration.name);
482
- }
483
- }
484
- }
package/src/qa/adapter.ts DELETED
@@ -1,16 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import { QA } from '@objectstack/spec';
4
-
5
- /**
6
- * Interface for executing test actions against a target system.
7
- * The target could be a local Kernel instance or a remote API.
8
- */
9
- export interface TestExecutionAdapter {
10
- /**
11
- * Execute a single test action.
12
- * @param action The action to perform (create_record, api_call, etc.)
13
- * @returns The result of the action (e.g. created record, API response)
14
- */
15
- execute(action: QA.TestAction, context: Record<string, unknown>): Promise<unknown>;
16
- }
@@ -1,116 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import { QA } from '@objectstack/spec';
4
- import { TestExecutionAdapter } from './adapter.js';
5
-
6
- export class HttpTestAdapter implements TestExecutionAdapter {
7
- constructor(private baseUrl: string, private authToken?: string) {}
8
-
9
- async execute(action: QA.TestAction, _context: Record<string, unknown>): Promise<unknown> {
10
- const headers: Record<string, string> = {
11
- 'Content-Type': 'application/json',
12
- };
13
- if (this.authToken) {
14
- headers['Authorization'] = `Bearer ${this.authToken}`;
15
- }
16
- // If action.user is specified, maybe add a specific header for impersonation if supported?
17
- if (action.user) {
18
- headers['X-Run-As'] = action.user;
19
- }
20
-
21
- switch (action.type) {
22
- case 'create_record':
23
- return this.createRecord(action.target, action.payload || {}, headers);
24
- case 'update_record':
25
- return this.updateRecord(action.target, action.payload || {}, headers);
26
- case 'delete_record':
27
- return this.deleteRecord(action.target, action.payload || {}, headers);
28
- case 'read_record':
29
- return this.readRecord(action.target, action.payload || {}, headers);
30
- case 'query_records':
31
- return this.queryRecords(action.target, action.payload || {}, headers);
32
- case 'api_call':
33
- return this.rawApiCall(action.target, action.payload || {}, headers);
34
- case 'wait':
35
- const ms = Number(action.payload?.duration || 1000);
36
- return new Promise(resolve => setTimeout(() => resolve({ waited: ms }), ms));
37
- default:
38
- throw new Error(`Unsupported action type in HttpAdapter: ${action.type}`);
39
- }
40
- }
41
-
42
- private async createRecord(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {
43
- const response = await fetch(`${this.baseUrl}/api/data/${objectName}`, {
44
- method: 'POST',
45
- headers,
46
- body: JSON.stringify(data)
47
- });
48
- return this.handleResponse(response);
49
- }
50
-
51
- private async updateRecord(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {
52
- const id = data.id;
53
- if (!id) throw new Error('Update record requires id in payload');
54
- const response = await fetch(`${this.baseUrl}/api/data/${objectName}/${id}`, {
55
- method: 'PUT',
56
- headers,
57
- body: JSON.stringify(data)
58
- });
59
- return this.handleResponse(response);
60
- }
61
-
62
- private async deleteRecord(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {
63
- const id = data.id;
64
- if (!id) throw new Error('Delete record requires id in payload');
65
- const response = await fetch(`${this.baseUrl}/api/data/${objectName}/${id}`, {
66
- method: 'DELETE',
67
- headers
68
- });
69
- return this.handleResponse(response);
70
- }
71
-
72
- private async readRecord(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {
73
- const id = data.id;
74
- if (!id) throw new Error('Read record requires id in payload');
75
- const response = await fetch(`${this.baseUrl}/api/data/${objectName}/${id}`, {
76
- method: 'GET',
77
- headers
78
- });
79
- return this.handleResponse(response);
80
- }
81
-
82
- private async queryRecords(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {
83
- // Assuming query via POST or GraphQL-like endpoint
84
- const response = await fetch(`${this.baseUrl}/api/data/${objectName}/query`, {
85
- method: 'POST',
86
- headers,
87
- body: JSON.stringify(data)
88
- });
89
- return this.handleResponse(response);
90
- }
91
-
92
- private async rawApiCall(endpoint: string, data: Record<string, unknown>, headers: Record<string, string>) {
93
- const method = (data.method as string) || 'GET';
94
- const body = data.body ? JSON.stringify(data.body) : undefined;
95
- const url = endpoint.startsWith('http') ? endpoint : `${this.baseUrl}${endpoint}`;
96
-
97
- const response = await fetch(url, {
98
- method,
99
- headers,
100
- body
101
- });
102
- return this.handleResponse(response);
103
- }
104
-
105
- private async handleResponse(response: Response) {
106
- if (!response.ok) {
107
- const text = await response.text();
108
- throw new Error(`HTTP Error ${response.status}: ${text}`);
109
- }
110
- const contentType = response.headers.get('content-type');
111
- if (contentType && contentType.includes('application/json')) {
112
- return response.json();
113
- }
114
- return response.text();
115
- }
116
- }
package/src/qa/index.ts DELETED
@@ -1,5 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- export * from './adapter.js';
4
- export * from './runner.js';
5
- export * from './http-adapter.js';