@object-ui/data-objectstack 2.0.0 → 3.0.1
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.cjs +429 -6
- package/dist/index.d.cts +496 -1
- package/dist/index.d.ts +496 -1
- package/dist/index.js +420 -5
- package/package.json +6 -6
- package/src/cache/MetadataCache.ts +22 -0
- package/src/cloud.ts +109 -0
- package/src/contracts.ts +115 -0
- package/src/index.ts +73 -5
- package/src/integration.ts +192 -0
- package/src/security.ts +230 -0
- package/src/studio.ts +152 -0
- package/src/v3-compat.test.ts +240 -0
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:
|
|
483
|
-
total
|
|
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 ?
|
|
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.
|
|
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": "
|
|
3
|
+
"version": "3.0.1",
|
|
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": "^
|
|
24
|
-
"@object-ui/core": "
|
|
25
|
-
"@object-ui/types": "
|
|
23
|
+
"@objectstack/client": "^3.0.2",
|
|
24
|
+
"@object-ui/core": "3.0.1",
|
|
25
|
+
"@object-ui/types": "3.0.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"tsup": "^8.
|
|
29
|
-
"typescript": "^5.
|
|
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
|
+
}
|