@object-ui/data-objectstack 0.5.0 → 3.0.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": "@object-ui/data-objectstack",
3
- "version": "0.5.0",
3
+ "version": "3.0.0",
4
4
  "description": "ObjectStack Data Adapter for Object UI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,14 +20,14 @@
20
20
  "README.md"
21
21
  ],
22
22
  "dependencies": {
23
- "@objectstack/client": "^0.9.1",
24
- "@object-ui/core": "0.5.0",
25
- "@object-ui/types": "0.5.0"
23
+ "@objectstack/client": "^3.0.2",
24
+ "@object-ui/core": "3.0.0",
25
+ "@object-ui/types": "3.0.0"
26
26
  },
27
27
  "devDependencies": {
28
- "tsup": "^8.0.1",
29
- "typescript": "^5.3.3",
30
- "vitest": "^1.2.0"
28
+ "tsup": "^8.5.1",
29
+ "typescript": "^5.9.3",
30
+ "vitest": "^4.0.18"
31
31
  },
32
32
  "publishConfig": {
33
33
  "access": "public"
@@ -204,6 +204,28 @@ export class MetadataCache {
204
204
  };
205
205
  }
206
206
 
207
+ /**
208
+ * Get a cached value synchronously without triggering a fetch.
209
+ * Returns undefined if not in cache or expired.
210
+ */
211
+ getCachedSync<V = unknown>(key: string): V | undefined {
212
+ const entry = this.cache.get(key);
213
+ if (!entry) return undefined;
214
+
215
+ // Check TTL
216
+ if (this.ttl > 0 && Date.now() - entry.timestamp > this.ttl) {
217
+ this.cache.delete(key);
218
+ return undefined;
219
+ }
220
+
221
+ // Update access order for LRU
222
+ this.cache.delete(key);
223
+ this.cache.set(key, entry);
224
+ this.stats.hits++;
225
+
226
+ return entry.data as V;
227
+ }
228
+
207
229
  /**
208
230
  * Check if a key exists in the cache (and is not expired)
209
231
  *
package/src/cloud.ts ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ /**
10
+ * Cloud namespace integration for @objectstack/spec v3.0.0
11
+ * Replaces the legacy Hub namespace for cloud deployment, hosting, and marketplace schemas.
12
+ */
13
+
14
+ export interface CloudDeploymentConfig {
15
+ /** Target environment */
16
+ environment: 'development' | 'staging' | 'production';
17
+ /** Cloud region */
18
+ region?: string;
19
+ /** Auto-scaling configuration */
20
+ scaling?: {
21
+ minInstances: number;
22
+ maxInstances: number;
23
+ targetCPU?: number;
24
+ };
25
+ /** Environment variables */
26
+ envVars?: Record<string, string>;
27
+ }
28
+
29
+ export interface CloudHostingConfig {
30
+ /** Custom domain */
31
+ customDomain?: string;
32
+ /** SSL configuration */
33
+ ssl?: {
34
+ enabled: boolean;
35
+ autoRenew: boolean;
36
+ };
37
+ /** CDN configuration */
38
+ cdn?: {
39
+ enabled: boolean;
40
+ regions?: string[];
41
+ };
42
+ }
43
+
44
+ export interface CloudMarketplaceEntry {
45
+ /** Plugin or app ID */
46
+ id: string;
47
+ /** Display name */
48
+ name: string;
49
+ /** Description */
50
+ description: string;
51
+ /** Version */
52
+ version: string;
53
+ /** Author */
54
+ author: string;
55
+ /** Category */
56
+ category: string;
57
+ /** Tags */
58
+ tags: string[];
59
+ /** Rating (1-5) */
60
+ rating?: number;
61
+ /** Install count */
62
+ installCount?: number;
63
+ /** Price (0 = free) */
64
+ price?: number;
65
+ }
66
+
67
+ /**
68
+ * Cloud operations helper for ObjectStack adapter.
69
+ * Provides methods for cloud deployment, hosting, and marketplace operations.
70
+ */
71
+ export class CloudOperations {
72
+ constructor(private getClient: () => any) {}
73
+
74
+ /**
75
+ * Deploy an application to the cloud.
76
+ */
77
+ async deploy(appId: string, config: CloudDeploymentConfig): Promise<{ deploymentId: string; status: string }> {
78
+ const client = this.getClient();
79
+ const result = await client.cloud?.deploy?.(appId, config);
80
+ return result ?? { deploymentId: `deploy-${Date.now()}`, status: 'pending' };
81
+ }
82
+
83
+ /**
84
+ * Get deployment status.
85
+ */
86
+ async getDeploymentStatus(deploymentId: string): Promise<{ status: string; url?: string }> {
87
+ const client = this.getClient();
88
+ const result = await client.cloud?.getDeployment?.(deploymentId);
89
+ return result ?? { status: 'unknown' };
90
+ }
91
+
92
+ /**
93
+ * Search marketplace entries.
94
+ */
95
+ async searchMarketplace(query?: string, category?: string): Promise<CloudMarketplaceEntry[]> {
96
+ const client = this.getClient();
97
+ const result = await client.cloud?.marketplace?.search?.({ query, category });
98
+ return result?.items ?? [];
99
+ }
100
+
101
+ /**
102
+ * Install a marketplace plugin.
103
+ */
104
+ async installPlugin(pluginId: string): Promise<{ success: boolean }> {
105
+ const client = this.getClient();
106
+ const result = await client.cloud?.marketplace?.install?.(pluginId);
107
+ return result ?? { success: false };
108
+ }
109
+ }
@@ -98,3 +98,44 @@ describe('Batch Progress Events', () => {
98
98
  unsubscribe();
99
99
  });
100
100
  });
101
+
102
+ describe('getDiscovery', () => {
103
+ it('should return discoveryInfo from the underlying client after connect', async () => {
104
+ const mockDiscovery = {
105
+ name: 'test-server',
106
+ version: '1.0.0',
107
+ services: {
108
+ auth: { enabled: false, status: 'unavailable' },
109
+ data: { enabled: true, status: 'available' },
110
+ },
111
+ };
112
+
113
+ const adapter = new ObjectStackAdapter({
114
+ baseUrl: 'http://localhost:3000',
115
+ autoReconnect: false,
116
+ });
117
+
118
+ // Mock the underlying client's connect method and discoveryInfo property
119
+ const client = adapter.getClient();
120
+ vi.spyOn(client, 'connect').mockResolvedValue(mockDiscovery as any);
121
+ // Simulate what connect() does: sets discoveryInfo
122
+ (client as any).discoveryInfo = mockDiscovery;
123
+
124
+ const discovery = await adapter.getDiscovery();
125
+ expect(discovery).toEqual(mockDiscovery);
126
+ expect((discovery as any)?.services?.auth?.enabled).toBe(false);
127
+ });
128
+
129
+ it('should return null when connection fails', async () => {
130
+ const adapter = new ObjectStackAdapter({
131
+ baseUrl: 'http://localhost:3000',
132
+ autoReconnect: false,
133
+ });
134
+
135
+ const client = adapter.getClient();
136
+ vi.spyOn(client, 'connect').mockRejectedValue(new Error('Connection failed'));
137
+
138
+ const discovery = await adapter.getDiscovery();
139
+ expect(discovery).toBeNull();
140
+ });
141
+ });
@@ -0,0 +1,115 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ /**
10
+ * Contracts module integration for @objectstack/spec v3.0.0
11
+ * Provides plugin contract validation and marketplace publishing utilities.
12
+ */
13
+
14
+ export interface PluginContract {
15
+ /** Plugin name */
16
+ name: string;
17
+ /** Plugin version */
18
+ version: string;
19
+ /** Required peer dependencies */
20
+ peerDependencies?: Record<string, string>;
21
+ /** Exported component types */
22
+ exports: PluginExport[];
23
+ /** Required permissions */
24
+ permissions?: string[];
25
+ /** API surface contract */
26
+ api?: PluginAPIContract;
27
+ }
28
+
29
+ export interface PluginExport {
30
+ /** Export name */
31
+ name: string;
32
+ /** Export type */
33
+ type: 'component' | 'hook' | 'utility' | 'provider';
34
+ /** Description */
35
+ description?: string;
36
+ }
37
+
38
+ export interface PluginAPIContract {
39
+ /** Consumed data sources */
40
+ dataSources?: string[];
41
+ /** Required object schemas */
42
+ requiredSchemas?: string[];
43
+ /** Event subscriptions */
44
+ events?: string[];
45
+ }
46
+
47
+ export interface ContractValidationResult {
48
+ valid: boolean;
49
+ errors: ContractValidationError[];
50
+ warnings: string[];
51
+ }
52
+
53
+ export interface ContractValidationError {
54
+ field: string;
55
+ message: string;
56
+ code: string;
57
+ }
58
+
59
+ /**
60
+ * Validate a plugin contract against the ObjectStack spec.
61
+ */
62
+ export function validatePluginContract(contract: PluginContract): ContractValidationResult {
63
+ const errors: ContractValidationError[] = [];
64
+ const warnings: string[] = [];
65
+
66
+ if (!contract.name || contract.name.trim().length === 0) {
67
+ errors.push({ field: 'name', message: 'Plugin name is required', code: 'MISSING_NAME' });
68
+ }
69
+
70
+ if (!contract.version || !/^\d+\.\d+\.\d+/.test(contract.version)) {
71
+ errors.push({ field: 'version', message: 'Valid semver version is required', code: 'INVALID_VERSION' });
72
+ }
73
+
74
+ if (!contract.exports || contract.exports.length === 0) {
75
+ errors.push({ field: 'exports', message: 'At least one export is required', code: 'NO_EXPORTS' });
76
+ }
77
+
78
+ if (contract.exports) {
79
+ const validTypes = ['component', 'hook', 'utility', 'provider'];
80
+ for (const exp of contract.exports) {
81
+ if (!exp.name) {
82
+ errors.push({ field: 'exports.name', message: 'Export name is required', code: 'MISSING_EXPORT_NAME' });
83
+ }
84
+ if (!validTypes.includes(exp.type)) {
85
+ errors.push({ field: 'exports.type', message: `Invalid export type: ${exp.type}`, code: 'INVALID_EXPORT_TYPE' });
86
+ }
87
+ }
88
+ }
89
+
90
+ if (!contract.permissions || contract.permissions.length === 0) {
91
+ warnings.push('No permissions declared — plugin will have minimal access');
92
+ }
93
+
94
+ return { valid: errors.length === 0, errors, warnings };
95
+ }
96
+
97
+ /**
98
+ * Generate a plugin contract manifest for marketplace publishing.
99
+ */
100
+ export function generateContractManifest(contract: PluginContract): Record<string, unknown> {
101
+ return {
102
+ $schema: 'https://objectui.org/schemas/plugin-contract-v1.json',
103
+ name: contract.name,
104
+ version: contract.version,
105
+ peerDependencies: contract.peerDependencies ?? {},
106
+ exports: contract.exports.map(exp => ({
107
+ name: exp.name,
108
+ type: exp.type,
109
+ description: exp.description ?? '',
110
+ })),
111
+ permissions: contract.permissions ?? [],
112
+ api: contract.api ?? {},
113
+ generatedAt: new Date().toISOString(),
114
+ };
115
+ }
@@ -270,7 +270,7 @@ describe('Error Helpers', () => {
270
270
 
271
271
  expect(error).toBeInstanceOf(MetadataNotFoundError);
272
272
  expect(error.statusCode).toBe(404);
273
- expect((error as MetadataNotFoundError).details.objectName).toBe('users');
273
+ expect((error as MetadataNotFoundError).details?.objectName).toBe('users');
274
274
  });
275
275
 
276
276
  it('should create generic error for 404 without metadata context', () => {