@object-ui/data-objectstack 2.0.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/dist/index.js CHANGED
@@ -125,6 +125,22 @@ var MetadataCache = class {
125
125
  hitRate
126
126
  };
127
127
  }
128
+ /**
129
+ * Get a cached value synchronously without triggering a fetch.
130
+ * Returns undefined if not in cache or expired.
131
+ */
132
+ getCachedSync(key) {
133
+ const entry = this.cache.get(key);
134
+ if (!entry) return void 0;
135
+ if (this.ttl > 0 && Date.now() - entry.timestamp > this.ttl) {
136
+ this.cache.delete(key);
137
+ return void 0;
138
+ }
139
+ this.cache.delete(key);
140
+ this.cache.set(key, entry);
141
+ this.stats.hits++;
142
+ return entry.data;
143
+ }
128
144
  /**
129
145
  * Check if a key exists in the cache (and is not expired)
130
146
  *
@@ -332,6 +348,352 @@ function isErrorType(error, errorClass) {
332
348
  return error instanceof errorClass;
333
349
  }
334
350
 
351
+ // src/cloud.ts
352
+ var CloudOperations = class {
353
+ constructor(getClient) {
354
+ this.getClient = getClient;
355
+ }
356
+ /**
357
+ * Deploy an application to the cloud.
358
+ */
359
+ async deploy(appId, config) {
360
+ const client = this.getClient();
361
+ const result = await client.cloud?.deploy?.(appId, config);
362
+ return result ?? { deploymentId: `deploy-${Date.now()}`, status: "pending" };
363
+ }
364
+ /**
365
+ * Get deployment status.
366
+ */
367
+ async getDeploymentStatus(deploymentId) {
368
+ const client = this.getClient();
369
+ const result = await client.cloud?.getDeployment?.(deploymentId);
370
+ return result ?? { status: "unknown" };
371
+ }
372
+ /**
373
+ * Search marketplace entries.
374
+ */
375
+ async searchMarketplace(query, category) {
376
+ const client = this.getClient();
377
+ const result = await client.cloud?.marketplace?.search?.({ query, category });
378
+ return result?.items ?? [];
379
+ }
380
+ /**
381
+ * Install a marketplace plugin.
382
+ */
383
+ async installPlugin(pluginId) {
384
+ const client = this.getClient();
385
+ const result = await client.cloud?.marketplace?.install?.(pluginId);
386
+ return result ?? { success: false };
387
+ }
388
+ };
389
+
390
+ // src/contracts.ts
391
+ function validatePluginContract(contract) {
392
+ const errors = [];
393
+ const warnings = [];
394
+ if (!contract.name || contract.name.trim().length === 0) {
395
+ errors.push({ field: "name", message: "Plugin name is required", code: "MISSING_NAME" });
396
+ }
397
+ if (!contract.version || !/^\d+\.\d+\.\d+/.test(contract.version)) {
398
+ errors.push({ field: "version", message: "Valid semver version is required", code: "INVALID_VERSION" });
399
+ }
400
+ if (!contract.exports || contract.exports.length === 0) {
401
+ errors.push({ field: "exports", message: "At least one export is required", code: "NO_EXPORTS" });
402
+ }
403
+ if (contract.exports) {
404
+ const validTypes = ["component", "hook", "utility", "provider"];
405
+ for (const exp of contract.exports) {
406
+ if (!exp.name) {
407
+ errors.push({ field: "exports.name", message: "Export name is required", code: "MISSING_EXPORT_NAME" });
408
+ }
409
+ if (!validTypes.includes(exp.type)) {
410
+ errors.push({ field: "exports.type", message: `Invalid export type: ${exp.type}`, code: "INVALID_EXPORT_TYPE" });
411
+ }
412
+ }
413
+ }
414
+ if (!contract.permissions || contract.permissions.length === 0) {
415
+ warnings.push("No permissions declared \u2014 plugin will have minimal access");
416
+ }
417
+ return { valid: errors.length === 0, errors, warnings };
418
+ }
419
+ function generateContractManifest(contract) {
420
+ return {
421
+ $schema: "https://objectui.org/schemas/plugin-contract-v1.json",
422
+ name: contract.name,
423
+ version: contract.version,
424
+ peerDependencies: contract.peerDependencies ?? {},
425
+ exports: contract.exports.map((exp) => ({
426
+ name: exp.name,
427
+ type: exp.type,
428
+ description: exp.description ?? ""
429
+ })),
430
+ permissions: contract.permissions ?? [],
431
+ api: contract.api ?? {},
432
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
433
+ };
434
+ }
435
+
436
+ // src/integration.ts
437
+ var IntegrationManager = class {
438
+ constructor() {
439
+ __publicField(this, "integrations", /* @__PURE__ */ new Map());
440
+ }
441
+ /**
442
+ * Register a new integration.
443
+ */
444
+ register(id, config) {
445
+ this.integrations.set(id, config);
446
+ }
447
+ /**
448
+ * Remove an integration.
449
+ */
450
+ unregister(id) {
451
+ this.integrations.delete(id);
452
+ }
453
+ /**
454
+ * Get all registered integrations.
455
+ */
456
+ getAll() {
457
+ return new Map(this.integrations);
458
+ }
459
+ /**
460
+ * Get integrations that match a specific event.
461
+ */
462
+ getForEvent(event) {
463
+ return Array.from(this.integrations.values()).filter(
464
+ (integration) => integration.enabled && integration.triggers?.some((t) => t.event === event)
465
+ );
466
+ }
467
+ /**
468
+ * Dispatch an event to all matching integrations.
469
+ * Returns results for each integration.
470
+ */
471
+ async dispatch(event, payload) {
472
+ const matching = this.getForEvent(event);
473
+ const results = [];
474
+ for (const [id, integration] of this.integrations) {
475
+ if (!matching.includes(integration)) continue;
476
+ try {
477
+ await this.send(integration, payload);
478
+ results.push({ id, success: true });
479
+ } catch (err) {
480
+ results.push({ id, success: false, error: err.message });
481
+ }
482
+ }
483
+ return results;
484
+ }
485
+ /**
486
+ * Send payload to a specific integration.
487
+ */
488
+ async send(integration, payload) {
489
+ switch (integration.provider) {
490
+ case "webhook": {
491
+ const cfg = integration.config;
492
+ const url = cfg.url;
493
+ try {
494
+ const parsed = new URL(url);
495
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
496
+ throw new Error(`Invalid URL protocol: ${parsed.protocol}`);
497
+ }
498
+ } catch (e) {
499
+ if (e instanceof TypeError) {
500
+ throw new Error(`Invalid webhook URL: ${url}`);
501
+ }
502
+ throw e;
503
+ }
504
+ await fetch(url, {
505
+ method: cfg.method,
506
+ headers: {
507
+ "Content-Type": "application/json",
508
+ ...cfg.headers
509
+ },
510
+ body: JSON.stringify(payload)
511
+ });
512
+ break;
513
+ }
514
+ case "slack": {
515
+ const cfg = integration.config;
516
+ const url = cfg.webhookUrl;
517
+ try {
518
+ const parsed = new URL(url);
519
+ if (parsed.protocol !== "https:") {
520
+ throw new Error(`Invalid Slack webhook URL protocol: ${parsed.protocol}`);
521
+ }
522
+ } catch (e) {
523
+ if (e instanceof TypeError) {
524
+ throw new Error(`Invalid Slack webhook URL: ${url}`);
525
+ }
526
+ throw e;
527
+ }
528
+ await fetch(url, {
529
+ method: "POST",
530
+ headers: { "Content-Type": "application/json" },
531
+ body: JSON.stringify({
532
+ channel: cfg.channel,
533
+ username: cfg.username,
534
+ icon_emoji: cfg.iconEmoji,
535
+ text: JSON.stringify(payload)
536
+ })
537
+ });
538
+ break;
539
+ }
540
+ // Email and other providers would require server-side implementation
541
+ default:
542
+ break;
543
+ }
544
+ }
545
+ };
546
+
547
+ // src/security.ts
548
+ var SecurityManager = class {
549
+ constructor(policy = {}) {
550
+ __publicField(this, "policy");
551
+ __publicField(this, "auditLog", []);
552
+ this.policy = policy;
553
+ }
554
+ /**
555
+ * Generate a CSP header string from the configuration.
556
+ */
557
+ generateCSPHeader() {
558
+ const csp = this.policy.csp;
559
+ if (!csp) return "";
560
+ const directives = [];
561
+ if (csp.scriptSrc?.length) directives.push(`script-src ${csp.scriptSrc.join(" ")}`);
562
+ if (csp.styleSrc?.length) directives.push(`style-src ${csp.styleSrc.join(" ")}`);
563
+ if (csp.imgSrc?.length) directives.push(`img-src ${csp.imgSrc.join(" ")}`);
564
+ if (csp.connectSrc?.length) directives.push(`connect-src ${csp.connectSrc.join(" ")}`);
565
+ if (csp.fontSrc?.length) directives.push(`font-src ${csp.fontSrc.join(" ")}`);
566
+ if (csp.frameSrc?.length) directives.push(`frame-src ${csp.frameSrc.join(" ")}`);
567
+ if (csp.reportUri) directives.push(`report-uri ${csp.reportUri}`);
568
+ return directives.join("; ");
569
+ }
570
+ /**
571
+ * Record an audit log entry.
572
+ */
573
+ recordAudit(entry) {
574
+ if (!this.policy.auditLog?.enabled) return;
575
+ if (this.policy.auditLog.events && !this.policy.auditLog.events.includes(entry.event)) return;
576
+ const fullEntry = {
577
+ ...entry,
578
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
579
+ };
580
+ this.auditLog.push(fullEntry);
581
+ const dest = this.policy.auditLog.destination ?? "console";
582
+ if (dest === "console" || dest === "both") {
583
+ console.info("[AUDIT]", fullEntry.event, fullEntry.userId, fullEntry.resource ?? "");
584
+ }
585
+ }
586
+ /**
587
+ * Get audit log entries.
588
+ */
589
+ getAuditLog(filter) {
590
+ let entries = [...this.auditLog];
591
+ if (filter?.event) entries = entries.filter((e) => e.event === filter.event);
592
+ if (filter?.userId) entries = entries.filter((e) => e.userId === filter.userId);
593
+ if (filter?.since) entries = entries.filter((e) => e.timestamp >= filter.since);
594
+ return entries;
595
+ }
596
+ /**
597
+ * Apply data masking to a record.
598
+ */
599
+ maskRecord(record, userRoles = []) {
600
+ if (!this.policy.dataMasking?.rules?.length) return record;
601
+ const masked = { ...record };
602
+ const maskChar = this.policy.dataMasking.maskChar ?? "*";
603
+ for (const rule of this.policy.dataMasking.rules) {
604
+ if (!(rule.field in masked) || masked[rule.field] == null) continue;
605
+ if (rule.exemptRoles?.some((role) => userRoles.includes(role))) continue;
606
+ const value = String(masked[rule.field]);
607
+ switch (rule.strategy) {
608
+ case "full":
609
+ masked[rule.field] = maskChar.repeat(value.length);
610
+ break;
611
+ case "partial": {
612
+ const visible = rule.visibleChars ?? 4;
613
+ if (value.length <= visible) {
614
+ masked[rule.field] = maskChar.repeat(value.length);
615
+ } else {
616
+ masked[rule.field] = value.slice(0, visible) + maskChar.repeat(value.length - visible);
617
+ }
618
+ break;
619
+ }
620
+ case "hash":
621
+ masked[rule.field] = `[HASHED:${simpleHash(value)}]`;
622
+ break;
623
+ case "redact":
624
+ masked[rule.field] = "[REDACTED]";
625
+ break;
626
+ }
627
+ }
628
+ return masked;
629
+ }
630
+ /**
631
+ * Update the security policy.
632
+ */
633
+ updatePolicy(policy) {
634
+ this.policy = { ...this.policy, ...policy };
635
+ }
636
+ /**
637
+ * Get current security policy.
638
+ */
639
+ getPolicy() {
640
+ return { ...this.policy };
641
+ }
642
+ };
643
+ function simpleHash(str) {
644
+ let hash = 0;
645
+ for (let i = 0; i < str.length; i++) {
646
+ const char = str.charCodeAt(i);
647
+ hash = (hash << 5) - hash + char;
648
+ hash = hash & hash;
649
+ }
650
+ return Math.abs(hash).toString(36);
651
+ }
652
+
653
+ // src/studio.ts
654
+ function createDefaultCanvasConfig(overrides) {
655
+ return {
656
+ width: 1200,
657
+ height: 800,
658
+ background: "grid",
659
+ gridSize: 8,
660
+ snapToGrid: true,
661
+ zoom: {
662
+ min: 0.25,
663
+ max: 3,
664
+ step: 0.1,
665
+ current: 1
666
+ },
667
+ panOffset: { x: 0, y: 0 },
668
+ showMinimap: false,
669
+ minimapPosition: "bottom-right",
670
+ ...overrides
671
+ };
672
+ }
673
+ function snapToGrid(x, y, gridSize) {
674
+ return {
675
+ x: Math.round(x / gridSize) * gridSize,
676
+ y: Math.round(y / gridSize) * gridSize
677
+ };
678
+ }
679
+ function calculateAutoLayout(items, canvasWidth, padding = 40, gap = 40) {
680
+ const positions = [];
681
+ let currentX = padding;
682
+ let currentY = padding;
683
+ let rowMaxHeight = 0;
684
+ for (const item of items) {
685
+ if (currentX + item.width + padding > canvasWidth) {
686
+ currentX = padding;
687
+ currentY += rowMaxHeight + gap;
688
+ rowMaxHeight = 0;
689
+ }
690
+ positions.push({ id: item.id, x: currentX, y: currentY });
691
+ currentX += item.width + gap;
692
+ rowMaxHeight = Math.max(rowMaxHeight, item.height);
693
+ }
694
+ return positions;
695
+ }
696
+
335
697
  // src/index.ts
336
698
  var ObjectStackAdapter = class {
337
699
  constructor(config) {
@@ -478,13 +840,15 @@ var ObjectStackAdapter = class {
478
840
  };
479
841
  }
480
842
  const resultObj = result;
843
+ const records = resultObj.records || resultObj.value || [];
844
+ const total = resultObj.total ?? resultObj.count ?? records.length;
481
845
  return {
482
- data: resultObj.value || [],
483
- total: resultObj.count || (resultObj.value ? resultObj.value.length : 0),
846
+ data: records,
847
+ total,
484
848
  // Calculate page number safely
485
849
  page: params?.$skip && params.$top ? Math.floor(params.$skip / params.$top) + 1 : 1,
486
850
  pageSize: params?.$top,
487
- hasMore: params?.$top ? (resultObj.value?.length || 0) === params.$top : false
851
+ hasMore: params?.$top ? records.length === params.$top : false
488
852
  };
489
853
  }
490
854
  /**
@@ -707,7 +1071,7 @@ var ObjectStackAdapter = class {
707
1071
  await this.connect();
708
1072
  try {
709
1073
  const schema = await this.metadataCache.get(objectName, async () => {
710
- const result = await this.client.meta.getObject(objectName);
1074
+ const result = await this.client.meta.getItem("object", objectName);
711
1075
  if (result && result.item) {
712
1076
  return result.item;
713
1077
  }
@@ -793,6 +1157,49 @@ var ObjectStackAdapter = class {
793
1157
  return null;
794
1158
  }
795
1159
  }
1160
+ /**
1161
+ * Get a page definition from ObjectStack.
1162
+ * Uses the metadata API to fetch page layouts.
1163
+ * Returns null if the server doesn't support page metadata.
1164
+ */
1165
+ async getPage(pageId) {
1166
+ await this.connect();
1167
+ try {
1168
+ const cacheKey = `page:${pageId}`;
1169
+ return await this.metadataCache.get(cacheKey, async () => {
1170
+ const result = await this.client.meta.getItem("pages", pageId);
1171
+ if (result && result.item) return result.item;
1172
+ return result ?? null;
1173
+ });
1174
+ } catch {
1175
+ return null;
1176
+ }
1177
+ }
1178
+ /**
1179
+ * Get multiple metadata items from ObjectStack.
1180
+ * Uses v3.0.0 metadata API pattern: getItems for batch retrieval.
1181
+ */
1182
+ async getItems(category, names) {
1183
+ await this.connect();
1184
+ const results = await Promise.all(
1185
+ names.map(async (name) => {
1186
+ const cacheKey = `${category}:${name}`;
1187
+ return this.metadataCache.get(cacheKey, async () => {
1188
+ const result = await this.client.meta.getItem(category, name);
1189
+ if (result && result.item) return result.item;
1190
+ return result;
1191
+ });
1192
+ })
1193
+ );
1194
+ return results;
1195
+ }
1196
+ /**
1197
+ * Get cached metadata if available, without triggering a fetch.
1198
+ * Uses v3.0.0 metadata API pattern: getCached for synchronous cache access.
1199
+ */
1200
+ getCached(key) {
1201
+ return this.metadataCache.getCachedSync(key);
1202
+ }
796
1203
  /**
797
1204
  * Get cache statistics for monitoring performance.
798
1205
  */
@@ -912,13 +1319,21 @@ function createObjectStackAdapter(config) {
912
1319
  export {
913
1320
  AuthenticationError,
914
1321
  BulkOperationError,
1322
+ CloudOperations,
915
1323
  ConnectionError,
1324
+ IntegrationManager,
916
1325
  MetadataNotFoundError,
917
1326
  ObjectStackAdapter,
918
1327
  ObjectStackError,
1328
+ SecurityManager,
919
1329
  ValidationError,
1330
+ calculateAutoLayout,
1331
+ createDefaultCanvasConfig,
920
1332
  createErrorFromResponse,
921
1333
  createObjectStackAdapter,
1334
+ generateContractManifest,
922
1335
  isErrorType,
923
- isObjectStackError
1336
+ isObjectStackError,
1337
+ snapToGrid,
1338
+ validatePluginContract
924
1339
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/data-objectstack",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "ObjectStack Data Adapter for Object UI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,13 +20,13 @@
20
20
  "README.md"
21
21
  ],
22
22
  "dependencies": {
23
- "@objectstack/client": "^2.0.7",
24
- "@object-ui/core": "2.0.0",
25
- "@object-ui/types": "2.0.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",
28
+ "tsup": "^8.5.1",
29
+ "typescript": "^5.9.3",
30
30
  "vitest": "^4.0.18"
31
31
  },
32
32
  "publishConfig": {
@@ -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
+ }